たまによくある

あれこれ

Igor Proでグラフ上の選択範囲だけを線形フィットする

Igor Proでグラフを見ていると、「この範囲だけ直線でフィットしたい」という場面がたまによくあります。

たとえば、吸収端の立ち上がり部分、CVの一部の直線領域、時間変化データの初期速度、ベースラインの傾き確認などです。 グラフ全体をフィットするのではなく、見た目で範囲を選んで、その部分だけをさっと直線フィットできるとかなり便利です。

今回は、Igor ProのGraphMarquee機能を使って、グラフ上でドラッグ選択した範囲だけを線形フィットするマクロを紹介します。 マクロ名はスクリプト内では LineFitMarquee です。

できること

  • グラフ上でマウスドラッグした範囲だけを線形フィットする
  • 複数traceがある場合は、どのtraceをフィットするか選べる
  • X wave付きのXYグラフにも、wave scalingだけの通常グラフにも対応する
  • フィット結果を青い点線として元のグラフに重ね描きする
  • フィットに使ったX/Y wave、フィット結果wave、係数waveを元データと同じdata folderに保存する

要するに、グラフを見ながら「ここからここまで」と囲むだけで、その範囲の直線フィットを作れるマクロです。

使い方

  1. フィットしたいwaveをグラフに表示します。
  2. グラフ上で、フィットしたい範囲をマウスでドラッグして囲みます。

  3. 右クリックしてGraphMarqueeメニューから Line Fit Marquee Range を選びます。

  4. 複数traceがある場合は、フィットしたいtraceを選びます。

  5. 選択範囲だけが線形フィットされ、結果がグラフに重ね描きされます。

    (aが切片,bが傾き,V_r2がR^2値としてコマンドラインに出力される)

実行後には、IgorのHistoryにフィット対象のtrace名、元のY wave、フィット結果wave、係数waveの保存場所が表示されます。 あとから結果を確認したいときは、この出力を見ると分かりやすいです。

出力されるwave名は、元のY wave名をもとにして自動で作られます。

fitX_元wave名
fitY_元wave名
fit_元wave名
coef_元wave名



すでに同じ名前のwaveがある場合は、Igorの UniqueName によって重複しない名前になります。 

注意点

  • 選択範囲内に有効な点が2点未満の場合はフィットできません。
  • 現在のマクロでは、marqueeのX方向の範囲を使ってデータを選びます。

つまり、縦方向にどれだけ大きく囲むかよりも、横方向にどこからどこまで囲むかが重要です。 グラフ上でX範囲を指定する道具として使うイメージです。

マクロの仕組み

このマクロは、Igor Proの GraphMarquee メニューに項目を追加しています。

Menu "GraphMarquee"
    "Line Fit Marquee Range", LineFitMarquee()
End

GraphMarquee は、グラフ上で選択範囲を作ったときに使うメニューです。 ここにマクロを置いておくと、「グラフ上で囲んだ範囲に対して何かする」という操作を自然に追加できます。

1. グラフとmarqueeの有無を確認する

最初に、現在の対象ウィンドウがグラフかどうかを確認します。 さらに、marquee範囲が作られていなければ処理を止めます。

if (WinType(WinName(0, -1)) != 1)
    Abort "The target window must be a graph."
endif

GetMarquee left
if (V_Flag == 0)
    Abort "Please create a marquee (drag to select) before running this macro."
endif

ここで GetMarquee を使っています。 これが今回のマクロの肝です。 グラフ上で囲んだ範囲の座標をIgor側から取得できます。

2. フィットするtraceを選ぶ

次に、アクティブなグラフに載っているtrace一覧を取得します。 traceが複数ある場合は、ポップアップで対象を選べるようにしています。

String traceList = TraceNameList("", ";", 1)
String traceName = StringFromList(0, traceList)

Prompt traceName, "Trace to fit", popup, traceList
DoPrompt "Quick linear fit", traceName

複数のデータを同じグラフに重ねているとき、どれをフィットするのかを毎回選べるのは大事です。 ここを決め打ちにすると、気づかないうちに別のtraceをフィットしてしまうことがあります。

3. traceからY waveとX waveを取得する

選ばれたtraceから、Y waveとX waveを取得します。

WAVE/Z yWave = TraceNameToWaveRef("", traceName)
WAVE/Z xWave = XWaveRefFromTrace("", traceName)
Variable isXY = WaveExists(xWave)

XYグラフの場合はX waveがあります。 一方、X waveを指定せずに表示した通常のwaveでは、X waveが存在しません。 その場合は pnt2x を使って、wave scalingからX座標を計算します。

if (isXY)
    xv = xWave[p]
else
    xv = pnt2x(yWave, p)
