「PIONEチュートリアル」の版間の差分

提供: Eospedia
移動: 案内検索
(デバッグのための条件文)
(デバッグのための条件文)
行1,334: 行1,334:
 
End
 
End
 
</pre>
 
</pre>
デバッグ実行時に出力したいファイルは元々Action内で作成されているファイルをMainまであげて確認する場合も多いと思います。上記のように各ルールに3行程度の記述を施して、Mainまで$LodFile内のファイルが上がるようにしています。ここでnullはファイル無しを意味しています。<br>
+
デバッグ実行時に出力したいファイルは元々Action内で作成されているファイルをMainまであげて確認する場合も多いと思います。上記のように記述すると、末端のルールからMainまで$LodFile内のファイルを渡すことができます。なお、ここでnullはファイル無しを意味します。<br>
 
<br>
 
<br>
  

2015年3月27日 (金) 00:26時点における版

PIONEチュートリアル
 ここでは、PIONEのチュートリアルを行います。始める前にPIONEのインストールをしておきましょう。

目次

基本

基本1(特定のファイルを出力する)

 まず、PIONEを動かすにはルール定義書をつくる必要があります。 どんな言語でも最初に作成するHelloプログラムを作ってみましょう。この場合、出力ファイルをひとつ指定することになります。

 次の内容のファイル'HelloWorld.pione'を作成してみましょう。

Rule Main
	output 'message.txt'

Action
	echo "Hello PIONE world !" > message.txt
End

このファイルはこちらからダウンロードできます。

 この後、pione-clientを実行します。

$ pione-client HelloWorld.pione -b helloOutput

例えば、次のような出力が流れます。

 ==> &Anonymous:Root([],{})
   --> Rule Application: &Anonymous:Root([],{})
     --> Distribution: &Anonymous:Root([],{})
           >>> &Anonymous:Main([],{})
 ==> &Anonymous:Main([],{})
  SH ------------------------------------------------------------
  SH 	echo "Hello PIONE world !" > message.txt
  SH ------------------------------------------------------------
 <== &Anonymous:Main([],{})
     <-- Distribution: &Anonymous:Root([],{})
   <-- Rule Application: &Anonymous:Root([],{})
 <== &Anonymous:Root([],{})

その結果、helloOutputというディレクトリができます。その中に指定したファイルmessage.txtが出力されています。

$ cat helloOutput/output/message.txt
Hello PIONE world !

もう一度、実行すると今度は、実行の必要がないために次のようなものが出力されます。

 ==> &Anonymous:Root([],{})
   --> Rule Application: &Anonymous:Root([],{})
   <-- Rule Application: &Anonymous:Root([],{})
 <== &Anonymous:Root([],{})

さっきと比べると、Main Ruleが動いていないことが分かります。

さて、改めて設定したファイルを眺めてみます。

まず、最初に呼び出されるルール(Main)が定義されています。

Rule Main

Mainは、C言語などと同様に特別な意味をもつルールです。

次に、出力ファイルが定義されています。

	output 'message.txt'

ここに書かれたファイルが最終的に-bで指定されたディレクトリに出力として戻ってきます。 Action以降が実際に起動するプログラムになります。

Action
	echo "Hello PIONE world !" > message.txt

実際にはシェルスクリプトが動きますので、どんなものも実行が可能です。

#!/bin/csh

で始めれば、cshを使って記述することもできますし、スクリプト系の言語であれば自由に記述し、実行形式を実行出来ます。

最後に、RuleをEndで終了します。

End

これが、一番シンプルなルールの書き方です。入力ファイルがないので、出力ファイルがなければ作成し、あれば、作成しないという動作をします。必要以上の動作をしないところが通常のシェルスクリプトを実行する場合と異なる点です。

基本2(特定のファイルを入力し、出力する(更新判定))

 次に、入力ファイルから出力ファイルを作成する場合の定義書について作成してみましょう。

#Multiplying.pione

Rule Main
	input 'test.in'
	output 'test.out'

Action 
	awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
End

このファイルはこちらからダウンロードできます。

今回は、test.inという入力ファイルからその積算をして、test.outというファイルを作り出すルールです。入力ファイルの更新判定により、ルールを実行するかどうかが変わってきます。

awkの使い方については、別途勉強してみて下さい。ここではファイルの中にある行頭の数字を2倍して、出力することができます。

まず、

$ mkdir MultiplyingInput
$ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/

として、入力ファイルがあるディレクトリ(指定しなければ、現在のディレクトリ)を指定して実行してみます。

 ==> &Anonymous:Root([],{})
   --> Rule Application: &Anonymous:Root([],{})
   <-- Rule Application: &Anonymous:Root([],{})
 <== &Anonymous:Root([],{})

入力ファイルがありませんので、何もする事がないとして終了してしまいます。先ほどとの違いに気がついたでしょうか。

 次に、ファイルを作成して、実行してみます。

$ echo "3"   > MultiplyingInput/test.in
$ echo "5" >> MultiplyingInput/test.in
$ cat MultiplyingInput/test.in
3
5
$ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/
 ==> &Anonymous:Root([test.in],{})
   --> Rule Application: &Anonymous:Root([test.in],{})
     --> Distribution: &Anonymous:Root([test.in],{})
           >>> &Anonymous:Main([test.in],{})
 ==> &Anonymous:Main([test.in],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
     <-- Distribution: &Anonymous:Root([test.in],{})
   <-- Rule Application: &Anonymous:Root([test.in],{})
 <== &Anonymous:Main([test.in],{})
 <== &Anonymous:Root([test.in],{})

$ cat MultiplyingOutput/output/test.out 
6
10

となり、確かに2倍の値のファイルができあがっていることが分かります。 もう一度、実行すると、

$ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/
 ==> &Anonymous:Root([test.in],{})
   --> Rule Application: &Anonymous:Root([test.in],{})
   <-- Rule Application: &Anonymous:Root([test.in],{})
 <== &Anonymous:Root([test.in],{})

となり、ここでも何も実行しません。つまり、入力ファイルに比べて出力ファイルのほうが新しいので、更新判定の結果、実行しなくてよいと判断したことになります。賢いですね。

さて、ここで、ファイルを更新してみましょう。3行目に7を付け加えます。

$ echo "7"   >> MultiplyingInput/test.in

そして、実行してみましょう。 $ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/

 ==> &Anonymous:Root([test.in],{})
   --> Rule Application: &Anonymous:Root([test.in],{})
     --> Distribution: &Anonymous:Root([test.in],{})
           >>> &Anonymous:Main([test.in],{})
 ==> &Anonymous:Main([test.in],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
     <-- Distribution: &Anonymous:Root([test.in],{})
 <== &Anonymous:Main([test.in],{})
   <-- Rule Application: &Anonymous:Root([test.in],{})
 <== &Anonymous:Root([test.in],{})

$ cat MultiplyingOutput/output/test.out 
6
10
14

今度は実行されました。これが更新判定によりルールが実行されるかどうかが判定されているということです。 もう一度実行しても、今度は実行されません。

$ pione-client Multiplying.pione -b MultiplyingOutput -i MultiplyingInput/
 ==> &Anonymous:Root([test.in],{})
   --> Rule Application: &Anonymous:Root([test.in],{})
   <-- Rule Application: &Anonymous:Root([test.in],{})
 <== &Anonymous:Root([test.in],{})

さて、出力ファイルを削除してみるとどうなるでしょうか。

$ rm  MultiplyingOutput/output/test.out 

今度は、実行されますね。

 では、内容は変えずに、touch コマンドを使って、入力ファイルの修正時刻を変えるとどうなるでしょうか。

$ touch MultiplyingInput/test.in

今回も、予想通り、動作しました。

 もし、動作しないようだったら、バグです。すぐに、GITHUB/PIONEに報告しましょう。

 さて、この基本2が理解できれば、まず、PIONEの動きの基本が理解できたことになります。

 ところで、Actionで定義した動作の中に、見慣れない記号( {$I[1]}, {$O[1]} )が現れています。

Action 
	awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
End

{$I[1]}は入力ファイル、{$O[1]}は出力ファイルを表しています。それぞれの[]の中の数字1は、 input/outputで定義したそれぞれの1番目ということを意味しています。次の基本3での複数ファイルの入出力では1以外が使われることになります。  まだ、何が便利か少し分かりづらいと思いますが、例えば、Multiplying2.pioneとして、次のように記述するとどのように動作するでしょうか。

Rule Main
	input 'test.in'
	output '{$I[1]}.out'

Action 
	awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
End

少し考えてみて下さい。そして、動作させてみましょう。

$ pione-client Multiplying2.pione -b MultiplyingOutput -i MultiplyingInput/

MultiplyingOutput/outputのディレクトリにはどんなファイルができあがったでしょうか。

そうですね。test.in.outができあがっています。何故かを考えてみましょう。

基本3(複数ファイルの入力と複数ファイルの出力)

 さて、更新判定が理解できたところで、複数ファイルの入出力について考えてみましょう。test1.in, test2.inから、それぞれ2倍した数が格納された、test1.out, test2.outが出力できるPIONE定義書を作成してみましょう。ここで、名前をMultiplyingFiles.pioneとして作成してみましょう。


 問題1:上記のMultiplyingFiles.pioneを作成して実行してみて下さい。


 解答例1:

Rule Main
	input 'test1.in'
	input 'test2.in'
	output 'test1.out'
	output 'test2.out'

Action 
	awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
	awk '{ print $1*2 }' {$I[2]} > {$O[2]} 
End

このファイルはこちらからダウンロードできます。

 どうでしょうか。思ったように動きましたか。複数のファイルがあれば、複数書き並べればよいのです。

 でもなんだか、同じ事をするのに何度も行を書くのは嫌だなと思いませんか。もし、2倍を5倍に変えたくなったら全ての行を書き換えなくてはなりませんね。10行ぐらいならそれでもよいけれど、100個、1000個とファイルが増えてきたらどうでしょうか。だんだん嫌になってきましたか。  では、どうすれば良いでしょうか。例えば、シェルスクリプトが得意なひとは次の様なプログラムを設定することも出来ます。


 解答例2:

Rule Main
	input 'test1.in'
	input 'test2.in'
	output 'test1.out'
	output 'test2.out'

Action 
       for i in `ls *.in`; do
            awk '{ print $1*2 }' $i > `basename $i .in`.out 
       done
End

これでも、ファイルが増えるたびにinput/outputを書き換える必要があります。 そこで、全てのファイルを入力し、全てのファイルを出力するように入出力を表現することも出来ます。


 解答例3:

Rule Main
	input '*.in'.all
	output '*.out'.all

Action 
       for i in `ls *.in`; do
            awk '{ print $1*2 }' $i > `basename $i .in`.out 
       done
End

どうでしょうか。うまく動作したでしょうか。ここで、

	input '*.in'.all
	output '*.out'.all

の部分は、*は任意の文字列を表現します。つまり、*.inは、ファイルの最後が.inで終了しているファイル全て、*.outは、ファイルの最後が、.outで終了しているファイル全てを表します。.allがついていると、全てのファイルを取り扱うことを意味しています。逆に、.allが付いていないときには、.eachがついていることがdefaultとして決まっています。一つ一つのファイルを別々に実行するという意味になります。

 これでずいぶんと記述しやすくなりました。

 でも、これだと、*.inのどれかのファイルが更新されていると全て変更になってしまいます。試してみて下さい。なんだか無駄ですね。また、同時にできるはずのことをfor文を使って順次実行しているので、時間も無駄ですね。このくらいのタスクであれば、たいして時間がかかるわけではないのですが、もっと時間のかかるタスクだったらどうでしょうか。必要なファイルの更新だけを、できれば複数のホストや一台でも最近のPCだったら、マルチコアCPUをもっているので、同時に動かすともっと早く終了できるようになるはずです。

 さて、それではどのようにすればよいのでしょうか。それが次の基本4になります。だんだんPIONEらしくなってきます。

基本4(複数ファイルの入力と複数ファイルの出力の並列処理)

次は、さっきと違ってallが付いていません。なにが起きるかを考えてみましょう。

Rule Main
	input '*.in'
	output '{$I[1][1]}.out'
Action
   awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
End

このファイルはこちらからダウンロードできます。
ここで、{$I[1][1]}.outは1番目のinputで拡張子を除いたファイル名を指します。(参照: PIONE定義書#入力定義

下は実行した例です。

 $ pione-client MultiplyingFilesEach.pione -b MultiplyingFilesEachOutput -i MultiplyingFilesInput/  -t 4
 ==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   --> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
     --> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
           >>> &Anonymous:Main([test1.in],{})
           >>> &Anonymous:Main([test2.in],{})
           >>> &Anonymous:Main([test3.in],{})
           >>> &Anonymous:Main([test4.in],{})
           >>> &Anonymous:Main([test5.in],{})
           >>> &Anonymous:Main([test6.in],{})
           >>> &Anonymous:Main([test7.in],{})
 ==> &Anonymous:Main([test1.in],{})
 ==> &Anonymous:Main([test2.in],{})
 ==> &Anonymous:Main([test3.in],{})
 ==> &Anonymous:Main([test4.in],{})
  SH ------------------------------------------------------------
  SH awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
  SH ------------------------------------------------------------
  SH awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
  SH ------------------------------------------------------------
  SH awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
  SH ------------------------------------------------------------
  SH awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
 <== &Anonymous:Main([test2.in],{})
 <== &Anonymous:Main([test1.in],{})
 <== &Anonymous:Main([test3.in],{})
 <== &Anonymous:Main([test4.in],{})
 ==> &Anonymous:Main([test5.in],{})
 ==> &Anonymous:Main([test6.in],{})
 ==> &Anonymous:Main([test7.in],{})
  SH ------------------------------------------------------------
  SH awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
  SH ------------------------------------------------------------
  SH awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
  SH ------------------------------------------------------------
  SH awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
 <== &Anonymous:Main([test6.in],{})
 <== &Anonymous:Main([test5.in],{})
 <== &Anonymous:Main([test7.in],{})
     <-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   <-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})

4つのタスクが並列に動いている様子が分かるでしょうか。確かに4つずつのタスク( ==> &Anonymous:Main([testN.in],{}))が並列に動作しているようです。 もう一度実行してみると下のようになります。

$ pione-client MultiplyingFilesEach.pione -b MultiplyingFilesEachOutput -i MultiplyingFilesInput/  -t 4
 ==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   --> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   <-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})

今度は実行されません。そこで、ひとつファイルを更新してみます。

$ touch MultiplyingFilesInput/test3.in 
$ pione-client MultiplyingFilesEach.pione -b MultiplyingFilesEachOutput -i MultiplyingFilesInput/  -t 4
 ==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   --> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
     --> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
           >>> &Anonymous:Main([test3.in],{})
 ==> &Anonymous:Main([test3.in],{})
  SH ------------------------------------------------------------
  SH awk '{ print $1*2 }' {$I[1]} > {$O[1]} 
  SH ------------------------------------------------------------
 <== &Anonymous:Main([test3.in],{})
     <-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   <-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})

となり、test3.inだけが更新されているのが分かります。これがeachとしての振る舞いです。

基本4の補足(eachでの速度検証)

eachを利用すれば、各々のファイル毎で並列処理を行うことができます。このとき、入力ファイルを各タスクの分だけコピーが行われますが、同じ入力ファイルを使うときでもコピー処理が行われるため無駄な処理が発生する問題があります。PIONEの場合、同じファイルについてはリンクを作成することによって、逐次コピーする時間を削減しています。

例えば、下記のようなmrcファイルの情報を出力する2つのコードがあったとします。(1)は全てのファイル(A.mrc, B.mrc, C.mrc, D.mrc, E.mrc)について1回ずつSizeOutを呼び、(2)は1つのA.mrcファイルについて5回SizeOutを呼ぶ仕組みとしています。サイズの大きいファイル(512×512×512のmrc画像: mrcImageNullImageCreateを使用)を入力として動作を比較します。

