ソフトウェア/Igor/システムの基本 のバックアップ(No.5)

更新


ソフトウェア/Igor

もっともお手軽な定型処理の仕方 - コマンドラインへの貼り付け

複数のグラフの見た目を揃えたい場合など、 ある動作を繰り返し行いたい場合に使える最もお手軽な方法は、 コマンドウィンドウに(複数の)コマンドをクリップボードから貼り付けて実行するやりかたです。

コマンドウィンドウとは

コマンドウィンドウというのは、
[ウィンドウ]-[コマンドウィンドウ]
をせんたくするか、もっと普通には
Ctrl+J
を押すと現れる、

IgorCommandWindow.PNG

このウィンドウです。

このウィンドウは通常「操作のログが残るウィンドウ」として認識されているかもしれませんが、 ここに Igor のコマンドを手で打ち込むことで、任意のコマンドを実行することができます。

Hello world!

例えばここに、

LANG:Igor
DoAlert 0, "Hello world!"

と打ち込んで、最後に ENTER を押すと、

CommandWindowHelloWorld.PNG

のようにアラートを表示できます。

複数行のコマンドを貼り付け

このコマンドウィンドウには複数行のコマンドを一度に貼り付けられます。

次のテキストをコピーしてコマンドウィンドウに貼り付け、 最後に ENTER を押すと、

LANG:Igor
Make/N=100 wave0 = sin(2*pi*x/99*2)
Make/N=100 wave1 = cos(2*pi*x/99*3)
Display wave1 vs wave0
ModifyGraph width={Plan,1,bottom,left}
ModifyGraph mode=3,marker=60

CommandWindowDemo.PNG

このように一連の処理を一度に実行できます。

いくつもあるグラフの見た目を揃える

雑多なグラフが並んでいる状態から、

LANG:Igor
make wave0=gnoise(1), wave1=gnoise(1), wave2=gnoise(1), wave3=gnoise(1)
display wave0
display wave1
display wave2
display wave3

次の手順で1つのグラフ (Graph3:wave3) の見た目を整えました。

  • 青線でマーカーを付ける
  • グラフの大きさを 3cm x 6cm にする
  • フォントを Times New Roman 14pt にする
  • 縦横軸にラベルを付ける

コマンドウィンドウには一連の操作のログが残っていることを確認できます (黒く選択されている領域)。

CommandWindowDemo2.PNG

このように操作ログをマウスカーソルで選択して Ctrl+C でコピーすると、 操作ログをクリップボードに入れられます。今の場合、その内容は

LANG:Igor
*ModifyGraph mode=4,marker=19,msize=1,rgb=(0,0,65280)
*ModifyGraph msize=3
*ModifyGraph width=170.079,height=85.0394,gFont="Times New Roman",gfSize=12
*Label left "Value [a.u.]";DelayUpdate
*Label bottom "Time [s]"

のようになります。
(始め、マーカーサイズを1にしましたが、小さすぎたので3に直した様子も残っていますね)

この内容はコマンドのログのようでもありますが、 実際には同じ動作を行うためのコマンドのリストになっていて、 先頭の * さえ取り除けばそのまま Igor のコマンドとして解釈可能です。

ここで、Graph0:wave0 のグラフを選択し、前面に持ってきてから
Ctrl+J でコマンドウィンドウを出し、
Ctrl+V で先ほどコピーしたコマンドリストを貼り付けます。
(各行の先頭に * が付いていますが気にしなくて大丈夫です)

CommandWindowDemo3.PNG

このように貼り付け後は最終行しか見えませんが、 実際にはちゃんとすべての行が貼り付けられています。

ここで ENTER を押すと、先ほどのコマンドがすべて実行され、 Graph0:wave0 の見た目が Graph3:wave3 と一致します。

CommandWindowDemo4.PNG

この操作を Graph1:wave1, Graph2:wave2 についても行うことで、 すべてのグラフの見た目を統一することができました。

CommandWindowDemo5.png

Igor で一連の動作を繰り返すにはこのように、

  1. まずは一回メニューなどから必要な操作をやってみて、
  2. コマンドウィンドウのログから必要部分を取りだして、
  3. 必要に応じてテキストエディタなどで編集して、
    • コマンドウィンドウに貼り付けて実行する
      または
    • Ctrl+M で出るプロシージャウィンドウに貼り付けて関数とする

というのが一般的な手順になります。

始めからコマンドをすべて覚えていなくても メニューから選んだコマンドの名前をコマンドウィンドウのログから 知ることができるのが良いところですね。

Igor のマルチスレッド対応

