CASToR  3.2
Tomographic Reconstruction (PET/SPECT/CT)
src/image/iImageConvolverStationaryIsotropicGaussian.cc
Go to the documentation of this file.
1 
8 #include "vImageConvolver.hh"
9 #include "oImageDimensionsAndQuantification.hh"
10 #include "oImageSpace.hh"
11 #include "iImageConvolverStationaryIsotropicGaussian.hh"
12 
13 // =====================================================================
14 // ---------------------------------------------------------------------
15 // ---------------------------------------------------------------------
16 // =====================================================================
17 
19 {
20  m_nbSigmas = -1.;
21  m_FWHM = -1.;
22 }
23 
24 // =====================================================================
25 // ---------------------------------------------------------------------
26 // ---------------------------------------------------------------------
27 // =====================================================================
28 
30 {
31 }
32 
33 // =====================================================================
34 // ---------------------------------------------------------------------
35 // ---------------------------------------------------------------------
36 // =====================================================================
37 
39 {
40  cout << "This convolver is based on a classic stationary isotropic Gaussian kernel." << endl;
41  cout << "To speed up computation time, three consecutive 1D convolutions are performed." << endl;
42  cout << "The following options are used (in this particular order when provided as a list, separated by commas):" << endl;
43  cout << " FWHM: to set the isotropic FWHM (in mm)" << endl;
44  cout << " number of sigmas: to set the number of sigmas included in each dimension of the kernel (recommendations: at least 3. and maximum 5.)" << endl;
45 }
46 
47 // =====================================================================
48 // ---------------------------------------------------------------------
49 // ---------------------------------------------------------------------
50 // =====================================================================
51 
52 int iImageConvolverStationaryIsotropicGaussian::ReadConfigurationFile(const string& a_configurationFile)
53 {
54  string key_word = "";
55  // Read the FWHM option
56  key_word = "FWHM";
57  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_FWHM, 1, KEYWORD_MANDATORY))
58  {
59  Cerr("***** iImageConvolverStationaryIsotropicGaussian::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
60  return 1;
61  }
62  // Read the number of sigma option
63  key_word = "number of sigmas";
64  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_nbSigmas, 1, KEYWORD_MANDATORY))
65  {
66  Cerr("***** iImageConvolverStationaryIsotropicGaussian::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
67  return 1;
68  }
69  // Normal end
70  return 0;
71 }
72 
73 // =====================================================================
74 // ---------------------------------------------------------------------
75 // ---------------------------------------------------------------------
76 // =====================================================================
77 
79 {
80  // There are 2 floating point variables as options
81  FLTNB options[2];
82  // Read them
83  if (ReadStringOption(a_optionsList, options, 2, ",", "Stationary Isotropic Gaussian convolver configuration"))
84  {
85  Cerr("***** iImageConvolverStationaryIsotropicGaussian::ReadConfigurationFile() -> Failed to correctly read the list of options !" << endl);
86  return 1;
87  }
88  // Affect options
89  m_FWHM = options[0];
90  m_nbSigmas = options[1];
91  // Normal end
92  return 0;
93 }
94 
95 // =====================================================================
96 // ---------------------------------------------------------------------
97 // ---------------------------------------------------------------------
98 // =====================================================================
99 
101 {
102  // Check number of sigmas
103  if (m_nbSigmas<=0.)
104  {
105  Cerr("***** iImageConvolverStationaryIsotropicGaussian::CheckSpecificParameters() -> Number of sigmas included in the kernel must be strictly positive !" << endl);
106  return 1;
107  }
108  // Check the FWHM
109  if (m_FWHM<0.)
110  {
111  Cerr("***** iImageConvolverStationaryIsotropicGaussian::CheckSpecificParameters() -> FWHM is negative !" << endl);
112  return 1;
113  }
114  // Normal end
115  return 0;
116 }
117 
118 // =====================================================================
119 // ---------------------------------------------------------------------
120 // ---------------------------------------------------------------------
121 // =====================================================================
122 
124 {
125  // Verbose
126  if (m_verbose>=2)
127  {
128  Cout("iImageConvolverStationaryIsotropicGaussian::BuildConvolutionKernel() -> Compute convolution kernel" << endl);
129  Cout(" --> Isotropic FWHM: " << m_FWHM << " mm" << endl);
130  Cout(" --> Included sigmas: " << m_nbSigmas << endl);
131  }
132 
133  // --------------------------------
134  // Stationary kernel
135  // --------------------------------
136 
137  // Set the number of kernels to 3
138  m_nbKernels = 3;
139  // This convolver is stationary but we overload the Convolve() function (however, the ConvolveTranspose() function calls the Convolve() function when stationary)
140  m_stationary = true;
141  // Compute kernel half size based on the number of sigmas included in the kernel
142  FLTNB two_sqrt_two_ln_two = 2.*sqrt(2.*log(2.));
143  FLTNB half_kern_floatX = m_FWHM * m_nbSigmas / (two_sqrt_two_ln_two*mp_ImageDimensionsAndQuantification->GetVoxSizeX());
144  FLTNB half_kern_floatY = m_FWHM * m_nbSigmas / (two_sqrt_two_ln_two*mp_ImageDimensionsAndQuantification->GetVoxSizeY());
145  FLTNB half_kern_floatZ = m_FWHM * m_nbSigmas / (two_sqrt_two_ln_two*mp_ImageDimensionsAndQuantification->GetVoxSizeZ());
146  // For each half size, if the decimal part is below 0.5, then it will be taken into account into the central kernel's voxel,
147  // otherwise, we had a voxel to the half size
148  INTNB half_kern_dimX = ((INTNB)(half_kern_floatX));
149  if (half_kern_floatX-((FLTNB)half_kern_dimX)>0.5) half_kern_dimX++;
150  INTNB half_kern_dimY = ((INTNB)(half_kern_floatY));
151  if (half_kern_floatY-((FLTNB)half_kern_dimY)>0.5) half_kern_dimY++;
152  INTNB half_kern_dimZ = ((INTNB)(half_kern_floatZ));
153  if (half_kern_floatZ-((FLTNB)half_kern_dimZ)>0.5) half_kern_dimZ++;
154  // Allocate kernel dimensions
155  mp_dimKernelX = (INTNB*)malloc(m_nbKernels*sizeof(INTNB));
156  mp_dimKernelY = (INTNB*)malloc(m_nbKernels*sizeof(INTNB));
157  mp_dimKernelZ = (INTNB*)malloc(m_nbKernels*sizeof(INTNB));
158  // Compute kernel dimensions
159  for (INTNB k=0; k<m_nbKernels; k++)
160  {
161  mp_dimKernelX[k] = half_kern_dimX * 2 + 1;
162  mp_dimKernelY[k] = half_kern_dimY * 2 + 1;
163  mp_dimKernelZ[k] = half_kern_dimZ * 2 + 1;
164  }
165  // Verbose
166  if (m_verbose>=2) Cout (" --> Kernel dimensions: [" << mp_dimKernelX[0] << ";" << mp_dimKernelY[0] << ";" << mp_dimKernelZ[0] << "]" << endl);
167  // Allocate kernel
168  m2p_kernel = (FLTNB**)malloc(m_nbKernels*sizeof(FLTNB*));
169  m2p_kernel[KERNEL_1D_X] = (FLTNB*)malloc(mp_dimKernelX[0]*sizeof(FLTNB));
170  m2p_kernel[KERNEL_1D_Y] = (FLTNB*)malloc(mp_dimKernelY[0]*sizeof(FLTNB));
171  m2p_kernel[KERNEL_1D_Z] = (FLTNB*)malloc(mp_dimKernelZ[0]*sizeof(FLTNB));
172 
173  // --------------------------------
174  // Compute the kernel
175  // --------------------------------
176 
177  // Variables
178  FLTNB sigma = m_FWHM / two_sqrt_two_ln_two;
179  FLTNB mu_x = (FLTNB)(half_kern_dimX);
180  FLTNB mu_y = (FLTNB)(half_kern_dimY);
181  FLTNB mu_z = (FLTNB)(half_kern_dimZ);
182  // Compute kernel by integration of the Gaussian distribution (this method is not exactly exact... but is closer to it compared to single Gaussian points)
183  FLTNB sum_kernel_X = 0.;
184  for (int x=0; x<mp_dimKernelX[0]; x++)
185  {
186  m2p_kernel[KERNEL_1D_X][x] = 0.5 * ( erf( ( (((FLTNB)x)-mu_x+0.5)*mp_ImageDimensionsAndQuantification->GetVoxSizeX()) / (sqrt(2.)*sigma) )
187  - erf( ( (((FLTNB)x)-mu_x-0.5)*mp_ImageDimensionsAndQuantification->GetVoxSizeX()) / (sqrt(2.)*sigma) ) );
188  sum_kernel_X += m2p_kernel[KERNEL_1D_X][x];
189  }
190  FLTNB sum_kernel_Y = 0.;
191  for (int y=0; y<mp_dimKernelY[0]; y++)
192  {
193  m2p_kernel[KERNEL_1D_Y][y] = 0.5 * ( erf( ( (((FLTNB)y)-mu_y+0.5)*mp_ImageDimensionsAndQuantification->GetVoxSizeY()) / (sqrt(2.)*sigma) )
194  - erf( ( (((FLTNB)y)-mu_y-0.5)*mp_ImageDimensionsAndQuantification->GetVoxSizeY()) / (sqrt(2.)*sigma) ) );
195  sum_kernel_Y += m2p_kernel[KERNEL_1D_Y][y];
196  }
197  FLTNB sum_kernel_Z = 0.;
198  for (int z=0; z<mp_dimKernelZ[0]; z++)
199  {
200  m2p_kernel[KERNEL_1D_Z][z] = 0.5 * ( erf( ( (((FLTNB)z)-mu_z+0.5)*mp_ImageDimensionsAndQuantification->GetVoxSizeZ()) / (sqrt(2.)*sigma) )
201  - erf( ( (((FLTNB)z)-mu_z-0.5)*mp_ImageDimensionsAndQuantification->GetVoxSizeZ()) / (sqrt(2.)*sigma) ) );
202  sum_kernel_Z += m2p_kernel[KERNEL_1D_Z][z];
203  }
204  // Normalize kernels
205  for (int x=0; x<mp_dimKernelX[0]; x++) m2p_kernel[KERNEL_1D_X][x] /= sum_kernel_X;
206  for (int y=0; y<mp_dimKernelY[0]; y++) m2p_kernel[KERNEL_1D_Y][y] /= sum_kernel_Y;
207  for (int z=0; z<mp_dimKernelZ[0]; z++) m2p_kernel[KERNEL_1D_Z][z] /= sum_kernel_Z;
208  // Normal end
209  return 0;
210 }
211 
212 // =====================================================================
213 // ---------------------------------------------------------------------
214 // ---------------------------------------------------------------------
215 // =====================================================================
216 
218 {
219  #ifdef CASTOR_DEBUG
220  if (!m_initialized)
221  {
222  Cerr("***** iImageConvolverStationaryIsotropicGaussian::Convolve() -> Called while not initialized !" << endl);
223  Exit(EXIT_DEBUG);
224  }
225  #endif
226 
227  INTNB stationary_kernel = 0;
228 
229  // Convolve along Z
230  INTNB z;
231  #pragma omp parallel for private(z) schedule(guided)
233  {
234  // As a first step, we virtually go through the kernel along Z to compute the actual kernel integral that will
235  // be really used in the convolution. For each virtual slice contributing to the convolved slice, we test if it
236  // is inside or outside the image; in the former case we count it into the integral and in the latter we don't.
237  FLTNB kernel_integral = 0.;
238  for (INTNB zk=0; zk<mp_dimKernelZ[stationary_kernel]; zk++)
239  {
240  // Index of the virtual slice which will (or not) contribute to the convolved slice
241  INTNB virtualZ = z + zk - m_offsetZ;
242  // Test if it is not in the image, then we move onto the next virtual slice
243  if (virtualZ<0 || virtualZ>=mp_ImageDimensionsAndQuantification->GetNbVoxZ()) continue;
244  // Otherwise, we update the kernel integral
245  kernel_integral += m2p_kernel[KERNEL_1D_Z][zk];
246  }
247  // Some precomputation for maximum efficiency
249  INTNB indexZ_pad_base = z*m_dimPadXY;
251  {
252  // Some precomputation for maximum efficiency
253  INTNB indexY_img_base = y*mp_ImageDimensionsAndQuantification->GetNbVoxX();
254  INTNB indexY_pad_base = (y+mp_dimKernelY[stationary_kernel]/2)*m_dimPadX;
256  {
257  // Output image index
258  INTNB index_img = indexZ_img_base + indexY_img_base + x;
259  // Base of padded index
260  INTNB index_pad_base = indexZ_pad_base + indexY_pad_base + x + mp_dimKernelX[stationary_kernel]/2;
261  // Inner loop on convolution kernel dimension
262  for (INTNB zk=0; zk<mp_dimKernelZ[stationary_kernel]; zk++)
263  {
264  // Padded image index
265  INTNB index_pad = index_pad_base + zk * m_dimPadXY;
266  // Apply contribution
267  ap_outputImage[index_img] += mp_paddedImage[index_pad] * m2p_kernel[KERNEL_1D_Z][zk] / kernel_integral;
268  }
269  }
270  }
271  }
272  // Copy back to padded image
273  CopyToPaddedImage(ap_outputImage);
274  // Convolve along Y
275  #pragma omp parallel for private(z) schedule(guided)
277  {
278  // Some precomputation for maximum efficiency
280  INTNB indexZ_pad_base = (z+m_offsetZ)*m_dimPadXY;
282  {
283  // Some precomputation for maximum efficiency
284  INTNB indexY_img_base = y*mp_ImageDimensionsAndQuantification->GetNbVoxX();
285  INTNB indexY_pad_base = y*m_dimPadX;
287  {
288  // Output image index
289  INTNB index_img = indexZ_img_base + indexY_img_base + x;
290  // Base of padded index
291  INTNB index_pad_base = indexZ_pad_base + indexY_pad_base + x + m_offsetX;
292  // Inner loop on convolution kernel dimension
293  for (INTNB yk=0; yk<mp_dimKernelY[stationary_kernel]; yk++)
294  {
295  // Padded image index
296  INTNB index_pad = index_pad_base + yk * m_dimPadX;
297  // Apply contribution
298  ap_outputImage[index_img] += mp_paddedImage[index_pad] * m2p_kernel[KERNEL_1D_Y][yk];
299  }
300  }
301  }
302  }
303  // Copy back to padded image
304  CopyToPaddedImage(ap_outputImage);
305  // Convolve along X
306  #pragma omp parallel for private(z) schedule(guided)
308  {
309  // Some precomputation for maximum efficiency
311  INTNB indexZ_pad_base = (z+mp_dimKernelZ[stationary_kernel]/2)*m_dimPadXY;
313  {
314  // Some precomputation for maximum efficiency
315  INTNB indexY_img_base = y*mp_ImageDimensionsAndQuantification->GetNbVoxX();
316  INTNB indexY_pad_base = (y+mp_dimKernelY[stationary_kernel]/2)*m_dimPadX;
318  {
319  // Output image index
320  INTNB index_img = indexZ_img_base + indexY_img_base + x;
321  // Base of padded index
322  INTNB index_pad_base = indexZ_pad_base + indexY_pad_base + x;
323  // Inner loop on convolution kernel dimension
324  for (INTNB xk=0; xk<mp_dimKernelX[stationary_kernel]; xk++)
325  {
326  // Padded image index
327  INTNB index_pad = index_pad_base + xk;
328  // Apply contribution
329  ap_outputImage[index_img] += mp_paddedImage[index_pad] * m2p_kernel[KERNEL_1D_X][xk];
330  }
331  }
332  }
333  }
334  // Normal end
335  return 0;
336 }
int ReadStringOption(const string &a_input, T *ap_return, int a_nbElts, const string &sep, const string &a_option)
Parse the &#39;a_input&#39; string corresponding to the &#39;a_option&#39; into &#39;a_nbElts&#39; elements, using the &#39;sep&#39; separator. The results are returned in the templated &#39;ap_return&#39; dynamic templated array. Call "ConvertFromString()" to perform the correct conversion depending on the type of the data to convert.
FLTNB GetVoxSizeX()
Get the voxel&#39;s size along the X axis, in mm.
#define Cerr(MESSAGE)
FLTNB GetVoxSizeZ()
Get the voxel&#39;s size along the Z axis, in mm.
void Exit(int code)
int BuildConvolutionKernel()
A private function used to build the convolution kernel specific to the child convolver.
oImageDimensionsAndQuantification * mp_ImageDimensionsAndQuantification
~iImageConvolverStationaryIsotropicGaussian()
The destructor of iImageConvolverStationaryIsotropicGaussian.
FLTNB GetVoxSizeY()
Get the voxel&#39;s size along the Y axis, in mm.
iImageConvolverStationaryIsotropicGaussian()
The constructor of iImageConvolverStationaryIsotropicGaussian.
#define KEYWORD_MANDATORY
void ShowHelp()
A function used to show help about the child module.
int CheckSpecificParameters()
A private function used to check the parameters settings specific to the child module.
INTNB GetNbVoxX()
Get the number of voxels along the X axis.
INTNB GetNbVoxZ()
Get the number of voxels along the Z axis.
void CopyToPaddedImage(FLTNB *ap_inputImage)
int ReadDataASCIIFile(const string &a_file, const string &a_keyword, T *ap_return, int a_nbElts, bool a_mandatoryFlag)
Look for "a_nbElts" elts in the "a_file" file matching the "a_keyword" string passed as parameter a...
#define Cout(MESSAGE)
This abstract class is the generic image convolver class used by the oImageConvolverManager.
INTNB GetNbVoxY()
Get the number of voxels along the Y axis.