CASToR  1.1
Tomographic Reconstruction (PET/SPECT)
 All Classes Files Functions Variables Typedefs Macros Groups Pages
vImageConvolver.cc
Go to the documentation of this file.
1 
2 /*
3  Implementation of class vImageConvolver
4 
5  - separators: done
6  - doxygen: done
7  - default initialization: done
8  - CASTOR_DEBUG:
9  - CASTOR_VERBOSE:
10 */
11 
18 #include "vImageConvolver.hh"
20 #include "oImageSpace.hh"
21 
22 // =====================================================================
23 // ---------------------------------------------------------------------
24 // ---------------------------------------------------------------------
25 // =====================================================================
26 
28 {
29  // Standards
31  m_verbose = -1;
32  // Booleans
33  m_checked = false;
34  m_initialized = false;
35  m_stationary = false;
36  // Padded image
37  mp_paddedImage = NULL;
38  m_offsetX = -1;
39  m_offsetY = -1;
40  m_offsetZ = -1;
41  m_dimPadX = -1;
42  m_dimPadY = -1;
43  m_dimPadZ = -1;
44  m_dimPadXY = -1;
45  m_dimPadXYZ = -1;
46  // Convolution kernel
47  m_nbKernels = -1;
48  mp_dimKernelX = NULL;
49  mp_dimKernelY = NULL;
50  mp_dimKernelZ = NULL;
51  m2p_kernel = NULL;
52 }
53 
54 // =====================================================================
55 // ---------------------------------------------------------------------
56 // ---------------------------------------------------------------------
57 // =====================================================================
58 
60 {
61  // Simply free what should be
62  if (mp_paddedImage) free(mp_paddedImage);
63  if (mp_dimKernelX) free(mp_dimKernelX);
64  if (mp_dimKernelY) free(mp_dimKernelY);
65  if (mp_dimKernelZ) free(mp_dimKernelZ);
66  if (m2p_kernel)
67  {
68  for (int k=0; k<m_nbKernels; k++) if (m2p_kernel[k]) free(m2p_kernel[k]);
69  free(m2p_kernel);
70  }
71 }
72 
73 // =====================================================================
74 // ---------------------------------------------------------------------
75 // ---------------------------------------------------------------------
76 // =====================================================================
77 
78 // Check global parameters then check the specific parameters
80 {
81  // Check verbose
82  if (m_verbose<0)
83  {
84  Cerr("***** vImageConvolver::CheckParameters() -> The verbose level must be set positive !" << endl);
85  return 1;
86  }
87  // Check oImageDimensionsAndQuantification
89  {
90  Cerr("***** vImageConvolver::CheckParameters() -> The image dimensions must be set through a oImageDimensionsAndQuantification !" << endl);
91  return 1;
92  }
93  // Finally check the specific parameters from the child convolver
95  {
96  Cerr("***** vImageConvolver::CheckParameters() -> A problem occured while checking specific parameters of the child convolver !" << endl);
97  return 1;
98  }
99  // All set
100  m_checked = true;
101  // Normal end
102  return 0;
103 }
104 
105 // =====================================================================
106 // ---------------------------------------------------------------------
107 // ---------------------------------------------------------------------
108 // =====================================================================
109 
110 // Check that the kernel has been successfully built, then allocate the padded image
112 {
113  // Check if parameters have been checked
114  if (!m_checked)
115  {
116  Cerr("***** vImageConvolver::Initialize() -> Parameters have not been checked ! Please call CheckParameters() before." << endl);
117  return 1;
118  }
119 
120  // Build the convolution kernel
122  {
123  Cerr("***** vImageConvolver::Initialize() -> A problem occured while building the convolution kernel !" << endl);
124  return 1;
125  }
126 
127  // ---------------------------------------------------------------------------------------
128  // Check that all kernel dimensions are odd and find the maximum size along each dimension
129  // ---------------------------------------------------------------------------------------
130 
131  // Check that the number of kernels is not null or negative
132  if (m_nbKernels<1)
133  {
134  Cerr("***** vImageConvolver::Initialize() -> The number of kernels is negative or null !" << endl);
135  return 1;
136  }
137  // Check that the kernel dimensions have been allocated
138  if (mp_dimKernelX==NULL || mp_dimKernelY==NULL || mp_dimKernelZ==NULL)
139  {
140  Cerr("***** vImageConvolver::Initialize() -> The kernel dimensions are not allocated !" << endl);
141  return 1;
142  }
143  // Check that the kernel has been allocated
144  if (m2p_kernel==NULL)
145  {
146  Cerr("***** vImageConvolver::Initialize() -> The kernel is not allocated !" << endl);
147  return 1;
148  }
149  // In case the kernel is stationary and the user forgot to explicitely specify it, we force it here
150  if (m_nbKernels==1) m_stationary = true;
151 
152  // Maximum kernel sizes along each dimension
153  INTNB max_kern_size_Z = 0;
154  INTNB max_kern_size_Y = 0;
155  INTNB max_kern_size_X = 0;
156 
157  // Loop over all kernels
158  for (int k=0; k<m_nbKernels; k++)
159  {
160  // Check oddity along Z
161  if (mp_dimKernelZ[k]%2==0)
162  {
163  Cerr("***** vImageConvolver::Initialize() -> Dimension of " << k << "th convolution kernel along Z (" << mp_dimKernelZ[k] << " is not odd !" << endl);
164  return 1;
165  }
166  // Check oddity along Y
167  if (mp_dimKernelY[k]%2==0)
168  {
169  Cerr("***** vImageConvolver::Initialize() -> Dimension of " << k << "th convolution kernel along Y (" << mp_dimKernelY[k] << " is not odd !" << endl);
170  return 1;
171  }
172  // Check oddity along X
173  if (mp_dimKernelX[k]%2==0)
174  {
175  Cerr("***** vImageConvolver::Initialize() -> Dimension of " << k << "th convolution kernel along X (" << mp_dimKernelX[k] << " is not odd !" << endl);
176  return 1;
177  }
178  // Check maximum along Z
179  if (mp_dimKernelZ[k]>max_kern_size_Z) max_kern_size_Z = mp_dimKernelZ[k];
180  // Check maximum along Y
181  if (mp_dimKernelY[k]>max_kern_size_Y) max_kern_size_Y = mp_dimKernelY[k];
182  // Check maximum along X
183  if (mp_dimKernelX[k]>max_kern_size_X) max_kern_size_X = mp_dimKernelX[k];
184  }
185 
186  // Set the offsets
187  m_offsetZ = max_kern_size_Z / 2;
188  m_offsetY = max_kern_size_Y / 2;
189  m_offsetX = max_kern_size_X / 2;
190 
191  // In the convolution we assume an intensity conservation along the Z axis, so we check if the offset is higher or equal to the image dimension.
192  // In this case, the convolution will still work (it is implemented so), but computation time will be lost for nothing, so we throw a warning.
194  {
195  FLTNB over_computation_factor = ((FLTNB)(max_kern_size_Z)) / ((FLTNB)(mp_ImageDimensionsAndQuantification->GetNbVoxZ()*2-1));
196  Cerr("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl);
197  Cerr("!!!!! vImageConvolver::Initialize() -> The maximum half size of the convolution kernels is higher or equal to the number of slices of the image." << endl);
198  Cerr("!!!!! This won't affect the results, but a convolution running time factor of " << over_computation_factor << " will be lost for nothing." << endl);
199  Cerr("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl);
200  }
201 
202  // ---------------------------------------------------------------------------------------
203  // Build the padded image
204  // ---------------------------------------------------------------------------------------
205 
206  // Compute the padded image dimensions
207  m_dimPadZ = mp_ImageDimensionsAndQuantification->GetNbVoxZ() + max_kern_size_Z - 1;
208  m_dimPadY = mp_ImageDimensionsAndQuantification->GetNbVoxY() + max_kern_size_Y - 1;
209  m_dimPadX = mp_ImageDimensionsAndQuantification->GetNbVoxX() + max_kern_size_X - 1;
212 
213  // Allocate the padded image and fill it with 0.
214  mp_paddedImage = (FLTNB*)calloc(m_dimPadXYZ,sizeof(FLTNB));
215 
216  // All set
217  m_initialized = true;
218  // Normal end
219  return 0;
220 }
221 
222 // =====================================================================
223 // ---------------------------------------------------------------------
224 // ---------------------------------------------------------------------
225 // =====================================================================
226 
228 {
229  #ifdef CASTOR_DEBUG
230  if (!m_initialized)
231  {
232  Cerr("***** vImageConvolver::ApplyConvolution() -> Called while not initialized !" << endl);
233  Exit(EXIT_DEBUG);
234  }
235  #endif
236 
237  // Copy image into padded buffer image
238  CopyToPaddedImage(ap_image);
239  // Then convolve
240  if (Convolve(ap_image))
241  {
242  Cerr("***** vImageConvolver::ApplyConvolution() -> A problem occured while actually convolving !" << endl);
243  return 1;
244  }
245  // Normal end
246  return 0;
247 }
248 
249 // =====================================================================
250 // ---------------------------------------------------------------------
251 // ---------------------------------------------------------------------
252 // =====================================================================
253 
255 {
256  #ifdef CASTOR_DEBUG
257  if (!m_initialized)
258  {
259  Cerr("***** vImageConvolver::ApplyConvolutionTranspose() -> Called while not initialized !" << endl);
260  Exit(EXIT_DEBUG);
261  }
262  #endif
263 
264  // Copy image into padded buffer image
265  CopyToPaddedImage(ap_image);
266  // Then convolve
267  if (ConvolveTranspose(ap_image))
268  {
269  Cerr("***** vImageConvolver::ApplyConvolutionTranspose() -> A problem occured while actually convolving !" << endl);
270  return 1;
271  }
272  // Normal end
273  return 0;
274 }
275 
276 // =====================================================================
277 // ---------------------------------------------------------------------
278 // ---------------------------------------------------------------------
279 // =====================================================================
280 
282 {
283  #ifdef CASTOR_DEBUG
284  if (!m_initialized)
285  {
286  Cerr("***** vImageConvolver::CopyToPaddedImage() -> Called while not initialized !" << endl);
287  Exit(EXIT_DEBUG);
288  }
289  #endif
290 
291  // Copy the image into the padded buffer, centered with respect to the pad
292  INTNB z;
293  #pragma omp parallel for private(z) schedule(guided)
295  {
296  // Pad coordinate Z
297  INTNB padz = z + m_offsetZ;
299  {
300  // Pad coordinate Y
301  INTNB pady = y + m_offsetY;
303  {
304  // Pad coordinate X
305  INTNB padx = x + m_offsetX;
306  // Global original coordinate
308  // Global pad coordinate
309  INTNB coord_pad = padz*m_dimPadXY + pady*m_dimPadX + padx;
310  // Affect buffer
311  mp_paddedImage[coord_pad] = ap_inputImage[coord_orig];
312  // Zero image
313  ap_inputImage[coord_orig] = 0.;
314  }
315  }
316  }
317 }
318 
319 // =====================================================================
320 // ---------------------------------------------------------------------
321 // ---------------------------------------------------------------------
322 // =====================================================================
323 
324 int vImageConvolver::Convolve(FLTNB* ap_outputImage)
325 {
326  #ifdef CASTOR_DEBUG
327  if (!m_initialized)
328  {
329  Cerr("***** vImageConvolver::Convolve() -> Called while not initialized !" << endl);
330  Exit(EXIT_DEBUG);
331  }
332  #endif
333 
334  // This function is designed to work universally and exclusively on a stationary kernel.
335  // Note that we suppose that a stationary kernel is also symmetric (but not necessarily isotropic).
336  // For asymetric and non-stationary kernels, have to overload this function in the child convolver.
337  // Note also that we choose the out-to-in implementation (outer loop on the output image) so that the
338  // parallelism is operated on the output and is thus thread-safe.
339  // Finally note that transaxially, we do not implement an image intensity preservation policy as we suppose that
340  // the distance between the object and the image border is taller than the kernel half-width (it should be so).
341  if (m_stationary)
342  {
343  INTNB stationary_kernel = 0;
344  // Convolve (OpenMP on Z dimension)
345  INTNB z;
346  #pragma omp parallel for private(z)
348  {
349  // As a first step, we virtually go through the kernel along Z to compute the actual kernel integral that will
350  // be really used in the convolution. For each virtual slice contributing to the convolved slice, we test if it
351  // is inside or outside the image; in the former case we count it into the integral and in the latter we don't.
352  FLTNB kernel_integral = 0.;
353  for (INTNB zk=0; zk<mp_dimKernelZ[stationary_kernel]; zk++)
354  {
355  // Index of the virtual slice which will (or not) contribute to the convolved slice
356  INTNB virtualZ = z + zk - m_offsetZ;
357  // Test if it is not in the image, then we move onto the next virtual slice
358  if (virtualZ<0 || virtualZ>=mp_ImageDimensionsAndQuantification->GetNbVoxZ()) continue;
359  // Otherwise, we update the kernel integral
360  INTNB kernZ_base = zk * mp_dimKernelX[stationary_kernel] * mp_dimKernelY[stationary_kernel];
361  for (INTNB xyk=0; xyk<mp_dimKernelX[stationary_kernel]*mp_dimKernelY[stationary_kernel]; xyk++)
362  kernel_integral += m2p_kernel[stationary_kernel][kernZ_base+xyk];
363  }
364  // Some precomputation for maximum efficiency
367  {
368  // Some precomputation for maximum efficiency
369  INTNB indexZY_base = indexZ_base + y*mp_ImageDimensionsAndQuantification->GetNbVoxX();
371  {
372  // Output image index
373  INTNB index_img = indexZY_base + x;
374  // Inner loops on convolution kernel dimensions
375  for (INTNB zk=0, index_kern=0; zk<mp_dimKernelZ[stationary_kernel]; zk++)
376  {
377  // Some precomputation for maximum efficiency
378  INTNB indexZ_pad = (z + zk) * m_dimPadXY;
379  for (INTNB yk=0; yk<mp_dimKernelY[stationary_kernel]; yk++)
380  {
381  // Some precomputation for maximum efficiency
382  INTNB indexZY_pad = indexZ_pad + (y + yk) * m_dimPadX;
383  for (INTNB xk=0; xk<mp_dimKernelX[stationary_kernel]; xk++, index_kern++)
384  {
385  // Padded image index
386  INTNB index_pad = indexZY_pad + x + xk;
387  // Apply contribution
388  ap_outputImage[index_img] += mp_paddedImage[index_pad] * m2p_kernel[stationary_kernel][index_kern] / kernel_integral;
389  }
390  }
391  }
392  }
393  }
394  }
395  // Normal end
396  return 0;
397  }
398  // Otherwise the convolve function has to be implemented by the child non-stationary convolver
399  else
400  {
401  Cerr("***** vImageConvolver::Convolve() -> To use a non-stationary kernel, you should overload this function to implement your own in your child convolver !" << endl);
402  return 1;
403  }
404 }
405 
406 // =====================================================================
407 // ---------------------------------------------------------------------
408 // ---------------------------------------------------------------------
409 // =====================================================================
410 
412 {
413  #ifdef CASTOR_DEBUG
414  if (!m_initialized)
415  {
416  Cerr("***** vImageConvolver::ConvolveTranspose() -> Called while not initialized !" << endl);
417  Exit(EXIT_DEBUG);
418  }
419  #endif
420 
421  // This function is designed to work universally on a stationary kernel.
422  // Note that we suppose a stationary kernel is also symmetric (otherwise it is absurd)
423  if (m_stationary)
424  {
425  return Convolve(ap_outputImage);
426  }
427  // Otherwise the convolve function has to be implemented by the child non-stationary convolver
428  else
429  {
430  Cerr("***** vImageConvolver::ConvolveTranspose() -> To use a non-stationary kernel, you should overload this function to implement your own in your child convolver !" << endl);
431  return 1;
432  }
433 }
434 
435 // =====================================================================
436 // ---------------------------------------------------------------------
437 // ---------------------------------------------------------------------
438 // =====================================================================
Declaration of class oImageDimensionsAndQuantification.
#define FLTNB
Definition: gVariables.hh:55
virtual int ConvolveTranspose(FLTNB *ap_outputImage)
A private function used to apply the transpose convolution on the padded image to the provided output...
int Initialize()
A public function used to initialize the module.
virtual int BuildConvolutionKernel()=0
A private function used to build the convolution kernel specific to the child convolver.
void Exit(int code)
oImageDimensionsAndQuantification * mp_ImageDimensionsAndQuantification
#define Cerr(MESSAGE)
int CheckParameters()
A public function used to check the parameters settings.
INTNB GetNbVoxXY()
Get the number of voxels in a slice.
int ApplyConvolutionTranspose(FLTNB *ap_image)
A public function used to apply the transpose convolution module on the provided image.
virtual ~vImageConvolver()
The destructor of vImageConvolver.
vImageConvolver()
The constructor of vImageConvolver.
#define INTNB
Definition: gVariables.hh:64
int ApplyConvolution(FLTNB *ap_image)
A public function used to apply the convolution module on the provided image.
Declaration of class oImageSpace.
virtual int CheckSpecificParameters()=0
A private function used to check the parameters settings specific to the child convolver.
#define EXIT_DEBUG
Definition: gVariables.hh:69
virtual int Convolve(FLTNB *ap_outputImage)
A private function used to apply the convolution on the padded image to the provided output image...
INTNB GetNbVoxX()
Get the number of voxels along the X axis.
INTNB GetNbVoxZ()
Get the number of voxels along the Z axis.
Declaration of class vImageConvolver.
void CopyToPaddedImage(FLTNB *ap_inputImage)
A private function used to copy the provided image into the padded buffer.
INTNB GetNbVoxY()
Get the number of voxels along the Y axis.