endif

この処理のおかげで、XYグラフでも普通のwaveグラフでも同じマクロを使えます。

4. marqueeのX範囲を取得する

実際にフィット範囲として使うのは、marqueeのX方向の範囲です。

GetMarquee bottom
Variable xMin = V_left
Variable xMax = V_right

左から右へドラッグしても、右から左へドラッグしても動くように、大小が逆なら入れ替えています。

if (xMin > xMax)
    Variable tmpX = xMin
    xMin = xMax
    xMax = tmpX
endif

5. 範囲内の点だけを抜き出す

元のwaveを先頭から最後まで走査し、X座標が選択範囲内にある点だけを fitXfitY にコピーします。 このとき、NaN などの無効値は除外しています。

if ((xv >= xMin) && (xv <= xMax) && (numtype(xv) == 0) && (numtype(yv) == 0))
    InsertPoints count, 1, fitX, fitY
    fitX[count] = xv
    fitY[count] = yv
    count += 1
endif

この部分で、グラフ上の見た目の範囲と、実際にフィットへ渡すデータ点が対応します。

6. CurveFitで直線フィットする

範囲内の点が2点以上あれば、Igor標準の CurveFit で直線フィットします。

CurveFit/N=1 line, kwCWave=fitCoef, fitY /X=fitX /D=fitResult /I=0

fitCoef にはフィット係数が入ります。 直線フィットなので、基本的には切片と傾きです。 fitResult には、フィット曲線のY値が入ります。

7. 結果をグラフに重ねる

最後に、フィット結果を元のグラフに追加します。 結果は青色の点線で表示されます。

AppendToGraph fitResult vs fitX
ModifyGraph rgb($NameOfWave(fitResult)) = (0,0,65535)
ModifyGraph lstyle($NameOfWave(fitResult)) = 2

元データの上にフィット線が直接重なるので、選択範囲やフィットの妥当性をすぐ確認できます。 この「見ながら選んで、すぐ結果を見る」感じがかなり使いやすいです。

どんな場面で便利か

このマクロは、厳密な自動解析というより、「人間がグラフを見て、ここだと思った範囲をすばやく定量する」ための道具です。

  • UV-visの吸収端の傾きを見たいとき
  • 時間変化データの初期速度をざっくり確認したいとき
  • グラフの一部だけを直線近似したいとき

スクリプト

最後に、記事用にコメントを少し整理した版を載せておきます。

Menu "GraphMarquee"
    "Line Fit Marquee Range", LineFitMarquee()
End

Macro LineFitMarquee()
    DoQuickFitMarquee()
End

Function DoQuickFitMarquee()
    if (WinType(WinName(0, -1)) != 1)
        Abort "The target window must be a graph."
    endif

    GetMarquee left
    if (V_Flag == 0)
        Abort "Please create a marquee (drag to select) before running this macro."
    endif

    QuickFitMarquee()
End

Function QuickFitMarquee()
    String traceList = TraceNameList("", ";", 1)
    if (ItemsInList(traceList) == 0)
        Abort "No trace was found in the active graph."
    endif

    String traceName = StringFromList(0, traceList)

    Prompt traceName, "Trace to fit", popup, traceList
    DoPrompt "Quick linear fit", traceName
    if (V_Flag)
        Abort "Canceled."
    endif

    PauseUpdate; Silent 1

    WAVE/Z yWave = TraceNameToWaveRef("", traceName)
    if (!WaveExists(yWave))
        Abort "Failed to get the Y wave from the selected trace."
    endif

    WAVE/Z xWave = XWaveRefFromTrace("", traceName)
    Variable isXY = WaveExists(xWave)

    String oldDF = GetDataFolder(1)
    String yFullPath = GetWavesDataFolder(yWave, 2)
    String yBareName = NameOfWave(yWave)
    String yDF = yFullPath[0, strlen(yFullPath) - strlen(yBareName) - 1]

    SetDataFolder $yDF

    String outBase = CleanupName(NameOfWave(yWave), 0)

    String fitXName      = UniqueName("fitX_" + outBase, 1, 0)
    String fitYName      = UniqueName("fitY_" + outBase, 1, 0)
    String fitResultName = UniqueName("fit_"  + outBase, 1, 0)
    String fitCoefName   = UniqueName("coef_" + outBase, 1, 0)

    Make/O/N=0 $fitXName, $fitYName
    Make/O/N=2 $fitCoefName

    WAVE fitX = $fitXName
    WAVE fitY = $fitYName
    WAVE fitCoef = $fitCoefName

    SetDataFolder $oldDF

    GetMarquee bottom
    Variable xMin = V_left
    Variable xMax = V_right

    if (xMin > xMax)
        Variable tmpX = xMin
        xMin = xMax
        xMax = tmpX
    endif

    Variable count = 0
    Variable p
    Variable xv, yv
    Variable n = numpnts(yWave)

    for (p = 0; p < n; p += 1)
        if (isXY)
            xv = xWave[p]
        else
            xv = pnt2x(yWave, p)
        endif

        yv = yWave[p]

        if ((xv >= xMin) && (xv <= xMax) && (numtype(xv) == 0) && (numtype(yv) == 0))
            InsertPoints count, 1, fitX, fitY
            fitX[count] = xv
            fitY[count] = yv
            count += 1
        endif
    endfor

    if (count < 2)
        KillWaves/Z fitX, fitY, fitCoef
        Abort "Not enough valid points in the selected range to fit."
    endif

    SetDataFolder $yDF
    Make/O/N=(count) $fitResultName
    WAVE fitResult = $fitResultName
    SetDataFolder $oldDF

    CurveFit/N=1 line, kwCWave=fitCoef, fitY /X=fitX /D=fitResult /I=0

    AppendToGraph fitResult vs fitX
    ModifyGraph rgb($NameOfWave(fitResult)) = (0,0,65535)
    ModifyGraph lstyle($NameOfWave(fitResult)) = 2

    Print "Fitted trace: ", traceName
    Print "Y wave: ", GetWavesDataFolder(yWave, 2)
    Print "Fit result wave: ", GetWavesDataFolder(fitResult, 2)
    Print "Fit coefficient wave: ", GetWavesDataFolder(fitCoef, 2)