Igor Pro 6.1 から、マルチスレッド対応が強化されたそうです。

LANG:Igor
wave0 = any_expression(x)

のような代入文で時間がかかっている場合、

LANG:Igor
MultiThread wave0 = any_expression(x)

とするだけで、個々の座標値に対する計算を異なるスレッドで行ってくれます。

当然、wave の個々の座標の値の間に依存関係がある場合にはうまくいきませんが、 独立に計算できる場合にはこれだけでかなり速度が上がります。

より高度なマルチスレッド

もっと複雑なことをしたい場合には、 ThreadGroupCreate / ThreadStart / ThreadGroupPutDF / ThreadGroupGetDF / ThreadGroupWait / ThreadGroupRelease といった関数を使うことで、きめ細やかなマルチスレッド制御を行えます。

実際やってみると、メモリ確保などに時間がかかっていることも多いようで、 単純にスレッド数倍もの速度向上は望めませんが、シングルスレッド比で 2倍くらいの速度は簡単に出せるようでした。

例えば、Intel Core i7-3520M @ 2.9GHz + Windows 7 (64bit) で 1024 本のスペクトルに対して解析を行った結果、

  • シングルスレッド = 11.0 秒
  • マルチスレッド = 4.3秒

という結果が得られました。

注意点

このようにマルチスレッドで使う関数、例えば SomeMutiThreadableFunction(x) には ThreadSafe というキーワードを付けて定義する必要があります。

LANG:Igor
ThreadSafe function SomeMutiThreadableFunction(x)

    // マルチスレッド対応コード

end

このような ThreadSafe な関数からは、同じく ThreadSafe とマークされた関数以外を呼び出せません。

やってみるとすぐ分かるのですが、現在のところ ThreadSafe とマークした関数には デバッガ機能が働かず、ブレークやステップ実行ができません。

シングルスレッドで十分にテストしてからマルチスレッドに移行しないと、 うまく動かないときに面倒なことになります。

デバッグ環境設定のようなところで「Multithread 機能をオフにして、 すべてをシングルスレッド(ノンプリエンプティブマルチスレッド)で動作させる」 なんていうのを選べると、デバッグが楽になって良いんですけどね・・・

幸い、print や printf は ThreadSafe 関数からも呼べるので、 デバッグにはこれらを使って頑張るしか今のところ手はないようです?

コードのモジュール化 Tips

static function の利用

モジュール内(1つの .ipf ファイル内)でしか使わない関数には static を付けておくことで、そのモジュールの外から呼び出せなくなります。

この「隠蔽」により、モジュールの利用者がモジュールの使い方を見分けやすくなります。

FUNCREF を利用したコールバック

FUNCREF を利用したコールバックを使うとコードをモジュール化できる場合があります。

例えば、2次元データの中から条件に合致する点を探し、 そのデータ点に対して処理を行いたい場合に、

