spm_est_smoothness.m 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. function [FWHM,VRpv,R] = spm_est_smoothness(V,VM,ndf)
  2. % Estimation of smoothness based on [residual] images
  3. % FORMAT [FWHM,VRpv,R] = spm_est_smoothness(V,VM,[ndf])
  4. %
  5. % V - Filenames or mapped standardized residual images
  6. % VM - Filename of mapped mask image
  7. % ndf - A 2-vector, [n df], the original n & dof of the linear model
  8. %
  9. % FWHM - estimated FWHM in all image directions
  10. % VRpv - handle of Resels per Voxel image
  11. % R - vector of resel counts
  12. %__________________________________________________________________________
  13. %
  14. % spm_est_smoothness returns a spatial smoothness estimator based on the
  15. % variances of the normalized spatial derivatives as described in K.
  16. % Worsley, (1996). Inputs are a mask image and a number of standardized
  17. % residual images, or any set of mean zero, unit variance images. Output
  18. % is a global estimate of the smoothness expressed as the FWHM of an
  19. % equivalent Gaussian point spread function. An estimate of resels per
  20. % voxels (see spm_spm) is written as an image file ('RPV.<ext>') to the
  21. % current directory.
  22. %
  23. % To improve the accuracy of the smoothness estimation the error degrees
  24. % of freedom can be supplied. Since it is not assumed that all residual
  25. % images are passed to this function, the full, original sample size n
  26. % must be supplied as well.
  27. %
  28. % The mask image specifies voxels, used in smoothness estimation, by
  29. % assigning them non-zero values. The dimensions, voxel sizes, orientation
  30. % of all images must be the same. The dimensions of the images can be of
  31. % dimensions 0, 1, 2 and 3.
  32. %
  33. % Note that 1-dim images (lines) must exist in the 1st dimension and
  34. % 2-dim images (slices) in the first two dimensions. The estimated fwhm
  35. % for any non-existing dimension is infinity.
  36. %__________________________________________________________________________
  37. %
  38. % Refs:
  39. %
  40. % K.J. Worsley (1996). An unbiased estimator for the roughness of a
  41. % multivariate Gaussian random field. Technical Report, Department of
  42. % Mathematics and Statistics, McGill University
  43. %
  44. % S.J. Kiebel, J.B. Poline, K.J. Friston, A.P. Holmes, and K.J. Worsley.
  45. % Robust Smoothness Estimation in Statistical Parametric Maps Using
  46. % Standardized Residuals from the General Linear Model. NeuroImage,
  47. % 10:756-766, 1999.
  48. %
  49. % S. Hayasaka, K. Phan, I. Liberzon, K.J. Worsley, T.E. Nichols (2004).
  50. % Nonstationary cluster-size inference with random field and permutation
  51. % methods. NeuroImage, 22:676-687, 2004.
  52. %__________________________________________________________________________
  53. % Copyright (C) 2002-2015 Wellcome Trust Centre for Neuroimaging
  54. % Stefan Kiebel, Tom Nichols
  55. % $Id: spm_est_smoothness.m 6894 2016-09-30 16:48:46Z spm $
  56. %-Assign input arguments
  57. %--------------------------------------------------------------------------
  58. if nargin < 1
  59. V = spm_select(Inf,'image','Select residual images',{},pwd,'^ResI.*.{3}$');
  60. end
  61. if nargin < 2
  62. VM = spm_select(1,'image','Select mask image',{},pwd,'^mask\..{3}$');
  63. end
  64. if nargin < 3, ndf = [NaN NaN]; end
  65. if numel(ndf) ~= 2
  66. error('ndf argument must be of length 2 ([n df]).')
  67. end
  68. %-Initialise
  69. %--------------------------------------------------------------------------
  70. V = spm_vol(V);
  71. VM = spm_vol(VM);
  72. if any(isnan(ndf))
  73. ndf = [numel(V) numel(V)]; % Assume full df
  74. end
  75. n_full = ndf(1);
  76. edf = ndf(2);
  77. %-Initialise RESELS per voxel image
  78. %--------------------------------------------------------------------------
  79. VRpv = struct( 'fname',['RPV' spm_file_ext],...
  80. 'dim', VM.dim(1:3),...
  81. 'dt', [spm_type('float64') spm_platform('bigend')],...
  82. 'mat', VM.mat,...
  83. 'pinfo', [1 0 0]',...
  84. 'descrip', 'spm_spm: resels per voxel');
  85. VRpv = spm_create_vol(VRpv);
  86. %-Dimensionality of image
  87. %--------------------------------------------------------------------------
  88. D = 3 - sum(VM.dim(1:3) == 1);
  89. if D == 0
  90. FWHM = [Inf Inf Inf];
  91. R = [0 0 0];
  92. return;
  93. end
  94. %-Find voxels within mask
  95. %--------------------------------------------------------------------------
  96. d = spm_read_vols(VM);
  97. [Ix,Iy,Iz] = ndgrid(1:VM.dim(1),1:VM.dim(2),1:VM.dim(3));
  98. Ix = Ix(d~=0); Iy = Iy(d~=0); Iz = Iz(d~=0);
  99. %-Compute covariance of derivatives
  100. %--------------------------------------------------------------------------
  101. str = 'Spatial non-sphericity (over scans)';
  102. fprintf('%-40s: %30s',str,'...estimating derivatives'); %-#
  103. spm_progress_bar('Init',100,'smoothness estimation','');
  104. L = zeros(size(Ix,1),D,D);
  105. ssq = zeros(size(Ix,1),1);
  106. for i = 1:numel(V)
  107. [d,dx,dy,dz] = spm_sample_vol(V(i),Ix,Iy,Iz,1);
  108. % sum of squares
  109. %----------------------------------------------------------------------
  110. ssq = ssq + d.^2;
  111. % covariance of finite differences
  112. %----------------------------------------------------------------------
  113. if D >= 1
  114. L(:,1,1) = L(:,1,1) + dx.*dx;
  115. end
  116. if D >= 2
  117. L(:,1,2) = L(:,1,2) + dx.*dy;
  118. L(:,2,2) = L(:,2,2) + dy.*dy;
  119. end
  120. if D >= 3
  121. L(:,1,3) = L(:,1,3) + dx.*dz;
  122. L(:,2,3) = L(:,2,3) + dy.*dz;
  123. L(:,3,3) = L(:,3,3) + dz.*dz;
  124. end
  125. spm_progress_bar('Set',100*i/numel(V));
  126. end
  127. spm_progress_bar('Clear')
  128. %-Scale sum into an average (and account for DF)
  129. % The standard result uses normalised residuals e/sqrt(RSS) and
  130. %
  131. % \hat\Lambda = grad(e/sqrt(RSS))' * grad(e/sqrt(RSS))
  132. %
  133. % In terms of standardized residuals e/sqrt(RMS) this is
  134. %
  135. % \hat\Lambda = (1/DF) * grad(e/sqrt(RMS))' * grad(e/sqrt(RMS))
  136. %
  137. % but both of these expressions assume that the RSS or RMS correspond to
  138. % the full set of residuals considered. However, spm_spm only considers
  139. % upto MAXRES residual images. To adapt, re-write the above as a scaled
  140. % average over n scans
  141. %
  142. % \hat\Lambda = (n/DF) * ( (1/n) * grad(e/sqrt(RMS))' * grad(e/sqrt(RMS)) )
  143. %
  144. % I.e. the roughness estimate \hat\Lambda is an average of outer products
  145. % of standardized residuals (where the average is over scans), scaled by
  146. % n/DF. Hence, we can use only a subset of scans simply by replacing this
  147. % last average term with an average over the subset.
  148. %
  149. % See Hayasaka et al, p. 678, for more on estimating roughness with
  150. % standardized residuals (e/sqrt(RMS)) instead of normalised residuals
  151. % (e/sqrt(RSS)). Note that the names arise from the fact that
  152. % sqrt(RSS) = sqrt(r'*r) is norm(r), while sqrt(RMS) = sqrt(r'*r/edf)
  153. % is the unbiased (ReML) estimate of the standard deviation.
  154. %--------------------------------------------------------------------------
  155. L = L/numel(V); % Average
  156. L = L*(n_full/edf); % Scale
  157. %-Evaluate determinant (and xyz components for FWHM)
  158. %--------------------------------------------------------------------------
  159. if D == 1
  160. resel_xyz = L;
  161. resel_img = L;
  162. end
  163. if D == 2
  164. resel_xyz = [L(:,1,1) L(:,2,2)];
  165. resel_img = L(:,1,1).*L(:,2,2) - ...
  166. L(:,1,2).*L(:,1,2);
  167. end
  168. if D == 3
  169. resel_xyz = [L(:,1,1) L(:,2,2) L(:,3,3)];
  170. resel_img = L(:,1,1).*L(:,2,2).*L(:,3,3) + ...
  171. L(:,1,2).*L(:,2,3).*L(:,1,3)*2 - ...
  172. L(:,1,1).*L(:,2,3).*L(:,2,3) - ...
  173. L(:,1,2).*L(:,1,2).*L(:,3,3) - ...
  174. L(:,1,3).*L(:,2,2).*L(:,1,3);
  175. end
  176. resel_img(resel_img<0) = 0;
  177. % Convert det(Lambda) and diag(Lambda) to units of resels
  178. resel_img = sqrt(resel_img/(4*log(2))^D);
  179. resel_xyz = sqrt(resel_xyz/(4*log(2)));
  180. %-Optional mask-weighted smoothing of RPV image
  181. %--------------------------------------------------------------------------
  182. if spm_get_defaults('stats.rft.nonstat')
  183. fwhm_vox = 3;
  184. else
  185. fwhm_vox = 0;
  186. end
  187. if any(fwhm_vox)
  188. if length(fwhm_vox) == 1, fwhm_vox = fwhm_vox([1 1 1]); end
  189. % Convert resel_img at in-mask voxels to resel volume
  190. mask = spm_read_vols(VM) > 0;
  191. RPV = zeros(size(mask));
  192. RPV(mask) = resel_img;
  193. % Remove invalid mask voxels, i.e. edge voxels with missing derivatives
  194. smask = double(mask & isfinite(RPV)); % leaves mask for resel_img below
  195. % Smooth RPV volume (note that NaNs are treated as zeros in spm_smooth)
  196. spm_smooth(RPV, RPV, fwhm_vox);
  197. % Smooth mask and decide how far to trust smoothing-based extrapolation
  198. spm_smooth(smask, smask, fwhm_vox);
  199. infer = smask > 1e-3; % require sum of voxel's in-mask weights > 1e-3
  200. % Normalise smoothed RPV by smoothed mask
  201. RPV( infer) = RPV(infer) ./ smask(infer);
  202. RPV(~infer) = NaN; % spm_list handles remaining (unlikely) in-mask NaNs
  203. % Get data at in-mask voxels; smoothed resel_img conforms with original
  204. resel_img = RPV(mask);
  205. end
  206. %-Write Resels Per Voxel image
  207. %--------------------------------------------------------------------------
  208. fprintf('%s%30s',repmat(sprintf('\b'),1,30),'...writing resels/voxel image');%-#
  209. for i = 1:VM.dim(3)
  210. d = NaN(VM.dim(1:2));
  211. I = find(Iz == i);
  212. if ~isempty(I)
  213. d(sub2ind(VM.dim(1:2), Ix(I), Iy(I))) = resel_img(I);
  214. end
  215. VRpv = spm_write_plane(VRpv, d, i);
  216. end
  217. %-(unbiased) RESEL estimator and Global equivalent FWHM
  218. % where we desire FWHM with components proportional to 1./mean(resel_xyz),
  219. % but scaled so prod(1./FWHM) agrees with (the unbiased) mean(resel_img).
  220. %--------------------------------------------------------------------------
  221. i = isnan(ssq) | ssq < sqrt(eps);
  222. resel_img = mean(resel_img(~i,:));
  223. resel_xyz = mean(resel_xyz(~i,:));
  224. RESEL = resel_img^(1/D)*(resel_xyz/prod(resel_xyz)^(1/D));
  225. FWHM = full(sparse(1,1:D,1./RESEL,1,3));
  226. FWHM(isnan(FWHM)) = 0;
  227. FWHM(~FWHM) = 1;
  228. %-resel counts for search volume (defined by mask)
  229. %--------------------------------------------------------------------------
  230. % R0 = spm_resels_vol(VM,[1 1 1])';
  231. % R = R0.*(resel.^([0:3]/3));
  232. % OR
  233. R = spm_resels_vol(VM,FWHM)';
  234. fprintf('%s%30s\n',repmat(sprintf('\b'),1,30),'...done'); %-#