End

まとめ

LineFitMarquee を使うと、Igor Proのグラフ上で選択した範囲だけをすばやく線形フィットできます。 範囲指定をグラフ上のmarquee操作に任せることで、データ点の番号やX範囲を手入力する必要がありません。

特に、グラフを見ながら「このあたりだけ直線で見たい」という作業にはかなり向いています。 GraphMarqueeは地味ですが、うまく使うとIgorの操作感をかなり良くできる機能だと思います。

Igor Proでファイルを一括ロード

nino_LoadPackage.ipf は、Igor Proでテキストデータを読み込むための小さなProcedureファイルです。このページの最後にスクリプトを記載しています。

できること

  • fileload: 1つのファイルを読み込み、ファイル名由来のデータフォルダに保存する
  • folderload_ver2: 選択したフォルダ内の .txt ファイルをまとめて読み込む。読み込むファイル名を文字列で絞り込める。

導入方法

nino_LoadPackage.ipf を Igor Pro の Procedures フォルダに置きます。 たとえば以下のような場所です。

C:\Users\ninomiya\Documents\WaveMetrics\Igor Pro 10 User Files\Igor Procedures\nino_LoadPackage.ipf

Igor ProでProcedureをコンパイルすると、メニューバーの Load に項目が追加されます。

このProcedureでは、次のように Menu "Load" を定義しています。

Menu "Load"
    "fileload/2"
    "folderload_ver2/3", folderload_ver2()
End

Menu "Load" は、Igor Proのメニューバーに Load というメニューを作る命令です。 その下に書いた文字列が、メニュー項目として表示されます。

メニュー項目 実行内容
"fileload/2" fileload マクロを実行
"folderload_ver2/3", folderload_ver2() folderload_ver2() 関数を実行

メニュー項目名の後ろに付いている /2/3 は、Igor Proのメニューショートカット指定です。

"fileload/2"
"folderload_ver2/3"

このように書くと、メニューから実行するだけでなく、キーボードショートカットでもCtrl+2やCtrl+3で呼び出せるようになります。 環境によっては既存のショートカットと重なる可能性があるため、うまく動かない場合は /2/3 を別のキーに変えるか、削除します。

fileload の使い方

  1. Igor Proのメニューから Load > fileload を選びます。
  2. 読み込みたいファイルを選択します。
  3. 読み込まれたwaveが、ファイル名をもとにしたデータフォルダへ保存されます。

内部的には、まず一時フォルダにファイルを読み込みます。 読み込みに成功したら、その一時フォルダをファイル名由来のフォルダ名に変更します。

同じ名前のデータフォルダが既にある場合は、自動で番号付きの名前になります。 これにより、既存データを上書きしにくくなっています。

folderload_ver2 の使い方

  1. Igor Proのメニューから Load > folderload_ver2 を選びます。
  2. .txt ファイルが入っているフォルダを選択します。
  3. その後、ファイル名に含めたい文字列を入力します。

     

  4. 条件に一致した .txt ファイルが順番に読み込まれます。

