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

提供: Eospedia
移動: 案内検索
(PIONE定義書)
(HTMLファイル)
行2,406: 行2,406:
 
</pre>
 
</pre>
 
このページでは入力ファイルと回転モードと及び各角度毎の最小値、最大値、加算値を設定して実行できるようにしています。<br>
 
このページでは入力ファイルと回転モードと及び各角度毎の最小値、最大値、加算値を設定して実行できるようにしています。<br>
 +
<br>
 +
 +
index2.html<br>
 +
<pre>
 +
<!DOCTYPE html>
 +
<html>
 +
<head>
 +
<meta charset="utf-8">
 +
<title>InteractiveCommand Index Page</title>
 +
</head>
 +
<body>
 +
<img src="Ref3DtoRef2D.gif"><br>
 +
<br>
 +
<a href="Finish.cgi">決定</a><br>
 +
<a href="Retry.cgi">やり直し</a>
 +
</body>
 +
</html>
 +
</pre>
 +
これはルールResultのときに画像を確認しながら、決定かやり直しかを選択すためのページです。<br>
 
<br>
 
<br>
  
 
===== [[CGI]]ファイル =====
 
===== [[CGI]]ファイル =====
 
<br>
 
<br>

2015年1月8日 (木) 01:10時点における版

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で作成したページ

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がテキストの内容で作成されます。

操作を終えるときには終了をクリックして下さい。これによりインタラクティブ処理が完了します。

基本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

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

応用

さて、ここまでの基本を組み合わせて、次の問題を考えてみましょう。

応用1(フローの制御)

問題1

*.mrcと*.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.1.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 '*.!*-{$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には下記の内容をbodyに追記します。

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


以下のように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>
		<a href="Finish.cgi">決定</a><br>
		<a href="Retry.cgi">やり直し</a>
	</body>
</html>

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

CGIファイル