spkie log

主にPythonを使って医用画像処理をしている研究者の備忘録

SPM12b (SPM8)でバッチ処理をしてみた <追記あり>

どうもこんにちは。


SPMによる解析を今作っているプログラムからやりたいという目的で、
SPM解析をコマンドラインから実行する方法について調べたのでその備忘録を残したいと思います。
あんまりドキュメントも無く、公式のマニュアルも理解するのにもかなり苦労したので残しておけば後々ラクだろうということで(^^)


私が今使っているのはSPM12bですが、SPM12bとSPM8のバッチ処理の仕組みはほとんど同じ模様。
という訳で公式のSPM8のマニュアルを読みながらやってみました。


マニュアルやソースコードのhelpによると、どうやらcfg_utilという関数で処理の追加やパラメータの設定をすることができるようなのですが、これがなかなか難解で、多分SPMの中身をかなり理解しないと使いこなすのは難しそう。
いろいろと四苦八苦したところ、どうもBatch editorからもバッチ処理のためのスクリプトを簡単に作ることができるらしい。
という訳でBatch editorからスクリプトを作り、それを自分のプログラムから呼び出すことにしました。
今回はtwo sample t-testのためのデザインマトリクスの作成を例にやります。


Batch editorはSPMのメイン画面の「Batch」をクリックすることでも表示できますが、コマンドラインからは以下のコマンドを打つことでも表示できます。

>> cfg_ui

メニューバーの「SPM」をクリックして処理を選んで、各パラメータを選択した画面がコレ。

f:id:spike_fairway:20131114105934p:plain

ここから以下のように「File」→「Save Batch and Script」を選択するとファイル選択画面が現れるので、好きな名前を入力するとその名前のスクリプトとパラメータ設定のためのスクリプトが作成されます。

f:id:spike_fairway:20131114110608p:plain

作られたスクリプト(test_hogehoge.m)。

% List of open inputs
nrun = X; % enter the number of runs here<このXを好みの繰り返し回数に変える>
jobfile = {<パラメータ設定スクリプトファイルの絶対パス>};
jobs = repmat(jobfile, 1, nrun);
inputs = cell(0, nrun);
for crun = 1:nrun
end
job_id = cfg_util('initjob', jobs);
sts    = cfg_util('filljob', job_id, inputs{:});
if sts
    cfg_util('run', job_id);
end
cfg_util('deljob', job_id);

パラメータ設定スクリプト(test_hogehoge_job.m)がこちら。

%-----------------------------------------------------------------------
% Job saved on 13-Nov-2013 17:30:37 by cfg_util (rev $Rev: 4972 $)
% spm SPM - SPM12b (5581)
% cfg_basicio BasicIO - Unknown
%-----------------------------------------------------------------------
matlabbatch{1}.spm.stats.factorial_design.dir = {<データの保存ディレクトリ>};
matlabbatch{1}.spm.stats.factorial_design.des.t2.scans1 = {
                                                           <解析する画像ファイル>
                                                           };
matlabbatch{1}.spm.stats.factorial_design.des.t2.scans2 = {
                                                           <解析する画像ファイル>
                                                           };
matlabbatch{1}.spm.stats.factorial_design.des.t2.dept = 0;
matlabbatch{1}.spm.stats.factorial_design.des.t2.variance = 1;
matlabbatch{1}.spm.stats.factorial_design.des.t2.gmsca = 0;
matlabbatch{1}.spm.stats.factorial_design.des.t2.ancova = 0;
matlabbatch{1}.spm.stats.factorial_design.cov = struct('c', {}, 'cname', {}, 'iCFI', {}, 'iCC', {});
matlabbatch{1}.spm.stats.factorial_design.masking.tm.tm_none = 1;
matlabbatch{1}.spm.stats.factorial_design.masking.im = 1;
matlabbatch{1}.spm.stats.factorial_design.masking.em = {<マスク画像ファイル>};
matlabbatch{1}.spm.stats.factorial_design.globalc.g_omit = 1;
matlabbatch{1}.spm.stats.factorial_design.globalm.gmsca.gmsca_no = 1;
matlabbatch{1}.spm.stats.factorial_design.globalm.glonorm = 1;

このスクリプトで作られるmatlabbatch(構造体を含むセル配列)を内部でやりとりすることで処理を行うようです。
このセル配列の各要素が定義した処理それぞれに対応しています。


作られたスクリプト(test_hoghoge.m)を実行すると定義した処理が実行出来ます。

>> test_hogehoge

ただバッチ処理をするならば、解析のためのパラメータはともかく、使う画像ファイルを変えたりして実行したいところです。
その場合は以下のように変えたいパラメータをbatch editor上で選択した上で「Edit」→「Clear Value」を選択すると、パラメータがクリアされて「<-X」に変わります。

f:id:spike_fairway:20131114110611p:plain

この上でさっきと同じ手順でスクリプトを作ると、クリアしたパラメータの値が

'<UNDEFINED>'

になります。

