thePlotSpm.m 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. % thePlotSpm
  2. %
  3. % This is a Matlab script that uses custom code and spm12 routines to plot
  4. % a version of THE PLOT from Jonathan Power. See:
  5. % - https://www.jonathanpower.net/2017-ni-the-plot.html
  6. % - https://www.ncbi.nlm.nih.gov/pubmed/27510328
  7. % (code has been partly adopted from Power's script)
  8. %
  9. % THE PLOT is a useful visual quality checking tool for fMRI timeseries
  10. % data. It is a 2D plot of voxel intensities over time, with voxels ordered
  11. % in bins (GM, WM, CSF) to indicate some directionality in the data. Motion
  12. % and physiological noise are more easily visually inspected with this
  13. % tool, especially if viewed next to other quality traces, like FD, DVARS
  14. % or physiological recordings.
  15. % My script does the following:
  16. % - Run preprocessing on input data:
  17. % - Checks if T1w segments already exist.
  18. % - If not, realigns all images in time series to first functional
  19. % image, coregisters (estimate) T1w image to first functional image,
  20. % segments the coregistered T1w image into GM, WM and CSF
  21. % compartments, reslices all segments into functional image grid,
  22. % smooths realigned functional images, smooths unprocessed functional
  23. % images.
  24. % - Calculates framewise displacement
  25. % - Creates brain mask (GM+WM+CSF)
  26. % - Calculates BOLD percentage signal changes for masked brain voxels
  27. % - Creates figure with FD and THEPLOT (with blue and red lines
  28. % separating GM (top), WM (middle), and CSF(bottom))
  29. %
  30. % INPUTS:
  31. % structural_fn - filename of T1-weighted structural scan, a 4D nifti
  32. % file is expected (e.g. 'mprage.nii')
  33. % funcional4D_fn - filename of main functional scan, a 4D nifti file is
  34. % expected (e.g. 'rest.nii')
  35. %
  36. % DEPENDENCIES:
  37. % thePlotSpmPreproc.m - preprocesses data (functional realignment, coregistration of
  38. % T1w to first functional image, segmentation into
  39. % tissue types, reslicing to functional grid)
  40. % createBinarySegments.m - constructs 3D binary images for GM, WM and CSF based
  41. % on the relative value of GM/WM/CSF tissue probability
  42. % maps per voxel.
  43. %
  44. % NOTES/ASSUMPTIONS:
  45. % - This script makes use of spm12 batch scripting
  46. % - This script assumes that image files are in the Nifti file format
  47. % - This script was developed in Matlab 2016b on a Mac OS Sierra, and not
  48. % tested on other platforms (it wasn't even tested much on my Mac)
  49. % - This is a simplified, Matlab-SPM-only version of Power's THEPLOT and
  50. % excludes things like erosion of masks and segmentation into more bins
  51. % (done in freesurfer).
  52. % - I might have made some mistakes in the code, or it could be improved in
  53. % many ways. Please contribute!
  54. %
  55. % 2018-08-17 Remi Gau
  56. % This seems to work on windows 10 with matlab 2017a.
  57. % While looking around I realized that Cyril Pernet has a function
  58. % (spmup_timeseriesplot) to plot this (and do quite a few other things as
  59. % well) in his spmup toolbox: % https://github.com/CPernet/spmup
  60. % But I like your script a lot as well as it is more stand-alone and
  61. % therefore more pedagogical.
  62. %
  63. %__________________________________________________________________________
  64. % Copyright (C) 2018 - Stephan Heunis
  65. clear
  66. clc
  67. %% User defined variables:
  68. % -------------------------------------------------------------------------
  69. data_dir = ''; % e.g. '/users/me/matlab/data/subj1'
  70. functional4D_fn = [data_dir filesep '']; % e.g. [data_dir filesep 'rest.nii']
  71. structural_fn = [data_dir filesep '']; % e.g. [data_dir filesep 'mprage.nii']
  72. spm_dir = ''; % e.g. '/users/me/matlab/spm12'
  73. fwhm = 6; % for preprocessing smoothing steps
  74. use_processed = 0; % use realigned data for the plot? yes = 1
  75. intensity_scale = [-6 6]; % scaling for plot image intensity, see what works
  76. davg_def = 50; %Average distance to the surface of the brain ; 50mm = assumed brain radius; from article
  77. % -------------------------------------------------------------------------
  78. % RG
  79. % -------------------------------------------------------------------------
  80. fwhm = 6;
  81. [spm_dir, ~, ~] = fileparts(which('spm'));
  82. data_dir = 'D:';
  83. subj = 'sub-01';
  84. % structural image
  85. structural_fn = spm_select('FPList', fullfile(data_dir , subj , 'anat'), ['^' subj '_T1w.*nii$']);
  86. % 4D BOLD time series
  87. functional4D_fn = spm_select('FPList', fullfile(data_dir , subj , 'func'), ['^' subj '_task-.*_bold*.nii$']);
  88. use_processed = 0; % use realigned data for the plot? yes = 1
  89. intensity_scale = [-6 6]; % scaling for plot image intensity, see what works
  90. davg_def = 65;
  91. % -------------------------------------------------------------------------
  92. %% Get image information
  93. func_spm = spm_vol(functional4D_fn);
  94. tsize = size(func_spm);
  95. Nt = tsize(1);
  96. Ni= func_spm(1).dim(1);
  97. Nj= func_spm(1).dim(2);
  98. Nk= func_spm(1).dim(3);
  99. %% Data check
  100. % Preprocess structural and functional data. First check if a resliced grey
  101. % matter image is present in the specified data directory. If true, assume
  102. % that all preprocessing has been completed by thePlotSpmPreproc, declare
  103. % appropriate variables and
  104. [dir, file, ext] = fileparts(structural_fn);
  105. if exist([dir filesep 'rc1' file ext], 'file')
  106. disp('Preproc done!')
  107. preproc_data = struct;
  108. preproc_data.rstructural_fn = fullfile(dir , ['r' file ext]);
  109. preproc_data.rgm_fn = fullfile(dir , ['rc1' file ext]);
  110. preproc_data.rwm_fn = fullfile(dir , ['rc2' file ext]);
  111. preproc_data.rcsf_fn = fullfile(dir , ['rc3' file ext]);
  112. [dir, file, ext] = fileparts(functional4D_fn);
  113. preproc_data.rfunctional_fn = fullfile(dir , ['r' file ext]);
  114. preproc_data.srfunctional_fn = fullfile(dir , ['sr' file ext]);
  115. preproc_data.sfunctional_fn = fullfile(dir , ['s' file ext]);
  116. preproc_data.mp_fn = fullfile(dir , ['rp_' file '.txt']);
  117. preproc_data.MP = load(preproc_data.mp_fn);
  118. else
  119. preproc_data = thePlotSpmPreproc(functional4D_fn, structural_fn, fwhm, spm_dir);
  120. end
  121. % Additional check to make sure that the functional images and the resliced
  122. % TPMs have same dimensions
  123. files = {};
  124. files{1,1} = preproc_data.rgm_fn;
  125. files{2,1} = [preproc_data.rfunctional_fn ',1'];
  126. files = char(files);
  127. spm_check_orientations(spm_vol(files));
  128. %% Try to compute the average surface to the brain
  129. % from motion finger print functions and script (mw_mfp.m) from Marko Wilke
  130. % https://www.medizin.uni-tuebingen.de/kinder/en/research/neuroimaging/software/
  131. % see http://www.dx.doi.org/10.1016/j.neuroimage.2011.10.043
  132. % and http://www.dx.doi.org/10.1371/journal.pone.0106498
  133. [dir, file, ext] = fileparts(structural_fn);
  134. try
  135. if isempty(spm_select('FPList',dir,'^rc1.*\.surf\.gii$'))
  136. % create a surface of the brain using the TPM if we don't have one
  137. files = spm_select('FPList',dir,'^rc[12].*\.nii$');
  138. spm_surf(files,2);
  139. clear files
  140. end
  141. % compute the average surface to the brain
  142. FV = gifti(spm_select('FPList',dir,'^rc1.*\.surf\.gii$'));
  143. center = FV.vertices(FV.faces(:,:),:);
  144. center = reshape(center, [size(FV.faces,1) 3 3]);
  145. center = squeeze(mean(center,2));
  146. ori_dist = sqrt(sum((center.*-1).^2,2))';
  147. davg = mean(ori_dist);
  148. clear ori_dist center FV
  149. catch
  150. davg = davg_def;
  151. end
  152. fprintf(' Average distance to the cortex surface: %0.2f ', davg)
  153. %% Calculate Framewise displacement:
  154. % - First demean and detrend the movement parameters and
  155. % - then calculate FD.
  156. % See also spmup_FD from https://github.com/CPernet/spmup
  157. MP2 = preproc_data.MP - repmat(mean(preproc_data.MP, 1), Nt,1);
  158. X = (1:Nt)';
  159. X2 = X - mean(X);
  160. X3 = [ones(Nt,1) X2];
  161. b = X3\MP2;
  162. MP_corrected = MP2 - X3(:, 2)*b(2,:);
  163. MP_mm = MP_corrected;
  164. MP_mm(:,4:6) = MP_mm(:,4:6)*davg;
  165. MP_diff = [zeros(1, 6); diff(MP_mm)];
  166. FD = sum(abs(MP_diff),2);
  167. %% Calculate BOLD percentage signal change (PSC)
  168. % - First create binary masks for GM, WM and CSF,
  169. % - Then combine them to get a brain mask for which calculations are run
  170. % - Then compute PSC for unprocessed or realigned data (user selection)
  171. % - Then plot everything
  172. [GM_img_bin, WM_img_bin, CSF_img_bin] = createBinarySegments(preproc_data.rgm_fn, preproc_data.rwm_fn, preproc_data.rcsf_fn, 0.1);
  173. I_GM = find(GM_img_bin);
  174. I_WM = find(WM_img_bin);
  175. I_CSF = find(CSF_img_bin);
  176. mask_reshaped = GM_img_bin | WM_img_bin | CSF_img_bin;
  177. I_mask = find(mask_reshaped);
  178. Nmaskvox = numel(I_mask);
  179. %% Load and detrend data
  180. % Use realigned or unprocessed data for plot, user-selected
  181. if use_processed == 1
  182. F_img = spm_read_vols(spm_vol(preproc_data.srfunctional_fn));
  183. else
  184. F_img = spm_read_vols(spm_vol(preproc_data.sfunctional_fn));
  185. end
  186. % Remove linear and polynomial trends from data
  187. F_2D = reshape(F_img, Ni*Nj*Nk, Nt);
  188. X_design = [ (1:Nt)' ((1:Nt).^2/(Nt^2))' ((1:Nt).^3/(Nt^3))'];
  189. X_design = X_design - mean(X_design);
  190. X_design = [X_design ones(Nt,1)];
  191. betas = X_design\F_2D';
  192. F_detrended = F_2D' - X_design(:, 1:(end-1))*betas(1:(end-1), :);
  193. F_detrended = F_detrended';
  194. % Mask, mean and PSC
  195. F_masked = F_detrended(I_mask, :);
  196. F_mean = mean(F_masked, 2);
  197. F_masked_psc = 100*(F_masked./repmat(F_mean, 1, Nt)) - 100;
  198. F_masked_psc(isnan(F_masked_psc))=0;
  199. F_psc_img = zeros(Ni, Nj, Nk, Nt);
  200. F_2D_psc = reshape(F_psc_img, Ni*Nj*Nk, Nt);
  201. F_2D_psc(I_mask, :) = F_masked_psc;
  202. F_psc_img = reshape(F_2D_psc, Ni, Nj, Nk, Nt);
  203. %% Figure
  204. % Create image to plot by concatenation
  205. GM_img = F_2D_psc(I_GM, :);
  206. WM_img = F_2D_psc(I_WM, :);
  207. CSF_img = F_2D_psc(I_CSF, :);
  208. all_img = [GM_img; WM_img; CSF_img];
  209. % Identif limits between the different tissue compartments
  210. line1_pos = numel(I_GM);
  211. line2_pos = numel(I_GM) + numel(I_WM);
  212. figure
  213. fontsizeL = 17;
  214. fontsizeM = 15;
  215. % plot functional data
  216. ax1 = subplot(5,1,[2:5]);
  217. imagesc(ax1, all_img);
  218. colormap(gray);
  219. caxis(intensity_scale);
  220. title(ax1, 'thePlotSpm','fontsize',fontsizeL)
  221. ylabel(ax1, 'Voxels','fontsize',fontsizeM)
  222. xlabel(ax1, 'fMRI volumes','fontsize',fontsizeM)
  223. hold on;
  224. line([1 Nt],[line1_pos line1_pos], 'Color', 'b', 'LineWidth', 2 )
  225. line([1 Nt],[line2_pos line2_pos], 'Color', 'r', 'LineWidth', 2 )
  226. hold off;
  227. % plot Framewise displacement
  228. ax2 = subplot(5,1,1);
  229. plot(ax2, FD, 'LineWidth', 2); grid;
  230. axis tight
  231. set(ax2,'Xticklabel',[]);
  232. title(ax2, 'FD','fontsize',fontsizeL)
  233. ylabel(ax2, 'mm','fontsize',fontsizeM)