Print

Print


PS:
I just had closer look at the code John kindly provided and compared one 
dimension of krn calculated with the result from spm_smoothkern.
Is it correct to assume, that their differences are due to the 
continuity issue John mentioned?

Additionally, I compared a convolution of spm_smoothkern(4, -39:39,1) 
with  d1 = squeeze(volume2smooth(40, 48, : )) ;
This nearly perfectly replicates the result from the SPM-gui-smoothed 
volume.

Regards,

Peter

Am 05.02.2020 um 11:13 schrieb Peter Sperrer:
>
> Dear Guillaume, Dear John,
>
> thank you for your kind answers.
>
> I have to admit, until now my mind somehow clung to the idea of kernel 
> and voxel values being not defined anywhere outside of the volume, but 
> of course this is not an imperative and can be done differently.
> Especially  the explanation considering the choice of boundary 
> conditions was very helpful.
>
>   * Considering the observed difference between Matlab's convolution
>     and SPM's convolution:
>     The test volume was defined as
>
>     volume2smooth = zeros(79,95,79) ; volume2smooth(40,48,40) = 1;
>
>     and saved with voxel dimensions of 2*2*2 mm as 'testImageDot.nii'.
>     This volume was smoothed in SPM's gui using an isotropic FWHM=8mm
>     kernel.
>     Then, I tried replicating the smoothing process. Since the voxel
>     of interest is far away from the borders, I assumed no significant
>     boundary effects should be observed.
>
>     As I use voxels for distances below, not mm, I divided the FWHM by 2:
>
>     FWHM = 8;
>     FWHM = FWHM/2;
>
>     s = FWHM/(sqrt(8*log(2)));
>     s6 = round(6*s);
>
>     w1  =  0.5*sqrt(2/s);
>     w2  = -0.5/s;
>     w3  = sqrt(s/2/pi);
>
>     filePath = '/XXX/testImageDot.nii';
>     volume2smooth = niftiread(filePath);
>
>     filePathSmoothed = '/XXX/stestImageDot.nii';
>     smoothedVolumeSPM = niftiread(filePathSmoothed);
>     smoothedVolumeSPM = 100*smoothedVolumeSPM/max(smoothedVolumeSPM,
>     [], 'all');
>
>     % dimensions of the volume
>     xi = 79;
>     xii = 95;
>     xiii = 79;
>
>     x = double(bwdist(logical(volume2smooth), 'euclidean'));
>
>     % restrict kernel size to 6*s:
>     xx = x(ceil(xi/2)-s6:ceil(xi/2)+s6, ceil(xii/2)-s6:ceil(xii/2)+s6,
>     ceil(xiii/2)-s6:ceil(xiii/2)+s6);
>
>     krn = 0.5*(erf(w1*(xx+1)).*(xx+1) + erf(w1*(xx-1)).*(xx-1) -
>     2*erf(w1*xx).* xx)...
>         + w3*(exp(w2*(xx+1).^2) + exp(w2*(xx-1).^2) - 2*exp(w2*xx.^2));
>     krn(krn<0) = 0;
>
>     g = 1/(sqrt(2*pi)*s)^3*exp(-x.^2/(2*s^2));
>
>     smoothedVolumeKrn = convn(volume2smooth, krn, 'same');
>     smoothedVolumeKrn = 100*smoothedVolumeKrn/max(smoothedVolumeKrn,
>     [], 'all');
>
>     smoothedVolumeG = convn(volume2smooth, g, 'same');
>     smoothedVolumeG = 100*smoothedVolumeG/max(smoothedVolumeG, [], 'all');
>
>     figure
>     hold on
>     plot(squeeze(smoothedVolumeG(40, 48, :)));
>     plot(squeeze(smoothedVolumeKrn(40, 48, :)));
>     plot(squeeze(smoothedVolumeSPM(40, 48, :)));
>     legend('Gaussian', 'Erf', 'SPM');
>     hold off
>
>     When comparing smoothedVolumeKrn and smoothedVolumeSPM I would
>     expect them to be (nearly) identical, but smoothedVolumeSPM is
>     much closer to SmoothedVolumeG.
>     Visual comparison was performed by plotting a histogram of the
>     normalized intensities (so all the maxima are the same) along the
>     y-coordinate with x=40 and z=48.
>     Probably the difference is caused by me missing additional
>     normalization of the kernel?
>
>   * Considering smoothing along different dimensions:
>     Yes, you are right, when smoothing a volume  with just ones, the
>     visible difference along z compared to x or y is (mostly?)
>     attributed to the boundary conditions.
>     Still, when smoothing a single non-zero voxel in the center of a
>     large volume, I would expect the voxels along x, y and z to be
>     identical (as they are for Matlab's convolution). To test this I used
>
>     a = squeeze(smoothedVolumeSPM(40, 48, 40:60));
>     b = squeeze(smoothedVolumeSPM(40, 48:68, 40))';
>
>     c = a - b;
>
>     Admittedly, max(c) is in the order of 10^-14 and thus is probably
>     unlikely to significantly influence the results; But since the
>     volume used double precision I think this difference should not be
>     be attributed to numerical errors?
>
> Again, thank you very much, I appreciate your help.
>
> Kind regards,
>
> Peter
>
>
>
>
> Am 04.02.2020 um 16:37 schrieb Flandin, Guillaume:
>> Dear Peter,
>>
>> Your description is accurate and you are looking at the right functions
>> (spm_smooth.m, spm_smoothkern.m, spm_conv_vol.m/c).
>> The (separable) convolution kernel is a Gaussian convolved with a 1st
>> degree B-spline (triangular) basis function, thresholded at 6 sigmas and
>> normalised to sum to 1.
>> For the convolution itself, the boundary conditions correspond to use
>> zero-padding in-plane (x and y) and truncation out-of-plane (z). See e.g.:
>>    https://en.wikipedia.org/wiki/Kernel_(image_processing)#Convolution
>>    https://en.wikipedia.org/wiki/Gaussian_blur#Implementation
>> Concerning your evaluation, it would be useful if you shared the code
>> you used. Could it be that you are using an image not large enough
>> compared to the size of the kernel and you observe the effect of
>> boundary conditions?
>>
>> Best regards,
>> Guillaume.
>>
>>
>> On 30/01/2020 12:05, Peter Sperrer wrote:
>>> Dear SPM-Experts,
>>>
>>> I am currently trying to understand what exactly SPM(12) does from a
>>> mathematical point of view during smoothing (for default values).
>>> Unfortunately, neither code nor documentation answered all my questions
>>> and I could not find related questions in the mailing list.
>>>
>>> Could you please explain to me, how smoothing is performed or refer me
>>> to a paper or manual with such an explanation?
>>> In detail, the question is how the kernel function is exactly defined
>>> and how the convolution is applied.
>>>
>>> Doing some tests (FWHM = 8 mm, voxel size = 2x2x2 mm) and digging into
>>> SPM's code and documentation I came to the following insights so far:
>>>
>>>    * SPM seems to use something similar (but not identical) to a Gaussian
>>>      kernel:
>>>
>>>      spm_smooth.m calls spm_smoothkern.m
>>>      This returns (as default) the following kernel krn, which according
>>>      to the comments resembles a Gaussian convolved with a 1st degree
>>>      B-spline:
>>>
>>>      w1  =  0.5*sqrt(2/s);
>>>      w2  = -0.5/s;
>>>      w3  = sqrt(s/2/pi);
>>>      krn = 0.5*(erf(w1*(x+1)).*(x+1) + erf(w1*(x-1)).*(x-1) -
>>>      2*erf(w1*x   ).* x)+w3*(exp(w2*(x+1).^2)     + exp(w2*(x-1).^2)
>>>      - 2*exp(w2*x.^2));
>>>      krn(krn<0) = 0;
>>>
>>>      with s being the standard deviation of a Gaussian with the given
>>>      FWHM and x being in the rounded range of [-6 s, 6s].
>>>
>>>      Then, spm_conv_vol.c is called, which according to spm_cov_vol.m
>>>      should do some sort of convolution.
>>>      Unfortunately, my knowledge of C is rather limited, so I am having
>>>      trouble understanding what exactly is going on there.
>>>
>>>    * Even with isotropic FWHM values, smoothing in z is not the same than
>>>      for x and y.
>>>
>>>      This can best be seen when smoothing a volume of constant voxel
>>>      values, but is also mentioned  in the comments of spm_conv_vol.m:
>>>      "The convolution assumes zero padding in x and y with truncated
>>>      smoothing in z."
>>>      I assume, this also hints the use of something different than a
>>>      simple 3D convolution?
>>>
>>>    * For a single, non-zero voxel in the center of a volume, the
>>>      (normalized) results do not match a convolution of the image with
>>>      the above function (krn).
>>>
>>> It would be great, if any of you could help me understand what exactly
>>> happens during the smoothing procedure.
>>>
>>> Thank you very much for your time and your expertise!
>>>
>>> Kind regards,
>>>
>>> Peter Sperrer
>>>
>>>