ファイル名フィルタは空欄でも構いません。 空欄にすると、選択したフォルダ内のすべての .txt ファイルが読み込み対象になります。日付や実験番号,サンプル名で絞り込むと便利ですね。

入力例

Ta      → ファイル名に Ta を含む .txt だけ読み込み
sample  → ファイル名に sample を含む .txt だけ読み込み
空欄    → すべての .txt を読み込み

まとめ

nino_LoadPackage.ipf を使うと、Igor Proでのテキストデータ読み込みをメニューから実行できます。 単一ファイルの確認読み込みには fileload、フォルダ内の一括読み込みには folderload_ver2 を使います。

特に folderload_ver2 は、フォルダ選択後にファイル名フィルタを入力できるため、 実際に読み込み対象のフォルダを見てから条件を決められるのが便利です。

nino_LoadPackage.ipf 全文

最後に、現在の nino_LoadPackage.ipf の全文を載せておきます。

#pragma TextEncoding = "UTF-8"
#pragma rtGlobals=3

// ==========================================================
// Nino load package
// ==========================================================

Menu "Load"
    "fileload/2"
    "folderload_ver2/3", folderload_ver2()
End

Macro fileload()
    String tempFolder = NINO_UniqueRootFolderName("_nino_fileload_tmp_")
    String outputFolder

    SetDataFolder root:
    NewDataFolder/O root:$tempFolder
    SetDataFolder root:$tempFolder
    PauseUpdate; Silent 1

    LoadWave/A/G/W/L={0, 0, 0, 0, 0}
    if (V_flag <= 0)
        SetDataFolder root:
        KillDataFolder $tempFolder
        Abort "File load was canceled."
    endif

    SetDataFolder root:
    outputFolder = NINO_UniqueRootFolderName(S_filename)
    RenameDataFolder $tempFolder, $outputFolder
EndMacro

Function folderload_ver2()
    String filterStr = ""

    SetDataFolder root:

    // Select a folder first, then enter a filename filter.
    NewPath/O/Q/M="Select folder containing text files" ninoLoadPath
    PathInfo ninoLoadPath
    if ((V_flag == 0) || (strlen(S_path) == 0))
        Abort "Folder path was not acquired."
    endif

    Prompt filterStr, "Text included in file name (blank = all files)"
    DoPrompt "File name filter", filterStr
    if (V_flag)
        Abort "File name filter input was canceled."
    endif

    Variable fileIndex = 0
    Variable loadedCount = 0
    String fileName
    String filePath
    String tempFolder
    String outputFolder

    do
        fileName = IndexedFile(ninoLoadPath, fileIndex, ".txt")
        if (strlen(fileName) == 0)
            break
        endif

        if ((strlen(filterStr) == 0) || StringMatch(fileName, "*" + filterStr + "*"))
            filePath = S_path + fileName
            tempFolder = NINO_UniqueRootFolderName("_nino_folderload_tmp_")

            NewDataFolder/O root:$tempFolder
            SetDataFolder root:$tempFolder
            LoadWave/A/O/G/W filePath

            SetDataFolder root:
            if (V_flag > 0)
                outputFolder = NINO_UniqueRootFolderName(S_filename)
                RenameDataFolder $tempFolder, $outputFolder
                loadedCount += 1
            else
                KillDataFolder $tempFolder
                Print "Skipped file: " + fileName
            endif
        endif

        fileIndex += 1
    while (1)

    Print "Loaded files: " + num2str(loadedCount)
End

Function/S NINO_UniqueRootFolderName(baseName)
    String baseName

    String folderName = baseName
    Variable suffix = 1

    do
        if (!DataFolderExists("root:" + folderName))
            return folderName
        endif

        folderName = baseName + num2str(suffix)
        suffix += 1
    while (1)
End

Igor ProのプロシージャをAIに直接編集させる方法

Igor Proのマクロやプロシージャを書くとき、AIのコーディング支援機能を使うとかなり便利です。特に最近、知識が無くてもお手軽にコーディングができるようになりましたね。

今回は手始めに、Igorのプロシージャファイルを外部エディタから編集し、AIにマクロを作成・修正してもらう方法を紹介します。

Igor側の設定

まず、Igor Pro側で外部エディタを使えるように設定します。

  1. Igor Proを開く
  2. メニューから「その他」→「その他の設定」→「テキスト編集」を開く
  3. 外部エディタの設定を行う

設定画面は以下のような感じです。

この設定をしておくと、Igorのプロシージャを外部エディタで直接編集できるようになります。

外部エディタ側で保存した内容がIgor側にも反映されるため、普段使っているエディタでマクロを書けるようになります。

外部エディタ側の設定

自分の場合は、外部エディタとしてVisual Studio Codeを使っています。