コード 入力ファイル 1ファイル毎のSizeOut 処理結果(ProM
(1)
Rule Main
	input '*.mrc'
	output '*.txt'.all
Flow
	rule SizeOut {val:1}
End

Rule SizeOut
	input '*.mrc'
	output '{$I[1][1]}{$val}.txt'
	param $val
Action
	wc {$I[1]} > {$O[1]}
End

A.mrc
B.mrc
C.mrc
D.mrc
E.mrc

1回 Outdata-PIONE-Advanced4-1.png
(2)
Rule Main
	input 'A.mrc'
	output '*.txt'.all
Flow
	rule SizeOut {val:1.upto(5)}
End

Rule SizeOut
	input 'A.mrc'
	output 'A{$val}.txt'
	param $val
Action
	wc {$I[1]} > {$O[1]}
End

A.mrc

5回 Outdata-PIONE-Advanced4-2.png

いずれにしてもSizeOutは5回処理されていますが、(2)の場合は(1)に比べ時間が削減されています。同じマシン内においてeachの処理ではリンクを利用することにより同じ入力ファイルを何度もコピーすることを避けていることが確認できます。つまり入力ファイルのサイズを特に気にすることなくeachで並列処理を行えることが分かります。

では、eachとallの速度の違いを見てみましょう。いずれもA.mrcの情報をそれぞれ5回出力している処理ですが、(3)ではSizeOutをall出力とし、(4)ではSizeOutをeach出力としています。eachを実行するときはpione-clientのオプション-t 5として5つの並列処理を行って、動作を比較してみます。

コード 処理結果(ProM
(3) all
Rule Main
	input 'A.mrc'
	output '*.txt'.all
Flow
	rule SizeOut
End

Rule SizeOut
	input 'A.mrc'
	output '*.txt'.all
Action
	for (( i=1; i<6; i++ ))
	do
		wc {$I[1]} > "{$I[1][1]}$i.txt"
	done
End
Outdata-PIONE-Advanced4-3.png
(4) each
Rule Main
	input 'A.mrc'
	output '*.txt'.all
Flow
	rule SizeOut {val:1.upto(5)}
End

Rule SizeOut
	input 'A.mrc'
	output '{$I[1][1]}{$val}.txt'
	param $val
Action
	wc {$I[1]} > {$O[1]}
End
Outdata-PIONE-Advanced4-4.png
eachによる並列処理により、時間が大幅に削減されていることが確認できます。

基本5(直列:フロールールの設定 )

 さて、もう少し複雑なルールについて設定してみましょう。 まず、ふたつのルールを組み合わせて、並列計算することを考えてみます。

Rule Main
	input   '*.in'.all
	output  '*.out'.all
Flow
	rule First
	rule Second
End

Rule First
	input   '*.in'
	output  '{$I[1][1]}.route'
Action
	awk '{ print $1*2 }' {$I[1]} > {$O[1]}	
End

Rule Second
	input   '*.route'
	output  '{$I[1][1]}.out'
Action
	awk '{ print $1+1 }' {$I[1]} > {$O[1]}	
End

このファイルはこちらからダウンロードできます。

 実行結果を以下に示します。最初に5つのタスク(First)が並列で動いているのがわかります。 その後、終了した順に、次のルールが動いています。

$ pione-client Serial2.pione -i SerialInput/ -b Serial2Output
 ==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   --> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
     --> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
           >>> &Anonymous:Main([test1.in],{})
           >>> &Anonymous:Main([test2.in],{})
           >>> &Anonymous:Main([test3.in],{})
           >>> &Anonymous:Main([test4.in],{})
           >>> &Anonymous:Main([test5.in],{})
 ==> &Anonymous:Main([test1.in],{})
   --> Rule Application: &Anonymous:Main([test1.in],{})
     --> Distribution: &Anonymous:Main([test1.in],{})
           >>> &Anonymous:First([test1.in],{})
 ==> &Anonymous:Main([test2.in],{})
   --> Rule Application: &Anonymous:Main([test2.in],{})
     --> Distribution: &Anonymous:Main([test2.in],{})
           >>> &Anonymous:First([test2.in],{})
 ==> &Anonymous:Main([test3.in],{})
   --> Rule Application: &Anonymous:Main([test3.in],{})
     --> Distribution: &Anonymous:Main([test3.in],{})
           >>> &Anonymous:First([test3.in],{})
 ==> &Anonymous:Main([test4.in],{})
   --> Rule Application: &Anonymous:Main([test4.in],{})
     --> Distribution: &Anonymous:Main([test4.in],{})
           >>> &Anonymous:First([test4.in],{})
 ==> &Anonymous:Main([test5.in],{})
   --> Rule Application: &Anonymous:Main([test5.in],{})
     --> Distribution: &Anonymous:Main([test5.in],{})
           >>> &Anonymous:First([test5.in],{})
 ==> &Anonymous:First([test1.in],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1*2 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:First([test1.in],{})
     <-- Distribution: &Anonymous:Main([test1.in],{})
     --> Distribution: &Anonymous:Main([test1.in],{})
           >>> &Anonymous:Second([test1.route],{})
 ==> &Anonymous:First([test2.in],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1*2 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:First([test2.in],{})
     <-- Distribution: &Anonymous:Main([test2.in],{})
     --> Distribution: &Anonymous:Main([test2.in],{})
           >>> &Anonymous:Second([test2.route],{})
 ==> &Anonymous:First([test3.in],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1*2 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:First([test3.in],{})
     <-- Distribution: &Anonymous:Main([test3.in],{})
     --> Distribution: &Anonymous:Main([test3.in],{})
           >>> &Anonymous:Second([test3.route],{})
 ==> &Anonymous:First([test4.in],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1*2 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:First([test4.in],{})
     <-- Distribution: &Anonymous:Main([test4.in],{})
     --> Distribution: &Anonymous:Main([test4.in],{})
           >>> &Anonymous:Second([test4.route],{})
 ==> &Anonymous:First([test5.in],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1*2 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:First([test5.in],{})
     <-- Distribution: &Anonymous:Main([test5.in],{})
     --> Distribution: &Anonymous:Main([test5.in],{})
           >>> &Anonymous:Second([test5.route],{})
 ==> &Anonymous:Second([test1.route],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1+1 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:Second([test1.route],{})
     <-- Distribution: &Anonymous:Main([test1.in],{})
   <-- Rule Application: &Anonymous:Main([test1.in],{})
 <== &Anonymous:Main([test1.in],{})
 ==> &Anonymous:Second([test2.route],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1+1 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:Second([test2.route],{})
     <-- Distribution: &Anonymous:Main([test2.in],{})
   <-- Rule Application: &Anonymous:Main([test2.in],{})
 <== &Anonymous:Main([test2.in],{})
 ==> &Anonymous:Second([test3.route],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1+1 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:Second([test3.route],{})
     <-- Distribution: &Anonymous:Main([test3.in],{})
   <-- Rule Application: &Anonymous:Main([test3.in],{})
 <== &Anonymous:Main([test3.in],{})
 ==> &Anonymous:Second([test4.route],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1+1 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:Second([test4.route],{})
     <-- Distribution: &Anonymous:Main([test4.in],{})
   <-- Rule Application: &Anonymous:Main([test4.in],{})
 <== &Anonymous:Main([test4.in],{})
 ==> &Anonymous:Second([test5.route],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1+1 }' {$I[1]} > {$O[1]}	
  SH ------------------------------------------------------------
 <== &Anonymous:Second([test5.route],{})
     <-- Distribution: &Anonymous:Main([test5.in],{})
   <-- Rule Application: &Anonymous:Main([test5.in],{})
     <-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Main([test5.in],{})
   <-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})

一度、終了しましたので、次は実行されません。

$ pione-client Serial2.pione -i SerialInput/ -b Serial2Output
 ==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   --> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   <-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})

さて、ここで、ファイルをひとつ(test6.in)増やしてみましょう。

$ pione-client Serial2.pione -i SerialInput/ -b Serial2Output
 ==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   --> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
     --> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
           >>> &Anonymous:Main([test6.in],{})
 ==> &Anonymous:Main([test6.in],{})
   --> Rule Application: &Anonymous:Main([test6.in],{})
     --> Distribution: &Anonymous:Main([test6.in],{})
           >>> &Anonymous:First([test6.in],{})
 ==> &Anonymous:First([test6.in],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1*2 }' test6.in > test6.route	
  SH ------------------------------------------------------------
 <== &Anonymous:First([test6.in],{})
     <-- Distribution: &Anonymous:Main([test6.in],{})
     --> Distribution: &Anonymous:Main([test6.in],{})
           >>> &Anonymous:Second([test6.route],{})
 ==> &Anonymous:Second([test6.route],{})
  SH ------------------------------------------------------------
  SH 	awk '{ print $1+1 }' test6.route > test6.out	
  SH ------------------------------------------------------------
 <== &Anonymous:Second([test6.route],{})
     <-- Distribution: &Anonymous:Main([test6.in],{})
   <-- Rule Application: &Anonymous:Main([test6.in],{})
     <-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Main([test6.in],{})
     --> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
     <-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   <-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})

新しくできたファイルだけが作られていることがわかります。 それでは、ファイルの更新がかかったときはどうなるでしょうか。test2.inをtouchコマンドで更新してみました。

$ pione-client Serial2.pione -i SerialInput/ -b Serial2Output2
 ==> &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   --> Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
     --> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
           >>> &Anonymous:Main([test2.in],{})
           >>> &Anonymous:Main([test6.in],{})
 ==> &Anonymous:Main([test2.in],{})
   --> Rule Application: &Anonymous:Main([test2.in],{})
   <-- Rule Application: &Anonymous:Main([test2.in],{})
 <== &Anonymous:Main([test2.in],{})
 ==> &Anonymous:Main([test6.in],{})
   --> Rule Application: &Anonymous:Main([test6.in],{})
   <-- Rule Application: &Anonymous:Main([test6.in],{})
 <== &Anonymous:Main([test6.in],{})
     <-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
     --> Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
     <-- Distribution: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
   <-- Rule Application: &Anonymous:Root([test1.in,test2.in,test3.in,...],{})
 <== &Anonymous:Root([test1.in,test2.in,test3.in,...],{})

となり、新しいファイルであるtest2.in, test6.inに関しては、変更の可能性があるので起動し始めますが、何のルールも適用されないまま終了しています。

基本5の補足

 ルールを組み合わせ方次第で並列処理できるところ増やして速度アップをはかることができます。しかし、ルール間ではファイルのやり取りが行われるので、処理の短いルールを何度も呼んでしまうとこの部分の割合が多くなってしまいます。そこで1ファイルについて並列処理できない部分のルールはまとめて長く処理されるようにすると、そのルールを各ファイル毎で並列処理すればよいので速度を改善できます。

コード 処理結果(ProM
(1)
Rule Main
	input   '*.in'.all
	output  '*.out'.all
Flow
	rule First
	rule Second
End

Rule First
	input   '*.in'
	output  '{$I[1][1]}.route'
Action
	awk '{ print $1*2 }' {$I[1]} > {$O[1]}	
End

Rule Second
	input   '*.route'
	output  '{$I[1][1]}.out'
Action
	awk '{ print $1+1 }' {$I[1]} > {$O[1]}	
End
Outdata-PIONE-Basic5-1.png
(2)
Rule Main
	input   '*.in'.all
	output  '*.out'.all
Flow
	rule FirstSecond
End

Rule FirstSecond
	input   '*.in'
	output  '{$I[1][1]}.out'
Action
	awk '{ print $1*2 }' {$I[1]} > {$I[1][1]}.route	
	awk '{ print $1+1 }' {$I[1][1]}.route	 > {$O[1]}	
End
Outdata-PIONE-Basic5-2.png

(1)のコードでは最初の入力ファイルを.inとして、Firstで.routeを作り、Secondで出力ファイル.outを作っています。Second実行するためには.routeファイルを入力としているので、1ファイルについてFirst, Secondを並列で処理することはできません。また、Firstの処理後は.routeファイルをMainに渡し、そこからSecondに渡しているので、その分の時間が掛かっています。そこで、(2)のように1ファイルについて並列処理ができないルールはまとめてしまった方がファイルのやり取りなどの時間が削減できます。

基本6(パラメータの定義)

入力データはファイルから読み取る以外にパラメータとして使用する方法があります。パラメータにはbasicとadvancedの2つがあります。処理上の違いは特にありませんが、前者を基本パラメータ、後者を上級者向けパラメータとして定義することでユーザのレベルに応じて変更可能なパラメータを区別することができます。下記のように定義します。(参照:PIONE定義書#パラメータ定義

param $val := 123
basic param $b_val := 456
advanced param $a_val := 987

Basic Param
	$b_val1 := 135
	$b_val2 := 246
End

Advanced Param
	$a_val1 := 975
	$a_val2 := 864
End

Rule Main
	output 'message.txt'
	param $main_val := $val * 10
Flow
	rule Sub {sub_val1: $main_val}
End

Rule Sub
	output 'message.txt'
	param $sub_val1
	param $sub_val2 := $val * 100
Action
	echo "Basic Parameters:" > {$O[1]};
	echo "	val:		{$val}" >> {$O[1]};
	echo "	b_val:		{$b_val}" >> {$O[1]};
	echo "	b_val1:		{$b_val1}" >> {$O[1]};
	echo "	b_val2:		{$b_val2}" >> {$O[1]};
	echo "Advanced Parameters:" >> {$O[1]};
	echo "	a_val:		{$a_val}" >> {$O[1]};
	echo "	a_val1:		{$a_val1}" >> {$O[1]};
	echo "	a_val2:		{$a_val2}" >> {$O[1]};
	echo "Parameters in Sub:" >> {$O[1]};
	echo "	sub_val1:	{$sub_val1}" >> {$O[1]};
	echo "	sub_val2:	{$sub_val2}" >> {$O[1]};
End

1つずつ定義するときはそれぞれbasic param XXX, advanced param YYYのように記述します。(単にparam ZZZとしたときはbasic扱いとなります。)まとめて定義したいときはBasic Param ~ End, Advanced Param ~ Endで囲みます。このとき定義する変数名の先頭には$を付けて記述します。またAction内で使用するときは入力ファイルや出力ファイルと同様に{ }で括ります。

設定したパラメータはオプション--params="{XXX:N}"で値を設定して実行します。また、定義書内でデフォルト値を記述しておけば、--params内で設定しなかったパラメータもデフォルト値で処理されます。

今回はbasicのみに値を設定して実行してみます。

/Basic6$ pione-client ParamEcho.pione -b ParamEcho --params="{val:1,b_val:3,b_val1:5,b_val2:7}"
"{val:1,b_val:3,b_val1:5,b_val2:7}"
  ==> &Anonymous:Root([],{})
    --> Rule Application: &Anonymous:Root([],{})
      --> Distribution: &Anonymous:Root([],{})
            >>> &Anonymous:Main([],{main_val:(<i>10)})
  ==> &Anonymous:Main([],{main_val:(<i>10)})
    --> Rule Application: &Anonymous:Main([],{main_val:(<i>10)})
      --> Distribution: &Anonymous:Main([],{main_val:(<i>10)})
            >>> &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
  ==> &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
   SH ------------------------------------------------------------
   SH 	echo "Basic Parameters:" > message.txt;
   SH 	echo "	val:		1" >> message.txt;
   SH 	echo "	b_val:		3" >> message.txt;
   SH 	echo "	b_val1:		5" >> message.txt;
   SH 	echo "	b_val2:		7" >> message.txt;
   SH 	echo "Advanced Parameters:" >> message.txt;
   SH 	echo "	a_val:		987" >> message.txt;
   SH 	echo "	a_val1:		975" >> message.txt;
   SH 	echo "	a_val2:		864" >> message.txt;
   SH 	echo "Parameters in Sub:" >> message.txt;
   SH 	echo "	sub_val1:	10" >> message.txt;
   SH 	echo "	sub_val2:	100" >> message.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
      <-- Distribution: &Anonymous:Main([],{main_val:(<i>10)})
    <-- Rule Application: &Anonymous:Main([],{main_val:(<i>10)})
      <-- Distribution: &Anonymous:Root([],{})
  <== &Anonymous:Main([],{main_val:(<i>10)})
    <-- Rule Application: &Anonymous:Root([],{})
  <== &Anonymous:Root([],{})
/Basic6$ cat ParamEcho/output/message.txt 
Basic Parameters:
	val:		1
	b_val:		3
	b_val1:		5
	b_val2:		7
Advanced Parameters:
	a_val:		987
	a_val1:		975
	a_val2:		864
Parameters in Sub:
	sub_val1:	10
	sub_val2:	100
/Basic6$ 

設定を反映した処理が実行できました。

基本6の補足

ではパラメータの値を複数(シーケンス)使用した場合はどうなるのでしょうか。valに複数の値(1|2)を入れてみます。(参照:PIONEの式#シーケンス

/Basic6$ pione-client ParamEcho.pione -b ParamEcho --params="{val:(1|2)}"
"{val:(1|2)}"
  ==> &Anonymous:Root([],{})
    --> Rule Application: &Anonymous:Root([],{})
      --> Distribution: &Anonymous:Root([],{})
            >>> &Anonymous:Main([],{main_val:(<i>10)})
            >>> &Anonymous:Main([],{main_val:(<i>20)})
  ==> &Anonymous:Main([],{main_val:(<i>10)})
    --> Rule Application: &Anonymous:Main([],{main_val:(<i>10)})
      --> Distribution: &Anonymous:Main([],{main_val:(<i>10)})
            >>> &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
            >>> &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>200)})
  ==> &Anonymous:Main([],{main_val:(<i>20)})
    --> Rule Application: &Anonymous:Main([],{main_val:(<i>20)})
      --> Distribution: &Anonymous:Main([],{main_val:(<i>20)})
            >>> &Anonymous:Sub([],{sub_val1:(<i>20),sub_val2:(<i>100)})
            >>> &Anonymous:Sub([],{sub_val1:(<i>20),sub_val2:(<i>200)})
  ==> &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
   SH ------------------------------------------------------------
   SH 	echo "Basic Parameters:" > message.txt;
   SH 	echo "	val:		1 2" >> message.txt;
   SH 	echo "	b_val:		456" >> message.txt;
   SH 	echo "	b_val1:		135" >> message.txt;
   SH 	echo "	b_val2:		246" >> message.txt;
   SH 	echo "Advanced Parameters:" >> message.txt;
   SH 	echo "	a_val:		987" >> message.txt;
   SH 	echo "	a_val1:		975" >> message.txt;
   SH 	echo "	a_val2:		864" >> message.txt;
   SH 	echo "Parameters in Sub:" >> message.txt;
   SH 	echo "	sub_val1:	10" >> message.txt;
   SH 	echo "	sub_val2:	100" >> message.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>100)})
  ==> &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>200)})
   SH ------------------------------------------------------------
   SH 	echo "Basic Parameters:" > message.txt;
   SH 	echo "	val:		1 2" >> message.txt;
   SH 	echo "	b_val:		456" >> message.txt;
   SH 	echo "	b_val1:		135" >> message.txt;
   SH 	echo "	b_val2:		246" >> message.txt;
   SH 	echo "Advanced Parameters:" >> message.txt;
   SH 	echo "	a_val:		987" >> message.txt;
   SH 	echo "	a_val1:		975" >> message.txt;
   SH 	echo "	a_val2:		864" >> message.txt;
   SH 	echo "Parameters in Sub:" >> message.txt;
   SH 	echo "	sub_val1:	10" >> message.txt;
   SH 	echo "	sub_val2:	200" >> message.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Sub([],{sub_val1:(<i>10),sub_val2:(<i>200)})
      <-- Distribution: &Anonymous:Main([],{main_val:(<i>10)})
    <-- Rule Application: &Anonymous:Main([],{main_val:(<i>10)})
  <== &Anonymous:Main([],{main_val:(<i>10)})
  ==> &Anonymous:Sub([],{sub_val1:(<i>20),sub_val2:(<i>100)})
   SH ------------------------------------------------------------
   SH 	echo "Basic Parameters:" > message.txt;
   SH 	echo "	val:		1 2" >> message.txt;
   SH 	echo "	b_val:		456" >> message.txt;
   SH 	echo "	b_val1:		135" >> message.txt;
   SH 	echo "	b_val2:		246" >> message.txt;
   SH 	echo "Advanced Parameters:" >> message.txt;
   SH 	echo "	a_val:		987" >> message.txt;
   SH 	echo "	a_val1:		975" >> message.txt;
   SH 	echo "	a_val2:		864" >> message.txt;
   SH 	echo "Parameters in Sub:" >> message.txt;
   SH 	echo "	sub_val1:	20" >> message.txt;
   SH 	echo "	sub_val2:	100" >> message.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Sub([],{sub_val1:(<i>20),sub_val2:(<i>100)})
  ==> &Anonymous:Sub([],{sub_val1:(<i>20),sub_val2:(<i>200)})
   SH ------------------------------------------------------------
   SH 	echo "Basic Parameters:" > message.txt;
   SH 	echo "	val:		1 2" >> message.txt;
   SH 	echo "	b_val:		456" >> message.txt;
   SH 	echo "	b_val1:		135" >> message.txt;
   SH 	echo "	b_val2:		246" >> message.txt;
   SH 	echo "Advanced Parameters:" >> message.txt;
   SH 	echo "	a_val:		987" >> message.txt;
   SH 	echo "	a_val1:		975" >> message.txt;
   SH 	echo "	a_val2:		864" >> message.txt;
   SH 	echo "Parameters in Sub:" >> message.txt;
   SH 	echo "	sub_val1:	20" >> message.txt;
   SH 	echo "	sub_val2:	200" >> message.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Sub([],{sub_val1:(<i>20),sub_val2:(<i>200)})
      <-- Distribution: &Anonymous:Main([],{main_val:(<i>20)})
    <-- Rule Application: &Anonymous:Main([],{main_val:(<i>20)})
      <-- Distribution: &Anonymous:Root([],{})
  <== &Anonymous:Main([],{main_val:(<i>20)})
    <-- Rule Application: &Anonymous:Root([],{})
  <== &Anonymous:Root([],{})
/Basic6$ 

デフォルトではval=(1|2)がeachとして動作します。この場合はmain_val=(10|20)となって、それぞれsub_val1=(10|20), sub_val2=(100|200)となります。このとき、sub_val1, sub_val2はともにeachですので、上記のように(10, 100), (10, 200), (20, 100), (20, 200)と2*2の組み合わせでルールが動きます。

では、val=(1|2).allではどうなるでしょうか。

/Basic6$ pione-client ParamEcho.pione -b ParamEcho --params="{val:(1|2).all}"
"{val:(1|2).all}"
  ==> &Anonymous:Root([],{})
    --> Rule Application: &Anonymous:Root([],{})
      --> Distribution: &Anonymous:Root([],{})
            >>> &Anonymous:Main([],{main_val:(<i>10|20)})
  ==> &Anonymous:Main([],{main_val:(<i>10|20)})
    --> Rule Application: &Anonymous:Main([],{main_val:(<i>10|20)})
      --> Distribution: &Anonymous:Main([],{main_val:(<i>10|20)})
            >>> &Anonymous:Sub([],{sub_val1:(<i>10|20),sub_val2:(<i>100|200)})
  ==> &Anonymous:Sub([],{sub_val1:(<i>10|20),sub_val2:(<i>100|200)})
   SH ------------------------------------------------------------
   SH 	echo "Basic Parameters:" > message.txt;
   SH 	echo "	val:		1 2" >> message.txt;
   SH 	echo "	b_val:		456" >> message.txt;
   SH 	echo "	b_val1:		135" >> message.txt;
   SH 	echo "	b_val2:		246" >> message.txt;
   SH 	echo "Advanced Parameters:" >> message.txt;
   SH 	echo "	a_val:		987" >> message.txt;
   SH 	echo "	a_val1:		975" >> message.txt;
   SH 	echo "	a_val2:		864" >> message.txt;
   SH 	echo "Parameters in Sub:" >> message.txt;
   SH 	echo "	sub_val1:	10 20" >> message.txt;
   SH 	echo "	sub_val2:	100 200" >> message.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Sub([],{sub_val1:(<i>10|20),sub_val2:(<i>100|200)})
      <-- Distribution: &Anonymous:Main([],{main_val:(<i>10|20)})
    <-- Rule Application: &Anonymous:Main([],{main_val:(<i>10|20)})
  <== &Anonymous:Main([],{main_val:(<i>10|20)})
      <-- Distribution: &Anonymous:Root([],{})
    <-- Rule Application: &Anonymous:Root([],{})
  <== &Anonymous:Root([],{})
/Basic6$ 

allの場合はsub_val1=(10|20), sub_val2=(100|200)として組み合わせるので、上記のように1つのルールが動くだけです。このようにパラメータについてもeach, allを使うことでルールの振り分けが可能です。

基本7(条件文)

if文

今回はif文などを用いて実行したいルールを制御してみましょう。

param $val := 123

Rule Main
	output 'message.txt'
Flow
	if $val % 2 == 0
		rule Even
	else
		rule Odd
	end
End

Rule Even
	output 'message.txt'
Action
	echo "{$val} is even." > message.txt;
End

Rule Odd
	output 'message.txt'
Action
	echo "{$val} is odd." > message.txt;
End

このファイルはこちらからダウンロードできます。
最初のパラメータvalに対して、偶数の場合と奇数の場合でそれぞれ別のルールを実行するようにしています。Rule Mainにif文がありますが、PIONEの記述ではif ~ endと記述します。

では、valをデフォルト値で実行してみましょう。

/Basic7$ pione-client EvenOdd.pione -b EvenOdd
  ==> &Anonymous:Root([],{})
    --> Rule Application: &Anonymous:Root([],{})
      --> Distribution: &Anonymous:Root([],{})
            >>> &Anonymous:Main([],{})
  ==> &Anonymous:Main([],{})
    --> Rule Application: &Anonymous:Main([],{})
      --> Distribution: &Anonymous:Main([],{})
            >>> &Anonymous:Odd([],{})
  ==> &Anonymous:Odd([],{})
   SH ------------------------------------------------------------
   SH 	echo "123 is odd." > message.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Odd([],{})
      <-- Distribution: &Anonymous:Main([],{})
    <-- Rule Application: &Anonymous:Main([],{})
      <-- Distribution: &Anonymous:Root([],{})
  <== &Anonymous:Main([],{})
    <-- Rule Application: &Anonymous:Root([],{})
  <== &Anonymous:Root([],{})
/Basic7$ cat EvenOdd/output/message.txt 
123 is odd.
/Basic7$ 

val=123で定義されていますので、この場合は奇数のルールが動いているのが分かります。

次にvalを偶数にして実行してみます。

/Basic7$ pione-client EvenOdd.pione -b EvenOdd --params="{val:456}"
"{val:456}"
  ==> &Anonymous:Root([],{})
    --> Rule Application: &Anonymous:Root([],{})
      --> Distribution: &Anonymous:Root([],{})
            >>> &Anonymous:Main([],{})
  ==> &Anonymous:Main([],{})
    --> Rule Application: &Anonymous:Main([],{})
      --> Distribution: &Anonymous:Main([],{})
            >>> &Anonymous:Even([],{})
  ==> &Anonymous:Even([],{})
   SH ------------------------------------------------------------
   SH 	echo "456 is even." > message.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Even([],{})
      <-- Distribution: &Anonymous:Main([],{})
    <-- Rule Application: &Anonymous:Main([],{})
      <-- Distribution: &Anonymous:Root([],{})
  <== &Anonymous:Main([],{})
    <-- Rule Application: &Anonymous:Root([],{})
  <== &Anonymous:Root([],{})
/Basic7$ cat EvenOdd/output/message.txt 
456 is even.
/Basic7$ 

偶数のルールが動きます。

以上のようにして条件に合わせてルールを制御することができます。

case文

case文の場合は下記のように記述します。

	case $val % 2
	when 0
		rule Even
	else
		rule Odd
	end


constraint

constraintを使用して下記のように記述する方法もあります。

param $val := 123

Rule Main
	output 'message.txt'
Flow
	rule Even
	rule Odd
End

Rule Even
	output 'message.txt'
	constraint $val % 2 == 0
Action
	echo "{$val} is even." > {$O[1]};
End

Rule Odd
	output 'message.txt'
	constraint $val % 2 == 1
Action
	echo "{$val} is odd." > {$O[1]};
End


デバッグのための条件文

Flowを使用したルールが何重にもなっているPIONE定義書にて末端のルールについてデバッグしようとすると、出力ファイルをMainまで引き継ぐためには呼び出し元からMainのルールまでに全て宣言しなければなりません。さらに、デバッグ対象のルールを変えるたびにそれぞれを変更する必要があります。デバッグの切り替えの手間が少なくなる方法を考えてみましょう。