LANG:Igor
function DoSomethingOnThePoints()
    wave image
    variable i, j
    for(i=0; i<DimSize(image, 0); i+=1)
        for(j=0; j<DimSize(image, 1); j+=1)
            if( #condition# )
                DoSomething(i, j, image[i][j])
            endif
        endfor
    endfor
end

というようなコードが思い浮かびます。

しかし、同様の点に対して、異なる関数 DoAnotherThing(i, j, image[i][j]) を呼び出したい時に、DoAnotherThingOnThePoints() という別の関数を書いて、 そこにループ処理を貼り付けるのはばからしく感じられます。そこで、

LANG:Igor
function DoSomethingProcProto(i, j, v)
variable i, j, v
    // DoSomething のプロトタイプなので、実際の処理は何もしない
end

function DoAnythingOnThePoints(DoSomethingCallback)
string DoSomethingCallback
    FUNCREF DoSomethingProcProto DoSomething = $DoSomethingCallback
    wave image
    variable i, j
    for(i=0; i<DimSize(image, 0); i+=1)
        for(j=0; j<DimSize(image, 1); j+=1)
            if( #condition# )
                DoSomething(i, j, image[i][j])
            endif
        endfor
    endfor
end

この DoAnythingOnThePoints と言う関数に、呼び出して欲しい関数名を与えれば、 任意の関数を同じ手順で呼び出すことができるようになります。

DoSomethingProcProto は DoSomething という関数リファレンスの「型」、 すなわち、引数リストの型及び返り値の型を規定するためのダミー関数なので、 中身は何も書かなくて構いません。

このように文字列で書いた任意の関数を FUNCREF で呼び出す手法は、 特定のデータフォルダにグローバル変数としてコールバック関数名を書いておくことで コードの動作を替えるなど、様々な使い道があって、コードとコードの依存性を遮断して、 モジュール性を高めるために活用できます。

イディオム

モジュール名のデータフォルダの中に特定の文字列変数を用意して、 そこに呼び出してもらいたい関数名を書いておくと適当なタイミングで 呼んでもらえるようにする。

例えばそのモジュールではある画像を表示する役割を持っていて、 画像上をクリックするとその点の座標を引数に、 (登録されていれば)そのコールバック関数を呼ぶような場合。

モジュール名を Module1.ipf とすると、

Module1.ipf 内で、

LANG:Igor
// コールバック関数のプロトタイプ
// x, y には画像上でクリックした座標が入る
function ImageClickProcProto(x, y)
variable x, y
end

// 画像をクリックしたときにモジュール内で呼び出される。
// 必要に応じてユーザー指定のコールバック関数を呼ぶ
static function ImageClicked(x, y)
variable x, y
    // モジュール名と同じ名前のデータフォルダー root:Packages:Module1 に 
    // ImageClickProc という文字列変数が定義されていて
    SVAR/Z ImageClickProc = root:Packages:Module1:ImageClickProc
    if (SVAR_exists(ImageClickProc))
        // その文字列が示す名前のユーザー定義関数が存在するなら
        if (exists(ImageClickProc)==6)
            // その関数を呼び出す
            FUNCREF ImageClickProcProto ImageClickProcRef = $(ImageClickProc)
            ImageClickProcRef(x, y)
        endif
    endif
end

Module1 を使う側では、

LANG:Igor
// 始めに : を付けておくことで Experiment と
// 同じフォルダにある Module1.ipf をインクルードする
#include ":Module1"

// 初期化処理でコールバック関数を登録する
function initialize()
    NewDataFolder/O root:Packages
    NewDataFolder/O root:Packages:Module1
    string/g root:Packages:Module1:ImageClickProc = "ImageClick"
end

function ImageClick(x, y)
variable x, y

    // 画像がクリックされると呼び出されるので、
    // ここで望みの処理を行う

end

のようにします。

こうすることで、モジュール側のコードをいじることなく、 クリックされたときの動作を差し替えることができるため、 コードの再利用が可能となります。特に、Module1 が ImageClick などのコールバック関数に依存しなくなるため、 モジュールの独立性が向上することがうれしいところです。

コールバック関数呼び出し用関数

以下のような一連の関数を作っておくと、 上記のようなフック関数を呼び出すときに便利に使えます。

動作としては、SVAR の名前(上の例で言えば "root:Packages:Module1:ImageClickProc") を与えると、その SVAR の中身を調べ、そこに関数名が指定されていればそれを呼び出す、 というものになります。

FUNCREF で関数を呼び出すには関数形に合わせてそれぞれコードを書かなければならないですが、 下記のように、
引数無しで数値を返す関数を func2N,
引数無しで文字列を返す関数を func2S,
数値を1つ与えて数値を返す関数を funcN2N,
数値を1つ与えて文字列を返す関数を funcN2S,
数値を2つ与えて数値を返す関数を funcNN2N,
数値を1つ、文字列を2つ与えて数値を返す関数を funcNSS2N,
などと名前を付けると分かりやすそう。

funcXX2X の形のプロトタイプ宣言だけでもいろいろ使い道がありますし、 また、命名規約さえ決めてしまえばここに無い形の関数も容易に追加できるはずです。

CallbackUtility.ipf

LANG:Igor(linenumber)
//
// svar_name という名の svar で指定される関数があれば呼び出す
// default_proc を渡すことで、コールバックが指定されていないときの
// 動作を定義可能
// 
// function   CallbackFunc2N(svar_name)
// function/S CallbackFunc2S(svar_name)
// function   CallbackFuncN2N(svar_name, n)
// function/S CallbackFuncN2S(svar_name, n)
// function   CallbackFuncS2N(svar_name, s)
// function/S CallbackFuncS2S(svar_name, s)
// function   CallbackFuncNN2N(svar_name, n1, n2)
// function/S CallbackFuncNN2S(svar_name, n1, n2)
//
// default_proc を渡すときは、
//     string svar_name = "root:Packages:ModuleName:SomeCallbackProc"
//     CallbackFunc2N(svar_name, default_proc=some_proc)
// のようにする

#pragma rtGlobals=1     // Use modern global access method.
#pragma ModuleName= CallbackUtility

function func2N()
end

function/S func2S()
end

function funcN2N(n)
variable n
end
                                
function/S funcN2S(n)
variable n
end

function funcS2N(s)
string s
end

function/S funcS2S(s)
string s
end

function funcNN2N(n1, n2)
variable n1, n2
end

function/S funcNN2S(n1, n2)
variable n1, n2
end

// svar_name という名の svar で指定される関数があれば proc_name に関数名を返す
static function CallbackFuncExists(svar_name, proc_name)
string svar_name, &proc_name
    SVAR/Z ProcName = $svar_name
    if(SVAR_exists(ProcName) && exists(ProcName)==6)
        proc_name = ProcName
        return 1
    endif
    return 0
end

function CallbackFunc2N(svar_name, [default_proc])
string svar_name
FUNCREF func2N default_proc
    string proc_name
    if( CallbackFuncExists(svar_name, proc_name) )
        FUNCREF func2N ProcRef = $proc_name
        return ProcRef()
    elseif (paramisdefault(default_proc))
    	return default_proc()
    endif
    return Nan
end

function/S CallbackFunc2S(svar_name, [default_proc])
string svar_name
FUNCREF func2S default_proc
    string proc_name
    if( CallbackFuncExists(svar_name, proc_name) )
        FUNCREF func2S ProcRef = $proc_name
        return ProcRef()
    elseif (paramisdefault(default_proc))
    	return default_proc()
    endif
    return ""
end

function CallbackFuncN2N(svar_name, n, [default_proc])
string svar_name
variable n
FUNCREF funcN2N default_proc
    string proc_name
    if( CallbackFuncExists(svar_name, proc_name) )
        FUNCREF funcN2N ProcRef = $proc_name
        return ProcRef(n)
    elseif (paramisdefault(default_proc))
    	return default_proc(n)
    endif
    return Nan
end

function/S CallbackFuncN2S(svar_name, n, [default_proc])
string svar_name
variable n
FUNCREF funcN2S default_proc
    string proc_name
    if( CallbackFuncExists(svar_name, proc_name) )
        FUNCREF funcN2S ProcRef = $proc_name
        return ProcRef(n)
    elseif (paramisdefault(default_proc))
    	return default_proc(n)
    endif
    return ""
end

function CallbackFuncS2N(svar_name, s, [default_proc])
string svar_name, s
FUNCREF funcS2N default_proc
    string proc_name
    if( CallbackFuncExists(svar_name, proc_name) )
        FUNCREF funcS2N ProcRef = $proc_name
        return ProcRef(s)
    elseif (paramisdefault(default_proc))
    	return default_proc(s)
    endif
    return Nan
end

function/S CallbackFuncS2S(svar_name, s, [default_proc])
string svar_name, s
FUNCREF funcS2S default_proc
    string proc_name
    if( CallbackFuncExists(svar_name, proc_name) )
        FUNCREF funcS2S ProcRef = $proc_name
        return ProcRef(s)
    elseif (paramisdefault(default_proc))
    	return default_proc(s)
    endif
    return ""
end

function CallbackFuncNN2N(svar_name, n1, n2, [default_proc])
string svar_name
variable n1, n2
FUNCREF funcNN2N default_proc
    string proc_name
    if( CallbackFuncExists(svar_name, proc_name) )
        FUNCREF funcNN2N ProcRef = $proc_name
        return ProcRef(n1, n2)
    elseif (paramisdefault(default_proc))
    	return default_proc(n1, n2)
    endif
    return Nan
end

function/S CallbackFuncNN2S(svar_name, n1, n2, [default_proc])
string svar_name
variable n1, n2
FUNCREF funcNN2S default_proc
    string proc_name
    if( CallbackFuncExists(svar_name, proc_name) )
        FUNCREF funcNN2S ProcRef = $proc_name
        return ProcRef(n1, n2)
    elseif (paramisdefault(default_proc))
    	return default_proc(n1, n2)
    endif
    return ""
end

使用例:上記の例を書き換え

LANG:Igor
// 画像をクリックしたときにモジュール内で呼び出される。
// 必要に応じてユーザー指定のコールバック関数を呼ぶ
static function ImageClicked(x, y)
variable x, y
    // モジュール名と同じ名前のデータフォルダー root:Packages:Module1 に 
    // ImageClickProc という文字列変数が定義されていて
    // その文字列が示す名前のユーザー定義関数が存在するなら
    // その関数を呼び出す
    CallbackFuncNN2N("root:Packages:Module1:ImageClickProc", x, y)
end

質問・コメント





Counter: 33473 (from 2010/06/03), today: 7, yesterday: 0