VS CodeにAIコーディング支援用の拡張機能を入れておくと、IgorのマクロをAIに直接編集してもらえます。

  • VS Codeをインストールする
  • CodexなどのAIコーディング支援プラグインを入れる
  • 必要に応じてIgor Pro用の拡張機能も入れる

自分はChatGPTに課金しているのでCodexを使っていますが、各社の生成AIサービスにもコーディング支援用の拡張機能があります。使いやすいものを選べばよいと思います。また、VS CodeにIgor用の拡張機能を入れておくと、プロシージャ内のトークンや構文がハイライトされるので、かなり見やすくなります。

Igor付属のサンプルが学習元として強力

AIにIgorマクロを書かせるときは、参考になる既存のプロシージャを読ませると精度が上がります。特に強力だったのが、Igor Proに付属しているデモスクリプトやパッケージです。インストール場所は環境によって違うと思いますが、自分の環境では以下のフォルダが参考になりました。

C:/Program Files/WaveMetrics/Igor Pro 10 Folder

この中には、Igor公式のデモやパッケージが多数入っています。AIに「このフォルダ内の書き方を参考にして」と伝えると、Igorらしい書き方に寄せてマクロを作ってくれます。

AIに直接マクロを作成・編集してもらう

ここまで設定すれば、あとはAIに指示するだけです。

たとえば、次のように頼むことができます。

  • このプロシージャの処理を整理して
  • この関数にエラーチェックを追加して
  • この波形処理を自動化するマクロを作って
  • Igor Proの書き方に合わせてリファクタリングして

外部エディタ上でプロシージャを開いておけば、AIがそのファイルを直接編集してくれます。簡単な修正だけでなく、少し大がかりなマクロの作成にもかなり使えますし,曖昧な支持も意図を汲みとって使いやすい仕様にしてくれます。

学習元としてちゃんとしたIgorマクロを与えておくと、かなり精度よくコードを書いてくれる印象です。自分の場合は、Igor付属のサンプルに加えて、自作マクロやネット上で見つけたマクロも適当に読ませています。

まとめ

Igor Proの外部エディタ設定と、VS CodeなどのAIコーディング支援機能を組み合わせると、Igorマクロの作成・編集がかなり楽になります。特に、Igor付属のデモスクリプトやパッケージを参考資料として使うと、AIがIgorらしい書き方を学習しやすくなります。これからIgorマクロを書き始める人や、既存のマクロを整理したい人にはかなりおすすめです。

【Igor】menuでマクロを整理する

Igorで作成したマクロが整理されていなくて実行の際に手間取るという経験はないだろうか.そんな時に「Menu」を使用することで,自分でマクロを収納したタブを作成したり,ショートカットを作成することができて便利であるため今回紹介したい.

 

基本の使い方は以下のとおりである.

プロシージャ内に

Menu "メニュー名"

    "マクロ名"

Endmacro

と入力すると,エクスペリメントのウィンドウ上部に「メニュー名」タブが作成され,その中にマクロが収納される.

また,「submenu」を用いることで階層構造を持ったタブを作成することもできる.(詳細はご自身でヘルプ(F1)をご覧いただきたい)

 

これにより自分で作成したタブの中にマクロを仕分けることができたわけである.これは非常に便利で,用途別にタブを作成することで,マクロの呼び出しがスムーズになるだろう.

 

さらに,スクリプト内の「マクロ名」の部分を「マクロ名/0」のように/+一桁の数字を付け足すと,このマクロをCtrl+数字で呼び出せるショートカットを設定することができる.ショートカットキーはこの他にもあり,

/F5 : F5キー

/SCF5 :  Shift+Ctrl+F5

/05 : Alt+5

といったキーを割り当てることができる.

 

是非ご活用いただきたい.Menuを使った応用例は他にもあるため,またの機会に紹介する.

 

【GaussianからIgorへ】振動解析の結果からIRスペクトルをシミュレーションする

Gaussianの出力ファイルは非常に長い。

ライトユーザーからしたら本当に欲しい情報は原子座標、虚数振動があるかどうか、振動モードはどうなっているか、全電子エネルギーがいくらか、くらいでその他の大部分には関心がないだろう。

私の場合、振動解析の結果からIRスペクトルをシミュレーションしたかった。そのためにはわざわざ長い長い出力ファイル内の振動解析の情報がある場所を探す必要がある(GaussViewを使え、という声はいったん無視する)。しかも、いざ探し出すとその表示形式の不便さに絶望するだろう。