%-----------------------------------------------------------------------
% Job saved on 13-Nov-2013 17:30:37 by cfg_util (rev $Rev: 4972 $)
% spm SPM - SPM12b (5581)
% cfg_basicio BasicIO - Unknown
%-----------------------------------------------------------------------
matlabbatch{1}.spm.stats.factorial_design.dir = {'<UNDEFINED>'};
matlabbatch{1}.spm.stats.factorial_design.des.t2.scans1 = {'<UNDEFINED>'};
matlabbatch{1}.spm.stats.factorial_design.des.t2.scans2 = {'<UNDEFINED>'};
matlabbatch{1}.spm.stats.factorial_design.des.t2.dept = 0;
matlabbatch{1}.spm.stats.factorial_design.des.t2.variance = 1;
matlabbatch{1}.spm.stats.factorial_design.des.t2.gmsca = 0;
matlabbatch{1}.spm.stats.factorial_design.des.t2.ancova = 0;
matlabbatch{1}.spm.stats.factorial_design.cov = struct('c', {}, 'cname', {}, 'iCFI', {}, 'iCC', {});
matlabbatch{1}.spm.stats.factorial_design.masking.tm.tm_none = '<UNDEFINED>';
matlabbatch{1}.spm.stats.factorial_design.masking.im = 1;
matlabbatch{1}.spm.stats.factorial_design.masking.em = {<マスク画像ファイル>};
matlabbatch{1}.spm.stats.factorial_design.globalc.g_omit = 1;
matlabbatch{1}.spm.stats.factorial_design.globalm.gmsca.gmsca_no = 1;
matlabbatch{1}.spm.stats.factorial_design.globalm.glonorm = 1;

そして以下のような処理を実行するためのスクリプトが作成されます。

% List of open inputs
nrun = X; % enter the number of runs here<このXを好みの繰り返し回数に変える>
jobfile = {<パラメータ設定スクリプトファイルの絶対パス>};
jobs = repmat(jobfile, 1, nrun);
inputs = cell(0, nrun);
for crun = 1:nrun
    inputs{1, crun} = MATLAB_CODE_TO_FILL_INPUT;
    inputs{2, crun} = MATLAB_CODE_TO_FILL_INPUT;
    inputs{3, crun} = MATLAB_CODE_TO_FILL_INPUT;
    inputs{4, crun} = MATLAB_CODE_TO_FILL_INPUT;
end
job_id = cfg_util('initjob', jobs);
sts    = cfg_util('filljob', job_id, inputs{:});
if sts
    cfg_util('run', job_id);
end
cfg_util('deljob', job_id);

先ほどのスクリプトとは違い、inputsというセル配列への値の代入が書かれています。
このセル配列の各要素が先ほどクリアしたパラメータに対応しています。
順序は設定の出現順になっており、この場合は

  1. Directory
  2. Group 1 Scans
  3. Group 2 Scans
  4. Threshold masking

の順になってます。
従ってそれぞれの「MATLAB_CODE_TO_FILL_INPUT」の部分に各パラメータを動的に出力するコマンドを書けばいいわけです。
すると、スクリプト中のcfg_util('filljob', job_id, inputs{:})により、コマンドで出力したパラメータを設定する事ができます。
後はこのスクリプトを実行すれば先ほどと同様の処理を異なるファイルやパラメータについてもすることができます。

>> test_hogehoge

英語のマニュアルに慣れず、ここまで理解するのにかなり時間が掛かってしまった。
10年間英語の論文を読み続け、もう5ヶ月くらいアメリカで暮らしているのに未だに英語に慣れない…。
私がへっぽこ研究者たる所以やな(苦笑)

<追記>

このクリアした値を埋める方法で処理できるのはどうやらその設定値がファイル名である場合のみである模様。
更に言えばCovariatesなどはクリア出来ないため、この方法で動的にCovariatesを設定するのは難しそう。


どうしたものかと調べてみたところ、作成される2つのスクリプトをマージするようなやり方をアップされている方がいらっしゃいました。

2.Brains Crack/SPM8 Batch | SHIKA@PukiWiki

要は

% List of open inputs
nrun = X; % enter the number of runs here<このXを好みの繰り返し回数に変える>

matlabbatch{1}.spm.stats.factorial_design.dir = {<データの保存ディレクトリ>};
matlabbatch{1}.spm.stats.factorial_design.des.t2.scans1 = {
                                                           <解析する画像ファイル>
                                                           };
matlabbatch{1}.spm.stats.factorial_design.des.t2.scans2 = {
                                                           <解析する画像ファイル>
                                                           };
matlabbatch{1}.spm.stats.factorial_design.des.t2.dept = 0;
matlabbatch{1}.spm.stats.factorial_design.des.t2.variance = 1;
matlabbatch{1}.spm.stats.factorial_design.des.t2.gmsca = 0;
matlabbatch{1}.spm.stats.factorial_design.des.t2.ancova = 0;
matlabbatch{1}.spm.stats.factorial_design.cov = struct('c', {}, 'cname', {}, 'iCFI', {}, 'iCC', {});
matlabbatch{1}.spm.stats.factorial_design.masking.tm.tm_none = 1;
matlabbatch{1}.spm.stats.factorial_design.masking.im = 1;
matlabbatch{1}.spm.stats.factorial_design.masking.em = {<マスク画像ファイル>};
matlabbatch{1}.spm.stats.factorial_design.globalc.g_omit = 1;
matlabbatch{1}.spm.stats.factorial_design.globalm.gmsca.gmsca_no = 1;
matlabbatch{1}.spm.stats.factorial_design.globalm.glonorm = 1;

job_id = cfg_util('initjob', matlabbatch);
cfg_util('run', job_id);
cfg_util('deljob', job_id);

という風に変数jobsを設定スクリプトの出力で置き換えてしまうというわけです。
確かにコレならば設定スクリプトのところに動的に値を変える関数なり処理なりを加えればいいので、
フレキシブルなバッチ処理が出来そうです。


ちなみに細かい話ですが、今回載せたスクリプトはcfg_ui経由で起動したBatch editorで作ったせいか、cfg_util関数で書かれてますが、
SPMメイン画面経由で起動したBatch editorから作るとspm_jobman関数で書かれています。
公式のマニュアルにもspm_jobmanの方のサンプルコードが載っているし、
こっちのがわかりやすいのでspm_jobmanを使うことをオススメします(^^;