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