「PIONEチュートリアル」の版間の差分
(→基本) |
(→基本12(チケット)) |
||
行1,427: | 行1,427: | ||
</pre> | </pre> | ||
上記のようにチケット名は<>で囲んで、ルールとチケット間を==>で繋いで使用します。<br> | 上記のようにチケット名は<>で囲んで、ルールとチケット間を==>で繋いで使用します。<br> | ||
+ | <br> | ||
+ | |||
+ | チケット名を省略したい場合は下記のように記述します。<br> | ||
+ | <pre> | ||
+ | rule Touch0 >>> CreateList | ||
+ | </pre> | ||
+ | <br> | ||
+ | |||
+ | 下記のように連続させることも可能です。<br> | ||
+ | <pre> | ||
+ | rule First >>> Second >>> Third | ||
+ | </pre> | ||
<br> | <br> | ||
行1,466: | 行1,478: | ||
</pre> | </pre> | ||
今度は0.dataが含まれるようになりました。<br> | 今度は0.dataが含まれるようになりました。<br> | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
<br> | <br> | ||
2015年1月14日 (水) 01:56時点における版
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としての振る舞いです。
基本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に関しては、変更の可能性があるので起動し始めますが、何のルールも適用されないまま終了しています。
基本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_val: $main_val} End Rule Sub output 'message.txt' param $sub_val 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_val: {$sub_val}" >> {$O[1]}; End
1つずつ定義するときはそれぞれbasic param XXX, advanced param YYYのように記述します。(単にparam ZZZとしたときはbasic扱いとなります。)まとめて定義したいときはBasic Param ~ End, Advanced Param ~ Endで囲みます。このとき定義する変数名の先頭には$を付けて記述します。また使用するときは入力ファイルや出力ファイルと同様に{ }で括ります。
設定したパラメータはオプション--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$
設定を反映した処理が実行できました。
基本7(制御文)
今回は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と記述します。case文の場合は下記のように記述します。
case $val % 2 when 0 rule Even else rule Odd 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$
偶数のルールが動きます。
以上のようにして条件に合わせてルールを制御することができます。
基本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で作成したページ
-> |
基本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"を出力するようにしています。
では、実行させてみましょう。
まずは選択...ボタンからテキストファイルを選択し、作成(ファイル)ボタンでサーバ内にファイルを作成(アップロード)してみましょう。
これによりAAA.txtが同じ内容で作成(アップロード)されます。終了した後にダウンロードすると/output/AAA.txtの内容を確認できます。
また、ファイル一覧の取得ボタンよりサーバ上のファイル一覧を閲覧することができます。
AAA.txtが確認できます。
そして削除ボタンを押すと、AAA.txtを削除します。
AAA.txtがなくなっているのが確認できます。
次はテキストボックスに文字列を書き込んで、作成(テキスト)ボタンを押してみましょう。
これによりAAA.txtがテキストの内容で作成されます。
操作を終えるときには終了をクリックして下さい。これによりインタラクティブ処理が完了します。
基本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
このようにすることでループに掛かる時間が改善されます。
問題:これを踏まえて#基本10(ループ文)のループを改善してみましょう。
解答例
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の式#四則演算における注意事項)
基本12(チケット)
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が含まれるようになりました。
応用
さて、ここまでの基本を組み合わせて、次の問題を考えてみましょう。
応用1(フローの制御)
問題1
但し、*.parametersは以下のフォーマットで切り出しサイズ(Sx, Sy, Sz)を格納しているファイルとします。
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
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)
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
roiファイル2
後処理をしたくないときは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はファイルに従ったサイズになっています。
1VOM-N.roiはNxがコマンドの引数、Ny, Nzはデフォルト値のパラメータによるサイズになります。
応用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)
最小 最大 |
-18651.7 (10, 1, 0) 52942.7 (24, 39, 0) |
実行例1 | ||
> | ||
実行例2 | ||
> | ||
実行例3 | ||
> |
終了後に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.lpfを削除しています。
削除完了画面が表示されるので、ブラウザのバックボタンで戻ります。
画面表示を更新するとOutdata.lpfが消えていることが確認できます。また、インタラクティブ操作を終了し、ファイルをダウンロードするとOudata.lpfが無いことも確認できます。これにより不要ファイルを削除できたことが分かりました。
応用3(参照像の作成)
今回はループ文を使用した例を示します。
問題3
入力ファイルを参照用の3次元画像(.ref3d)として、指定した範囲の角度で参照像(.ref2d)を作成する処理を考えます。PIONE WebClientを利用し、作成した各参照像の画像を確認しながら、ユーザ操作によって適切な角度を決定できるようにします。
応用問題3:上記の機能を持つパッケージRef3DtoRef2D.ppgを作成してみましょう。
解答例
PIONE定義書
ユーザ操作によって3次元画像と投影する角度を設定し、そこから参照像を作成して、結果を画像として確認し、決定かやり直しかを選択できる流れとしています。
Annotation.pione
.@ PackageName :: "Ref3DtoRef2D" .@ Editor :: "Kinoshita" .@ Tag :: "v0.1.1"
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 '*.!*-{$val}'.all input '*.ref3d-{$val}' output '*.ref2d-{$val}'.all output 'Ref3DtoRef2D.info-{$val}' param $val Flow rule FlowLoop3Dto2D1 {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 FlowLoop3Dto2D1 input '*!*!*.!Rot1-{$val}' input '*!*!*.!Rot2-{$val}' input '*!*!*.!Rot3-{$val}' input '*.!Mode-{$val}' input '*.ref3d-{$val}' output '*.ref2d-{$val}'.all $min1 := '{$I[1][1]}'.str().f() $max1 := '{$I[1][2]}'.str().f() $delta1 := '{$I[1][3]}'.str().f() $loop1 := ($max1 - $min1) / $delta1 param $val1 := 0.0 param $val Flow rule FlowLoop3Dto2D2 {val1 : $val1, val :$val} if $val1 + 1.0 <= $loop1 rule FlowLoop3Dto2D1 {val1 : $val1 + 1.0, val :$val} end End Rule FlowLoop3Dto2D2 input '*!*!*.!Rot1-{$val}' input '*!*!*.!Rot2-{$val}' input '*!*!*.!Rot3-{$val}' input '*.!Mode-{$val}' input '*.ref3d-{$val}' output '*.ref2d-{$val}'.all $min2 := '{$I[2][1]}'.str().f() $max2 := '{$I[2][2]}'.str().f() $delta2 := '{$I[2][3]}'.str().f() $loop2 := ($max2 - $min2) / $delta2 param $val1 param $val2 := 0.0 param $val Flow rule FlowLoop3Dto2D3 {val1 : $val1, val2 : $val2, val :$val} if $val2 + 1.0 <= $loop2 rule FlowLoop3Dto2D2 {val1 : $val1, val2 : $val2 + 1.0, val :$val} end End Rule FlowLoop3Dto2D3 input '*!*!*.!Rot1-{$val}' input '*!*!*.!Rot2-{$val}' input '*!*!*.!Rot3-{$val}' input '*.!Mode-{$val}' input '*.ref3d-{$val}' output '*.ref2d-{$val}'.all $min3 := '{$I[3][1]}'.str().f() $max3 := '{$I[3][2]}'.str().f() $delta3 := '{$I[3][3]}'.str().f() $loop3 := ($max3 - $min3) / $delta3 param $val1 param $val2 param $val3 := 0.0 param $val Flow rule One3Dto2D {val1 : $val1, val2 : $val2, val3 : $val3, val :$val} if $val3 + 1.0 <= $loop3 rule FlowLoop3Dto2D3 {val1 : $val1, val2 : $val2, val3 : $val3 + 1.0, val :$val} end End Rule One3Dto2D input '*!*!*.!Rot1-{$val}' input '*!*!*.!Rot2-{$val}' input '*!*!*.!Rot3-{$val}' input '*.!Mode-{$val}' input '*.ref3d-{$val}' output '*.ref2d-{$val}'.all param $val1 param $val2 param $val3 param $val Action min1={$I[1][1]} min2={$I[2][1]} min3={$I[3][1]} delta1={$I[1][3]} delta2={$I[2][3]} delta3={$I[3][3]} mode={$I[4][1]} rot1=$(echo "scale=5; {$val1} * $delta1 + $min1" | bc) rot2=$(echo "scale=5; {$val2} * $delta2 + $min2" | bc) rot3=$(echo "scale=5; {$val3} * $delta3 + $min3" | bc) output="{$I[5][1]}-$mode-$rot1-$rot2-$rot3.ref2d-{$val}" mrc3Dto2D -i {$I[5]} -o $output \ -Rot1 $rot1 $rot1 1 \ -Rot2 $rot2 $rot2 1 \ -Rot3 $rot3 $rot3 1 \ -EulerMode $mode; End
ルールRef3DtoRef2Dは大きくFlowLoop3Dto2D1とInfo3Dto2Dの2つのルールに分かれます。ここでもやり直しに対応するためにファイルの末尾に番号を付けます。ルールInfo3Dto2Dでは画像作成のために2次元画像の一覧を作成します。ルールFlowLoop3Dto2D1ではループを利用して3次元画像からmrc3Dto2Dによって2次元画像を次々に作成します。このとき3軸毎に回転角度を設定するためにループ文も3つに分けています。ループは自分自身を再帰的に呼び出すことで実現していますので、出来るだけ分けるようにした方が良いでしょう。第1軸のループがFlowLoop3Dto2D1、第2軸のループがFlowLoop3Dto2D2、第3軸のループがFlowLoop3Dto2D3、そして本処理がOne3Dto2Dです。
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次元画像を作成してみます。
最小 最大 |
0 (0, 0, 0) 3398.12 (23, 55, 41) |
入力ファイル及び回転のモード(参照: オイラー角)、角度の範囲を設定して開始ボタンを押します。
すると、上記のようにインタラクティブ操作は完了して、再びPIONEにて2次元像の作成処理が動きます。(このページは閉じて構いません)
再びInteractionボタンが赤色になりますので、開くと作成された2次元像の一覧が画像にて表示されます。この内容を見ながら「決定」か「やり直し」を選択します。
同様にインタラクティブ操作が完了しますので、ページを閉じます。
「やり直し」を選択すると、再度設定画面を開くことができます。また、「決定」を選択するとこのときの結果がouputへ出力されます。このようにして結果を確認しながら適切な設定を選び直すことが出来ます。