例えば、下記のPIONE定義書があったとします。末端のSub3にログファイル{$I[1][1]}.logを出力したいときには、下記コメントアウトの部分のようにSub2, Sub1, Mainにも{$I[1][1]}.logを出力ファイルとして宣言しなければなりません。この場合、ログを出力するかしないかで各行のコメントアウトを操作する必要があります。しかも、この構文ではSub3に1つ問題がありActionを実行する前に{$O[2]}を変換しようとするので、{$O[2]}が設定されていない場合はエラーとなってしまいます。

Rule Main
	input '*.txt'
	output '{$I[1][1]}.out'
#	output '{$I[1][1]}.log'
Flow
	rule Sub1
End

Rule Sub1
	input '*.txt'
	output '{$I[1][1]}.out'
#	output '{$I[1][1]}.log'
Flow
	rule Sub2
End

Rule Sub2
	input '*.txt'
	output '{$I[1][1]}.out'
#	output '{$I[1][1]}.log'
Flow
	rule Sub3
End

Rule Sub3
	input '*.txt'
	output '{$I[1][1]}.out'
#	output '{$I[1][1]}.log'
Action
	touch {$O[1]}
#	wc {$I[1]} > {$O[2]}
End


そこでデバッグフラグとして変数束縛を用いて、trueのときだけログを実行できるようにします。さらにログファイル名の変更が一箇所で済むようにファイル名でも変数束縛を用いています。

$LogFlag := false
$LogFile := '{$I[1][1]}.log'

Rule Main
	input '*.txt'
	output '{$I[1][1]}.out'
	if $LogFlag
		output $LogFile
	end
Flow
	rule Sub1
End

Rule Sub1
	input '*.txt'
	output '{$I[1][1]}.out'
	if $LogFlag
		output $LogFile
	end
Flow
	rule Sub2
End

Rule Sub2
	input '*.txt'
	output '{$I[1][1]}.out'
	if $LogFlag
		output $LogFile
	end
Flow
	rule Sub3
End

Rule Sub3
	input '*.txt'
	output '{$I[1][1]}.out'
	output $LogFile
Action
	touch {$O[1]}
	if {$LogFlag} ; then
		wc {$I[1]} > {$LogFile}
	fi
End

outputの宣言を囲んでいるif文について注意が必要です。Flowを持つルールではデバッグ時のみログファイルができることを期待しているので、outputの宣言をif文で制御します。Sub3のActionにて実際にログを書き込んでいる箇所で{$O[2]}を使わず、outputで登録したファイル名と同じファイル名をそのまま使用します。このときAction内のif文はシェルスクリプトで記述することに注意して下さい。

デバッグ実行時に出力したいファイルが複数ある場合は、リスト(シーケンス)にて定義し、各ルールで出力する記述方法もあります。

$Debug := false

if $Debug
	$LogFile := ('*.info' |
				'*.pwd' |
				'*.ls' |
				'*.log').all
else
	$LogFile := null
end

Rule Main
	input '*.txt'
	output '{$I[1][1]}.out'
	if $LogFile.empty?.not
		output $LogFile
	end
Flow
	rule Sub1
End

Rule Sub1
	input '*.txt'
	output '{$I[1][1]}.out'
	if $LogFile.empty?.not
		output $LogFile
	end
Flow
	rule Sub2
End

Rule Sub2
	input '*.txt'
	output '{$I[1][1]}.out'
	if $LogFile.empty?.not
		output $LogFile
	end
Flow
	rule Sub3
End

Rule Sub3
	input '*.txt'
	output '{$I[1][1]}.out'
	if $LogFile.empty?.not
		output $LogFile
	end
Action
	touch {$O[1]}
	wc {$I[1]} > {$I[1][1]}.info
	pwd > {$I[1][1]}.pwd
	ls > {$I[1][1]}.ls
	date > {$I[1][1]}.log
End

デバッグ実行時に出力したいファイルは元々Action内で作成されているファイルをMainまであげて確認する場合も多いと思います。上記のように記述すると、末端のルールからMainまで$LodFile内のファイルを渡すことができます。なお、ここでnullはファイル無しを意味します。

基本8(インタラクティブ操作)

 ここまではコマンドを実行すると、結果出力までの動作を全てプログラムに委ねる一方通行の処理を記述していきました。次は処理の途中でユーザが操作できるようにして、以降の処理を制御(インタラクティブ操作)できるようにしてみましょう。

今回はウェブページから選択した入力ファイルに対して、設定した演算子(+, *)、数値で計算したファイルを出力する処理を作成します。実行ファイル作成のためにパッケージを、実行のためにPIONE Webclientを利用します。

まず、インタラクティブ操作のための処理をPIONE定義書に記述します。

.@ PackageName :: "InteractiveCalc"

Rule Main
	output '*.out'.all
Flow
	rule Interaction
End

Rule Interaction
	output '*.out'.all