こんな風に、振動モードの番号順(波数が小さい順)に横に並んでいる。各モードについて波数、振動強度、原子座標の変位などの情報が縦に書かれている。画像内には3つのモードしか表示していないが、4つ目はどこにあるかと言えば、画像の右側ではなくなんと下である。この領域をコピペしてエクセルに貼って数値を抜き出すことは簡単にはできない。

嫌がらせである。

そこでこのようなbashスクリプトを作成してIRスペクトルに必要な情報を抜き出した.txtファイルを作成できるようにした。

#!/bin/bash

# ./ir.sh 入力ファイル名(.out拡張子不要)
#241104_ver1

# 入力ファイル名の取得
filename="$1"

# 出力ファイルの作成
base_name=$(basename $filename .out)
output_file="${base_name}_ir.txt"
touch "$output_file"

# 出力ファイルの最初の行に出力ファイル名を挿入
echo "$output_file" > "$output_file"
echo "mode,wavenumber (cm-1),IRint" >> "$output_file"  # ヘッダーを追加

# modeの番号を初期化
mode=1

# ファイルを行単位で読み込み
while IFS= read -r line; do
  # Frequencies行の処理
  if [[ "$line" == " Frequencies"* ]]; then
    # Frequencies行の3, 4, 5番目のフィールドを取得
    freq1=$(echo "$line" | awk '{print $3}')
    freq2=$(echo "$line" | awk '{print $4}')
    freq3=$(echo "$line" | awk '{print $5}')
   
    # 3行スキップしてからIR Inten行を読み込み
    read -r _     # 1行目をスキップ
    read -r _     # 2行目をスキップ
    read -r ir_line  # 3行目を読み込み(IR Inten行)

    # IR Inten行の4, 5, 6番目のフィールドを取得
    ir1=$(echo "$ir_line" | awk '{print $4}')
    ir2=$(echo "$ir_line" | awk '{print $5}')
    ir3=$(echo "$ir_line" | awk '{print $6}')

    # 各モードの情報を出力ファイルに書き込み
    echo "$mode,$freq1,$ir1" >> "$output_file"
    *1
    echo "$mode,$freq2,$ir2" >> "$output_file"
    *2
    echo "$mode,$freq3,$ir3" >> "$output_file"
    *3
  fi
done < "${base_name}.out"

出力結果はこんな感じだ。↓

こんな風に、コンマ区切りで各列に振動モード、波数、振動強度を出力している。1弁目の振動モードの波数がマイナスであれば虚数振動を持つことが一目でわかる。

ここからは、Igor上でIRスペクトルをシミュレーションする方法を紹介する。

まず、この出力ファイルを以下のようなIgorマクロで選択し、Igorにロードする。

Macro fileload()

    SetdataFolder root:

    if(exists("newfolder") ==0)

         newdataFolder/O newfolder

    else

    endif

    SetdataFolder newfolder

    Loadwave/A/G/W/L={0, 0, 0, 0, 0}

    renameDataFolder root:newfolder, $S_filename

    SetdataFolder root:

EndMacro

すると、Igorのエクスペリメント内にフォルダが作成され、各列の情報がwave化される。

さて、スペクトルをシミュレーションするには、振動のシグナルに分布を持たせ、全ての振動について足し合わせ(convolute)なくてはならない。

IRスペクトルの分布は次式で示されるローレンツ分布でフィッティングされることが一般的である。

1つの振動モードの分布を得るためには、x_0を計算で得られた波数、f(x_0)を振動強度とすればよい。ここでwは半値幅であり、実測のスペクトルと合うように任意に決める。(私は30 cm^-1をデフォルトに設定している。)

つまり、振動数、振動強度、半値幅を入れたらそれに対応する分布を出力してくれるような関数をIgor内に作ればよい。

///ローレンツ分布を得る関数

Function sig2Lorentz(fx0,x0,width,xx)

    variable fx0

     variable x0

     variable width

     variable xx

 

     variable Out1D

     Out1D = fx0*width^2/(4*(xx-x0)^2+width^2)

     return Out1D

end

そして、各振動モードの分布を全て足し合わせたものを出力すればよい。以下にそのマクロの例を示す。

Macro plotIRlorentz(selectedDir,width)

    String selectedDir

     Prompt selectedDir,"IRデータが入ったディレクトリを選択",popup Datafolderlist()

     variable width = 30

     prompt width,"幅/cm-1"

     SetdataFolder selectedDir ///選択したディレクトリに移動

 

         Display/K=1 IRint vs wavenumber__cm_1_

         ///グラフスタイルの変更は省略///

         Label bottom "Wavenumber / cm\\S–1"

 

     make/O/D/N=8000 multilorentz = 0

     setscale/I x 0,4000,"" multilorentz

     AppendToGraph multilorentz

     variable i = 1

     variable fx0

     variable x0

     do

         make/O/D/N=8000 $("z_singlelorentz" + num2str(i))

         setscale/I x 0,4000,"" $("z_singlelorentz" + num2str(i))

 

         fx0 = IRint[i]

         x0 = wavenumber__cm_1_[i]

         $("z_singlelorentz" + num2str(i)) = sig2lorentz(fx0,x0,width,x)

         multilorentz += $("z_singlelorentz" + num2str(i))

         i += 1

         PauseUpdate; Silent 1

     while(i<numpnts(IRint))

     setdataFolder root:

