DSEvars.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. function [V,Stat]=DSEvars(V0,varargin)
  2. %[V,Stat]=DSEvars(V0,varargin)
  3. %
  4. %%%INPUTS:
  5. % V0: Can be (1) a string indicating the path to the
  6. % nifti/cifti file (2) a numerical matrix of size IxT.
  7. % Where I is number of voxels (I=Nx x Ny x Nz) and T is
  8. % number of data-points.
  9. % OPTIONS:
  10. %
  11. %
  12. % 'DestDir' : Output directory. Should only be used when the
  13. % input is a nifti image and user needs to save the
  14. % S, D and E (3D and 4D) images.
  15. % e.g.: [V,Stat]=DSEvars(V0,'DestDir','~/Where/to/save/')
  16. %
  17. % 'saveDSEtable': If triggered and followed by a path + filename.csv it
  18. % saves the DSE table as a csv file
  19. %
  20. % 'Norm' : Intensity normalisation to a given scale.
  21. % e.g.: [V,Stat]=DSEvars(V0,'Norm',100)
  22. %
  23. % 'Scale' : Scale the intensity between the data-sets.
  24. % e.g.: [V,Stat]=DSEvars(V0,'Scale',1/10)
  25. %
  26. % 'verbose' : Set to 1 if you need the log of runing code
  27. % [default:1]
  28. % e.g.: [V,Stat]=DSEvars(V0,'verbose',1)
  29. %%%OUTPUTS:
  30. %
  31. % V: Structure contains the variance components:
  32. % V.{A,S,D,E}var: time series of var components
  33. % V.w_{A,S,D,E}var: sum of mean squared of components
  34. % V.g_{A,S,D,E}var: sum of mean squared of global components
  35. % V.ng_{A,S,D,E}var: sum of mean squared of non-global components
  36. % Stat: Structure contains the higher level parameters of the comps:
  37. % Stat.Labels: Labels indicating the order of next vars
  38. % Stat.SS: Sum-squared
  39. % Stat.MS: Mean-squared
  40. % Stat.RMS: Root-Mean-Squared
  41. % Stat.Prntg: Percentage of the whole variance
  42. % Stat.RelVar: Percentage of the whole variance relative
  43. % to the iid case.
  44. %
  45. % Stat.DeltapDvar: \Delta\%D-var
  46. % Stat.pDvar: \%D-var
  47. % Stat.DeltapSvar: \Delta\%S-var
  48. % Stat.pSvar: \%S-var
  49. %
  50. %
  51. %%%NOTES:
  52. % 1) It is recommended to only use time series of intra-cranial voxels as
  53. % inclusding the extra-cranial may inflate the variance. You can use
  54. % 'bet' in FSL package to remove the extra-cranial areas. The scripts
  55. % automatically remove the zero/NaN voxels.
  56. %
  57. % 2) If a destination directory doesn't exist, the function automatically
  58. % make a directory with the given 'DestDir'.
  59. %
  60. % 3) To fully exploit the DSEvars, the data should *NOT* be undergone any
  61. % form of temporal filtering, as temporal filtering may remove the high
  62. % freq fluctuations.
  63. %
  64. % 4) For inter-site/cohort comparison, it is recommended that the
  65. % intensity is scale accordingly by option 'Norm' or 'Scale'.
  66. %
  67. % 5) If the input is set to be a NIFTI file, you require Nifti_Util
  68. % (provided in the directory). For input of CIFTI you require to
  69. % addpath the FieldTrip toolbox from:
  70. % http://www.fieldtriptoolbox.org/reference/ft_read_cifti
  71. %
  72. %%%EXAMPLE:
  73. %
  74. % For iid case, numerical matrix:
  75. %
  76. % I=4e4; T=1200; Y=randn(I,T);
  77. % [V,Stat]=DSEvars(Y);
  78. % In this example, the function returns the variance components and print
  79. % the SS and ANOVA tables for input of numerical matrix.
  80. %
  81. % OneSub='~/100307/rfMRI_REST1_LR.nii.gz' %a HCP Subject
  82. % [V,Stat]=DSEvars(OneSub);
  83. % In this example, the function returns the variance components and print
  84. % the SS and ANOVA tables for input of nifti image.
  85. %
  86. % OneSub='~/100307/rfMRI_REST1_LR.nii.gz' %a HCP Subject
  87. % [V,Stat]=DSEvars(OneSub,'verbose',1,'DestDir','~/temp','Norm',100);
  88. %
  89. % Stat.DpDVARS : \Delta\%D-var (Exceed fast Standardised DVARS)
  90. % Stat.pDvar : \%D-var (Percentage of the whole var -A-var-)
  91. % In this example, the function returns the variance components and print
  92. % the SS and ANOVA tables for input of nifti image. It also saves the 4D
  93. % and 3D images of variance components in directory '~/temp'.
  94. %
  95. %
  96. %%%REFERENCES
  97. %
  98. % Afyouni S. & Nichols T.E., Insights and inference for DVARS, 2017
  99. % http://www.biorxiv.org/content/early/2017/04/06/125021.1
  100. %
  101. %
  102. %%%
  103. % Soroosh Afyouni & Thomas Nichols, UoW, Feb 2017
  104. %
  105. % https://github.com/asoroosh/DVARS
  106. % http://warwick.ac.uk/tenichols
  107. %
  108. % Please report bugs to srafyouni@gmail.com
  109. %_________________________________________________________________________
  110. fnnf=mfilename; if ~nargin; help(fnnf); return; end; clear fnnf;
  111. %_________________________________________________________________________
  112. %% ParCheck
  113. t3_varn = {'Avar','Dvar','Svar','Evar'};
  114. t3_rown = {'Whole','Global','non-Global'};
  115. Row_labs = {'Avar','Dvar','Svar','Evar','g_Avar','g_Dvar','g_Svar','g_Evar'};
  116. Col_labs = {'MS','RMS','Percentage_of_whole','Relative_to_iid'};
  117. % Input Check-------------------------
  118. gsrflag=0; verbose=1; DestDir=[]; DestDirTable=[]; md=[]; scl=[];
  119. if sum(strcmpi(varargin,'gsrflag'))
  120. gsrflag = varargin{find(strcmpi(varargin,'gsrflag'))+1};
  121. end
  122. if sum(strcmpi(varargin,'verbose'))
  123. verbose = varargin{find(strcmpi(varargin,'verbose'))+1};
  124. end
  125. if sum(strcmpi(varargin,'saveDSEtable'))
  126. DestDirTable = varargin{find(strcmpi(varargin,'saveDSEtable'))+1};
  127. end
  128. if sum(strcmpi(varargin,'destdir'))
  129. DestDir = varargin{find(strcmpi(varargin,'destdir'))+1};
  130. if sum(strcmpi(varargin,'images'))
  131. imagelist = varargin{find(strcmpi(varargin,'images'))+1};
  132. else
  133. imagelist = {'Dvar','Svar'};
  134. end
  135. %add something here to show the var images, just in case; with a verbose
  136. %trigger, of course!
  137. end
  138. if sum(strcmpi(varargin,'norm'))
  139. scl = varargin{find(strcmpi(varargin,'norm'))+1};
  140. end
  141. if sum(strcmpi(varargin,'scale'))
  142. scl = varargin{find(strcmpi(varargin,'scale'))+1};
  143. md = 1;
  144. end
  145. % Add toolbox to open the images-------
  146. if isempty(strfind(path,'Nifti_Util'))
  147. if verbose; disp('-Nifti_Util added to the path.'); end;
  148. addpath(genpath('Nifti_Util'));
  149. end
  150. %---temp
  151. % if sum(strcmpi(varargin,'MeanImage'))
  152. % mYr = varargin{find(strcmpi(varargin,'MeanImage'))+1};
  153. % mYr=mYr(mYr~=0 & ~isnan(mYr));
  154. % %size(mYr)
  155. % md = median(mYr);
  156. % end
  157. if ischar(V0)
  158. [ffpathstr,ffname,ffext]=fileparts(V0);
  159. if verbose; disp(['-Path to the image is: ' ffpathstr]); end;
  160. %if you are using MATLAB <2016, please replace 'contains' with 'strfind'
  161. if contains(ffname,'.dtseries') || contains(ffext,'.dtseries')
  162. if verbose; disp(['--File is CIFTI: ' ffname ffext]); end;
  163. V1=ft_read_cifti(V0);
  164. V2=V1.dtseries;
  165. I0=size(V2,1); T0=size(V2,2);
  166. Y=V2; clear V2 V1;
  167. %if you are using MATLAB <2016, please replace 'contains' with 'strfind'
  168. elseif ~contains(ffname,'.dtseries') || contains(ffname,'.nii')
  169. if verbose; disp(['--File is NIFTI: ' ffname ffext]); end;
  170. V1 = load_untouch_nii(V0);
  171. V2 = V1.img;
  172. X0 = size(V2,1); Y0 = size(V2,2); Z0 = size(V2,3); T0 = size(V2,4);
  173. I0 = prod([X0,Y0,Z0]);
  174. Y = reshape(V2,[I0,T0]); clear V2;
  175. else
  176. error('Unknown input image.')
  177. end
  178. if verbose; disp('-Image loaded.'); end;
  179. elseif isnumeric(V0) %&& size(V0,1)>size(V0,2)
  180. if verbose; disp('-Input is a Matrix.'); end;
  181. if size(V0,1)<=size(V0,2)
  182. warning('Check the input, matrix should be in form of IxT, where I=XxYxZ!');
  183. end
  184. Y = double(V0);
  185. I0= size(Y,1); T0 = size(Y,2);
  186. %elseif isnumeric(V0) && size(V0,1)<=size(V0,2)
  187. % if verbose; disp('-Input is a Matrix.'); end;
  188. % warning('Check the input, matrix should be in form of IxT, where I=XxYxZ!');
  189. end
  190. Y = double(Y);%to work with int 16bit as well.
  191. mvY_WholeImage = mean(Y,2);
  192. %Remove voxels of zeros/NaNs---------------------------------------------------
  193. nan_idx = find(isnan(sum(Y,2)));
  194. zeros_idx = find(sum(Y,2)==0);
  195. idx = 1:I0;
  196. idx([nan_idx;zeros_idx]) = [];
  197. Y([nan_idx;zeros_idx],:) = [];
  198. I1 = size(Y,1); %update number of voxels
  199. if verbose; disp(['-Extra-cranial areas removed: ' num2str(size(Y,1)) 'x' num2str(size(Y,2))]); end;
  200. mvY_Untouched = mean(Y,2);
  201. % Intensity Normalisation------------------------------------------------------
  202. IntnstyScl = @(Y,md,scl) (Y./md).*scl;
  203. if ~isempty(scl) && isempty(md)
  204. md = median(mean(Y,2)); %NB median of the mean image.
  205. %md = mean(mean(Y,2)); %NB *mean* of the mean image.
  206. Y = IntnstyScl(Y,md,scl);
  207. if verbose; disp(['-Intensity Normalised by ' num2str(scl) '&' num2str(md) '.']); end;
  208. elseif ~isempty(scl) && ~isempty(md)
  209. assert(md==1,'4D mean in scalling cannot be anything other than 1!')
  210. Y = IntnstyScl(Y,md,scl);
  211. if verbose; disp(['-Intensity Scaled by ' num2str(scl) '.']); end;
  212. elseif isempty(scl) && isempty(md)
  213. if verbose; disp('-No normalisation/scaling has been set!'); end;
  214. else
  215. error('Something is wrong with param re: intensity normalisation')
  216. end
  217. %Centre the data-----------------------------
  218. mvY_NormInt = mean(Y,2); %later will be used as grand mean! don't touch it!
  219. dmeaner = repmat(mvY_NormInt,[1,T0]);
  220. Y = Y-dmeaner; clear dmeaner
  221. mvY_Demeaned = mean(Y,2);
  222. %----------
  223. if verbose; disp(['-Data centred. Untouched Grand Mean: ' num2str(mean(mvY_Untouched)) ', Post-norm Grand Mean: ' num2str(mean(mvY_NormInt)) ', Post demean: ' num2str(mean(mvY_Demeaned))]); end;
  224. %Data GSRed--------------------------------ONLY FOR TEST-----------------
  225. if gsrflag
  226. Y = fcn_GSR(Y);
  227. if verbose; disp('-Data GSRed.'); end;
  228. end
  229. %------------------------------------------ONLY FOR TEST-----------------
  230. %% Lagged Images
  231. B.Ybar = sum(Y)./I1; %global signal is here!
  232. D = Y(:,1:end-1)-Y(:,2:end);
  233. B.Dbar = sum(D)./I1;
  234. S = Y(:,1:end-1)+Y(:,2:end);
  235. B.Sbar = sum(S)./I1;
  236. Ytail = Y(:,end); Yhead=Y(:,1);
  237. B.Ytbar = sum(Ytail)./I1;
  238. B.Y1bar = sum(Yhead)./I1;
  239. %% DSE Var Images
  240. %4D images
  241. V_Img.Avar_ts = Y.^2;
  242. V_Img.Dvar_ts = D.^2./4;
  243. V_Img.Svar_ts = S.^2./4;
  244. V_Img.Evar_ts = [Yhead,Ytail].^2./2;
  245. %3D images -- averaged across time.
  246. V_Img.Avar = mean(Y.^2,2);
  247. V_Img.Dvar = mean(D.^2,2)./2;
  248. V_Img.Svar = mean(S.^2,2)./2;
  249. V_Img.Evar = mean([Yhead.^2,Ytail.^2],2); % <<<< should be checked
  250. %% DSE Time series -- averaged across I
  251. V.Avar_ts = mean(V_Img.Avar_ts);
  252. V.Dvar_ts = mean(V_Img.Dvar_ts);
  253. V.Svar_ts = mean(V_Img.Svar_ts);
  254. V.Evar_ts = mean(V_Img.Evar_ts);
  255. %% Save Images?
  256. if ~isempty(DestDir) && ischar(V0)
  257. %if ~any(strfind(path,'spm')); warning('**SPM has not been added to the path!**'); end;
  258. if exist(DestDir,'dir')~=7; mkdir(DestDir); end;
  259. %savedir = [pwd '/' DestDir '/'];
  260. for is=imagelist
  261. if verbose; disp(['****' is{1} ':']); end;
  262. Var0_tmp = eval(['V_Img.' is{1} '_ts']);
  263. Var1_tmp = zeros(I0,size(Var0_tmp,2));
  264. Var1_tmp(idx,:) = Var0_tmp;
  265. Y_tmp = reshape(Var1_tmp,[X0 Y0 Z0 size(Var1_tmp,2)]);
  266. V_tmp = V1;
  267. V_tmp.hdr.dime.dim(2:5) = [X0 Y0 Z0 size(Var1_tmp,2)];
  268. V_tmp.img = Y_tmp;
  269. save_untouch_nii(V_tmp,[DestDir is{1} '_ts.nii.gz']);
  270. if verbose; disp([is{1} ' saved: ' DestDir is{1} '_ts.nii.gz']); end;
  271. clear *_tmp
  272. Var0_tmp = eval(['V_Img.' is{1}]);
  273. Var1_tmp = zeros(I0,size(Var0_tmp,2));
  274. Var1_tmp(idx) = Var0_tmp;
  275. Y_tmp = flipud(reshape(Var1_tmp,[X0 Y0 Z0])); %flip back here because save_nii flips it!
  276. nii_tmp = make_nii(sum(Y_tmp,4),[2,2,2],[0,0,0],64,['3D image of ' is{1}]);
  277. save_nii(nii_tmp,[DestDir is{1} '.nii.gz'])
  278. if verbose; disp([is{1} ' saved: ' DestDir is{1} '.nii.gz']); end;
  279. clear *_tmp
  280. end
  281. clear V_Img;
  282. else
  283. if verbose
  284. disp('-Variance images will NOT be saved:')
  285. disp('-- Either destination directory was not set OR the input is not a nifti.')
  286. end
  287. clear V_Img;
  288. end
  289. %% Global - Res (SED vars)
  290. V.w_Avar = sum(V.Avar_ts);
  291. V.w_Dvar = sum(V.Dvar_ts);
  292. V.w_Svar = sum(V.Svar_ts);
  293. V.w_Evar = sum(V.Evar_ts);
  294. %Global
  295. V.g_Avar_ts = B.Ybar.^2;
  296. V.g_Dvar_ts = B.Dbar.^2./4;
  297. V.g_Svar_ts = B.Sbar.^2./4;
  298. V.g_Evar_ts = B.Ybar([1,T0]).^2./2;
  299. % Global ts (Just for vis)
  300. V.g_Ats=B.Ybar;
  301. V.g_Dts=B.Dbar./2;
  302. V.g_Sts=B.Sbar./2;
  303. V.g_Avar = sum(V.g_Avar_ts);
  304. V.g_Dvar = sum(V.g_Dvar_ts);
  305. V.g_Svar = sum(V.g_Svar_ts);
  306. V.g_Evar = sum(V.g_Evar_ts);
  307. %V.g_Avar = sum(B.Ybar.^2);
  308. %V.g_Dvar = sum(B.Dbar.^2)./4;
  309. %V.g_Svar = sum(B.Sbar.^2)./4;
  310. %V.g_Evar = sum(B.Ybar([1,T0]).^2)./2;
  311. %Non-Global
  312. V.ng_Avar_ts = mean((Y-repmat(B.Ybar,[I1,1])).^2);
  313. V.ng_Dvar_ts = mean((D-repmat(B.Dbar,[I1,1])).^2)./4;
  314. V.ng_Svar_ts = mean((S-repmat(B.Sbar,[I1,1])).^2)./4;
  315. V.ng_Evar_ts = mean([(Yhead-B.Y1bar).^2,(Ytail-B.Ytbar).^2])./2;
  316. V.ng_Avar = sum(V.ng_Avar_ts);
  317. V.ng_Dvar = sum(V.ng_Dvar_ts);
  318. V.ng_Svar = sum(V.ng_Svar_ts);
  319. V.ng_Evar = sum(V.ng_Evar_ts);
  320. V.GrandMean_Untouched = mean(mvY_Untouched);
  321. V.GrandMean_NormInt = mean(mvY_NormInt);
  322. V.GrandMean_Demeaned = mean(mvY_Demeaned);
  323. V.GranMean_WholeBrain = mean(mvY_WholeImage);
  324. %V.ng_Avar = sum(mean((Y-repmat(B.Ybar,[I1,1])).^2));
  325. %V.ng_Dvar = sum(mean((D-repmat(B.Dbar,[I1,1])).^2))./4;
  326. %V.ng_Svar = sum(mean((S-repmat(B.Sbar,[I1,1])).^2))./4;
  327. %V.ng_Evar = sum(mean([(Yhead-B.Y1bar).^2,(Ytail-B.Ytbar).^2]))./2;
  328. % Sanity Chek - The moment of truth!
  329. % gvars = V.g_Dvar+V.g_Svar+V.g_Evar;
  330. % rgvars = V.rg_Dvar+V.rg_Svar+V.rg_Evar;
  331. % WholeWholeVar_Test=gvars+rgvars;
  332. % %assert(WholeWholeVar==WholeWholeVar_Test,'VarDecomp failed')
  333. % disp(['WholeVar= ' num2str(V.w_Avar) ' , sum of decomp var= ' num2str(WholeWholeVar_Test)])
  334. %% SED ANOVA table
  335. SS = I1*[V.w_Avar,V.w_Dvar,V.w_Svar,V.w_Evar,...
  336. V.g_Avar,V.g_Dvar,V.g_Svar,V.g_Evar];
  337. MS = SS/I1/T0;
  338. RMS = sqrt(MS);
  339. Prntg = RMS.^2./RMS(1).^2*100;
  340. Expd = [1,(T0-1)/T0/2,(T0-1)/T0/2,1/T0,...
  341. [1,(T0-1)/T0/2,(T0-1)/T0/2,1/T0]./I1];
  342. RelVar = Prntg./100./Expd;
  343. Var_Tab = [V.w_Avar,V.w_Dvar,V.w_Svar,V.w_Evar;...
  344. V.g_Avar,V.g_Dvar,V.g_Svar,V.g_Evar;...
  345. V.ng_Avar,V.ng_Dvar,V.ng_Svar,V.ng_Evar];
  346. DSETable = array2table([MS',RMS',Prntg',RelVar'],'VariableNames',Col_labs,'RowNames',Row_labs);
  347. if ~isempty(DestDirTable)
  348. writetable(DSETable,DestDirTable)
  349. end
  350. if verbose
  351. disp('----------------------')
  352. disp('Sum-of-Mean-Squared (SMS) Table')
  353. disp(array2table(fix(Var_Tab),'VariableNames',t3_varn,'RowNames',t3_rown))
  354. disp('------------')
  355. disp(DSETable)
  356. disp('----------------------')
  357. end
  358. %DSE ANOVE table
  359. Stat.Labels = Row_labs;
  360. Stat.SS = SS;
  361. Stat.MS = MS;
  362. Stat.RMS = RMS;
  363. Stat.Prntg = Prntg;
  364. Stat.RelVar = RelVar;
  365. Stat.VT = Var_Tab;
  366. %Config
  367. Stat.dim = [I1 T0]; %Inter Cranial sizes
  368. Stat.dim0 = [I0 T0]; %the 4D image initial dimensions
  369. %Standardised measures
  370. Stat.DeltapDvar = (V.Dvar_ts-median(V.Dvar_ts))./mean(V.Avar_ts)*100; % This is \Delta\%D-var i.e. How much it exceeded from it is *median* normalised by A-var.
  371. Stat.DeltapSvar = (V.Svar_ts-median(V.Svar_ts))./mean(V.Avar_ts)*100; % This is \Delta\%S-var i.e. How much it exceeded from it is *median* normalised by A-var.
  372. Stat.pDvar = V.Dvar_ts./mean(V.Avar_ts)*100; % & this is \%D-var. NB! *_ts is sum across *voxels*, I in nominator and denominator cancel out.
  373. Stat.pSvar = V.Svar_ts./mean(V.Avar_ts)*100; % & this is \%S-var
  374. %Mean -- 4 sanity checks
  375. Stat.GranMean_WholeBrain = mean(mvY_WholeImage);
  376. Stat.GrandMean_Untouched = mean(mvY_Untouched);
  377. Stat.GrandMean_NormInt = mean(mvY_NormInt);
  378. Stat.GrandMean_Demeaned = mean(mvY_Demeaned);
  379. function gsrY=fcn_GSR(Y)
  380. %Global Signal Regression. From FSLnets.
  381. %For the fMRIDiag, it needs to be transposed.
  382. % SA, UoW, 2017
  383. Y=Y';
  384. mgrot=mean(Y,2);
  385. gsrY=Y-(mgrot*(pinv(mgrot)*Y));
  386. gsrY=gsrY';