Action
	# build public directory for pione-interactive
	mkdir public
	cp etc/* public
	cp bin/* public

	echo "Interruption!" > result.log.out

	# start interactive operation
	pione-interactive browser --public public

	cp public/* .
	echo "finish!" > result.log.out
End

今回は.outファイルを出力ファイルとしてダウンロードできるようにしています。pione-interactive browserコマンドにてInteractiveページを開けるようにします。このとき--publicでページのために使用するhtmlファイルやcgiファイルなどがあるディレクトリを指定します。そのために事前にpublicディレクトリを用意し、etcディレクトリ内とbinディレクトリ内のファイルをそれぞれコピーしています。また、Interactiveページを終了した後は作成したファイルを処理できるようにpublicディレクトリ内のデータを全てワークスペースへコピーしています。

次に、ウェブページにて最初に呼び出されるindex.htmlを作成します。htmlファイルはetcディレクトリ内に置きます。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>InteractiveCalc Index Page</title>
	</head>
	<body>
		<form action="FileCalc.cgi" method="post" enctype="multipart/form-data">
			入力ファイル名
			<input type="file" name="inputfile"/>
			<br>
			演算子
			<select name="operator">
				<option value="add">+</option>
				<option value="mul">*</option>
			</select>
			数値
			<input type="text" name="value"/>
			<br>
			出力ファイル名
			<input type="text" name="outputfile"/>
			<br>
			<button type="submit">計算開始</button>
		</form>
		<br>
		<br>
		<a href="?pione-action=finish">終了</a>
	</body>
</html>

最後の方に<a href="?pione-action=finish">終了</a>とありますが、これは「終了」をクリックするとワーキンングディレクトリに終了通知を行い、インタラクティブ操作を終了させる処理です。詳しくはインタラクションAPIをご覧下さい。

そして、.cgiファイルを作成します。シェルスクリプトEosのコマンドなどの特殊な処理はこのファイルに記述します。cgiファイルはbinディレクトリ内に置きます。

#!/usr/bin/env ruby

require 'cgi'

cgi = CGI.new

HTMLstr=""

# Main Process

## Query to Parameters
input_file=cgi.params['inputfile'][0]
output_path=cgi['outputfile']
operator=cgi['operator']
value=cgi['value'].to_i

## Calc input to output
output_file = open(output_path, "w")

HTMLstr += "<table>"
HTMLstr += "<tr>"
HTMLstr += "<td>" + input_file.original_filename.to_s + "</td>"
HTMLstr += "<td>" + output_path + "</td>"
HTMLstr += "</tr>"

input_file.each do |input_line|
	HTMLstr += "<tr>"
	HTMLstr += "<td>" + input_line + "</td>"

	work_value = input_line.to_i

	case operator
	when "add"
		work_value += value
	when "mul"
		work_value *= value
	end
	
	output_line = work_value.to_s
	output_file.write(output_line + "\n")
	HTMLstr += "<td>" + output_line + "</td>"
	HTMLstr += "</tr>"
end

HTMLstr += "</table>"
output_file.close

# Output as html
cgi.out(type: "text/html") do
	HTMLstr
end

今回はRubyを使って記述しています。# Output as html以下のコードでhtml形式に変換するようにしています。そのためにCGIクラスが必要なので、require 'cgi'とcgi = CGI.newで用意しています。このようにすると、# Main Process内のようにHTMLstrに記述する文字列を加えるだけで、自由な処理を行ってその結果をhtmlのページを表示することができます。また、## Query to ParametersにてPOST形式のクエリを変数に変換していますが、typeがfileの場合、ファイル本体はCGIクラスメンバーParamsから受け取ります。このとき受け取るデータはファイルポインタ配列ですので、取得したい要素No.を指定する必要があります。今回、入力ファイルは1つしか格納していないので、cgi.params['inputfile'][0]となります。

コードが完成したら、パッケージ作成を行います。(PIONEチュートリアル-packageを参照)
なお、これらのファイルはこちらからダウンロードできます。

/PIONE$ pione package build Basic8/
info: update the package info file: local:/Eos/tutorial/SampleCode/PIONE/Basic8/pione-package.json
info: Package local:/Eos/tutorial/SampleCode/PIONE/InteractiveCalc.ppg has been built successfully.


作成したパッケージPIONE Webclientにて動かすことができます。PIONE Webclientチュートリアル#インタラクティブ操作を含むコマンドを実行にて動作方法と実行結果を記載しています。

index.htmlとFileCalc.cgiで作成したページ

PIONE-Webclient13.png -> PIONE-Webclient14.png


基本9(インタラクションAPIの使用)

 インタラクションAPIを使用してワーキングディレクトリに通知を送ることで、ファイル操作や終了などを命令することができます。.htmlや.cgiに記述することにより、ユーザからの操作を受けてこれらの命令を実行することができます。

Annotation.pione

.@ PackageName	:: "FileOperation"
.@ Editor		:: "Kinoshita"
.@ Tag			:: "v0.1.0"


Main.pione

Rule Main
	output '*.txt'.all
Flow
	rule Interaction
End

今回は.txtファイルのみを出力するようにしています。

Interaction.pione

Rule Interaction
	output '*.*'.all
Action
	# build public directory for pione-interactive
	mkdir public
	cp etc/* public
	cp bin/* public

	# start interactive operation
	pione-interactive browser --public public

	cp public/* .
End


etc/index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>FileOperation</title>
	</head>
	<body>
		<form action="./AAA.txt" method="post" enctype="multipart/form-data">
			<input type="hidden" name="pione-action" value="create"/>
			<button type="submit">作成(ファイル)</button>
			<input type="file" name="pione-content" value=""/>
		</form>
		<form action="./AAA.txt" method="post" enctype="multipart/form-data">
			<input type="hidden" name="pione-action" value="create"/>
			<button type="submit">作成(テキスト)</button>
			<input type="text" name="pione-content" value=""/>
		</form>
		<form action="./AAA.txt" method="post" enctype="multipart/form-data">
			<input type="hidden" name="pione-action" value="delete"/>
			<button type="submit">削除</button>
		</form>
		<form action="./list.cgi" method="post" enctype="multipart/form-data">
			<input type="hidden" name="pione-action" value="get"/>
			<button type="submit">ファイル一覧の取得</button>
		</form>
		<br>
		<br>
		<a href="?pione-action=finish">終了</a>
	</body>
</html>

それぞれのボタンよりファイル操作を行います。

作成(ファイル): 指定したファイルをサーバ上のファイル(AAA.txt)に書き込んでアップロードします。
作成(テキスト): 指定したテキストボックスの内容でサーバ上のファイル(AAA.txt)に書き込みます。
削除: サーバ上のファイル(AAA.txt)内容を削除します。
ファイル一覧の取得: サーバ上のファイル一覧を表示します。(list.cgiが動作します)
終了: 操作を終了します。


etc/list.cgi

#!/usr/bin/env ruby

require 'cgi'

cgi = CGI.new

strHTML = ""

strHTML += <<'Block-HTML'
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>CGIページ</title>
	</head>
	<body>
		<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
		<div id="textDiv"></div>
		<script type="text/javascript">
			var div = document.getElementById("textDiv");
			div.textContent = "";
			$.getJSON("./", {"pione-action": "list"}, function(data){
				$.each(data, function(file) {
					div.textContent += this.name +"\n";
				});
			});
		</script>
		<a href="Index.html">戻る</a>
		<br>
		<br>
		<a href="?pione-action=finish">終了</a>
	</body>
</html>
Block-HTML

# Output as html
cgi.out(type: "text/html") do
	strHTML
end

"./"に対して"pione-action=list"を送信したときの戻り値をgetJSONにより取得して、データ毎の"name"を出力するようにしています。

これらのファイルはこちらからダウンロードできます。
では、実行させてみましょう。

インタラクションページを開くと下記のようになっています。
Outdata-PIONE-Basic9-1.png

まずは選択...ボタンからテキストファイルを選択し、作成(ファイル)ボタンでサーバ内にファイルを作成(アップロード)してみましょう。
Outdata-PIONE-Basic9-2.png
これによりAAA.txtが同じ内容で作成(アップロード)されます。終了した後にダウンロードすると/output/AAA.txtの内容を確認できます。

また、ファイル一覧の取得ボタンよりサーバ上のファイル一覧を閲覧することができます。
Outdata-PIONE-Basic9-3.png
AAA.txtが確認できます。

そして削除ボタンを押すと、AAA.txtを削除します。
Outdata-PIONE-Basic9-4.png
AAA.txtがなくなっているのが確認できます。

次はテキストボックスに文字列を書き込んで、作成(テキスト)ボタンを押してみましょう。
Outdata-PIONE-Basic9-2.png
これによりAAA.txtがテキストの内容で作成されます。

操作を終えるときには終了をクリックして下さい。終了通知が送られてインタラクティブ処理が完了します。また、htmlなどを実装する上で終了時には必ず終了通知finishを送信するように注意しましょう。

基本10(ループ文)

PIONE定義書にてループ文を作成してみましょう。Action内はシェルスクリプトですので、for文やwhile文を使用することで容易にループを実現できます。しかし、Actionに記述した処理は一つのルールに書かれるため一つのマシンで実行されます。それゆえにループ内のひとつひとつの処理が大きければ、処理を分割できるPIONEの長所を活かせていません。そこで、Flowでループ文に相当する内容を作成して、ループ内の個別の処理を各々のマシンに振り分ける工夫を施します。

今回は#基本7(制御文)での偶数奇数の処理に、最小値、最大値、加算値を設定できるようにしてループ文で実行する処理を作成します。

param $maxval := 456
param $minval := 123
param $dval := 37

Rule Main
	output '*.txt'
Flow
	rule LoopSystem {val: $minval}
End

Rule LoopSystem
	output '*.txt'
	param $val
Flow
	if $val <= $maxval
		rule EvenOdd {val: $val}
	end
	if ($val + $dval) <= $maxval
		rule LoopSystem {val: $val + $dval}
	end
End

Rule EvenOdd
	output '*.txt'
	param $val
Flow
	if $val % 2 == 0
		rule Even {val: $val}
	else
		rule Odd {val: $val}
	end
End

Rule Even
	output '*.txt'
	param $val
Action
	echo "{$val} is even." >> Even{$val}.txt;
End

Rule Odd
	output '*.txt'
Action
	echo "{$val} is odd." >> Odd{$val}.txt;
End

最大値、最小値、加算値をパラメータで設定できるようにして、ルールLoopSystemにてループ文を実現しています。まず、MainルールからLoopSystemをval=最小値で呼び出し、その中でvalが最大値になるまでは加算値を加えながら再帰的にLoopSystemを呼び出しています。出力ファイル名を偶数のときはEven{$val}.txt、奇数のときはOdd{$val}.txtとしています。このファイルはこちらからダウンロードできます。


では、実行してみます。

/Basic10$ pione-client EvenOddLoop.pione
  ==> &Anonymous:Root([],{})
    --> Rule Application: &Anonymous:Root([],{})
      --> Distribution: &Anonymous:Root([],{})
            >>> &Anonymous:Main([],{})
  ==> &Anonymous:Main([],{})
    --> Rule Application: &Anonymous:Main([],{})
      --> Distribution: &Anonymous:Main([],{})
            >>> &Anonymous:LoopSystem([],{val:(<i>123)})
  ==> &Anonymous:LoopSystem([],{val:(<i>123)})
    --> Rule Application: &Anonymous:LoopSystem([],{val:(<i>123)})
      --> Distribution: &Anonymous:LoopSystem([],{val:(<i>123)})
            >>> &Anonymous:EvenOdd([],{val:(<i>123)})
            >>> &Anonymous:LoopSystem([],{val:(<i>160)})
  ==> &Anonymous:EvenOdd([],{val:(<i>123)})
    --> Rule Application: &Anonymous:EvenOdd([],{val:(<i>123)})
      --> Distribution: &Anonymous:EvenOdd([],{val:(<i>123)})
            >>> &Anonymous:Odd([],{val:(<i>123)})
  ==> &Anonymous:LoopSystem([],{val:(<i>160)})
    --> Rule Application: &Anonymous:LoopSystem([],{val:(<i>160)})
      --> Distribution: &Anonymous:LoopSystem([],{val:(<i>160)})
            >>> &Anonymous:EvenOdd([],{val:(<i>160)})
            >>> &Anonymous:LoopSystem([],{val:(<i>197)})
  ==> &Anonymous:Odd([],{val:(<i>123)})
   SH ------------------------------------------------------------
   SH 	echo "123 is odd." >> Odd123.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Odd([],{val:(<i>123)})


-中略-


  ==> &Anonymous:Even([],{val:(<i>456)})
   SH ------------------------------------------------------------
   SH 	echo "456 is even." >> Even456.txt;
   SH ------------------------------------------------------------
  <== &Anonymous:Even([],{val:(<i>456)})
      <-- Distribution: &Anonymous:EvenOdd([],{val:(<i>456)})
    <-- Rule Application: &Anonymous:EvenOdd([],{val:(<i>456)})
  <== &Anonymous:EvenOdd([],{val:(<i>456)})


-中略-


      <-- Distribution: &Anonymous:LoopSystem([],{val:(<i>160)})
    <-- Rule Application: &Anonymous:LoopSystem([],{val:(<i>160)})
  <== &Anonymous:LoopSystem([],{val:(<i>160)})
      <-- Distribution: &Anonymous:LoopSystem([],{val:(<i>123)})
    <-- Rule Application: &Anonymous:LoopSystem([],{val:(<i>123)})
  <== &Anonymous:LoopSystem([],{val:(<i>123)})
      <-- Distribution: &Anonymous:Main([],{})
    <-- Rule Application: &Anonymous:Main([],{})
      <-- Distribution: &Anonymous:Root([],{})
    <-- Rule Application: &Anonymous:Root([],{})
  <== &Anonymous:Root([],{})
  <== &Anonymous:Main([],{})

今回はパラメータをデフォルト値で実行しましたので、123から456まで37刻みで実行されます。lsコマンドで出力ファイルを確認します。

/Basic10$ ls process/output/
Even160.txt	Even308.txt	Even456.txt	Odd197.txt	Odd345.txt
Even234.txt	Even382.txt	Odd123.txt	Odd271.txt	Odd419.txt

それぞれの値について偶数奇数の処理が実行されていることが確認できました。

基本11(ループ文2)

#基本10(ループ文)でループの作成方法を示しました。しかし、この書き方では逐次処理となってしまうのでループ数に比例して時間が掛かってしまいます。

param $max := 64

Rule Main
	output '*.txt'
Flow
	rule LoopSystem {val: 0}
End

Rule LoopSystem
	output '*.txt'
	param $val
Flow
	rule Test {val: $val}
	if $val + 1 <= $max
		rule LoopSystem {val: $val + 1}
	end
End

Rule Test
	output '{$val}.txt'
	param $val
Action
	touch {$O[1]}
End


そこでループの箇所を下記のような分割した形に書き換えます。

Rule LoopSystem
	output '*.txt'
	param $val
Flow
	rule Test {val: $val}
	if ($val * 2) + 1 <= $max
		rule LoopSystem {val: ($val * 2) + 1}
	end
	if ($val * 2) + 2 <= $max
		rule LoopSystem {val: ($val * 2) + 2}
	end
End

このようにすることでループに掛かる時間が改善されます。

あるいはconstraintを使用して下記のように記述する方法もあります。

Rule LoopSystem
	output '*.txt'
	param $val
	constraint $val <= $max
Flow
	rule Test {val: $val}
	rule LoopSystem {val: ($val * 2) + 1}
	rule LoopSystem {val: ($val * 2) + 2}
End


また、valの値を加算している箇所ですが、

	rule LoopSystem {val: ($val * 2) + 1}
	rule LoopSystem {val: ($val * 2) + 2}


シーケンスを利用して下記のように書くこともできます。(参照:PIONEの式#シーケンス

	rule LoopSystem {val: ($val * 2) + (1|2)}


問題:これを踏まえて#基本10(ループ文)のループを改善してみましょう。

解答例1

Rule LoopSystem
	output '*.txt'.all
	param $val
Flow
	if $val <= $maxval
		rule EvenOdd {val: $val}
	end
	if (($val * 2) - $minval) + $dval <= $maxval
		rule LoopSystem {val: (($val * 2) - $minval) + $dval}
	end
	if (($val * 2) - $minval) + ($dval * 2) <= $maxval
		rule LoopSystem {val: (($val * 2) - $minval) + ($dval * 2)}
	end
End

Flow内の条件を分割しています。条件式が多少複雑になっていますので、演算の順番に注意しましょう。また、現在のバージョンでの四則演算は二項演算のみの対応となっていますので、三項以上の場合は必ず括弧で括るようにして下さい。(参照:PIONEの式#四則演算における注意事項
これらのファイルはこちらからダウンロードできます。

解答例2

Rule LoopSystem
	output '*.txt'.all
	param $val
	constraint $val <= $maxval
Flow
	rule EvenOdd {val: $val}
	rule LoopSystem {val: (($val * 2) - $minval) + ($dval * (1|2))}
End

constraintとシーケンスを利用した場合はこのように書きます。

これにより#基本10(ループ文)よりも呼び出されるルールの階層は浅くなります。しかし、いずれも入れ子で呼び出されるのでルールが多くなるほど処理が重くなるでしょう。そこで1つのルールから一気にループ数だけのルールを一気に呼び出す方法を考えます。この記述方法については#基本15(ループ文3)をご覧下さい。

基本12(チケット)

PIONEの目的の一つはできる限り並列処理を行うことですので、必ずしも記述の順番通りにルールが実行される訳ではありません。ルールに順番を持たせたいときにはチケットという機能を利用します。但し、使い過ぎると待ち時間が多くなり並列計算できるPIONEの長所が活かせなくなってしまいますので注意が必要です。

下記の内容をご覧下さい。(CreateList-instant.pione)

Rule Main
	input '*.data'.all
	output 'list.txt'
Flow
	rule Touch0
	rule CreateList
End

Rule Touch0
	output '0.data'
Action
	sleep 1
	touch {$O[1]}
End

Rule CreateList
	input '*.data'.all
	output 'list.txt'
Action
	ls *.data > {$O[1]}
End

これはCreateListで.dataファイルの一覧をlist.txtに記述する処理ですが、途中のTouch0でも0.dataファイルを作成しています。この0.dataが一覧に含まれるかを検証してみましょう。

入力ファイル用のディレクトリ(input)内

/Basic12$ ls input/
1.data	2.data


まずはMainルールを上記のようにそのまま記述している場合で実行してみます。

/Basic12$ pione-client CreateList-instant.pione -i input/
  ==> &Anonymous:Root([.DS_Store,1.data,2.data],{})
    --> Rule Application: &Anonymous:Root([.DS_Store,1.data,2.data],{})
      --> Distribution: &Anonymous:Root([.DS_Store,1.data,2.data],{})
            >>> &Anonymous:Main([1.data,2.data],{})
  ==> &Anonymous:Main([1.data,2.data],{})
    --> Rule Application: &Anonymous:Main([1.data,2.data],{})
      --> Distribution: &Anonymous:Main([1.data,2.data],{})
            >>> &Anonymous:Touch0([],{})
            >>> &Anonymous:CreateList([1.data,2.data],{})
  ==> &Anonymous:Touch0([],{})
   SH ------------------------------------------------------------
   SH 	sleep 1
   SH 	touch 0.data
   SH ------------------------------------------------------------
  <== &Anonymous:Touch0([],{})
  ==> &Anonymous:CreateList([1.data,2.data],{})
   SH ------------------------------------------------------------
   SH 	ls *.data > list.txt
   SH ------------------------------------------------------------
  <== &Anonymous:CreateList([1.data,2.data],{})
      <-- Distribution: &Anonymous:Main([1.data,2.data],{})
    <-- Rule Application: &Anonymous:Main([1.data,2.data],{})
      <-- Distribution: &Anonymous:Root([.DS_Store,1.data,2.data],{})
  <== &Anonymous:Main([1.data,2.data],{})
    <-- Rule Application: &Anonymous:Root([.DS_Store,1.data,2.data],{})
  <== &Anonymous:Root([.DS_Store,1.data,2.data],{})


では出力ファイルlist.txtを確認してみます。

/Basic12$ cat process/output/list.txt 
1.data
2.data

0.dataが含まれませんでした。これはTouch0とCreateListが同時に動き、Touch0が完了する前にCreateListが実行されているため作成された0.dataがCreateListの入力ファイルとして登録されなかったからです。

では、チケットの機能を使ってMainルールを下記のようにしてみます。

Rule Main
	input '*.data'.all
	output 'list.txt'
Flow
	rule Touch0 ==> <T>
	rule <T> ==> CreateList
End

上記のようにチケット名は<>で囲んで、ルールとチケット間を==>で繋いで使用します。

チケット名を省略したい場合は下記のように記述します。

	rule Touch0 >>> CreateList


下記のように連続させることも可能です。

	rule First >>> Second >>> Third


このファイルはこちらからダウンロードできます。
実行してみます。

/Basic12$ pione-client CreateList-ticket.pione -i input/
  ==> &Anonymous:Root([.DS_Store,1.data,2.data],{})
    --> Rule Application: &Anonymous:Root([.DS_Store,1.data,2.data],{})
      --> Distribution: &Anonymous:Root([.DS_Store,1.data,2.data],{})
            >>> &Anonymous:Main([1.data,2.data],{})
  ==> &Anonymous:Main([1.data,2.data],{})
    --> Rule Application: &Anonymous:Main([1.data,2.data],{})
      --> Distribution: &Anonymous:Main([1.data,2.data],{})
            >>> &Anonymous:Touch0([],{})
  ==> &Anonymous:Touch0([],{})
   SH ------------------------------------------------------------
   SH 	sleep 1
   SH 	touch 0.data
   SH ------------------------------------------------------------
  <== &Anonymous:Touch0([],{})
      <-- Distribution: &Anonymous:Main([1.data,2.data],{})
      --> Distribution: &Anonymous:Main([1.data,2.data],{})
            >>> &Anonymous:CreateList([1.data,2.data,0.data],{})
  ==> &Anonymous:CreateList([1.data,2.data,0.data],{})
   SH ------------------------------------------------------------
   SH 	ls *.data > list.txt
   SH ------------------------------------------------------------
  <== &Anonymous:CreateList([1.data,2.data,0.data],{})
      <-- Distribution: &Anonymous:Main([1.data,2.data],{})
    <-- Rule Application: &Anonymous:Main([1.data,2.data],{})
      <-- Distribution: &Anonymous:Root([.DS_Store,1.data,2.data],{})
  <== &Anonymous:Main([1.data,2.data],{})
    <-- Rule Application: &Anonymous:Root([.DS_Store,1.data,2.data],{})
  <== &Anonymous:Root([.DS_Store,1.data,2.data],{})
/Basic12$ cat process/output/list.txt 
0.data
1.data
2.data

今度は0.dataが含まれるようになりました。

基本13(orの使用例)

inputやoutputで宣言するときにorや|で繋ぐと、指定した複数種類のファイルからいずれかを入力や出力に使用することができます。

入力ファイルでの使用例

Rule Main
	input '1.txt' or '2.txt' or '3.txt'
	output 'out.txt'
Action
	cp {$I[1]} {$O[1]}
End

上記では入力ファイルに1.txt, 2.txt, 3.txtのいずれかがあればout.txtにコピーする処理となっています。

例えば2.txtだけあった場合でも実行されます。

/Basic13$ pione-client test.pione -i ./input/
  ==> &Anonymous:Root([.DS_Store,2.txt],{})
    --> Rule Application: &Anonymous:Root([.DS_Store,2.txt],{})
      --> Distribution: &Anonymous:Root([.DS_Store,2.txt],{})
            >>> &Anonymous:Main([2.txt],{})
  ==> &Anonymous:Main([2.txt],{})
   SH ------------------------------------------------------------
   SH 	cp 2.txt out.txt
   SH ------------------------------------------------------------
  <== &Anonymous:Main([2.txt],{})
      <-- Distribution: &Anonymous:Root([.DS_Store,2.txt],{})
    <-- Rule Application: &Anonymous:Root([.DS_Store,2.txt],{})
  <== &Anonymous:Root([.DS_Store,2.txt],{})

上記の使用例は入力ファイルが1つだけあった場合のみに対応しています。複数のファイルがあった場合、それぞれ同じファイルにコピーされてしまいます。

出力ファイルでの使用例

param $Mode := 0

Rule Main
	output 'out1.txt' or 'out2.txt' or 'err.txt'
Action
	if [ {$Mode} -eq 0 ] ; then
		echo "Mode: {$Mode}" > {$O[1].nth(1)}
	elif [ {$Mode} -eq 1 ] ;then
		echo "Mode: {$Mode}" > {$O[1].nth(2)}
	else
		echo "Error: Mode {$Mode} is not Supported!" > {$O[1].nth(3)}
	fi
End

これはパラメータModeが1のときはout1.txtを作成し、2のときはout2.txtを作成し、それ以外についてはエラーを残す処理となっています。out1.txt, out2.txt, err.txtのいずれかが出力されることで処理が完了しますので、状況に応じて作成するファイルを変えたいときに有効です。.nth()は引数で指定した要素を取り出すシーケンスのメソッドです。(参照:PIONEの式#シーケンス

デフォルト(Mode=1)で実行してみます。

/Basic13$ pione-client OutputOr.pione
  ==> &Anonymous:Root([],{})
    --> Rule Application: &Anonymous:Root([],{})
      --> Distribution: &Anonymous:Root([],{})
            >>> &Anonymous:Main([],{})
  ==> &Anonymous:Main([],{})
   SH ------------------------------------------------------------
   SH 	if [ 1 -eq 1 ] ; then
   SH 		echo "Mode: 1" > out1.txt
   SH 	elif [ 1 -eq 2 ] ;then
   SH 		echo "Mode: 1" > out2.txt
   SH 	else
   SH 		echo "Error: Mode 1 is not Supported!" > err.txt
   SH 	fi
   SH ------------------------------------------------------------
      <-- Distribution: &Anonymous:Root([],{})
    <-- Rule Application: &Anonymous:Root([],{})
  <== &Anonymous:Root([],{})
  <== &Anonymous:Main([],{})
/Basic13$ cat process/output/out1.txt 
Mode: 1

out1.txtが作成されて完了します。

Mode=0で実行してみます。

/Basic13$ pione-client OutputOr.pione --params={Mode:0}
"{Mode:0}"
  ==> &Anonymous:Root([],{})
    --> Rule Application: &Anonymous:Root([],{})
      --> Distribution: &Anonymous:Root([],{})
            >>> &Anonymous:Main([],{})
  ==> &Anonymous:Main([],{})
   SH ------------------------------------------------------------
   SH 	if [ 0 -eq 1 ] ; then
   SH 		echo "Mode: 0" > out1.txt
   SH 	elif [ 0 -eq 2 ] ;then
   SH 		echo "Mode: 0" > out2.txt
   SH 	else
   SH 		echo "Error: Mode 0 is not Supported!" > err.txt
   SH 	fi
   SH ------------------------------------------------------------
      <-- Distribution: &Anonymous:Root([],{})
  <== &Anonymous:Main([],{})
    <-- Rule Application: &Anonymous:Root([],{})
  <== &Anonymous:Root([],{})
/Basic13$ cat process/output/err.txt 
Error: Mode 0 is not Supported!

err.txtが作成されて完了します。

これらのファイルはこちらからダウンロードできます。

基本14(例外)

PIONEでも入出力ファイルにワイルドカード*を使用して全ての該当ファイルを対象とできますが、exceptを使用することにより対象外とするファイルを指定できます。メソッドexcept, exceptionsについてはPIONEの式#データ表現型を参照して下さい。

Rule Main
	input 'AllData.txt'
	output '*.txt'.all.except($I[1])
Flow
	rule GetLine {filename:"Data1", line:1}
	rule GetLine {filename:"Data2", line:2}
	rule GetLine {filename:"Data3", line:3}
End

Rule GetLine
	input 'AllData.txt'
	output '{$filename}.txt'
	param $filename
	param $line
Action
	head -{$line} {$I[1]} | tail -1 > {$O[1]}
End

上記はAllData.txtから1, 2, 3行目を指定した.txtファイルに出力する処理です。出力ファイルは全ての.txtとしていますが、exceptによってAllData.txtを対象外としています。対象外としていない場合はAllData.txtが出力ファイルの条件を満たしていますので、発火しません。
このファイルはこちらからダウンロードできます。

AllData.txtを下記の内容として実行してみます。

ABC
DEF
GHI


実行結果

/Basic14$ pione-client DivideText.pione -i DivideTextInput/
  ==> &Anonymous:Root([.DS_Store,AllData.txt],{})
    --> Rule Application: &Anonymous:Root([.DS_Store,AllData.txt],{})
      --> Distribution: &Anonymous:Root([.DS_Store,AllData.txt],{})
            >>> &Anonymous:Main([AllData.txt],{})
  ==> &Anonymous:Main([AllData.txt],{})
    --> Rule Application: &Anonymous:Main([AllData.txt],{})
      --> Distribution: &Anonymous:Main([AllData.txt],{})
            >>> &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data1"),line:(<i>1)})
            >>> &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data2"),line:(<i>2)})
  ==> &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data1"),line:(<i>1)})
   SH ------------------------------------------------------------
   SH 	head -1 AllData.txt | tail -1 > Data1.txt
   SH ------------------------------------------------------------
            >>> &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data3"),line:(<i>3)})
  <== &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data1"),line:(<i>1)})
  ==> &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data2"),line:(<i>2)})
   SH ------------------------------------------------------------
   SH 	head -2 AllData.txt | tail -1 > Data2.txt
   SH ------------------------------------------------------------
  <== &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data2"),line:(<i>2)})
  ==> &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data3"),line:(<i>3)})
   SH ------------------------------------------------------------
   SH 	head -3 AllData.txt | tail -1 > Data3.txt
   SH ------------------------------------------------------------
  <== &Anonymous:GetLine([AllData.txt],{filename:(<s>"Data3"),line:(<i>3)})
      <-- Distribution: &Anonymous:Main([AllData.txt],{})
    <-- Rule Application: &Anonymous:Main([AllData.txt],{})
      <-- Distribution: &Anonymous:Root([.DS_Store,AllData.txt],{})
  <== &Anonymous:Main([AllData.txt],{})
    <-- Rule Application: &Anonymous:Root([.DS_Store,AllData.txt],{})
  <== &Anonymous:Root([.DS_Store,AllData.txt],{})
/Basic14$ cat process/output/Data1.txt 
ABC
/Basic14$ cat process/output/Data2.txt 
DEF
/Basic14$ cat process/output/Data3.txt 
GHI

1, 2, 3行目の内容がそれぞれのファイルに出力されました。

.except($I[1])を除いた場合は発火しません。

/Basic14$ pione-client DivideText.pione -i DivideTextInput/
  ==> &Anonymous:Root([.DS_Store,AllData.txt],{})
    --> Rule Application: &Anonymous:Root([.DS_Store,AllData.txt],{})
    <-- Rule Application: &Anonymous:Root([.DS_Store,AllData.txt],{})
  <== &Anonymous:Root([.DS_Store,AllData.txt],{})


また、exceptionsを使用すると設定している例外をリストで得ることができます。

Rule Main
	input '1.txt'
	input '2.txt'
	input '3.txt'
	output '*.txt'.all.except($I[1] or $I[2] or $I[3])
Action
	for data in {$O[1].exceptions()}
	do
		cat $data >> AllData.txt
		wc $data >> AllInfo.txt
	done
End

上記の例ではoutputに例外が$I[1] or $I[2] or $I[3]と設定されているので、{$O[1].exceptions()}では1.txt 2.txt 3.txtのリストとして得られるので、for文がこれらのファイルによって回ることになります。

基本15(ループ文3)

#基本10(ループ文)#基本11(ループ文2)にてループ文を記述する方法を示しました。しかし、いずれも多くのルールを入れ子で呼ぶことになり、ループ数が多くなると入出力ファイルの引き継ぎも多くなり時間が掛かってしまいます。今回は一つのルールから全てのルールを呼び出すようにしてループを行うようにしてみます。

下記の整数型メソッドuptoを使用すると、1つのルールからパラメータを一気に設定して各々の値でルールを実行することができます。uptoは引数の値まで1ずつ加算しながら整数を返すメソッドです。(参照:PIONEの式#整数型(integer)

param $min := 0
param $max := 64

Rule Main
	output '*.txt'
Flow
	rule Test {val: $min.upto($max)}
End

Rule Test
	output '{$val}.txt'
	param $val
Action
	touch {$O[1]}
End


またdowntoを使用する方法もあります。uptoは引数の値まで1ずつ減算しながら整数を返すメソッドです。(参照:PIONEの式#整数型(integer)

	rule Test {val: $max.downto($min)}


uptoを利用すると、#基本10(ループ文)は下記のようになります。

param $maxval := 456
param $minval := 123
param $dval := 37

Rule Main
	output '*.txt'.all
Flow
	rule EvenOdd {val: $minval + ($dval * (0.upto(($maxval - $minval)/$dval)))}
End

Rule EvenOdd
	output '*.txt'
	param $val
Flow
	if $val % 2 == 0
		rule Even {val: $val}
	else
		rule Odd {val: $val}
	end
End

Rule Even
	output '*.txt'
	param $val
Action
	echo "{$val} is even." >> Even{$val}.txt;
End

Rule Odd
	output '*.txt'
Action
	echo "{$val} is odd." >> Odd{$val}.txt;
End


valに設定する式が複雑な場合は下記のように一旦、変数束縛で計算したものを利用する方法もあります。

param $maxval := 456
param $minval := 123
param $dval := 37
$loop := ($maxval - $minval)/$dval

Rule Main
	output '*.txt'.all
Flow
	rule EvenOdd {val: $minval + ($dval * (0.upto($loop))}
End

-以下略-

上記の場合は変数loopにパラメータから計算したループ数を設定しています。

応用

さて、ここまでの基本を組み合わせて、Eosのコマンドを使用する問題を考えてみましょう。

応用1(フローの制御)

問題1

*.mrc(mrcImage)と*.prametersで指定される入力ファイルがあります。
それぞれの*.mrcファイルから最大値を中心として切り出した*.roiを出力するCenterGet.pioneを考えてみましょう。
但し、*.parametersは以下のフォーマットで切り出しサイズ(Sx, Sy, Sz)を格納しているファイルとします。


*.parametersのフォーマット
Sx Sy Sz



 応用問題1:上記のCenterGet.pioneを作成してみましょう。



解答例

Rule Main
	input '*.mrc'.all
	output '*.roi'
Flow
	rule First
	rule Second
End

Rule First
	input '*.mrc'
	output '{$I[1]}.info'
Action
	mrcImageInfo -I -i {$I[1]} \
	| head -2 | tail -1 \
	| awk '{printf("%s %s %s", $3, $4, $5)}' \
	| tr -c '[0-9]' ' ' \
	> {$O[1]}
End

Rule Second
	input '*.mrc'
	input '{$I[1]}.info'
	input '{$I[1][1]}.parameters'
	output '{$I[1][1]}.roi'
Action
	Center_x=$(awk '{printf("%s\n", $1)}' {$I[2]})
	Center_y=$(awk '{printf("%s\n", $2)}' {$I[2]})
	Center_z=$(awk '{printf("%s\n", $3)}' {$I[2]})
	N_x=$(awk '{printf("%s\n", $1)}' {$I[3]})
	N_y=$(awk '{printf("%s\n", $2)}' {$I[3]})
	N_z=$(awk '{printf("%s\n", $3)}' {$I[3]})
	mrcImageCenterGet -i {$I[1]} -o {$O[1]} \
		-Cx $Center_x -Cy $Center_y -Cz $Center_z \
		-Nx $N_x -Ny $N_y -Nz $N_z
End

このファイルはこちらからダウンロードできます。

主にmrcImageCenterGetを用いて切り出しを行っています。

Main: .mrcファイルについてFirst, Secondの処理を行います。
First: .mrcファイルの最大値の座標を得て、.mrc.infoファイルに格納します。
Second: .mrcから.mrc.infoの座標を中心に.parametersのサイズで切り出しを行います。


実行結果

入力ファイル
mrcファイル1

Input-1VOM.png
xy平面

Input1-1VOM.png
yz平面

Min:               0 (0, 0, 0)
Max:         3398.12 (23, 55, 41)
Mean:          72.129
SD:          294.805
SE:         0.368507
Sum:     4.61626e+07

最大値の座標: (23, 55, 41)

mrcファイル2

Input-1VOM-N.png
xy平面

Input1-1VOM-N.png
yz平面

Min:        -2390.42 (77, 14, 69)
Max:         4374.62 (17, 62, 46)
Mean:         72.1487
SD:           577.82
SE:         0.722275
Sum:     4.61752e+07

最大値の座標: (17, 62, 46)

parametersファイル1, 2の両方を下記に設定して実行してみます。

40 40 50


コマンド

pione-client CenterGet.pione -i CenterGetInput/ -b CenterGet


実行結果
1VOM.roi, 1VOM-N.roiのファイルが出力されます。

mrcImageInfoを使って、最大値の座標が中心になっているか確認してみます。

/Advanced1$ mrcImageInfo -I -i CenterGet/output/1VOM.roi
Min:               0 (0, 0, 0)
Max:         3398.12 (19, 19, 24)
Mean:         241.805
SD:          500.988
SE:          1.77126
Sum:     1.93444e+07
/Advanced1$ mrcImageInfo -I -i CenterGet/output/1VOM-N.roi
Min:         -1893.8 (0, 4, 33)
Max:         4374.62 (19, 19, 24)
Mean:          157.55
SD:          648.124
SE:          2.29147
Sum:      1.2604e+07

最大値を中心に切り出していることが分かります。

補足(ファイルの確認用コマンド)

出力したファイルと入力ファイルの違いを見るために後処理を追加します。

作成例

Rule Main
	input '*.mrc'.all
	output '*.roi'
	output '*.tiff'
Flow
	rule First
	rule Second
	rule Final
End

-中略-

Rule Final
	input '*.mrc'
	input '{$I[1][1]}.roi'
	output '{$I[1]}-1.tiff'
	output '{$I[2]}-1.tiff'
	output '{$I[1]}-2.tiff'
	output '{$I[2]}-2.tiff'
Action
	mrcImageProjection -i {$I[1]} -o {$I[1]}.2d
	mrc2tiff -i {$I[1]}.2d -o {$O[1]}
	mrcImageProjection -i {$I[1]} -o {$I[1]}.2d1 -m 1
	mrc2tiff -i {$I[1]}.2d1 -o {$O[3]}
	mrcImageProjection -i {$I[2]} -o {$I[2]}.2d
	mrc2tiff -i {$I[2]}.2d -o {$O[2]}
	mrcImageProjection -i {$I[2]} -o {$I[2]}.2d1 -m 1
	mrc2tiff -i {$I[2]}.2d1 -o {$O[4]}
End

これにより入力ファイル(mrc), 出力ファイル(roi)についてxy平面とyz平面へ投影した画像をtiffファイルで出力します。

出力ファイル
roiファイル1

Outdata-PIONE-Advanced1-1.png
xy平面

Outdata1-PIONE-Advanced1-1.png
yz平面


roiファイル2

Outdata-PIONE-Advanced1-2.png
xy平面

Outdata1-PIONE-Advanced1-2.png
yz平面


後処理をしたくないときはMainのoutputとFlow内をコメントアウトするだけです。

Rule Main
	input '*.mrc'.all
	output '*.roi'
#	output '*.tiff'
Flow
	rule Initial
	rule First
	rule Second
#	rule Final
End

-後略-


補足(デフォルトサイズの設定)

.parametersなどのファイルがない場合にパラメータ入力で設定したい場合の前処理を追加します。

作成例

Basic Param
	$Nx := 40
	$Ny := 40
	$Nz := 50
End

Rule Main
	input '*.mrc'.all
	input '*.parameters'
	output '*.roi'
	output '*.tiff'
Flow
	rule Initial
	rule First
	rule Second
	rule Final
End

Rule Initial
	input '*.mrc'
	output '{$I[1][1]}.parameters'
Action
	echo "{$Nx} {$Ny} {$Nz}" > {$O[1]}
End

-後略-

このようにすると、.parametersがない場合にはオプション--paramsまたはデフォルト値(40, 40, 50)によってサイズを設定することができます。但し、入力ファイルに.parametersが1つもない場合は動きません。その場合はtouchなどを使用して.parameterを1つは用意する必要があります。

1VOM.parametersのみ下記のように設定してみます。

64 64 64


Nx=80のみをパラメータとしてコマンド入力してみます。

/Advanced1$ pione-client CenterGet.pione -i ${EOS_HOME}/tutorial/SampleData/ -b CenterGet --params="{Nx:80}"

出力ファイル
1VOM.roiはファイルに従ったサイズになっています。

Outdata-PIONE-Advanced1-3.png
xy平面

Outdata1-PIONE-Advanced1-3.png
yz平面


1VOM-N.roiはNxがコマンドの引数、Ny, Nzはデフォルト値のパラメータによるサイズになります。

Outdata-PIONE-Advanced1-4.png
xy平面

Outdata1-PIONE-Advanced1-4.png
yz平面


応用2(ウェブページでパラメータを設定しながらコマンドを実行)

 次は#基本8(インタラクティブ操作)のようにウェブページの操作を行い、Eosのコマンドを実行する処理を作ってみましょう。

問題2

2Dファイル(mrcImage)を入力ファイルとし、ローパスフィルタ(mrcImageLowPassFilter)をウェブページの操作によって実行する次のコマンドを考えます。

  • 入力ファイルはファイル選択画面から自由に選べるようにし、オプションの値も画面操作で設定できるようにします。
  • コマンド実行後は入力ファイルと出力ファイルの画像ファイルをそれぞれ表示して比較できるようにします。
  • この画面を見ながら再設定するか、終了するかを選び、終了後に最後の変換ファイルを出力ファイルとして得られるようにします。



 応用問題2:上記の機能を持つパッケージLowPassFilter.ppgを作成してみましょう。



解答例

PIONE定義書htmlファイル、cgiファイルをそれぞれ作成します。

PIONE定義書

メイン(Main.pione)の処理を定義します。今回は設定のほとんどをウェブページに委ねるのでここでは出力ファイル名のみ定義しています。

Rule Main
	output '*.lpf'.all
Flow
	rule Interaction
End


インタラクティブ(Interaction.pione)の処理を定義します。このファイルは再利用できるように拡張子が付いた全てのファイルを呼び出し元に出力できるようにしています。

Rule Interaction
	output '*.*'.all
Action
	# build public directory for pione-interactive
	mkdir public
	cp etc/* public
	cp bin/* public

	# start interactive operation
	pione-interactive browser --public public

	cp public/* .
End


パッケージ用のアノテーション(Annotation.pione)を定義します。

.@ PackageName	:: "LowPassFilter"
.@ Editor		:: "Kinoshita"
.@ Tag			:: "v0.1.0"


htmlファイル

Index.htmlはコマンド選択画面にしています。ここから実行したいコマンドを選択します。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>InteractiveCommand Index Page</title>
	</head>
	<body>
		<a href="mrcImageLowPassFilter.html">mrcImageLowPassFilter</a>
		<br>
		<br>
		<a href="?pione-action=finish">終了</a>
	</body>
</html>


mrcImageLowPassFilter.htmlでコマンドや入力ファイルなどのオプションを設定できるようにします。開始ボタンでmrcImageLowPassFilterが実行されます。

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>mrcImageLowPassFilter</title>
	</head>
	<body>
		<form action="mrcImageLowPassFilter.cgi" method="post" enctype="multipart/form-data">
			<button type="submit">開始</button>
			<br>
			<table>
				<tr>
					<td>
						<input type="file" name="InputFile"/>
					</td>
					<td>
						入力ファイル名(-i)
					</td>
				</tr>
				<tr>
					<td>
						<input type="text" name="OutputFile" value="outdata.lpf"/>
					</td>
					<td>
						出力ファイル名(-o)
					</td>
				</tr>
				<tr>
					<td>
						<input type="text" name="HalfValuePoint" value="1.0"/>
					</td>
					<td>
						HalfValuePoint[A-1] (強度を半分に落とす空間周波数を示す)(-hvp)
					</td>
				</tr>
				<tr>
					<td>
						<input type="text" name="Width" value="1.0"/>
					</td>
					<td>
						空間周波数のcos関数の幅を設定(-w)
					</td>
				</tr>
				<tr>
					<td>
						<select name="Mode">
							<option value="1">1: ステップフィルタ</option>
							<option value="2">2: cosフィルタ</option>
							<option value="3">3: expフィルタ</option>
							<option value="4">4: ガウシアンフィルタ</option>
							<option value="5">5: ローレンツ型フィルタ</option>
						</select>
					</td>
					<td>
						モード(-m)
					</td>
				</tr>
			</table>
		</form>
		<br>
		<br>
		<a href="?pione-action=finish">終了</a>
	</body>
</html>


cgiファイル

mrcImageLowPassFilter.cgi

#!/usr/bin/env ruby

require 'cgi'

cgi = CGI.new

### Header
HTMLstr = ""
HTMLstr += "<!DOCTYPE html>"
HTMLstr += "<html>"
HTMLstr += "<head>"
HTMLstr += "<title>mrcImageLowPassFilter</title>"
HTMLstr += "</head>"

HTMLstr += "<body>"

### Close or Back
HTMLstr += '<a href="?pione-action=finish">Close</a>'
HTMLstr += "<br>"
HTMLstr += '<a href="mrcImageLowPassFilter.html">Back</a>'
HTMLstr += "<br>"
HTMLstr += "<br>"


# Main Process

## Query to Parameters

### Copy InputFile
fpQueryInputFile = cgi.params['InputFile'][0]
strInputFile = fpQueryInputFile.original_filename
fpInputFile = open(strInputFile, "wb")
fpInputFile.write(fpQueryInputFile.read)
fpInputFile.close

### Other Query
strOutputFile = cgi['OutputFile']
strHalfValuePoint = cgi['HalfValuePoint']
strWecth = cgi['Width']
strMode = cgi['Mode']

### mrcImageLowPassFilter
command = "mrcImageLowPassFilter"
command += ' -i "' + strInputFile + '"'
command += ' -o "' + strOutputFile + '"'
command += ' -hvp "' + strHalfValuePoint + '"'
command += ' -w "' + strWecth + '"'
command += ' -m "' + strMode + '"'
system(command)

### mrc2gif
#### For input
strGifInputFile = strInputFile + ".gif"
command = "mrc2gif"
command += ' -i "' + strInputFile + '"'
command += ' -o "' + strGifInputFile + '"'
system(command)

#### For output
strGifOutputFile = strOutputFile + ".gif"
command = "mrc2gif"
command += ' -i "' + strOutputFile + '"'
command += ' -o "' + strGifOutputFile + '"'
system(command)

### mrcImageInfo
#### For input
strInfoInputFile = strInputFile + ".info"
command = "mrcImageInfo"
command += ' -I'
command += ' -i "' + strInputFile + '"'
command += ' -o "' + strInfoInputFile + '"'
system(command)

#### For output
strInfoOutputFile = strOutputFile + ".info"
command = "mrcImageInfo"
command += ' -I'
command += ' -i "' + strOutputFile + '"'
command += ' -o "' + strInfoOutputFile + '"'
system(command)


## View as HTML Statement

### Table
HTMLstr += "<table>"

#### Title
HTMLstr += "<tr>"
HTMLstr += "<td>" + strInputFile + "</td>"
HTMLstr += "<td>" + strOutputFile + "</td>"
HTMLstr += "</tr>"

#### GifImage
##### For Input
HTMLstr += "<tr>"
HTMLstr += "<td>"
HTMLstr += '<img src="' + strGifInputFile + '">'
HTMLstr += "</td>"
##### For Output
HTMLstr += "<td>"
HTMLstr += '<img src="' + strGifOutputFile + '">'
HTMLstr += "</td>"
HTMLstr += "</tr>"

#### mrcImageInfo
##### For Input
HTMLstr += "<tr>"
HTMLstr += "<td>"
fpInfoInputFile = open(strInfoInputFile, "r")
fpInfoInputFile.each do |line|
	HTMLstr += line + "<br>"
end
fpInfoInputFile.close
HTMLstr += "</td>"
##### For Output
HTMLstr += "<td>"
fpInfoOutputFile = open(strInfoOutputFile, "r")
fpInfoOutputFile.each do |line|
	HTMLstr += line + "<br>"
end
fpInfoOutputFile.close
HTMLstr += "</td>"
HTMLstr += "</tr>"

HTMLstr += "</table>"


HTMLstr += "</body>"
HTMLstr += "</html>"

# Output as html
cgi.out(type: "text/html") do
	HTMLstr
end

htmlヘッダなどの書き込み、クエリを変数やファイルに変換、Eosのコマンド実行、実行結果をhtml化の順に記述しています。このコードではmrcImageLowPassFilterの結果をgif画像とmrcImageInfo(-I)の情報を表示して比較できるようにしています。

これらのファイルはこちらからダウンロードできます。

パッケージ作成

コードが完成したらパッケージ作成を行います。

/PIONE$ pione package build Advanced2
info: update the package info file: local:/Eos/tutorial/SampleCode/PIONE/Advanced2/pione-package.json
info: Package local:/Eos/tutorial/SampleCode/PIONE/LowPassFilter(Kinoshita)+v0.1.0.ppg has been built successfully.


実行結果

では、動作させてみましょう。
PIONE Webclientのジョブページで実行し、Interactionを選択してindexページを開きます。ここでmrcImageLowPassFilterを選択すると設定画面が表示されます。ここでは入力ファイルや設定を変更しながらコマンドの実行を試すことができます。終了やCloseを選択すると、最後に処理を行った結果で出力ファイルが残ります。また、出力ファイル名を変更すると複数のファイルを残すことができます。

入力ファイル(2D)

Input-1VOM-N-2D.png

最小

最大
平均値
標準偏差

標準誤差

-18651.7 (10, 1, 0)

52942.7 (24, 39, 0)
7214.87
10067.6

125.845


実行例1
Outdata-PIONE-Advanced2-1.png > Outdata1-PIONE-Advanced2-1.png
実行例2
Outdata-PIONE-Advanced2-2.png > Outdata1-PIONE-Advanced2-2.png
実行例3
Outdata-PIONE-Advanced2-3.png > Outdata1-PIONE-Advanced2-3.png


終了後にResult Filesからファイルをダウンロードすると、outputディレクトリにて最後に変換した.lpfファイルを得ることができます。あるいはこのPIONE定義書に処理を追加して、作成した.lpfファイルを入力ファイルとして使用することもできます。

補足(不要ファイルの削除)

今回の処理は操作によって自由にファイルが作成できます。しかし、中には不要なファイルも出てくる場合もあります。そこで、補足処理としてサーバ上のファイルリストを表示し、不要ファイルを削除できるようにしてみましょう。新しく下記のファイルを追加します。なお、pione-action=...についてはインタラクションAPIに詳細を記載しています。

/etc/FileDelete.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>InteractiveCommand Index Page</title>
	</head>
	<body>
		<a href="index.html">戻る</a><br>
		<a href="?pione-action=finish">終了</a><br>
		<br>
		<form action="FileDelete.cgi" method="post" enctype="multipart/form-data">
			削除ファイル名<br>
			<input type="text" name="DeleteFile" value=""/>
			<button type="submit">削除</button>
		</form>
		ファイルリスト<br>
		<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
		<div id="textDiv"></div>
		<script type="text/javascript">
			var div = document.getElementById("textDiv");
			div.textContent = "";
			$.getJSON("./", {"pione-action": "list"}, function(data){
				$.each(data, function() {
					strOut = ".lpf"
					if( this.name.indexOf(strOut) + strOut.length == this.name.length ){
						div.textContent += this.name +"\n";
					}
				});
			});
		</script>
	</body>
</html>

ファイルリストではpione-action=listによって得たファイルリストの中から最後が.lpfで終わるファイルのみを出力するようにしています。このリストを見ながら削除するファイルをテキストボックスに書き込んで、削除ボタンを押すと削除処理(FileDelete.cgi)が行われます。

/bin/FileDelete.cgi

#!/usr/bin/env ruby

require 'cgi'

cgi = CGI.new

### Header
HTMLstr = ""
HTMLstr += "<!DOCTYPE html>"
HTMLstr += "<html>"
HTMLstr += "<head>"
HTMLstr += "<title>FileDelete</title>"

## Query to Parameters
strDeleteFile = cgi['DeleteFile']

## View as HTML Statement
HTMLstr += '<meta http-equiv="refresh" content="0;URL=./' + strDeleteFile + '?pione-action=delete">'

HTMLstr += "</head>"
HTMLstr += "</html>"

# Output as html
cgi.out(type: "text/html") do
	HTMLstr
end

ヘッダにてDeleteFileを削除する操作(pione-action=delete)を行っています。

さらに出来上がったページへアクセスするためにindex.htmlのbody内に下記の内容を追加します。

		<a href="FileDelete.html">FileDelete</a><br>


これにより不要ファイルの削除を行うことができます。
Outdata-PIONE-Advanced2-4.png
上記の例ではOutdata.lpfを削除しています。

Outdata-PIONE-Advanced2-5.png
削除完了画面が表示されるので、ブラウザのバックボタンで戻ります。

Outdata-PIONE-Advanced2-6.png
画面表示を更新するとOutdata.lpfが消えていることが確認できます。また、インタラクティブ操作を終了し、ファイルをダウンロードするとOudata.lpfが無いことも確認できます。これにより不要ファイルを削除できたことが分かりました。

応用3(参照像の作成)

 今回はループ文を使用した例を示します。

問題3

 入力ファイルを参照用の3次元画像(.ref3d)として、指定した範囲の角度で参照像(.ref2d)を作成する処理を考えます。PIONE Webclientを利用し、作成した各参照像の画像を確認しながら、ユーザ操作によって適切な角度を決定できるようにします。


 応用問題3:上記の機能を持つパッケージRef3DtoRef2D.ppgを作成してみましょう。



解答例

PIONE定義書

 ユーザ操作によって3次元画像と投影する角度を設定し、そこから参照像を作成して、結果を画像として確認し、決定かやり直しかを選択できる流れとしています。

Annotation.pione

.@ PackageName	:: "Ref3DtoRef2D"
.@ Editor		:: "Kinoshita"
.@ Tag			:: "v0.2.0"


Main.pione

Rule Main
	output '*.ref3d'
	output '*.ref2d'
	output '*.mon'
	output '*.gif'
Flow
	rule Start
	rule SubMain
	rule Result
	rule Finish
End

Rule Start
	output 'Start0!Flag'
Action
	touch {$O[1]}
End

Rule SubMain
	input 'Start*!Flag'
	output '*.*'.all
Flow
	rule Interaction {val : '{$I[1][1]}'.str().i()}
	rule Ref3DtoRef2D {val : '{$I[1][1]}'.str().i()}
End

Rule Finish
	input 'Finish*!Flag'
	input '*.*-{$I[1][1]}'
	output '{$I[2][1]}.{$I[2][2]}'
Action
	cp {$I[2]} {$O[1]}
End

SubMainルールにて角度と3次元画像の設定とその2次元画像の作成を実行します。このルールはStartN!Flagで発火し、最初はStartルールで、やり直すときはResultルールでそれぞれStartN!Flagを作成します。このときのNはやり直しのために設定した整数で、前に作成されたファイルとこれから作成するファイルが混ざらないようにするために設定しています。ルールFinishで決定したときのファイルのみを取り出します。

Interaction.pione

Rule Interaction
	input 'Start{$val}!Flag'
	output '*.*'.all
	param $val
Action
	# build public directory for pione-interactive
	mkdir public
	cp etc/* public
	cp bin/* public

	# start interactive operation
	pione-interactive browser --public public

	cp public/* .
	for file in $(ls *Mode *Rot1 *Rot2 *Rot3 *.ref3d) ;
	do
		mv "$file" "$file-{$val}";
	done
End

やり直しに対応できるように処理の最後で作成されたファイルに番号を付けています。

Ref3DtoRef2D.pione

Rule Ref3DtoRef2D
	input '*!*!*.!Rot1-{$val}'
	input '*!*!*.!Rot2-{$val}'
	input '*!*!*.!Rot3-{$val}'
	input '*.!Mode-{$val}'
	input '*.ref3d-{$val}'
	output '*.ref2d-{$val}'.all
	output 'Ref3DtoRef2D.info-{$val}'
	$min1 := '{$I[1][1]}'.str().f()
	$max1 := '{$I[1][2]}'.str().f()
	$delta1 := '{$I[1][3]}'.str().f()
	$loop1 := (($max1 - $min1) / $delta1).i()
	$min2 := '{$I[2][1]}'.str().f()
	$max2 := '{$I[2][2]}'.str().f()
	$delta2 := '{$I[2][3]}'.str().f()
	$loop2 := (($max2 - $min2) / $delta2).i()
	$min3 := '{$I[3][1]}'.str().f()
	$max3 := '{$I[3][2]}'.str().f()
	$delta3 := '{$I[3][3]}'.str().f()
	$loop3 := (($max3 - $min3) / $delta3).i()
	$mode := '{$I[4][1]}'.str()
	param $val
Flow
	rule One3Dto2D {rot1: $min1 + ((0.upto($loop1)).f() * $delta1), rot2: $min2 + ((0.upto($loop2)).f() * $delta2), rot3: $min3 + ((0.upto($loop3)).f() * $delta3), mode : $mode, val : $val}
	rule Info3Dto2D {val : $val}
End

Rule Info3Dto2D
	input '*.ref2d-{$val}'.all
	output 'Ref3DtoRef2D.info-{$val}'
	param $val
Action
	for file in $(ls *.ref2d-{$val})
	do
		echo "./$file" >> {$O[1]}
	done
End

Rule One3Dto2D
	input '*.ref3d-{$val}'
	output '*.ref2d-{$val}'.all
	param $rot1
	param $rot2
	param $rot3
	param $mode
	param $val
Action
	output="{$I[1][1]}-{$mode}-{$rot1}-{$rot2}-{$rot3}.ref2d-{$val}"
	mrc3Dto2D	-i {$I[1]} -o $output \
				-Rot1 {$rot1} {$rot1} 1 \
				-Rot2 {$rot2} {$rot2} 1 \
				-Rot3 {$rot3} {$rot3} 1 \
				-EulerMode {$mode};
End

ルールRef3DtoRef2Dは大きくFlowLoop3Dto2D1とOne3Dto2Dの2つのルールに分かれます。ここでもやり直しに対応するためにファイルの末尾に番号を付けます。ルールInfo3Dto2Dでは画像作成のために2次元画像の一覧を作成します。ルールOne3Dto2D1ではuptoによるループを利用して3次元画像からmrc3Dto2Dによって2次元画像を次々に作成します。このとき3軸毎に回転角度をそれぞれuptoで設定しています。

Result.pione

Rule Result
	input 'Ref3DtoRef2D.info-*'
	input '*.ref2d-{$I[1][1]}'.all
	output '*!Flag'
	output 'Ref3DtoRef2D.mon-{$I[1][1]}'
	output 'Ref3DtoRef2D.gif-{$I[1][1]}'
Flow
	rule Ref2DtoGIF {val : '{$I[1][1]}'.str().i()}
	rule ResultCheck {val : '{$I[1][1]}'.str().i()}
End

Rule Ref2DtoGIF
	input 'Ref3DtoRef2D.info-{$val}'
	input '*.ref2d-{$val}'.all
	output 'Ref3DtoRef2D.mon-{$val}'
	output 'Ref3DtoRef2D.gif-{$val}'
	param $val
Action
	mrcImageMontageCreate -i {$I[1]} -o {$O[1]}
	mrc2gif -i {$O[1]} -o {$O[2]}
End

Rule ResultCheck
	input 'Ref3DtoRef2D.gif-{$val}'
	output '*!Flag'
	param $val
Action
	# build public directory for pione-interactive
	mkdir public
	cp etc/index2.html public/index.html
	cp bin/* public
	cp {$I[1]} public/Ref3DtoRef2D.gif

	# start interactive operation
	pione-interactive browser --public public

	cp public/* .
	if [ -e "Finish!Flag" ]; then
		mv "Finish!Flag" "Finish{$val}!Flag";
	elif [ -e "Start!Flag" ]; then
		mv "Start!Flag" "Start{$val + 1}!Flag";
	fi
End

ルールResultはRef2DtoGIFとResultCheckに分かれます。ルールRef2DtoGIFでは各2次元像をひとまとめにしたgif画像を作成します。ルールResultCheckではgif画像をindex2.htmlのページに表示して、ユーザ操作にて決定のときはFinishN!Flagをやり直しのときはStartN!Flagを作成するようにしています。

htmlファイル

index.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>InteractiveCommand Index Page</title>
	</head>
	<body>
		<a href="Ref3DtoRef2D.html">Ref3DtoRef2D</a><br>
		<br>
		<a href="?pione-action=finish">終了</a>
	</body>
</html>


Ref3DtoRef2D.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>Ref3DtoRef2D</title>
	</head>
	<body>
		<form action="Ref3DtoRef2D.cgi" method="post" enctype="multipart/form-data">
			<button type="submit">開始</button>
			<br>
			<table>
				<tr>
					<td>
						<input type="file" name="InputFile"/>
					</td>
					<td>
						入力ファイル名(.ref3d)
					</td>
				</tr>
				<tr>
					<td>
						<select name="Mode1">
							<option value="X">X</option>
							<option value="Y">Y</option>
							<option value="Z">Z</option>
						</select>
						<select name="Mode2">
							<option value="O">O</option>
							<option value="E">E</option>
						</select>
						<select name="Mode3">
							<option value="Y">Y</option>
							<option value="N">N</option>
						</select>
						<select name="Mode4">
							<option value="S">S</option>
							<option value="R">R</option>
						</select>
					</td>
					<td>
						回転モード
					</td>
				</tr>
			</table>
			<table>
				<tr>
					<td>
						
					</td>
					<td>
						最小値
					</td>
					<td>
						最大値
					</td>
					<td>
						加算値
					</td>
				</tr>
				<tr>
					<td>
						角度1
					</td>
					<td>
						<input type="text" name="RotMin1" value="0"/>
					</td>
					<td>
						<input type="text" name="RotMax1" value="359"/>
					</td>
					<td>
						<input type="text" name="RotDelta1" value="30"/>
					</td>
				</tr>
				<tr>
					<td>
						角度2
					</td>
					<td>
						<input type="text" name="RotMin2" value="0"/>
					</td>
					<td>
						<input type="text" name="RotMax2" value="359"/>
					</td>
					<td>
						<input type="text" name="RotDelta2" value="30"/>
					</td>
				</tr>
				<tr>
					<td>
						角度3
					</td>
					<td>
						<input type="text" name="RotMin3" value="0"/>
					</td>
					<td>
						<input type="text" name="RotMax3" value="0"/>
					</td>
					<td>
						<input type="text" name="RotDelta3" value="30"/>
					</td>
				</tr>
			</table>
		</form>
		<br>
		<br>
		<a href="index.html">戻る</a><br>
		<a href="?pione-action=finish">終了</a><br>
	</body>
</html>

このページでは入力ファイルと回転モードと及び各角度毎の最小値、最大値、加算値を設定して実行できるようにしています。

index2.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>InteractiveCommand Index Page</title>
	</head>
	<body>
		<img src="Ref3DtoRef2D.gif"><br>
		<br>
		<form action="Result.cgi" method="post" enctype="multipart/form-data">
			<button type="submit">決定</button><br>
			<input type="hidden" name="Result" value="Finish"/>
		</form>
		<form action="Result.cgi" method="post" enctype="multipart/form-data">
			<button type="submit">やり直し</button><br>
			<input type="hidden" name="Result" value="Start"/>
		</form>
	</body>
</html>

これはルールResultのときに画像を確認しながら、決定かやり直しかを選択するためのページです。

cgiファイル

Ref3DtoRef2D.cgi

#!/usr/bin/env ruby

require 'cgi'

cgi = CGI.new

### Header
strHTML = ""
strHTML += "<!DOCTYPE html>"
strHTML += "<html>"
strHTML += "<head>"
strHTML += "<title>Ref3DtoRef2D</title>"
### Auto Close
strHTML += '<meta http-equiv="REFRESH" content="0;URL=?pione-action=finish">'

# Main Process

## Query to Parameters

### Copy InputFile
fpQueryInputFile = cgi.params['InputFile'][0]
strInputFile = fpQueryInputFile.original_filename
fpInputFile = open(strInputFile, "wb")
fpInputFile.write(fpQueryInputFile.read)
fpInputFile.close

### Other Query
strMode = cgi['Mode1'] + cgi['Mode2'] + cgi['Mode3'] + cgi['Mode4']
strRotMin1 = cgi['RotMin1']
strRotMax1 = cgi['RotMax1']
strRotDelta1 = cgi['RotDelta1']
strRotMin2 = cgi['RotMin2']
strRotMax2 = cgi['RotMax2']
strRotDelta2 = cgi['RotDelta2']
strRotMin3 = cgi['RotMin3']
strRotMax3 = cgi['RotMax3']
strRotDelta3 = cgi['RotDelta3']

### Set Parameter to FileNames
strParamFile = strMode + ".!Mode"
command = "touch " + strParamFile
system(command)

strParamFile = strRotMin1 + "!" + strRotMax1 + "!" + strRotDelta1 + ".!Rot1"
command = "touch " + strParamFile
system(command)

strParamFile = strRotMin2 + "!" + strRotMax2 + "!" + strRotDelta2 + ".!Rot2"
command = "touch " + strParamFile
system(command)

strParamFile = strRotMin3 + "!" + strRotMax3 + "!" + strRotDelta3 + ".!Rot3"
command = "touch " + strParamFile
system(command)

command = "cp " + strInputFile + " Refer.ref3d"
system(command)

strHTML += "</head>"
strHTML += "</html>"

# Output as html
cgi.out(type: "text/html") do
	strHTML
end

Ref3DtoRef2D.htmlで設定したデータをファイルに保存します。処理後は自動的にインタラクティブ操作を終了し、以降はPIONEにてルールRef3DtoRef2Dを実行します。

#!/usr/bin/env ruby

require 'cgi'

cgi = CGI.new

### Header
strHTML = ""
strHTML += "<!DOCTYPE html>"
strHTML += "<html>"
strHTML += "<head>"
strHTML += "<title>Result</title>"
### Auto Close
strHTML += '<meta http-equiv="REFRESH" content="0;URL=?pione-action=finish">'

# Main Process
## Query to Parameters
Result = cgi['Result']

command = "touch " + Result + "!Flag"
system(command)

strHTML += "</head>"
strHTML += "</html>"

# Output as html
cgi.out(type: "text/html") do
	strHTML
end

index2.htmlの操作によって、決定またはやり直しを選択したときの処理です。決定のときはFinish!Flag、やり直しのときはStart!FLagを作成します。処理後は自動的にインタラクティブ操作を終了します。

これらのファイルはこちらからダウンロードできます。

パッケージ作成

コードが完成したらパッケージ作成を行います。

/PIONE$ pione package build Advanced3
info: update the package info file: local:/Eos/tutorial/SampleCode/PIONE/Advanced3/pione-package.json
info: Package local:/Eos/tutorial/SampleCode/PIONE/Ref3DtoRef2D(Kinoshita)+v0.1.1.ppg has been built successfully.


実行結果

では、動作させてみましょう。
PIONE Webclientのジョブページで実行し、Interactionを選択してindexページを開きます。ここでRef3DtoRef2Dを選択すると設定画面が表示されます。

今回は下記の入力ファイルを3次元像として2次元画像を作成してみます。

Input-1VOM.png
xy平面

Input1-1VOM.png
yz平面

最小

最大
平均値
標準偏差

標準誤差

0 (0, 0, 0)

3398.12 (23, 55, 41)
72.129
294.805

0.368507


Outdata-PIONE-Advanced3-1.png
入力ファイル及び回転のモード(参照: オイラー角)、角度の範囲を設定して開始ボタンを押します。

Outdata-PIONE-Advanced3-2.png
すると、上記のようにインタラクティブ操作は完了して、再びPIONEにて2次元像の作成処理が動きます。(このページは閉じて構いません)

Outdata-PIONE-Advanced3-3.png
再びInteractionボタンが赤色になりますので、開くと作成された2次元像の一覧が画像にて表示されます。この内容を見ながら「決定」か「やり直し」を選択します。

Outdata-PIONE-Advanced3-2.png
同様にインタラクティブ操作が完了しますので、ページを閉じます。

Outdata-PIONE-Advanced3-4.png
「やり直し」を選択すると、再度設定画面を開くことができます。また、「決定」を選択するとこのときの結果がouputへ出力されます。このようにして結果を確認しながら適切な設定を選び直すことが出来ます。

上記の設定で決定したときのgif画像(縮小表示)
Outdata-PIONE-Advanced3-5.gif

補足(チケットを使ってルールを制御する)

設定、処理、確認のルールが同時に動いてはそれぞれの役割が満たせませんので、チケットを使用して制御を加えてみましょう。もちろん入力ファイルや出力ファイルの関係がうまく作れていれば同時に動くことはありません。今回は念のための処置として作成します。

Main.pione

Rule Main
	output '*.ref3d'
	output '*.ref2d'
	output '*.mon'
	output '*.gif'
Flow
	rule Start >>> SubMain >>> Result
	rule Finish
End

Rule Start
	output 'Start0!Flag'
Action
	touch {$O[1]}
End

Rule SubMain
	input 'Start*!Flag'
	output '*.*'.all
Flow
	rule Interaction {val : '{$I[1][1]}'.str().i()} >>> Ref3DtoRef2D {val : '{$I[1][1]}'.str().i()}
End

Rule Finish
	input 'Finish*!Flag'
	input '*.*-{$I[1][1]}'
	output '{$I[2][1]}.{$I[2][2]}'
Action
	cp {$I[2]} {$O[1]}
End

MainルールとSubMainルールにて順番を指定しています。
このファイルはこちらからダウンロードできます。

応用4(単粒子解析)

 次はMakefileをベースとして単粒子解析のための処理を作ってみましょう。

問題4

元のMakefileが下記であったとします。

.SUFFIXES: .roi .smooth .shrink .pad .fit .corinfo .3dinfo .3dwholeinfo .3d4sinfo .lst .3dlst .padlst .3d .ds6 .3dwholelst .3dwhole .wholeds6 .3d4s .4sds6 .3d4sinfolst

#SHELL=/bin/bash


REFERENCE=1J4Z-move-shrink.stack

.lst.padlst:
	rm -f $*.padlst
	for i in `cat $*.lst`; do \
		FILENAME=`basename $$i .roi`; \
		echo $$FILENAME; \
		make $$FILENAME.pad ; \
		echo $$FILENAME.pad >> $*.padlst; \
	done

.padlst.3dlst:
	rm -f $*.3dlst
	rm -f $*.3dwholelst
	for i in `cat $*.padlst`; do \
		FILENAME=`basename $$i .pad`; \
		make $$FILENAME.fit ; \
		make $$FILENAME.3dinfo ; \
		cat $$FILENAME.3dinfo >> $*.3dlst ; \
		make $$FILENAME.3dwholeinfo ; \
		cat $$FILENAME.3dwholeinfo >> $*.3dwholelst ; \
	done

.padlst.3d4sinfolst:
	rm -f $*.3d4sinfolst
	for i in `cat $*.padlst`; do \
		FILENAME=`basename $$i .pad`; \
		make $$FILENAME.3d4sinfo ; \
		cat $$FILENAME.3d4sinfo >> $*.3d4sinfolst ; \
	done


.roi.smooth:
	mrcImageSmoothing -i $*.roi -o $*.smooth -m 1 -r 4 

.smooth.shrink:
	mrcImageShrink -i $*.smooth -o  $*.shrink -S 8

.shrink.pad:
	mrcImagePad -i $*.shrink -o $*.pad -W 32 -H 32 

.pad.fit:
	mrcImageAutoRotationCorrelation -i $*.pad -r $(REFERENCE) -fit  $*.fit -O $*.corinfo -n 72 -m 18 -nRot1 72 -nRot2 72 -nRot3 1 > /dev/null

.fit.3dinfo:
	awk '/Cor/ { print $$18,$$16,$$2,$$3,$$4,"0.0"}' $*.corinfo | sort -r | sed -e s/pad/fit/ > $*.3dinfolst
	head -n 1 $*.3dinfolst | awk ' {print $$2,$$3,$$4,$$5,$$6,$$1'} > $*.3dinfo	

.fit.3dwholeinfo:
	awk '/Cor/ { print $$18,$$16,$$2,$$3,$$4,$$9,$$11,$$12}' $*.corinfo | sort -r | sed -e s/pad/shift/ > $*.3dwholeinfolst
	head -n 1 $*.3dwholeinfolst | awk ' {print $$2,$$3,$$4,$$5,$$6,$$7,$$8,$$1'} > $*.3dwholeinfo	
	X=`awk '{print -8*$$6; }' $*.3dwholeinfo`; \
	Y=`awk '{print -8*$$7; }' $*.3dwholeinfo`; \
	echo $$X,$$Y; mrcImageShift -i $*.roi -o $*.shift -x $$X -y $$Y -z 0	

.3dlst.3d:
	mrc2Dto3D -I $*.3dlst -o $*.3d -InterpolationMode 2 -Double -DoubleCounter $*.3dcounter -CounterThreshold 0.5 -m 1 -WeightMode 6

.3d.ds6:
	mrc2map -i $*.3d -o $*.ds6 -m 3 

.3dwholelst.3dwhole:
	mrc2Dto3D -I $*.3dwholelst -o $*.3dwhole -InterpolationMode 2 -Double -DoubleCounter $*.3dwholecounter -CounterThreshold 0.5 -m 1 -WeightMode 6
.3dwhole.wholeds6:
	mrc2map -i $*.3dwhole -o $*.wholeds6 -m 3 
	ln -sf $*.wholeds6 $*.whole.ds6

.fit.3d4sinfo:
	awk '/Cor/ { print $$18,$$16,$$2,$$3,$$4,$$9,$$11,$$12}' $*.corinfo | sort -r | sed -e s/pad/shift/ > $*.3d4sinfolst
	head -n 1 $*.3d4sinfolst | awk ' {print $$2,$$3,$$4,$$5,$$6,$$7,$$8,$$1'} > $*.3d4sinfo
	X=`awk '{print -4*$$6; }' $*.3d4sinfo`; \
	Y=`awk '{print -4*$$7; }' $*.3d4sinfo`; \
	echo $$X,$$Y; 
	mrcImageShrink -i $*.roi -o  $*.4shrink -S 4
	mrcImagePad -i $*.4shrink -o $*.pad -W 64 -H 64
	mrcImageShift -i $*.roi -o $*.4shift -x $$X -y $$Y -z 0

.3d4sinfolst.3d4s:
	 mrc2Dto3D -I $*.3d4sinfolst -o $*.3d4s -InterpolationMode 2 -Double -DoubleCounter $*.3d4scounter -CounterThreshold 0.5 -m 1 -WeightMode 6
.3d4s.4sds6:
	mrc2map -i $*.3d4s -o $*.4sds6 -m 3
	ln -sf $*.4sds6 $*.4s.ds6

上記のMakefileは複数の電子顕微鏡画像(.roi)に対して、参照像を使って角度決定をした後に3次元再構成を実行する処理となっています。.roiファイルの一覧は.lstに書かれていて、参照像の2Dスタック(REFERENCEで設定)があることが前提となっています。このmakefileは相関マップ(.corinfo)を作成するときに1/8とした画像を使用することで速度の向上をはかっています。"make (.lstのファイル名).3d"で1/8サイズ、"make (.lstのファイル名).3dwhole"で1/1サイズ、"make (.lstのファイル名).3d4s"で1/4サイズの3次元像が作成されます。


 応用問題4:上記の機能を持つパッケージSingleParticle_3DReconstruction.ppgを作成してみましょう。



解答例1

解答例を一部示します。全体はこちらをご覧下さい。元の機能に加えて、サンプルの.roiファイルと参照像2Dスタック.stackを作成できるようにしています。この場合の入力ファイルは.mrcのみです。さらに、作成された3次元像を画像としてすぐに確認できるように3方向からの投影像をgifファイルで出力しています。また、機能毎にフラグを設けて作成したいデータのみを作成できるようにしています。

実装

Main.pione

Rule Main
	if $Flag_ROI or $Flag_Ref
		input '*.mrc'
	end

	if $Flag_ROI.not()
		input '*.lst'
		input '*.roi'.all
	end

	if $Flag_Ref.not()
		input '*.stack'
	end

	if $Flag_3D
		output '{$I[1][1]}.3dlst'
		output '{$I[1][1]}.3d'
		output '{$I[1][1]}.ds6'
	end

	if $Flag_3DWhole
		output '{$I[1][1]}.3dwholelst'
		output '{$I[1][1]}.3dwhole'
		output '{$I[1][1]}.wholeds6'
		output '{$I[1][1]}.whole.ds6'
	end

	if $Flag_3D4S
		output '{$I[1][1]}.3d4slst'
		output '{$I[1][1]}.3d4s'
		output '{$I[1][1]}.4sds6'
		output '{$I[1][1]}.4s.ds6'
	end

	if $Flag_ROI
		output '*.tiff'.all
	end

	output '*.gif'.all
	
Flow

	if $Flag_ROI
		rule Create_SampleROI
	end

	if $Flag_Ref
		rule Create_stack
	end

	rule Preprocess
	rule Create_fit

	if $Flag_3D
		rule Create_3dlst
		rule Create_3d
	end

	if $Flag_3DWhole
		rule Create_3dwholelst 
		rule Create_3dwhole
	end

	if $Flag_3D4S
		rule Create_3d4slst
		rule Create_3d4s
	end

	rule Projection_3d
End


Main.pione内で使用しているルールは下記のようにMakefileの処理と対応しています。

Main.pione内のルール サブルール Makefile 説明
Create_SampleROI - なし サンプル用の.roiファイルを複数作成
Create_stack - なし 参照像の2Dスタック.stackファイルを作成
Preprocess pad_padlst

roi_smooth
smooth_shrink
shrink_pad

.lst.padlst

.roi.smooth
.smooth.shrink
.shrink.pad

.padファイル一覧を作成

.roiの平滑化して.smoothを作成
.smoothを縮小して.shrinkを作成
.shrinkを参照画像と同じサイズにパディングして.padを作成

Create_fit - .pad.fit .padから.stackを使って相関マップ.corinfoを作成(最も近い画像.fitも作成)
Create_3dlst fit_3dinfo
Sum_3dinfo_3dlst
.fit.3dinfo 各相関マップ.corinfoから相関値が最大の角度を集めてリスト.3dlstを作成
Create_3d Reconstruct_3dlst_3d
Convert_3d_ds6
.3dlst.3d
.3d.ds6
リスト.3dlstと.fitを使って3次元像.3d及び.ds6を作成(1/8サイズ)
Create_3dwholelst fit_3dwholeinfo
Sum_3dwholeinfo_3dwholelst
.fit.3dwholeinfo Create_3dlstの1/1サイズバージョン
Create_3dwhole Reconstruct_3dwholelst_3dwhole
Convert_3dwhole_wholeds6
.3dwholelst.3dwhole
.3dwhole.wholeds6
Create_3dの1/1サイズバージョン
Create_3d4slst fit_3d4sinfo
Sum_3d4sinfo_3d4slst
.fit.3d4sinfo Create_3dlstの1/4サイズバージョン
Create_3d4s Reconstruct_3d4slst_3d4s
Convert_3d4s_4sds6
.3d4sinfolst.3d4s
.3d4s.4sds6
Create_3dの1/4サイズバージョン
Projection_3d - なし 作成された3次元像.3dなどを3方向から投影したgif画像を作成


また、下記のように画像サイズや回転方法をパラメータにて設定できるようにしています。

Parameter.pione

# basic param
## For Size
param $X_SIZE := 80
param $Y_SIZE := 80
param $SHRINK := 8
param $SHRINK4S := 4

## For AutoRotationCorrelation
param $N_ROT := 72
param $N_ROT1 := 72
param $N_ROT2 := 72
param $N_ROT3 := 1

## For RefData
param $REF_ROT_MODE := "XOYS"
param $REF_ROT1_START := 0
param $REF_ROT1_END := 359
param $REF_ROT1_DELTA := 15
param $REF_ROT2_START := 0
param $REF_ROT2_END := 359
param $REF_ROT2_DELTA := 15
param $REF_ROT3_START := 0
param $REF_ROT3_END := 0
param $REF_ROT3_DELTA := 15

## For SampleROI
param $ROI_ROT_MODE := "XOYS"
param $ROI_ROT1_START := 0
param $ROI_ROT1_END := 359
param $ROI_ROT1_DELTA := 30
param $ROI_ROT2_START := 0
param $ROI_ROT2_END := 359
param $ROI_ROT2_DELTA := 30
param $ROI_ROT3_START := 0
param $ROI_ROT3_END := 0
param $ROI_ROT3_DELTA := 30
param $ROI_SN := 1

# advanced param
advanced param $Flag_ROI := true
advanced param $Flag_Ref := true
advanced param $Flag_3D := true
advanced param $Flag_3DWhole := true
advanced param $Flag_3D4S := true


Makefile との動作比較

作成したPIONE定義書と元のMakefileとの処理時間を比較して、ここまでの実装の効果があったかを検証します。今回は.3dファイルの作成に関して比較します。

.roiファイルと.stackファイルは予めParameter.pioneを下記のようにして作成しています。(ROI画像: サイズ256×256, 2つの角度0 ~ 300: 60刻み)

param $X_SIZE := 256	#80
param $Y_SIZE := 256	#80

param $ROI_ROT1_START := 0
param $ROI_ROT1_END := 359
param $ROI_ROT1_DELTA := 60	#30

param $ROI_ROT2_START := 0
param $ROI_ROT2_END := 359
param $ROI_ROT2_DELTA := 60	#30

param $ROI_ROT3_START := 0
param $ROI_ROT3_END := 0
param $ROI_ROT3_DELTA := 30

advanced param $Flag_ROI := true
advanced param $Flag_Ref := true
advanced param $Flag_3D := false
advanced param $Flag_3DWhole := false
advanced param $Flag_3D4S := false


.roiや.stack作成で使用する.mrcファイル

Input-1VOM.png
xy平面

Input1-1VOM.png
yz平面

最小

最大
平均値
標準偏差

標準誤差

0 (0, 0, 0)

3398.12 (23, 55, 41)
72.129
294.805

0.368507


作成されたROIファイルの一覧をData.lstに書き込んでMakefileで動作できるようにします。また、Makefile内にてREFERENCEを作成した.stackファイル名に変更します。

#REFERENCE=1J4Z-move-shrink.stack
REFERENCE=1WDC.stack

以上の.roiファイル、.stackファイル、Makefileを入力用のディレクトリに置きます。

Makefileの処理時間の下記のPIONE定義書を作って、計測しています。
計測①コード

Rule Main
	input '*.lst'
	input '*.roi'.all
	input 'Makefile'
	input '*.stack'.all
	output '{$I[1][1]}.ds6'
Action
	make {$O[1]}
End

上記のルールの場合、(全体の処理)=(Makefile処理)+(入力ファイル処理)+(出力ファイル処理)となっているので、下記の計測②時間との差分を算出すれば、およそのMakefileの処理時間が算出できます。

計測②コード

Rule Main
	input '*.lst'
	input '*.roi'.all
	input 'Makefile'
	input '*.stack'.all
	input '*.ds6'
	output '{$I[5][1]}.ds6out'
Action
	cp {$I[5]} {$O[1]}
End


計測①結果(9分強)
Outdata-PIONE-Advanced4-12.png

計測②結果(1秒程度)
Outdata-PIONE-Advanced4-13.png

計測①が9分強、計測②が1秒程度なので、Makefileの処理時間は9分強掛かったことが分かりました。

では、今回実装した処理をタスク数2(-t 2)で実行してみます。

-t 2の結果(7分程度)
Outdata-PIONE-Advanced4-16.png
今回の実装においては2分程度の削減で、処理時間がMakefileの7/9となりました。しかし、この実装においてルールを細かく分けすぎているためにルール間の入出力ファイルのやり取りによる処理が各所で発生しています。次の項目にてこれらの無駄を省くためのコーディングを考えて、さらに時間を削減してみましょう。

速度アップのための実装

リスト作成の省略

まずは.lstなどを作成している部分について考えます。PIONEは1:複数の処理が可能ですのでリストの作成は省略できます。

Main.pione

Rule Main
	if ($Mode_ROI + $Mode_Ref) != 0
		input '*.mrc'
	end

	if $Mode_Ref == 0
		input '*.stack'
	end

	if $Mode_ROI == 0
		input '*.roi'.all
	end

	if $Flag_3D
		output '{$I[1][1]}.3dlst'
		output '{$I[1][1]}.3d'
		output '{$I[1][1]}.ds6'
	end

	if $Flag_3DWhole
		output '{$I[1][1]}.3dwholelst'
		output '{$I[1][1]}.3dwhole'
		output '{$I[1][1]}.wholeds6'
		output '{$I[1][1]}.whole.ds6'
	end

	if $Flag_3D4S
		output '{$I[1][1]}.3d4slst'
		output '{$I[1][1]}.3d4s'
		output '{$I[1][1]}.4sds6'
		output '{$I[1][1]}.4s.ds6'
	end

	if $Mode_ROI != 0
		output '*.tiff'.all
	end

	if $Flag_Projection
		output '*.gif'.all
	end
Flow
	case $Mode_ROI
	when 1
		rule Create_Sample3d
		rule Create_SampleROI_each {rot1: (0.upto($roi_rot1_loop))*$ROI_ROT1_DELTA, rot2: (0.upto($roi_rot2_loop))*$ROI_ROT2_DELTA, rot3: (0.upto($roi_rot3_loop))*$ROI_ROT3_DELTA}
	when 2
		rule Create_Sample3d
		rule Create_SampleROI_all
	end

	if $Mode_Ref != 0
		rule Create_stack
	end

	rule Preprocess
	
	case $Mode_Cor
	when 1
		rule Create_fit_each
	when 2
		rule Create_fit_all
	end

	if $Flag_3D
		rule Create_3dlst {filename: $I[1][1].str()}
		rule Create_3d
	end

	if $Flag_3DWhole
		rule Create_3dwholelst {filename: $I[1][1].str()}
		rule Create_3dwhole
	end

	if $Flag_3D4S
		rule Create_3d4slst {filename: $I[1][1].str()}
		rule Create_3d4s
	end

	if $Flag_Projection
		rule Projection_3d
	end
End

.roiファイル、.padファイルの一覧を記述した.lstファイル.padlstファイルを廃止して、入力用ディレクトリにある全ての.roiファイルについて処理を行うように変更しました。(但し、.3dlstなどは角度情報として使用するのでそのまま)また、入力ファイル1つに対して複数のファイルを作成する箇所についてeachで行うかallで行うかをパラメータによって決められるようにしています。今回の場合はeachを使用して複数のタスクで実行すると速度の改善が期待できます。(参照:#基本4の補足(eachでの速度検証)

allで実行した場合(約65秒)
Outdata-PIONE-Advanced4-7.png

eachで実行した場合(約81秒)(allよりも時間が掛かっている)
Outdata-PIONE-Advanced4-8.png

eachを-t 2で実行した場合(約47秒)(速度が改善されている)
Outdata-PIONE-Advanced4-9.png

eachを-t 3で実行した場合(約36秒)(さらに速度が改善されている)
Outdata-PIONE-Advanced4-10.png

eachを-t 4で実行した場合(約31秒)(若干速度が改善されている)
Outdata-PIONE-Advanced4-11.png

上記は1つのマシンにおいてallと複数のタスクによるeachの速度の違いです。eachを複数のタスクで動作させると、allの場合よりも速く処理できることが分かります。そして、タスク数が多いほどが速度が改善されています。しかし、タスク数を増やす毎に改善の度合いは緩やかになっています。マシン毎で一度に処理できるタスク数を把握し、全てのタスクをいくつに分けるかを考えると良いでしょう。

Param.pione

# basic param
## For Size
param $X_SIZE := 80
param $Y_SIZE := 80
param $SHRINK := 8
param $SHRINK4S := 4

## For AutoRotationCorrelation
param $N_ROT := 72
param $N_ROT1 := 72
param $N_ROT2 := 72
param $N_ROT3 := 1

## For RefData
param $REF_ROT_MODE := "XOYS"
param $REF_ROT1_START := 0
param $REF_ROT1_END := 359
param $REF_ROT1_DELTA := 15
param $REF_ROT2_START := 0
param $REF_ROT2_END := 359
param $REF_ROT2_DELTA := 15
param $REF_ROT3_START := 0
param $REF_ROT3_END := 0
param $REF_ROT3_DELTA := 15

## For SampleROI
param $ROI_ROT_MODE := "XOYS"
param $ROI_ROT1_START := 0
param $ROI_ROT1_END := 359
param $ROI_ROT1_DELTA := 30
$roi_rot1_loop := ($ROI_ROT1_END - $ROI_ROT1_START) / $ROI_ROT1_DELTA
param $ROI_ROT2_START := 0
param $ROI_ROT2_END := 359
param $ROI_ROT2_DELTA := 30
$roi_rot2_loop := ($ROI_ROT2_END - $ROI_ROT2_START) / $ROI_ROT2_DELTA
param $ROI_ROT3_START := 0
param $ROI_ROT3_END := 0
param $ROI_ROT3_DELTA := 30
$roi_rot3_loop := ($ROI_ROT3_END - $ROI_ROT3_START) / $ROI_ROT3_DELTA
param $ROI_SN := 1

# advanced param
## Mode 0: do not, 1: each, 2: all
advanced param $Mode_ROI := 1
advanced param $Mode_Ref := 1
advanced param $Mode_Cor := 1
## Flag true: do, false: do not
advanced param $Flag_3D := true
advanced param $Flag_3DWhole := true
advanced param $Flag_3D4S := true
advanced param $Flag_Projection := true

今回はMode_ROIとMode_Corのみ1: each, 2: allの切り替えが可能としています。$roi_rot1_loopなどの変数束縛を追加していますが、これはeachのループ数のためでユーザが入力する必要はありません。

これらのファイルはこちらからダウンロードできます。

同じようなルールをまとめる
Preprocess

Mainで呼んでいるPreprocessのルールは下記のようにしていました。

Main.pione内のルール サブルール Makefile 説明
Preprocess pad_padlst

roi_smooth
smooth_shrink
shrink_pad

.lst.padlst

.roi.smooth
.smooth.shrink
.shrink.pad

.padファイル一覧を作成

.roiの平滑化して.smoothを作成
.smoothを縮小して.shrinkを作成
.shrinkを参照画像と同じサイズにパディングして.padを作成


これらの処理は全て入力ファイルと出力ファイルが1:1対応であり、.roi -> .smooth -> .shrinkの順番に作成して最終的に.padファイルのみを得ることを目的としています。このような場合はルールを分けずに1つのルールの中で処理を行った方がルール間のファイルのやり取りが最小限で済みます。(参照:#基本5の補足

コード 処理結果(ProM
(1) 分けた場合
Rule Preprocess
	input '*.roi'
	output '{$I[1][1]}.pad'
Flow
	rule roi_smooth;
	rule smooth_shrink;
	rule shrink_pad;
End

#.roi.smooth:
Rule roi_smooth
	input '*.roi'
	output '{$I[1][1]}.smooth'
Action
	mrcImageSmoothing -i {$I[1]} -o {$O[1]} -m 1 -r 4 
End

#.smooth.shrink:
Rule smooth_shrink
	input '*.smooth'
	output '{$I[1][1]}.shrink'
Action
	mrcImageShrink -i {$I[1]} -o  {$O[1]} -S {$SHRINK}
End

#.shrink.pad:
Rule shrink_pad
	input '*.shrink'
	output '{$I[1][1]}.pad'
Action
	width=`expr {$X_SIZE} / {$SHRINK}`
	height=`expr {$Y_SIZE} / {$SHRINK}`
	mrcImagePad -i {$I[1]} -o {$O[1]} -W ${width} -H ${height}
End
Outdata-PIONE-Advanced4-5.png
(2) まとめた場合
Rule Preprocess
	input '*.roi'
	output '{$I[1][1]}.pad'
Action
	# roi_smooth
	mrcImageSmoothing -i {$I[1]} -o {$I[1][1]}.smooth -m 1 -r 4
	
	# smooth_shrink
	mrcImageShrink -i {$I[1][1]}.smooth -o  {$I[1][1]}.shrink -S {$SHRINK}
	
	# shrink_pad
	width=`expr {$X_SIZE} / {$SHRINK}`
	height=`expr {$Y_SIZE} / {$SHRINK}`
	mrcImagePad -i {$I[1][1]}.shrink -o {$O[1]} -W ${width} -H ${height}
End
Outdata-PIONE-Advanced4-6.png


上記の(2)のようにルールをまとめることで(1)に比べて、1ファイル毎に1秒ほど時間が短縮できました。

Create_fit, Create_3dlst, Create_3d

 Create_fitは各.padファイルから.fitファイルや.corinfoファイルをそれぞれ1:1で作成しています。また、Create_3dlstでもfit_3dinfoまでは1:1対応の処理です。

Main.pione内のルール サブルール Makefile 説明
Create_fit - .pad.fit .padから.stackを使って相関マップ.corinfoを作成(最も近い画像.fitも作成)
Create_3dlst fit_3dinfo
Sum_3dinfo_3dlst
.fit.3dinfo 各相関マップ.corinfoから相関値が最大の角度を集めてリスト.3dlstを作成
Create_3d Reconstruct_3dlst_3d
Convert_3d_ds6
.3dlst.3d
.3d.ds6
リスト.3dlstと.fitを使って3次元像.3d及び.ds6を作成(1/8サイズ)


従って、この部分もまとめた方が良いでしょう。同様にfit_3dwholeinfoとfit_3d4sinfoもまとめます。この処理は前項目のPreprocessともまとめられそうですが、単粒子解析のための3次元再構成では角度決定処理を繰り返し行うので、この部分は分けて実装します。(参照:単粒子解析#繰り返し(精密化))また、Create_3dlstのSum_3dinfo_3dlstとCreate_3dなども一つにまとめられます。

まとめたCreate_fit_each

Rule Create_fit_each
	input '*.pad'
	input '*.stack'
if ($Flag_3DWhole or $Flag_3D4S)
	input '{$I[1][1]}.roi'
end
if $Flag_3D
	output '{$I[1][1]}.fit'
	output '{$I[1][1]}.3dinfo'
end
if $Flag_3DWhole
	output '{$I[1][1]}.shift'
	output '{$I[1][1]}.3dwholeinfo'
end
if $Flag_3D4S
	output '{$I[1][1]}.4shift'
	output '{$I[1][1]}.3d4sinfo'
end
Action
	# Create_fit
	mrcImageAutoRotationCorrelation	-i {$I[1]} -r {$I[2]} -fit  {$I[1][1]}.fit -O {$I[1][1]}.corinfo \
									-n {$N_ROT} -m 18 -nRot1 {$N_ROT1} -nRot2 {$N_ROT2} -nRot3 {$N_ROT3} > /dev/null
	
	# fit_3dinfo
	if {$Flag_3D} ; then
		awk '/Cor/ { print $18,$16,$2,$3,$4,"0.0"}' {$I[1][1]}.corinfo | sort -r | sed -e s/pad/fit/ > {$I[1][1]}.3dinfolst
		head -n 1 {$I[1][1]}.3dinfolst | awk ' {print $2,$3,$4,$5,$6,$1'} > {$I[1][1]}.3dinfo
	fi

	# fit_3dwholeinfo
	if {$Flag_3DWhole} ; then
		awk '/Cor/ { print $18,$16,$2,$3,$4,$9,$11,$12}' {$I[1][1]}.corinfo | sort -r | sed -e s/pad/shift/ > {$I[1][1]}.3dwholeinfolst
		head -n 1 {$I[1][1]}.3dwholeinfolst | awk ' {print $2,$3,$4,$5,$6,$7,$8,$1'} > {$I[1][1]}.3dwholeinfo
		X=`awk '{print -{$SHRINK}*$6; }' {$I[1][1]}.3dwholeinfo`;
		Y=`awk '{print -{$SHRINK}*$7; }' {$I[1][1]}.3dwholeinfo`;
		echo $X,$Y; mrcImageShift -i {$I[1][1]}.roi -o {$I[1][1]}.shift -x $X -y $Y -z 0	
	fi

	# fit_3d4sinfo
	if {$Flag_3D4S} ; then
		awk '/Cor/ { print $18,$16,$2,$3,$4,$9,$11,$12}' {$I[1][1]}.corinfo | sort -r | sed -e s/pad/4shift/ > {$I[1][1]}.3d4sinfolst
		head -n 1 {$I[1][1]}.3d4sinfolst | awk ' {print $2,$3,$4,$5,$6,$7,$8,$1'} > {$I[1][1]}.3d4sinfo
		X=`awk '{print -{$SHRINK4S}*$6; }' {$I[1][1]}.3d4sinfo`;
		Y=`awk '{print -{$SHRINK4S}*$7; }' {$I[1][1]}.3d4sinfo`;
		echo $X,$Y; 
		mrcImageShrink -i {$I[1][1]}.roi -o  {$I[1][1]}.4shrink -S {$SHRINK4S}
		width=`expr {$X_SIZE} / {$SHRINK4S}`
		height=`expr {$Y_SIZE} / {$SHRINK4S}`
		mrcImagePad -i {$I[1][1]}.4shrink -o {$I[1][1]}.4spad -W ${width} -H ${height}
		mrcImageShift -i {$I[1][1]}.4spad -o {$I[1][1]}.4shift -x $X -y $Y -z 0
	fi
End


まとめたCreate_3d

Rule Create_3d
	input '*.3dinfo'.all
	input '*.fit'.all
	output '{$filename}.3d'
	output '{$filename}.ds6'
	param $filename
Action
	# Sum_3dinfo_3dlst
	for info in {$I[1]}
	do
		cat $info >> {$filename}.3dlst
	done

	# Reconstruct_3dlst_3d
	mrc2Dto3D -I {$filename}.3dlst -o {$O[1]} -InterpolationMode 2 -Double -DoubleCounter {$filename}.3dcounter -CounterThreshold 0.5 -m 1 -WeightMode 6

	# Convert_3d_ds6
	mrc2map -i {$O[1]} -o {$O[2]} -m 3 
End

Create_3d4s, Create_3dwholeについても同様にまとめています。

動作比較

作成したPIONE定義書と前回分や元のMakefileとの処理時間を比較して、ここまでの実装の効果があったかを検証します。今回は.3dファイルの作成に関して比較します。

では、今回の実装による処理時間を複数のタスク(-t 2と-t 4)で実行してみます。

-t 2の結果(5分強)
Outdata-PIONE-Advanced4-14.png

-t4の結果(3分強)
Outdata-PIONE-Advanced4-15.png

1つのマシンにおいて2つのタスクに分けることで4分削減して約5/9(改善前からは2分削減して約5/7)になりました。さらに4つのタスクに分けることで6分削減して約1/3になりました。入力ファイル(256×256:36枚)の場合において、今回の実装で有効な並列処理ができたと言えるでしょう。

機能追加のための実装

今回は作成したPIONE定義書パッケージについてさらに機能を追加する方法を考えます。

粒子の切り出しを追加する

.roiファイルが無い場合にユーザが切り出した画像を使用できるように追加してみましょう。(参照:単粒子解析#電子顕微鏡画像から粒子画像の抽出

この機能は.roiファイルを作成するものなので、Create_ROIと同列と考えて、$Mode_ROIに3を追加してこのときにユーザが切り出すようにします。

Main.pione(Cutout_ROIを追加)

	case $Mode_ROI
	when 1
		rule Create_Sample3d
		rule Create_SampleROI_each {rot1: (0.upto($roi_rot1_loop))*$ROI_ROT1_DELTA, rot2: (0.upto($roi_rot2_loop))*$ROI_ROT2_DELTA, rot3: (0.upto($roi_rot3_loop))*$ROI_ROT3_DELTA}
	when 2
		rule Create_Sample3d
		rule Create_SampleROI_all
	when 3
		rule Cutout_ROI
	end


Parameter.pione(コメントを追加)

## Mode 0: do not, 1: each, 2: all, 3: use Display2
advanced param $Mode_ROI := 3


Create_SampleROI.pione(Cutout_ROIを追加)

Rule Cutout_ROI
	input '*.mrc'
	output '*.roi'.all
	output '*.tiff'.all
Action
	Display2 -i {$I[1]}
	for data in $(ls *.roi)
	do
		mrc2tiff -i ${data} -o ${data}.tiff
	done
End

切り出しにはDisplay2を使用しています。

新しいルールを作成して、呼び出すだけで簡単に機能を追加することができました。

繰り返し機能を追加する

単粒子解析のための3次元再構成は出来上がった3次元像を参照像として繰り返して、3次元像の精密化を行うこともあります。今回はこの部分の対応を作成してみましょう。

Parameter.pione(繰り返し回数を追加)

## For repeat count
param $Repeat_Count := 2


Repeat_3d

Rule Repeat_3d
	input '*-{$count}.3d'
	output '{$I[1][1]}-{$count + 1}.stack'
	param $count
Action
	mrc3Dto2D	-i {$I[1]} -o {$O[1]} -m 1 -InterpolationMode 2 -EulerMode {$REF_ROT_MODE} \
				-Rot1 {$REF_ROT1_START} {$REF_ROT1_END} {$REF_ROT1_DELTA} \
				-Rot2 {$REF_ROT2_START} {$REF_ROT2_END} {$REF_ROT2_DELTA} \
				-Rot3 {$REF_ROT3_START} {$REF_ROT3_END} {$REF_ROT3_DELTA}
End

上記のように作成された.3dファイルから.stackファイルを作成します。このとき繰り返し回数が分かるようにファイル名にを付加しています。また、下記のルールについても繰り返し回数の対応を行います。

Create_stack.pione

	output '{$I[1][1]}-1.stack'


Main.pione(出力ファイルの宣言)

	if $Flag_3D
		output '{$I[1][1]}-{$Repeat_Count}.3d'
		output '{$I[1][1]}-{$Repeat_Count}.ds6'
	end

繰り返しの最後のファイルのみを出力するようにします。

Main.pione(Flow)

	case $Mode_3D
	when 1
		rule Create_fit_each
		
		if $Flag_3D
			rule Create_3d {filename: $I[1][1].str(), count: 1.upto($Repeat_Count)}
			rule Repeat_3d {count: 1.upto($Repeat_Count - 1)}
		end

Create_3d, Repeat_3dを繰り返し回数分呼び出します。

Create_stack.pione(最初の.stackファイル名に1を付加)

	output '{$I[1][1]}-1.stack'


Create_fit_each.pione(.3dのための処理)

Rule Create_fit_each
	input '*.pad'
	input '*-*.stack'
if $Flag_3D
	output '{$I[1][1]}.fit-{$I[2][2]}'
	output '{$I[1][1]}.3dinfo-{$I[2][2]}'
end
Action
	# Create_fit
	mrcImageAutoRotationCorrelation	-i {$I[1]} -r {$I[2]} -fit  {$I[1][1]}.fit-{$I[2][2]} -O {$I[1][1]}.corinfo-{$I[2][2]} \
									-n {$N_ROT} -m 18 -nRot1 {$N_ROT1} -nRot2 {$N_ROT2} -nRot3 {$N_ROT3} > /dev/null
	
	# fit_3dinfo
	if {$Flag_3D} ; then
		awk '/Cor/ { print $18,$16,$2,$3,$4,"0.0"}' {$I[1][1]}.corinfo-{$I[2][2]} | sort -r | sed -e s/pad/fit-{$I[2][2]}/ > {$I[1][1]}.3dinfolst-{$I[2][2]}
		head -n 1 {$I[1][1]}.3dinfolst-{$I[2][2]} | awk ' {print $2,$3,$4,$5,$6,$1'} > {$I[1][1]}.3dinfo-{$I[2][2]}
	fi


Create_3d.pione

Rule Create_3d
	input '*.3dinfo-{$count}'.all
	input '*.fit-{$count}'.all
	output '{$filename}-{$count}.3d'
	output '{$filename}-{$count}.ds6'
	param $filename
	param $count
Action
	# Sum_3dinfo_3dlst
	for info in {$I[1]}
	do
		cat $info >> {$filename}-{$count}.3dlst
	done

	# Reconstruct_3dlst_3d
	mrc2Dto3D -I {$filename}-{$count}.3dlst -o {$O[1]} -InterpolationMode 2 -Double -DoubleCounter {$filename}-{$count}.3dcounter -CounterThreshold 0.5 -m 1 -WeightMode 6

	# Convert_3d_ds6
	mrc2map -i {$O[1]} -o {$O[2]} -m 3 
End

このように実装することで指定した数Repeat_Countに応じた繰り返し処理を行うように機能追加ができます。

応用5(クラスター解析)

次はクラスター解析のMakefileを元にして速度改善を考えてみましょう。こちらのMakefileを元にしています。今回はMakefileのコマンドも使用します。これにより全体の実装量を削減できますが、makeコマンド実行するときに入出力ファイルがそれぞれ何に当たるのかを注意する必要があります。

前処理

前処理の並列化を考えます。これまでのPIONE定義書ではeachを使って1ファイル毎の並列処理として実装を行いました。今回はいくつかのグループに分けて、そのグループ毎で並列化を行うようにしてみます。

param $task := 2

Rule Main
	input '*.roi'.all
	input 'Makefile'
	input 'Makefile.config'
	output '*.pad'.all
Flow
	rule Preprocess {filelist:$I[1].all, num:1.upto($task), length:$I[1].length()}
End

Rule Preprocess
	input $filelist.nth(((($num-1)*((($length-1)/$task)+1))+1).upto((($num*((($length-1)/$task)+1))|$length).min()))
	input 'Makefile'
	input 'Makefile.config'
	output '*.pad'.all
Action
	for data in {$I[1]}
	do
		make $(basename ${data} ".roi").pad
	done
End

Preprocess内の処理はMakefile内のコマンドに委ねるようにしています。入力ファイルがかなり複雑となっていますが、これは次のように考えています。

まずパラメータtaskで分割したいグループ数を決め、この数だけルールPreprocessの呼び出すように考えます。

  • filelistにはファイル自体でなく、ファイル名の一覧を送る
  • numには分割数の内の何番目かを送る
  • lengthにはファイル名の要素数を送る

次にPreprocess側では受け取ったパラメータからグループ毎で使用するファイル名を決め、それらを入力ファイルとすることで分割を実現します。なお、今回使用しているupto, length, minなどのメソッドについてはPIONEの式を参照して下さい。

次のグラフは上記の前処理を256, 512, 1024, 2048, 4096(pixel)の画像ファイル100枚を入力としてタスク数1, 2, 4, 10で動作したときの処理時間です。
Outdata-PIONE-Advanced5-1.png
allは(前処理に関する)全体の処理、processは前処理自体、otherはそれ以外のファイル通信などの処理時間を表しています。

ここでタスク数を増やしたときの変化に注目します。全体の処理時間についてt=1(シングルタスク)との比率は下記のようになりました。
Outdata-PIONE-Advanced5-2.png
2048サイズまでは処理時間が短くなっています。今回はタスク数を増やしても速度の改善は見られません。タスク数を増やすことによって前処理自体の時間は軽減されますが、それ以外のファイル通信など処理で逆に時間がかかってしまうためです。

Outdata-PIONE-Advanced5-5.png Outdata-PIONE-Advanced5-6.png
前処理はタスク数が多い方が速くなる(このマシンではt=4まで) タスク数が多くなると並列処理の準備(ファイル通信など)は時間がかかる。

クラスター解析による分類

本処理のクラスター解析による分類の速度改善を考えてみましょう。MakefileではmrcImageClusterAnalysisによって画像毎の相関リストを作って近い画像同士を分類して、グループを作り、それぞれのグループによって平均画像を作成しています。今回はこのグループ毎の平均画像に対してリファインメントを行う部分にて並列化を行ってみます。

グループ分け

まず、全ての.padファイルを入力としてmakeコマンドによってクラスター解析による分類を行います。すると、分類された.padファイルの一覧all.padsortlstと樹形図all.treeinfoが出力されるので、この2つのファイルを指定した分割数divideだけ分割してそれぞれ.lst .treeファイルを作成します。また、最初のmakeコマンドによって1回目の平均画像が得られますので、この画像がどのグループに属するのか番号付けして出力しています。(ルートに近く分割できない画像は0として番号付けする)下記はその実装例です。

Rule Process
	input '*.pad'.all
	input 'Makefile'
	input 'Makefile.config'
	output 'all.padsortlst'
	output 'all.treeinfo'
	output ((1.upto($divide).str())+".lst").d().all()
	output ((1.upto($divide).str())+".tree").d().all()
	output ("*.pad"+(1.upto($divide).str())).d().all()
	output ("*.avg"+(0.upto($divide).str())).d().all()
	output '*.avglst'.all
Action
	ls -1 *.pad > all.padlst
	make Log
	make LogPS
	
	cp {$O[1]} 1.lst
	cp {$O[2]} 1.tree
	for (( i=2; i<={$divide}; i++ ))
	do
		max=0
		for data in $(ls *.lst)
		do
			num=$(wc -l ${data} | awk '{printf $1}')
			if [ ${num} -gt ${max} ] ; then
				max=${num}
				maxlist="${data}"
			fi
		done
		maxtree="$(basename ${maxlist} '.lst').tree"
		root=$(head -1 ${maxtree} | awk '{printf("%d", $1)}')
		rootname=$(basename $(awk -v val=${root} '$2==val {printf("%s", $1)}' ${maxlist}) ".pad")
		cp ${maxlist} ${rootname}.avglst
		cp ${rootname}.pad.avg ${rootname}.pad.avg0
		maxpos=$(awk -v val=${root} '$2==val {printf("%f", $3)}' ${maxlist})
		line=$(awk -v val=${root} '$2==val {print NR}' ${maxlist})
		head -$((${line} - 1)) ${maxlist} > ${i}.lst
		tail -$(($(wc -l ${maxlist} | awk '{print $1}') - ${line} + 1)) ${maxlist} > tmp
		cp tmp ${maxlist}
		
		max_t=$(wc -l ${maxtree} | awk '{printf $1}')
		max_l=$(wc -l ${maxlist} | awk '{printf $1}')
		line_t=$(awk -v val=${root} '$1==val {print NR}' ${maxtree})
		head -$((${max_l} + ${line_t} - 1)) ${maxtree} | tail -$((${max_l} - 1)) > tmp
		tail -$((${max_t} - ${max_l})) ${maxtree} > ${i}.tree
		cp tmp ${maxtree}
	done
	
	for (( i=1; i<={$divide}; i++ ))
	do
		for data in $(awk '{print $1}' ${i}.lst)
		do
			cp ${data} ${data}${i}
			cp ${data}.avg ${data}.avg${i}
		done
	done
End

.lst .treeファイルが最大のものから2分割するようにし、分割する前の.lstファイルを.avglstとしてグループ0の平均画像のデータとして取り扱うようにしています。

グループ毎の平均画像のリファインメント

グループ分けされた平均画像についてのリファインメントを行います。まず、.lst .treeファイルから平均画像を構成している画像リスト.avglstファイルを作成します。そして、そのリストを使用してリファインメントを行います。このとき、mrcImageAutoRotationCorrelationmrcImageAverageを使用していますが、設定内容をMakefileと同じにするためにMakefile.configから特定のパラメータを読み取るようにしています。

Rule Refinement
	input '*.pad{$num}'.all
	input '*.avg{$num}'.all
	input '{$num}.lst'
	input '{$num}.tree'
	input 'Makefile.config'
	output ($I[1][1].str()+".pad.avg").d().all()
	output ($I[1][1].str()+".pad.avg.tiff").d().all()
	output ($I[1][1].str()+".pad.avg"+$num.str()+".tiff").d().all()
Action
	max=$(wc -l {$num}.tree | awk '{printf $1}')
	data=$(head -1 {$num}.tree)
	i=$(echo ${data} | awk '{print $1}')
	name=$(basename $(awk -v i=${i} '$2==i {print $1}' {$num}.lst) ".pad")
	cp {$num}.lst ${name}.avglst
	cp {$num}.tree ${name}.tree
	
	for (( k=1; k<=max ; k++ ))
	do
		i=$(head -${k} {$num}.tree | tail -1 | awk '{print $1}')
		name=$(basename $(awk -v i=${i} '$2==i {print $1}' {$num}.lst) ".pad")

		if [ $(wc -l ${name}.avglst | awk '{print $1}') -gt 2 ] ; then
			tpos=$(awk -v i=${i} '$2==i {print NR}' ${name}.avglst)
			tline=$(wc -l ${name}.avglst | awk '{print $1}')
		
			head -$((${tline} - ${tpos} + 1)) ${name}.tree | tail -$((${tline} - ${tpos})) > tmp
			i_sub1=$(head -1 tmp | tail -1 | awk '{print $1}')
			name_sub1=$(basename $(awk -v i=${i_sub1} '$2==i {print $1}' ${name}.avglst) ".pad")
			if [ $(wc -l tmp | awk '{print $1}') -ne 0 ] ; then
				cp tmp ${name_sub1}.tree
				tail -$((${tline} - ${tpos} + 1)) ${name}.avglst > ${name_sub1}.avglst
			fi
		
			tail -$((${tpos} - 2)) ${name}.tree > tmp
			i_sub2=$(head -1 tmp | awk '{print $1}')
			name_sub2=$(basename $(awk -v i=${i_sub2} '$2==i {print $1}' ${name}.avglst) ".pad")
			if [ $(wc -l tmp | awk '{print $1}') -ne 0 ] ; then
				cp tmp ${name_sub2}.tree
				head -$((${tpos} - 1)) ${name}.avglst > ${name_sub2}.avglst
			fi
		fi
	done

	cat Makefile.config | sed -e s/'='/' '/ > Makefile.config.tmp
	ClusterCorrelationMode=$(awk '$1=="ClusterCorrelationMode" {print $2}' Makefile.config.tmp)
	ClusterRotationRangeMin=$(awk '$1=="ClusterRotationRangeMin" {print $2}' Makefile.config.tmp)
	ClusterRotationRangeMax=$(awk '$1=="ClusterRotationRangeMax" {print $2}' Makefile.config.tmp)
	ClusterRotationRangePartitionNumber=$(awk '$1=="ClusterRotationRangePartitionNumber" {print $2}' Makefile.config.tmp)
	ClusterRotationIterationNumber=$(awk '$1=="ClusterRotationIterationNumber" {print $2}' Makefile.config.tmp)
	
	for data in $(ls *.avglst)
	do
		name=$(basename ${data} ".avglst")
		awk '{print $1}' ${data} | sed -e s/.pad/.pad.fit/ > ${name}.fitlst
		cp ${name}.pad.avg{$num} ${name}.pad.avg
		for (( i=0; i < {$refine}; i++ ))
		do
			for element in $(awk '{print $1}' ${data})
			do
				mrcImageAutoRotationCorrelation	-i ${element}{$num} -r ${name}.pad.avg -fit ${element}.fit -cor ${element}.cor \
												-Method ${ClusterRotationCorrelationMode} -m ${ClusterCorrelationMode} -Iter ${ClusterRotationIterationNumber} \
												-range ${ClusterRotationRangeMin} ${ClusterRotationRangeMax} -n ${ClusterRotationRangePartitionNumber}
			done
			
			mrcImageAverage -i ${name}.fitlst -o ${name}.pad.avg
		done
		mrc2tiff -i ${name}.pad.avg -o ${name}.pad.avg.tiff
		mrc2tiff -i ${name}.pad.avg{$num} -o ${name}.pad.avg{$num}.tiff
	done
End


ルートに近い平均画像のリファインメント

ルートに近く、分割できない平均画像についてもリファインメントを行います。ルールRefinementとほとんど同じですが、平均画像を構成している画像リストはすでにルールProcessで作成されているので、本処理のみで十分です。

Rule RefinementRoot
	input '*.pad'.all
	input '*.pad.avg0'.all
	input ($I[2][1].str()+".avglst").d().all()
	input 'Makefile.config'
	output ($I[1][1].str()+".pad.avg").d().all()
	output ($I[1][1].str()+".pad.avg.tiff").d().all()
	output ($I[1][1].str()+".pad.avg0.tiff").d().all()
Action
	cat Makefile.config | sed -e s/'='/' '/ > Makefile.config.tmp
	ClusterCorrelationMode=$(awk '$1=="ClusterCorrelationMode" {print $2}' Makefile.config.tmp)
	ClusterRotationRangeMin=$(awk '$1=="ClusterRotationRangeMin" {print $2}' Makefile.config.tmp)
	ClusterRotationRangeMax=$(awk '$1=="ClusterRotationRangeMax" {print $2}' Makefile.config.tmp)
	ClusterRotationRangePartitionNumber=$(awk '$1=="ClusterRotationRangePartitionNumber" {print $2}' Makefile.config.tmp)
	ClusterRotationIterationNumber=$(awk '$1=="ClusterRotationIterationNumber" {print $2}' Makefile.config.tmp)
	ClusterRotationCorrelationMode=$(awk '$1=="ClusterRotationCorrelationMode" {print $2}' Makefile.config.tmp)
	
	for data in $(ls *.avglst)
	do
		name=$(basename ${data} ".avglst")
		awk '{print $1}' ${data} | sed -e s/.pad/.pad.fit/ > ${name}.fitlst
		cp ${name}.pad.avg0 ${name}.pad.avg
		for (( i=0; i < {$refine}; i++ ))
		do
			for element in $(awk '{print $1}' ${data})
			do
				mrcImageAutoRotationCorrelation	-i ${element} -r ${name}.pad.avg -fit ${element}.fit -cor ${element}.cor \
												-Method ${ClusterRotationCorrelationMode} -m ${ClusterCorrelationMode} -Iter ${ClusterRotationIterationNumber} \
												-range ${ClusterRotationRangeMin} ${ClusterRotationRangeMax} -n ${ClusterRotationRangePartitionNumber}
			done
			
			mrcImageAverage -i ${name}.fitlst -o ${name}.pad.avg
		done
		mrc2tiff -i ${name}.pad.avg -o ${name}.pad.avg.tiff
		mrc2tiff -i ${name}.pad.avg0 -o ${name}.pad.avg0.tiff
	done
End


動作検証

では実行してみましょう。
今回は入力画像を32×32(Pixel)を54枚用意しリファインメントを5回として、-t毎の実行時間を検証してみます。画像は全処理のログで、今回の実装部分はRefinement(ログの2分辺り)からです。(入力画像は同ディレクトリ内のMain_ROIr.pioneで作成しました)

t=1(5分強)
Outdata-PIONE-Advanced5-t1.png

t=2(4分程度)
Outdata-PIONE-Advanced5-t2.png

t=4(3分強)
Outdata-PIONE-Advanced5-t4.png

タスク数を増やす毎に処理時間の短縮(本処理が半分以下に軽減)が実現できています。今回の検証では入力画像の枚数が少ないので、ファイル通信があまりなく本処理の時間にほぼ直接影響されるため並列処理の効果が得られました。

Makefile内のパラメータ設定

今回は処理の一部をMakefileの処理に委ねていますが、使用するパラメータはMakefile.configに設定するようになっています。パラメータ管理をまとめて行えるようにこの部分のパラメータもPIONEで設定できるようにしてみましょう。

元のMakefile.configは下記のようになっています。

# Makefile.config for 2D Clustering 

# Cluster Name
CLUSTER=all

# Pad Parameter
#  Width(Nx) before shrinking 
PADWIDTH=32
#  Height(Ny) before shrinking 
PADHEIGHT=32
#  Shrink for Speed Up in clustering
SHRINK=1
#  Pad Mode 
PADMODE=13
#  LOWPASS
LowPassMode=4 
LowPassResolution=0.1

# Clustering
ClusterCorrelationMode=19
#
ClusterRotationRangeMin=0
ClusterRotationRangeMax=360
ClusterRotationRangePartitionNumber=72
ClusterRotationIterationNumber=2
ClusterRotationCorrelationMode=0
#
ClusterMode=2


# ClusterShow
TreeRootPositionX=0
TreeRootPositionY=400
TreeScaleX=10
TreeScaleY=100
TreeOffset=1e1

今回はPIONEで設定したパラメータから上記のようなMakefile.configを作成することで各種パラメータの管理を全てPIONE定義書内で行えるようにします。

Main.pione内に下記の内容を追加します。

# Cluster Name
param $CLUSTER := "all"

# Pad Parameter
#  Width(Nx) before shrinking 
param $PADWIDTH := 32
#  Height(Ny) before shrinking 
param $PADHEIGHT := 32
#  Shrink for Speed Up in clustering
param $SHRINK := 1
#  Pad Mode 
param $PADMODE := 13
#  LOWPASS
param $LowPassMode := 4 
param $LowPassResolution := 0.1

# Clustering
param $ClusterCorrelationMode := 19
#
param $ClusterRotationRangeMin := 0
param $ClusterRotationRangeMax := 360
param $ClusterRotationRangePartitionNumber := 72
param $ClusterRotationIterationNumber := 2
param $ClusterRotationCorrelationMode := 0
#
param $ClusterMode := 2


# ClusterShow
param $TreeRootPositionX := 0
param $TreeRootPositionY := 400
param $TreeScaleX := 10
param $TreeScaleY := 100
param $TreeOffset := "1e1"

# Sequence with Keys(Makefile.config for 2D Clustering)
$Config :=	("CLUSTER":$CLUSTER) |
			("PADWIDTH":$PADWIDTH) |
			("PADHEIGHT":$PADHEIGHT) |
			("SHRINK":$SHRINK) |
			("PADMODE":$PADMODE) |
			("LowPassMode":$LowPassMode) |
			("LowPassResolution":$LowPassResolution) |
			("ClusterCorrelationMode":$ClusterCorrelationMode) |
			("ClusterRotationRangeMin":$ClusterRotationRangeMin) |
			("ClusterRotationRangeMax":$ClusterRotationRangeMax) |
			("ClusterRotationRangePartitionNumber":$ClusterRotationRangePartitionNumber) |
			("ClusterRotationIterationNumber":$ClusterRotationIterationNumber) |
			("ClusterRotationCorrelationMode":$ClusterRotationCorrelationMode) |
			("ClusterMode":$ClusterMode) |
			("TreeRootPositionX":$TreeRootPositionX) |
			("TreeRootPositionY":$TreeRootPositionY) |
			("TreeScaleX":$TreeScaleX) |
			("TreeScaleY":$TreeScaleY) |
			("TreeOffset":$TreeOffset)

パラメータを設定しています。$TreeOffset := "1e1"のようにeを使用するような数値はPIONEの記法にはありませんので、" "で囲んで一旦文字列として設定しておきます。また、Makefile.configに書き出す処理を簡潔にするために設定したパラメータをキー付きシーケンスとして$Configに登録しています。新たにパラメータを追加したときは$Configにも追加するようにすれば後述で作成されるMakefile.configにも追加されるようになります。Configに値を直接登録することもできますが、実行ユーザがpione-client#オプション --paramsを利用してパラメータ設定できるようにparamで定義してから登録しています。

では、Makefile.config作成用のルールを作成して、Mainに追加します。

Rule SetParam
	output 'Makefile.config'
Action
	key=({$Config.keys})
	value=({$Config.values})
	for (( i=0; i<{$Config.length}; i++ ))
	do
		echo "${key[${i}]}=${value[${i}]}" >> {$O[1]}
	done
End

それぞれのパラメータをキー付きシーケンスとしてConfigに登録しているので、PIONEの式#キー付きシーケンスのメソッドkeysとvaluesを利用してパラメータ名と値を得ることができますので、Makefile.configに書き込むことができます。

SetParamで作成したMakefile.config

CLUSTER=all
PADWIDTH=32
PADHEIGHT=32
SHRINK=1
PADMODE=13
LowPassMode=4
LowPassResolution=0.1
ClusterCorrelationMode=19
ClusterRotationRangeMin=0
ClusterRotationRangeMax=360
ClusterRotationRangePartitionNumber=72
ClusterRotationIterationNumber=2
ClusterRotationCorrelationMode=0
ClusterMode=2
TreeRootPositionX=0
TreeRootPositionY=400
TreeScaleX=10
TreeScaleY=100
TreeOffset=1e1

元のMakefile.configと同様のファイルを作成することができました。

分類の点数付け

単粒子解析#クラスター解析で行った分類されたグループと元のモデルとの一致数を算出する処理を追加してみましょう。

# For GroupingPoint
param $filename := "121p-shift2"
param $ROT2 := (0.upto(3)) * 45

Rule GroupingPoint
	input '*.lst'.all
	input '*.tree'.all
	output '{$CLUSTER}.groupinfo'
Action
	array_rot2=( {$ROT2} )

	total=0
	element_total=0
	for num in {$I[1][1]}
	do
		element_num=$(wc -l ${num}.lst | awk '{print $1}')
		element_total=$(( ${element_total} + ${element_num} ))
		data=$(head -1 ${num}.tree)
		root_i=$(echo ${data} | awk '{print $1}')
		root_name=$(basename $(awk -v i=${root_i} '$2==i {print $1}' ${num}.lst) ".pad")
	
		max=0
		max_i=0
		i=0
		include_max_rot2=${array_rot2[0]}
		for rot2 in ${array_rot2[@]}
		do
			include_num=$(grep "{$filename}-0-${rot2}-" ${num}.lst | wc -l | awk '{print $1}')
			if [ ${include_num} -gt ${max} ] ; then
				max=${include_num}
				include_max_rot2=${rot2}
				max_i=${i}
			fi
			i=$(( ${i} + 1 ))
		done
		
		echo "group${root_i}:	match:	${max}	owner:	${include_max_rot2}" >> {$O[1]}
		total=$(( ${total} + ${max} ))
		
		unset array_rot2[${max_i}]
		array_rot2=("${array_rot2[@]}")
	done
	echo "				total:	${total}" >> {$O[1]}
	echo "${total} ${element_total}" | awk '{printf("parcentage:	%f\n", $1 / $2)}' >> {$O[1]}
End

今回、入力する使用ファイルはモデル有りで作成したサンプル画像のみを対象としています。(実際の電子顕微鏡画像での分類の評価はもっと複雑で、手法としてはクラスター解析単粒子解析#クラスター解析で議論する内容)ファイル名がAAAA-0-Y-Z-S.roiのような画像(同ディレクトリMain_ROI.pioneで作成される)でYの回転の違いのみでグループ分けされることを前提として、モデルとの一致数を.groupファイルに出力します。上記のパラメータでは各グループをY回転0°, 45°, 90°, 135°の内、最も一致するグループに当てはめて、その一致数と割合を下記のように出力します。

group000022:	match:	9	owner:	45
group000013:	match:	8	owner:	135
group000028:	match:	0	owner:	0
group000001:	match:	8	owner:	90
				total:	25
parcentage:	0.625000

group0000NNのNNはグループ分けされたルートのNo.、owenerは最も一致した角度、matchは一致数をそれぞれ表します。またtotalは一致数の合計、percentageは割合です。

今回はYの回転のみで分類を評価していますが、パラメータやfor文、ログの記述部分などを追加することで、Xの回転も組み合わせて分類することは可能です。また、パラメータROT2は(0.upto(3)) * 45のように等間隔で角度を与えていますが、|で繋いで直接記述することでMain_ROIr.pioneで作成した画像のように等間隔でないファイルについても同様に評価することができます。

param $ROT2 := (17.888783 | 114.250336 | 173.771591 | 273.340057)