endmacro

 

///datafolderlist関数

Function/S Datafolderlist()

    String selectedDir

 

    setdataFolder root:

    String itemList =""

 

    Variable i,n = CountObjects(":", 4)

    do

        String Fname = GetIndexedObjName(":", 4, i)

        itemList += Fname + ";"

        i = i + 1

   while (i != n)

    return itemlist

end

このマクロを実行すると、datafolderlist関数を使ってエクスペリメント内のフォルダを選択するように要求される。

振動解析の結果が入ったフォルダを選択すると、マクロによって各振動モードの分布が計算され、足し合わせたものがグラフに出力される。

実測のスペクトルと重ね合わせれば、こんな風になる。


ここで注意されたいのは、DFT計算における振動解析の波数は、汎関数と基底関数によって変わる。そのため、実測のスペクトルと照らし合わせる際には必要に応じて補正係数(scaling factor)を適用する。scaling factorの値は文献やNISTのデータベースを参考にするとよい。scaling factorは経験的な値であるため、モデル分子を用いて自身で決定してもよいだろう。

*1:mode++

*2:mode++

*3:mode++

【Igor】グラフウィンドウを縮小して並べるマクロ

Igorでグラフをたくさん作っていたら画面がごちゃごちゃになってきちゃったよ~

そんな時、Igorの付属の機能として、

「ウィンドウ>コントロール>重ねて表示or並べて表示」

を実行すると、↓のようにグラフの配置を整理することができる。

重ねて表示

並べて表示

 

本当にこれで満足だろうか?重ねて表示の場合は重なっているため結局目的のグラフを探さなくてはならない。並べて表示の場合はグラフのサイズがまちまちでイマイチ整理されている感じがしない。

そこで、マクロを組んで全てのグラフを縮小して、並べればいいのではないかとおもいついた。

Macro MinimizeWindows()

    String winList_ = WinList("*", ";", "WIN:1") // すべてのウィンドウを取得

    Variable i = 0, num = ItemsInList(winList_)

      String winname_

        do

                if (i >= num)

                    break

                endif

                winName_ = StringFromList(i, winList_)

                DoWindow/F $winName_ // フォーカスを当てる(ModifyGraph対象用)

                ModifyGraph expand=0.5

                i += 1

        while (1) // 無限ループ+break制御

 

    TileWindows/O=1/C/P

End

ここではエクスペリメント内の全てのグラフウィンドウに対して、縮小(ModifyGraph expand=0.5)し、並べて表示(TileWindows/O=1/C/P)を実行している。グラフウィンドウの拡大/縮小は[Ctrl+Shift++/-]でもできるので知っていると便利だろう。

実はこのマクロ、非常に汎用性が高く、斜体で表記した

               ModifyGraph expand=0.5

の部分に種々のコマンドを入れることで、全てのグラフに対して特定の処理を施すことができる。

【igor】txtファイルをまとめてロードしたいなら

いっぱい測定した(〃^∇^)o_彡☆

f:id:rpaka113:20250610235212j:image

しかしたくさんの生データのファイルを開いてコピペするのめんどくさい...。そんな時にはigorのマクロで複数のファイルを一気に読み込もう。

そもそも、ファイルをロードとは?という方は先に(近日公開予定)を読んで頂きたい。

 

今回は

・フォルダ内のtxtファイルを全てロードする

方法を紹介したい。

 

マクロで行うことは至って簡単で、

1)フォルダ内にある.txt拡張子のファイル名を取得する

2)取得したファイルを順にロードする

ということをすればよい。

 

まずはスクリプト全体を見てもらって、詳しく解説させていたいただく。

Macro folderload()

SetDataFolder root: // データフォルダをルートに設定

 

// 最初のLoadWave(ダイアログからファイルを1つ読み込む)

LoadWave/A/O/G/B="N='_skip_';N='_skip_';"

 

// 選択したファイルのパス(S_path)から、対象フォルダのパスを取得して "target" に登録

NewPath target, S_path

 

// 最大100個までのファイル名を格納するテキストWave flist を作成

Make/WAVE/O/N=100/T flist

 

// IndexedFile を使って、.txtファイル名を flist に格納

