1. 目的
2. 準備
2.1. MEDI toolboxにパスを通す
2.2. QSM解析実行のコード準備
2.3. Write_DICOM.mを修正
2.4. QSM_3Dを集める
3. 実行
3.1. 作業フォルダ(analyzeフォルダ)へ移動
3.2. runme.mを実行
3.3. Outputファイルの確認
4. 複数の被験者データをまとめて処理したい場合
4.1. run_sequential.m
1. 目的
Cornell大学が開発したQSM software (MEDI)を使って解析
(Cornell MRI Research Lab. QSMより、URL: http://pre.weill.cornell.edu/mri/pages/qsm.html)
2. 準備
####MEDI toolboxのダウンロード
こちらからMEDI_toolboxをダウンロード
MEDI_toolboxの中身はこのような感じ。
2.1. MEDI toolboxにパスを通す
MATLABを起動し、パスの設定からサブフォルダも追加をクリックしfunctionsフォルダを選択した後、保存。
2.2. QSM解析実行のコード準備
QSM解析を実行するためのコードは以下。
以下をrunme.m(※任意の名前でOK)で保存し、作業フォルダとなるanalyzeフォルダ(※任意の名前でOK)に保存する。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | clear all;clc;close all; % Set path % MEDI_set_path % STEP 1: Import data [iField,voxel_size,matrix_size,CF,delta_TE,TE,B0_dir,files]=Read_DICOM( 'AXL_QSM' ); %%%% Generate the Magnitude image %%%% iMag = sqrt(sum(abs(iField).^2,4)); %%%%% provide a Mask here if possible %%%%%% if (~exist( 'Mask' , 'var' )) % Mask = genMask(iField, voxel_size); Mask = BET(iMag,matrix_size,voxel_size); end %%%%% provide a noise_level here if possible %%%%%% if (~exist( 'noise_level' , 'var' )) noise_level = calfieldnoise(iField, Mask); end %%%%% normalize signal intensity by noise to get SNR %%% iField = iField/noise_level; % STEP 2a: Field Map Estimation %%%%%Estimate the frequency offset in each of the voxel using a %%%%%complex fitting %%%% [iFreq_raw N_std] = Fit_ppm_complex(iField); % STEP 2b: Spatial phase unwrapping %%%% iFreq = unwrapPhase(iMag, iFreq_raw, matrix_size); % STEP 2c: Background Field Removal %%%% Background field removal %%%% [RDF shim] = PDF(iFreq, N_std, Mask,matrix_size,voxel_size, B0_dir); % CSF Mask for zero referencing R2s=arlo(TE,abs(iField)); Mask_CSF = extract_CSF(R2s, Mask, voxel_size); % STEP 3: Dipole Inversion save RDF.mat RDF iFreq iFreq_raw iMag N_std Mask matrix_size... voxel_size delta_TE CF B0_dir Mask_CSF; %%%% run MEDI %%%%% QSM = MEDI_L1( 'lambda' ,1000, 'smv' , 5, 'merit' ); %%% save to DICOM % ignore warnings... Write_DICOM(QSM,files, 'QSM' ) |
2.3. Write_DICOM.mを修正
解析後のQSMの保存には、Write_DICOM.mを使用する。(runme.mを実行すれば自動で実行される。)
修正前のファイルでは、dicomwriteでエラーが発生する。
そのため、MEDI_toolbox/functions/Write_DICOM.mを以下のコードにそっくりそのまま書き換える。
やっていることは、dicomwriteのCreateModeをCopyからCreateへチェンジ。
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | function Write_DICOM(M,files,outdir,opts) %WRITE_DICOM Summary of this function goes here % Detailed explanation goes here defopts.SeriesDescription = 'QSM' ; defopts.SeriesInstanceUID = []; defopts.SeriesNumber = []; defopts.Convert = @(x) convert(x); defopts.Window = 0.500; defopts.Level = 0; defopts.flag3D = 0; % defopts.EchoNumber = []; % defopts.EchoTime = 0.0; if nargin<4; opts=struct; end deffields=fieldnames(defopts); for i=1:length(deffields) if ~isfield(opts, deffields{i}) opts.(deffields{i})=defopts.(deffields{i}); end end if isfield(files, 'M' ) filenames=files.M; elseif isfield(files, 'R' ) filenames=files.R; elseif isfield(files, 'info3D' ) opts.flag3D=1; else error( 'No filenames (M nor R) found.' ); end flag_signed=min(M(:))<0; if ~opts.flag3D if size(M,3) ~= size(filenames,1) error([num2str(size(filenames,1)) ' filenames given for ' num2str(size(M,3)) ' slices.' ]); end end if isempty(opts.SeriesInstanceUID) opts.SeriesInstanceUID=dicomuid; end progress= '' ; mkdir(outdir); warning( 'off' , 'images:dicom_add_attr:invalidAttribChar' ); load_flag=1; insert_flag=~opts.flag3D; for slice=1:size(M,3) if load_flag if opts.flag3D filename=files.info3D.filename; else filename=filenames{slice, end }; end info = dicominfo(filename); info.SeriesDescription = opts.SeriesDescription; info.SeriesInstanceUID = opts.SeriesInstanceUID; if isempty(opts.SeriesNumber) opts.SeriesNumber=info.SeriesNumber*100; end info.SeriesNumber = opts.SeriesNumber; info.SOPInstanceUID = dicomuid; info.InstanceNumber = slice; if opts.flag3D load_flag=0; end end if opts.flag3D item=files.info3D.slice2index{slice}; % info.PerFrameFunctionalGroupsSequence.(item).PlanePositionSequence.Item_1.ImagePositionPatient; info.PerFrameFunctionalGroupsSequence.(item).Private_2005_140f.Item_1.SOPInstanceUID = dicomuid; end im = M(:,:,slice); if (isfield(info, 'SmallestImagePixelValue' )) info.SmallestImagePixelValue=opts.Convert(min(im(:))); end if (isfield(info, 'LargestImagePixelValue' )) info.LargestImagePixelValue=opts.Convert(max(im(:))); end if (isfield(info, 'RescaleIntercept' )) info.RescaleIntercept=0; end if (isfield(info, 'RescaleSlope' )) info.RescaleSlope=1; end info.WindowCenter=opts.Convert(opts.Level); info.WindowWidth=opts.Convert(opts.Window); % if opts.flag3D % info.PerFrameFunctionalGroupsSequence.Item_1.PixelValueTransformationSequence.Item_1.RescaleIntercept=0; % info.PerFrameFunctionalGroupsSequence.Item_1.Private_2005_140f.Item_1.RescaleIntercept=0; % info.PerFrameFunctionalGroupsSequence.Item_1.PixelValueTransformationSequence.Item_1.RescaleSlope=1; % info.PerFrameFunctionalGroupsSequence.Item_1.Private_2005_140f.Item_1.RescaleSlope=1; % info.PerFrameFunctionalGroupsSequence.Item_1.FrameVOILUTSequence.Item_1.WindowCenter=opts.Convert(opts.Level); % info.PerFrameFunctionalGroupsSequence.Item_1.FrameVOILUTSequence.Item_1.WindowWidth=opts.Convert(opts.Window); % end info.SamplesPerPixel=1; info.BitsAllocated=16; info.BitsStored=16; info.HighBit=15; info.PixelRepresentation=flag_signed; if size(M,3)==slice insert_flag=1; end if insert_flag outfile=fullfile(outdir,sprintf( 'IM%05d.dcm' , slice)); print_progress(outfile); if opts.flag3D f=fieldnames(info.PerFrameFunctionalGroupsSequence); f=setdiff(f,files.info3D.slice2index, 'stable' ); for i=1:length(f) info.PerFrameFunctionalGroupsSequence=rmfield(info.PerFrameFunctionalGroupsSequence, f{i}); end for i=1:length(files.info3D.slice2index) item=files.info3D.slice2index{i}; info.PerFrameFunctionalGroupsSequence.(item).Private_2005_140f.Item_1.InstanceNumber=1; info.PerFrameFunctionalGroupsSequence.(item).PixelValueTransformationSequence.Item_1.RescaleIntercept=0; info.PerFrameFunctionalGroupsSequence.(item).Private_2005_140f.Item_1.RescaleIntercept=0; info.PerFrameFunctionalGroupsSequence.(item).PixelValueTransformationSequence.Item_1.RescaleSlope=1; info.PerFrameFunctionalGroupsSequence.(item).Private_2005_140f.Item_1.RescaleSlope=1; info.PerFrameFunctionalGroupsSequence.(item).FrameVOILUTSequence.Item_1.WindowCenter=opts.Convert(opts.Level); info.PerFrameFunctionalGroupsSequence.(item).FrameVOILUTSequence.Item_1.WindowWidth=opts.Convert(opts.Window); end info.NumberOfFrames=length(files.info3D.slice2index); sz=size(M); M=reshape(opts.Convert(M), sz(1), sz(2), 1, []); M=permute(M, [2 1 3 4]); if isfield(files, 'slices_added' ) if files.slices_added warning( 'Removing empty slice at bottom of volume' ); M=M(:,:,1: end -1); end end %dicomwrite(M,outfile, ... % 'CreateMode', 'copy', 'WritePrivate', true, info); dicomwrite(M,outfile, ... 'WritePrivate' , true, info); else if isfield(files, 'slices_added' ) if files.slices_added warning( 'Removing empty slice at bottom of volume' ); M=M(:,:,1: end -1); end end %dicomwrite(opts.Convert(M(:,:,slice))',outfile, ... % 'CreateMode', 'copy', 'WritePrivate', true, info); % dicomwrite(opts.Convert(M(:,:,slice))',outfile, ... 'WritePrivate' , true, info); end end end fprintf( '\n' ); function print_progress(arg) num=length(progress); num=num-numel(regexp(progress, '\\\\' )); for ii=1:num; fprintf( '\b' ); end progress=[ 'Writing file ' arg]; progress=regexprep(progress, '\',' \\\\'); fprintf(progress); end function y=convert(x) if flag_signed y=int16(x*1000); else y=uint16(x*1000); end end end |
2.4. QSM_3Dを集める
各フォルダには、強度画像と位相画像が入っている。
TEを7回変えて撮像している。
国際脳QSMの撮像は、1回撮像に128 slicesなので強度画像と位相画像はそれぞれ、896枚(=128 slices ×7 phase)
(Cornell MRI Research Lab. QSMより、URL: http://pre.weill.cornell.edu/mri/pages/qsm.html)
QSM_3Dにある強度画像と位相画像をanalyzeフォルダのrawdata(※必ずrawdata)にまとめて保存。
3. 実行
3.1. 作業フォルダ(analyzeフォルダ)へ移動
MATLABを起動し、analyzeフォルダへ移動。
3.2. runme.mを実行
runme.mをMATLABへDrag & Dropし、QSM解析を実行。
3.3. Outputファイルの確認
runme.mを実行後、
- QSM
- results
フォルダが生成される。
QSMフォルダにQSM画像がDICOM形式で保存される。
(Cornell MRI Research Lab. QSMより、URL: http://pre.weill.cornell.edu/mri/pages/qsm.html)
4. 複数の被験者データをまとめて処理したい場合
被験者ごとにフォルダを作成し一つのフォルダにまとめます。
さらに、後で紹介するまとめてMEDIを実行するスクリプトも入れておきます。
各被験者フォルダには強度画像と位相画像のDICOMがまとめて入ったrawdataフォルダがあります。
以下のスクリプトを実行すると、すべての被験者のQSM mapが計算できます。
4.1. run_sequential.m
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 | clear all;clc;close all; % change direct to study folder selpath = uigetdir( 'Select the folder including all subject' ); cd(selpath); % list folder folderlist = dir(selpath); folderlist = folderlist(~ismember({folderlist.name}, { '.' , '..' })); folderlist = folderlist([folderlist.isdir]); % run MEDI for i = 1:length(folderlist) disp([ 'processing ' folderlist(i).name ' ...' ]) main(folderlist(i).name, selpath); end % define function of MEDI processings function main(folder, basepath) % initialize clearvars -except selpath folderlist folder basepath i % move to subject folder cd(folder) % STEP 1: Import data [iField,voxel_size,matrix_size,CF,delta_TE,TE,B0_dir,files]=Read_DICOM( 'rawdata' ); %%%% Generate the Magnitude image %%%% iMag = sqrt(sum(abs(iField).^2,4)); %%%%% provide a Mask here if possible %%%%%% if (~exist( 'Mask' , 'var' )) % Mask = genMask(iField, voxel_size); Mask = BET(iMag,matrix_size,voxel_size); end %%%%% provide a noise_level here if possible %%%%%% if (~exist( 'noise_level' , 'var' )) noise_level = calfieldnoise(iField, Mask); end %%%%% normalize signal intensity by noise to get SNR %%% iField = iField/noise_level; % STEP 2a: Field Map Estimation %%%%%Estimate the frequency offset in each of the voxel using a %%%%%complex fitting %%%% [iFreq_raw N_std] = Fit_ppm_complex(iField); % STEP 2b: Spatial phase unwrapping %%%% iFreq = unwrapPhase(iMag, iFreq_raw, matrix_size); % STEP 2c: Background Field Removal %%%% Background field removal %%%% [RDF shim] = PDF(iFreq, N_std, Mask,matrix_size,voxel_size, B0_dir); % CSF Mask for zero referencing R2s=arlo(TE,abs(iField)); Mask_CSF = extract_CSF(R2s, Mask, voxel_size); % STEP 3: Dipole Inversion save RDF.mat RDF iFreq iFreq_raw iMag N_std Mask matrix_size... voxel_size delta_TE CF B0_dir Mask_CSF; %%%% run MEDI %%%%% QSM = MEDI_L1( 'lambda' ,1000, 'smv' , 5, 'merit' ); %%% save to DICOM % ignore warnings... Write_DICOM(QSM,files, 'QSM' ) % back to study folder cd(basepath) end |