123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- function [FWHM,VRpv,R] = spm_est_smoothness(V,VM,ndf)
- % Estimation of smoothness based on [residual] images
- % FORMAT [FWHM,VRpv,R] = spm_est_smoothness(V,VM,[ndf])
- %
- % V - Filenames or mapped standardized residual images
- % VM - Filename of mapped mask image
- % ndf - A 2-vector, [n df], the original n & dof of the linear model
- %
- % FWHM - estimated FWHM in all image directions
- % VRpv - handle of Resels per Voxel image
- % R - vector of resel counts
- %__________________________________________________________________________
- %
- % spm_est_smoothness returns a spatial smoothness estimator based on the
- % variances of the normalized spatial derivatives as described in K.
- % Worsley, (1996). Inputs are a mask image and a number of standardized
- % residual images, or any set of mean zero, unit variance images. Output
- % is a global estimate of the smoothness expressed as the FWHM of an
- % equivalent Gaussian point spread function. An estimate of resels per
- % voxels (see spm_spm) is written as an image file ('RPV.<ext>') to the
- % current directory.
- %
- % To improve the accuracy of the smoothness estimation the error degrees
- % of freedom can be supplied. Since it is not assumed that all residual
- % images are passed to this function, the full, original sample size n
- % must be supplied as well.
- %
- % The mask image specifies voxels, used in smoothness estimation, by
- % assigning them non-zero values. The dimensions, voxel sizes, orientation
- % of all images must be the same. The dimensions of the images can be of
- % dimensions 0, 1, 2 and 3.
- %
- % Note that 1-dim images (lines) must exist in the 1st dimension and
- % 2-dim images (slices) in the first two dimensions. The estimated fwhm
- % for any non-existing dimension is infinity.
- %__________________________________________________________________________
- %
- % Refs:
- %
- % K.J. Worsley (1996). An unbiased estimator for the roughness of a
- % multivariate Gaussian random field. Technical Report, Department of
- % Mathematics and Statistics, McGill University
- %
- % S.J. Kiebel, J.B. Poline, K.J. Friston, A.P. Holmes, and K.J. Worsley.
- % Robust Smoothness Estimation in Statistical Parametric Maps Using
- % Standardized Residuals from the General Linear Model. NeuroImage,
- % 10:756-766, 1999.
- %
- % S. Hayasaka, K. Phan, I. Liberzon, K.J. Worsley, T.E. Nichols (2004).
- % Nonstationary cluster-size inference with random field and permutation
- % methods. NeuroImage, 22:676-687, 2004.
- %__________________________________________________________________________
- % Copyright (C) 2002-2015 Wellcome Trust Centre for Neuroimaging
- % Stefan Kiebel, Tom Nichols
- % $Id: spm_est_smoothness.m 6894 2016-09-30 16:48:46Z spm $
- %-Assign input arguments
- %--------------------------------------------------------------------------
- if nargin < 1
- V = spm_select(Inf,'image','Select residual images',{},pwd,'^ResI.*.{3}$');
- end
- if nargin < 2
- VM = spm_select(1,'image','Select mask image',{},pwd,'^mask\..{3}$');
- end
- if nargin < 3, ndf = [NaN NaN]; end
- if numel(ndf) ~= 2
- error('ndf argument must be of length 2 ([n df]).')
- end
- %-Initialise
- %--------------------------------------------------------------------------
- V = spm_vol(V);
- VM = spm_vol(VM);
- if any(isnan(ndf))
- ndf = [numel(V) numel(V)]; % Assume full df
- end
- n_full = ndf(1);
- edf = ndf(2);
- %-Initialise RESELS per voxel image
- %--------------------------------------------------------------------------
- VRpv = struct( 'fname',['RPV' spm_file_ext],...
- 'dim', VM.dim(1:3),...
- 'dt', [spm_type('float64') spm_platform('bigend')],...
- 'mat', VM.mat,...
- 'pinfo', [1 0 0]',...
- 'descrip', 'spm_spm: resels per voxel');
- VRpv = spm_create_vol(VRpv);
- %-Dimensionality of image
- %--------------------------------------------------------------------------
- D = 3 - sum(VM.dim(1:3) == 1);
- if D == 0
- FWHM = [Inf Inf Inf];
- R = [0 0 0];
- return;
- end
- %-Find voxels within mask
- %--------------------------------------------------------------------------
- d = spm_read_vols(VM);
- [Ix,Iy,Iz] = ndgrid(1:VM.dim(1),1:VM.dim(2),1:VM.dim(3));
- Ix = Ix(d~=0); Iy = Iy(d~=0); Iz = Iz(d~=0);
- %-Compute covariance of derivatives
- %--------------------------------------------------------------------------
- str = 'Spatial non-sphericity (over scans)';
- fprintf('%-40s: %30s',str,'...estimating derivatives'); %-#
- spm_progress_bar('Init',100,'smoothness estimation','');
- L = zeros(size(Ix,1),D,D);
- ssq = zeros(size(Ix,1),1);
- for i = 1:numel(V)
-
- [d,dx,dy,dz] = spm_sample_vol(V(i),Ix,Iy,Iz,1);
-
- % sum of squares
- %----------------------------------------------------------------------
- ssq = ssq + d.^2;
-
- % covariance of finite differences
- %----------------------------------------------------------------------
- if D >= 1
- L(:,1,1) = L(:,1,1) + dx.*dx;
- end
- if D >= 2
- L(:,1,2) = L(:,1,2) + dx.*dy;
- L(:,2,2) = L(:,2,2) + dy.*dy;
- end
- if D >= 3
- L(:,1,3) = L(:,1,3) + dx.*dz;
- L(:,2,3) = L(:,2,3) + dy.*dz;
- L(:,3,3) = L(:,3,3) + dz.*dz;
- end
-
- spm_progress_bar('Set',100*i/numel(V));
-
- end
- spm_progress_bar('Clear')
- %-Scale sum into an average (and account for DF)
- % The standard result uses normalised residuals e/sqrt(RSS) and
- %
- % \hat\Lambda = grad(e/sqrt(RSS))' * grad(e/sqrt(RSS))
- %
- % In terms of standardized residuals e/sqrt(RMS) this is
- %
- % \hat\Lambda = (1/DF) * grad(e/sqrt(RMS))' * grad(e/sqrt(RMS))
- %
- % but both of these expressions assume that the RSS or RMS correspond to
- % the full set of residuals considered. However, spm_spm only considers
- % upto MAXRES residual images. To adapt, re-write the above as a scaled
- % average over n scans
- %
- % \hat\Lambda = (n/DF) * ( (1/n) * grad(e/sqrt(RMS))' * grad(e/sqrt(RMS)) )
- %
- % I.e. the roughness estimate \hat\Lambda is an average of outer products
- % of standardized residuals (where the average is over scans), scaled by
- % n/DF. Hence, we can use only a subset of scans simply by replacing this
- % last average term with an average over the subset.
- %
- % See Hayasaka et al, p. 678, for more on estimating roughness with
- % standardized residuals (e/sqrt(RMS)) instead of normalised residuals
- % (e/sqrt(RSS)). Note that the names arise from the fact that
- % sqrt(RSS) = sqrt(r'*r) is norm(r), while sqrt(RMS) = sqrt(r'*r/edf)
- % is the unbiased (ReML) estimate of the standard deviation.
- %--------------------------------------------------------------------------
- L = L/numel(V); % Average
- L = L*(n_full/edf); % Scale
- %-Evaluate determinant (and xyz components for FWHM)
- %--------------------------------------------------------------------------
- if D == 1
- resel_xyz = L;
- resel_img = L;
- end
- if D == 2
- resel_xyz = [L(:,1,1) L(:,2,2)];
- resel_img = L(:,1,1).*L(:,2,2) - ...
- L(:,1,2).*L(:,1,2);
- end
- if D == 3
- resel_xyz = [L(:,1,1) L(:,2,2) L(:,3,3)];
- resel_img = L(:,1,1).*L(:,2,2).*L(:,3,3) + ...
- L(:,1,2).*L(:,2,3).*L(:,1,3)*2 - ...
- L(:,1,1).*L(:,2,3).*L(:,2,3) - ...
- L(:,1,2).*L(:,1,2).*L(:,3,3) - ...
- L(:,1,3).*L(:,2,2).*L(:,1,3);
- end
- resel_img(resel_img<0) = 0;
- % Convert det(Lambda) and diag(Lambda) to units of resels
- resel_img = sqrt(resel_img/(4*log(2))^D);
- resel_xyz = sqrt(resel_xyz/(4*log(2)));
- %-Optional mask-weighted smoothing of RPV image
- %--------------------------------------------------------------------------
- if spm_get_defaults('stats.rft.nonstat')
- fwhm_vox = 3;
- else
- fwhm_vox = 0;
- end
- if any(fwhm_vox)
- if length(fwhm_vox) == 1, fwhm_vox = fwhm_vox([1 1 1]); end
-
- % Convert resel_img at in-mask voxels to resel volume
- mask = spm_read_vols(VM) > 0;
- RPV = zeros(size(mask));
- RPV(mask) = resel_img;
-
- % Remove invalid mask voxels, i.e. edge voxels with missing derivatives
- smask = double(mask & isfinite(RPV)); % leaves mask for resel_img below
-
- % Smooth RPV volume (note that NaNs are treated as zeros in spm_smooth)
- spm_smooth(RPV, RPV, fwhm_vox);
-
- % Smooth mask and decide how far to trust smoothing-based extrapolation
- spm_smooth(smask, smask, fwhm_vox);
- infer = smask > 1e-3; % require sum of voxel's in-mask weights > 1e-3
-
- % Normalise smoothed RPV by smoothed mask
- RPV( infer) = RPV(infer) ./ smask(infer);
- RPV(~infer) = NaN; % spm_list handles remaining (unlikely) in-mask NaNs
-
- % Get data at in-mask voxels; smoothed resel_img conforms with original
- resel_img = RPV(mask);
- end
- %-Write Resels Per Voxel image
- %--------------------------------------------------------------------------
- fprintf('%s%30s',repmat(sprintf('\b'),1,30),'...writing resels/voxel image');%-#
- for i = 1:VM.dim(3)
- d = NaN(VM.dim(1:2));
- I = find(Iz == i);
- if ~isempty(I)
- d(sub2ind(VM.dim(1:2), Ix(I), Iy(I))) = resel_img(I);
- end
- VRpv = spm_write_plane(VRpv, d, i);
- end
- %-(unbiased) RESEL estimator and Global equivalent FWHM
- % where we desire FWHM with components proportional to 1./mean(resel_xyz),
- % but scaled so prod(1./FWHM) agrees with (the unbiased) mean(resel_img).
- %--------------------------------------------------------------------------
- i = isnan(ssq) | ssq < sqrt(eps);
- resel_img = mean(resel_img(~i,:));
- resel_xyz = mean(resel_xyz(~i,:));
- RESEL = resel_img^(1/D)*(resel_xyz/prod(resel_xyz)^(1/D));
- FWHM = full(sparse(1,1:D,1./RESEL,1,3));
- FWHM(isnan(FWHM)) = 0;
- FWHM(~FWHM) = 1;
- %-resel counts for search volume (defined by mask)
- %--------------------------------------------------------------------------
- % R0 = spm_resels_vol(VM,[1 1 1])';
- % R = R0.*(resel.^([0:3]/3));
- % OR
- R = spm_resels_vol(VM,FWHM)';
- fprintf('%s%30s\n',repmat(sprintf('\b'),1,30),'...done'); %-#
|