Variable i = -1

do

    i += 1

    flist[i] = IndexedFile(target, i, ".txt")

while (CmpStr(flist[i], "") != 0) // 空文字列になるまで(ファイルが存在する間)

 

// ファイル名リスト flist に基づいて、各ファイルをロード

String loading // 読み込むファイルのパスを一時的に保持

Variable j = 0

do

    // ファイルパスを構築(フォルダパス + ファイル名)

    loading = S_path + root:flist[j]

    // 新しいデータフォルダ "newfolder" を作成し、その中に切り替え

    NewDataFolder/O newfolder

    SetDataFolder newfolder

    // Waveをロード(A=自動列ラベル, O=上書き, G=テキスト, W=ファイル名を指定)

    LoadWave/A/O/G/W loading

    // 元のルートフォルダに戻る

    SetDataFolder root:

    // 読み込んだファイル名(S_filename)を使ってフォルダ名をリネーム

    RenameDataFolder newfolder, $S_filename

    j += 1

while (CmpStr(flist[j], "") != 0) // ファイル名が空になるまで繰り返す

 

// flistの未使用領域(空欄)を削除

DeletePoints j, 100, flist

 

// flistを削除して後始末

KillWaves flist

EndMacro

 

<解説>

SetDataFolder root: // データフォルダをルートに設定

フォルダを作成する場合は毎度、root:フォルダに作成するのを推奨する。この一行が無ければ、カレントフォルダ内に新規フォルダが作成される。

まず、フォルダのパス(PC内のアドレス)を取得する必要がある。そのために簡単な方法として、中にあるloadwaveコマンドで選択して、その中身は読み込まない、という方法がある。以下のようにloadwaveを使い、しかしファイルの読み込みはスキップする。なお、A/やO/などはフラグと呼ばれ、コマンドのオプションを指定できる。

// 最初のLoadWave(ダイアログからファイルを1つ読み込む)

LoadWave/A/O/G/B="N='_skip_';N='_skip_';"

すると、S_pathという変数に読み込んだファイルのパス(アドレス)が入る。この後、実際にファイルをロードしていく際にS_pathは随時更新されていくので、targetという文字列型の変数にパスを入れる。

// 選択したファイルのパス(S_path)から、対象フォルダのパスを取得して "target" に登録

NewPath target, S_path

続いて、フォルダ内のファイル名をリスト化するためのテキストウェーブ(flist)を作成する。

// 最大100個までのファイル名を格納するテキストWave flist を作成

Make/WAVE/O/N=100/T flist

make: ウェーブを作成するコマンド

/wave: ウェーブであることを明示するプラグ

/O: 既に同じフォルダ内に同名のウェーブが存在する場合、上書きする

/N=100: ウェーブの点数を100とする。

/T: テキストウェーブであることを明示する。

 

続いてdo/whileループ処理を用いて、取得したフォルダ内の.txt拡張子のファイル名をflistに入れていく。

// IndexedFile を使って、.txtファイル名を flist に格納

Variable i = -1

do

i += 1

flist[i] = IndexedFile(target, i, ".txt")

while (CmpStr(flist[i], "") != 0) // 空文字列になるまで(ファイルが存在する間)

 

CmpStr(flist[i], "")、flist[i]と""を比較し、一致する場合は0を返す。

こうして、flistをファイル名の一覧にすることができた。次に、一覧の上から順にファイルを読み込んでいく。

ここではdo/whileループ処理でflist内に格納された各ファイル名のファイルを順に読み込んでいく。

    // ファイルパスを構築(フォルダパス + ファイル名)

    loading = S_path + root:flist[j]

ここでは文字列変数「loading」に読み込むファイルのパスを作成している。

    // 新しいデータフォルダ "newfolder" を作成し、その中に切り替え

    NewDataFolder/O newfolder

    SetDataFolder newfolder

では、新しくフォルダを作成して、その中にファイルを読み込む用意をしている。

    // Waveをロード(A=自動列ラベル, O=上書き, G=テキスト, W=ファイル名を指定)

    LoadWave/A/O/G/W loading

で、実際にファイルを読み込んでいる。そして、最後に以下の

    // 元のルートフォルダに戻る

    SetDataFolder root:

    // 読み込んだファイル名(S_filename)を使ってフォルダ名をリネーム

    RenameDataFolder newfolder, $S_filename

で新しいフォルダ名を読み込んだファイル名に直している。

この処理をflist内の全てのファイル名に対して行うことで仕事完了である。

 

実際にマクロを実行すると、↓のようにファイルを選択する画面が出てくる。

ここ一つ選択すると、

このようにフォルダにあったファイルがすべて取り込まれてくれる。