ソフトウェア/Igor/システムの基本

(273d) 更新

ソフトウェア/Igor

初期画面

Igor を起動すると、次のような画面が現れます。

igor-window.png

  • 上はデータを入力したり、表示したりするテーブル
  • 下は後で説明するコマンドウィンドウです

データを入力してウェーブを作る

上のテーブルの所に適当に10個の数値を入力しました。

wave0-table.png

上には勝手に wave0 という名前が付きました。

グラフ化するような一連の数値データのことを Igor ではウェーブと呼んでいます。

データブラウザ

で、wave0 というのは、今入力したウェーブに自動的に付けられた名前です。

Igor では複数のウェーブに好きな名前を付けてメモリ上に保持できます。

現在メモリ上に存在するウェーブ(やグローバル変数)の一覧を表示するには、 [データ]-[Data Browser] を使います。

data-browser.png

現れた Data Browser は root の直下に wave0 というウェーブが存在することを示しています。

左の Plot にチェックが入っていれば、wave0 をクリックして選択するだけで下の部分にグラフ概形が表示されます。

data-browser2.png

  • wave0 の部分をダブルクリックすると新しいテーブルが
  • プロットの部分をダブルクリックすると新しいグラフが

表示されます。

Igor では1つのウェーブを複数のテーブルやグラフに表示できるので、 うかつにダブルクリックすると、どんどん新しいグラフやテーブルが作成されてしまいます。

many-tables-graphs.png

グラフやテーブルはデータそのものではなく、データを表示するためだけの物なので、 グラフウィンドウを閉じてもデータそのものは失われません。

ですので、不必要なグラフは閉じて構いません。

で、グラフを閉じようとすると、何かを保存するかどうか聞かれるのですが、 これは、「今閉じようとしているグラフとまったく同じグラフ(大きさ、色、太さ、表示範囲など) を作るためのおまじない」を保存するかどうか聞かれているものです。

close-graph.png

必要なければ無視して [保存しない] を選んで大丈夫です。

ファイルからデータを読み込む

次のようなテキストファイルを作って、data.txt として保存しましょう。

1
2
4
5
7
5
4
2
1

これを読み込んでグラフにしてみます。[データ]-[ウェーブをロード]-[区切りテキストをロード] から、

load-data.png

ウェーブ名は既定値の wave1 のままにして [読み込み] すると、

load-data2.png

Data Browser に wave1 が表示され、プロットをダブルクリックすることでグラフを表示できました。

wave1-loaded.png

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

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

コマンドウィンドウとは

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

IgorCommandWindow.PNG

このウィンドウです。

このウィンドウには「操作のログ」が残ります。

上記の操作をした後であれば、次のようになっているはずで、

command-window2.png

  • "Display/K=0 wave0" は、wave0 のグラフを作製したこと
  • "Edit/K=0 'wave0'" は、wave0 のテーブルを作製したこと

などを表しています。

実はこの「ログ」はそのまま Igor のコマンドになっていて、 同じ文字列をコマンドウィンドウに打ち込むことで、 以前に行った操作を再度実行することができます。

例えば、"Display wave0" としてみると、新しいグラフが表示されるのを確認できるはずです。

display-wave0.png

Hello world!

コマンドは他にもたくさんあります。

一旦、Ctrl+N を押してこれまでのデータを消去してから始めましょう。

例えばここに、

LANG:Igor
DoAlert 0, "Hello world!"

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

CommandWindowHelloWorld.PNG

のように Hello world! というアラートを表示できます。

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

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

次のテキストをコピーしてコマンドウィンドウに貼り付け、 最後に 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

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

「名称はウェーブとしてすでに存在しています」となった場合には、 すでに wave0 や wave1 という名前のウェーブが存在していますので、 必要なデータをすべて保存してから Ctrl+N で新規エクスペリメントを作成してからリトライしてみて下さい。

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

次のコマンドで、ランダムデータからなる4つのウェーブを作成してグラフに表示します。

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) の見た目を整えました。

  • Graph3 を選択する

  • グラフ上で赤線部分をダブルクリックし、[カラー] で "青" を選択、[モード] を "折れ線とマーカー" にして、[サイズ] を "1"、形状を "●" にする。
    trace-appearance.png

  • [グラフ]-[グラフを修正] 幅を 6cm 高さを 3cm、フォントを Times New Roman 12pt にする
    modify-graph.png

  • [グラフ]-[軸ラベル] から縦横軸にラベルを付ける
    graph-axes-labels.png

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

CommandWindowDemo2.PNG

で、他の3つのグラフも同様にして見た目を揃えたい場合に、 これらの操作を一々繰り返すのは大変です。

そういう時にこの操作ログが大活躍するのです。

まず上図のように操作ログをマウスカーソルで選択して 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 を付けておくことで、そのモジュールの外から呼び出せなくなります。

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

また、異なるモジュールで関数名が被る心配が無くなるのも強みです。

どうしてもモジュール外部から参照したい場合には、

#pragma ModuleName = ModuleName

でモジュールに名前を付けておいて、

ModuleName#FunctionName

として参照します。

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

static 関数を渡すときには、モジュール名を付けて "ModuleName#FunctionName" として渡せばOKです。

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: 10809 (from 2010/06/03), today: 18, yesterday: 0