CASToR  3.0
Tomographic Reconstruction (PET/SPECT/CT)
vImageConvolver.cc
Go to the documentation of this file.
1 /*
2 This file is part of CASToR.
3 
4  CASToR is free software: you can redistribute it and/or modify it under the
5  terms of the GNU General Public License as published by the Free Software
6  Foundation, either version 3 of the License, or (at your option) any later
7  version.
8 
9  CASToR is distributed in the hope that it will be useful, but WITHOUT ANY
10  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
11  FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
12  details.
13 
14  You should have received a copy of the GNU General Public License along with
15  CASToR (in file GNU_GPL.TXT). If not, see <http://www.gnu.org/licenses/>.
16 
17 Copyright 2017-2019 all CASToR contributors listed below:
18 
19  --> Didier BENOIT, Claude COMTAT, Marina FILIPOVIC, Thibaut MERLIN, Mael MILLARDET, Simon STUTE, Valentin VIELZEUF
20 
21 This is CASToR version 3.0.
22 */
23 
30 #include "vImageConvolver.hh"
32 #include "oImageSpace.hh"
33 
34 // =====================================================================
35 // ---------------------------------------------------------------------
36 // ---------------------------------------------------------------------
37 // =====================================================================
38 
40 {
41  // Standards
43  m_verbose = -1;
44  // Booleans
45  m_checked = false;
46  m_initialized = false;
47  m_stationary = false;
48  // Padded image
49  mp_paddedImage = NULL;
50  m_offsetX = -1;
51  m_offsetY = -1;
52  m_offsetZ = -1;
53  m_dimPadX = -1;
54  m_dimPadY = -1;
55  m_dimPadZ = -1;
56  m_dimPadXY = -1;
57  m_dimPadXYZ = -1;
58  // Convolution kernel
59  m_nbKernels = -1;
60  mp_dimKernelX = NULL;
61  mp_dimKernelY = NULL;
62  mp_dimKernelZ = NULL;
63  m2p_kernel = NULL;
64 }
65 
66 // =====================================================================
67 // ---------------------------------------------------------------------
68 // ---------------------------------------------------------------------
69 // =====================================================================
70 
72 {
73  // Simply free what should be
74  if (mp_paddedImage) free(mp_paddedImage);
75  if (mp_dimKernelX) free(mp_dimKernelX);
76  if (mp_dimKernelY) free(mp_dimKernelY);
77  if (mp_dimKernelZ) free(mp_dimKernelZ);
78  if (m2p_kernel)
79  {
80  for (int k=0; k<m_nbKernels; k++) if (m2p_kernel[k]) free(m2p_kernel[k]);
81  free(m2p_kernel);
82  }
83 }
84 
85 // =====================================================================
86 // ---------------------------------------------------------------------
87 // ---------------------------------------------------------------------
88 // =====================================================================
89 
90 // Check global parameters then check the specific parameters
92 {
93  // Check verbose
94  if (m_verbose<0)
95  {
96  Cerr("***** vImageConvolver::CheckParameters() -> The verbose level must be set positive !" << endl);
97  return 1;
98  }
99  // Check oImageDimensionsAndQuantification
101  {
102  Cerr("***** vImageConvolver::CheckParameters() -> The image dimensions must be set through a oImageDimensionsAndQuantification !" << endl);
103  return 1;
104  }
105  // Finally check the specific parameters from the child convolver
107  {
108  Cerr("***** vImageConvolver::CheckParameters() -> A problem occurred while checking specific parameters of the child convolver !" << endl);
109  return 1;
110  }
111  // All set
112  m_checked = true;
113  // Normal end
114  return 0;
115 }
116 
117 // =====================================================================
118 // ---------------------------------------------------------------------
119 // ---------------------------------------------------------------------
120 // =====================================================================
121 
122 // Check that the kernel has been successfully built, then allocate the padded image
124 {
125  // Check if parameters have been checked
126  if (!m_checked)
127  {
128  Cerr("***** vImageConvolver::Initialize() -> Parameters have not been checked ! Please call CheckParameters() before." << endl);
129  return 1;
130  }
131 
132  // Build the convolution kernel
134  {
135  Cerr("***** vImageConvolver::Initialize() -> A problem occurred while building the convolution kernel !" << endl);
136  return 1;
137  }
138 
139  // ---------------------------------------------------------------------------------------
140  // Check that all kernel dimensions are odd and find the maximum size along each dimension
141  // ---------------------------------------------------------------------------------------
142 
143  // Check that the number of kernels is not null or negative
144  if (m_nbKernels<1)
145  {
146  Cerr("***** vImageConvolver::Initialize() -> The number of kernels is negative or null !" << endl);
147  return 1;
148  }
149  // Check that the kernel dimensions have been allocated
150  if (mp_dimKernelX==NULL || mp_dimKernelY==NULL || mp_dimKernelZ==NULL)
151  {
152  Cerr("***** vImageConvolver::Initialize() -> The kernel dimensions are not allocated !" << endl);
153  return 1;
154  }
155  // Check that the kernel has been allocated
156  if (m2p_kernel==NULL)
157  {
158  Cerr("***** vImageConvolver::Initialize() -> The kernel is not allocated !" << endl);
159  return 1;
160  }
161  // In case the kernel is stationary and the user forgot to explicitely specify it, we force it here
162  if (m_nbKernels==1) m_stationary = true;
163 
164  // Maximum kernel sizes along each dimension
165  INTNB max_kern_size_Z = 0;
166  INTNB max_kern_size_Y = 0;
167  INTNB max_kern_size_X = 0;
168 
169  // Loop over all kernels
170  for (int k=0; k<m_nbKernels; k++)
171  {
172  // Check oddity along Z
173  if (mp_dimKernelZ[k]%2==0)
174  {
175  Cerr("***** vImageConvolver::Initialize() -> Dimension of " << k << "th convolution kernel along Z (" << mp_dimKernelZ[k] << " is not odd !" << endl);
176  return 1;
177  }
178  // Check oddity along Y
179  if (mp_dimKernelY[k]%2==0)
180  {
181  Cerr("***** vImageConvolver::Initialize() -> Dimension of " << k << "th convolution kernel along Y (" << mp_dimKernelY[k] << " is not odd !" << endl);
182  return 1;
183  }
184  // Check oddity along X
185  if (mp_dimKernelX[k]%2==0)
186  {
187  Cerr("***** vImageConvolver::Initialize() -> Dimension of " << k << "th convolution kernel along X (" << mp_dimKernelX[k] << " is not odd !" << endl);
188  return 1;
189  }
190  // Check maximum along Z
191  if (mp_dimKernelZ[k]>max_kern_size_Z) max_kern_size_Z = mp_dimKernelZ[k];
192  // Check maximum along Y
193  if (mp_dimKernelY[k]>max_kern_size_Y) max_kern_size_Y = mp_dimKernelY[k];
194  // Check maximum along X
195  if (mp_dimKernelX[k]>max_kern_size_X) max_kern_size_X = mp_dimKernelX[k];
196  }
197 
198  // Set the offsets
199  m_offsetZ = max_kern_size_Z / 2;
200  m_offsetY = max_kern_size_Y / 2;
201  m_offsetX = max_kern_size_X / 2;
202 
203  // 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.
204  // 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.
206  {
207  FLTNB over_computation_factor = ((FLTNB)(max_kern_size_Z)) / ((FLTNB)(mp_ImageDimensionsAndQuantification->GetNbVoxZ()*2-1));
208  Cerr("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl);
209  Cerr("!!!!! vImageConvolver::Initialize() -> The maximum half size of the convolution kernels is higher or equal to the number of slices of the image." << endl);
210  Cerr("!!!!! This won't affect the results, but a convolution running time factor of " << over_computation_factor << " will be lost for nothing." << endl);
211  Cerr("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!" << endl);
212  }
213 
214  // ---------------------------------------------------------------------------------------
215  // Build the padded image
216  // ---------------------------------------------------------------------------------------
217 
218  // Compute the padded image dimensions
219  m_dimPadZ = mp_ImageDimensionsAndQuantification->GetNbVoxZ() + max_kern_size_Z - 1;
220  m_dimPadY = mp_ImageDimensionsAndQuantification->GetNbVoxY() + max_kern_size_Y - 1;
221  m_dimPadX = mp_ImageDimensionsAndQuantification->GetNbVoxX() + max_kern_size_X - 1;
224 
225  // Allocate the padded image and fill it with 0.
226  mp_paddedImage = (FLTNB*)calloc(m_dimPadXYZ,sizeof(FLTNB));
227 
228  // All set
229  m_initialized = true;
230  // Normal end
231  return 0;
232 }
233 
234 // =====================================================================
235 // ---------------------------------------------------------------------
236 // ---------------------------------------------------------------------
237 // =====================================================================
238 
240 {
241  #ifdef CASTOR_DEBUG
242  if (!m_initialized)
243  {
244  Cerr("***** vImageConvolver::ApplyConvolution() -> Called while not initialized !" << endl);
245  Exit(EXIT_DEBUG);
246  }
247  #endif
248 
249  // Copy image into padded buffer image
250  CopyToPaddedImage(ap_image);
251  // Then convolve
252  if (Convolve(ap_image))
253  {
254  Cerr("***** vImageConvolver::ApplyConvolution() -> A problem occurred while actually convolving !" << endl);
255  return 1;
256  }
257  // Normal end
258  return 0;
259 }
260 
261 // =====================================================================
262 // ---------------------------------------------------------------------
263 // ---------------------------------------------------------------------
264 // =====================================================================
265 
267 {
268  #ifdef CASTOR_DEBUG
269  if (!m_initialized)
270  {
271  Cerr("***** vImageConvolver::ApplyConvolutionTranspose() -> Called while not initialized !" << endl);
272  Exit(EXIT_DEBUG);
273  }
274  #endif
275 
276  // Copy image into padded buffer image
277  CopyToPaddedImage(ap_image);
278  // Then convolve
279  if (ConvolveTranspose(ap_image))
280  {
281  Cerr("***** vImageConvolver::ApplyConvolutionTranspose() -> A problem occurred while actually convolving !" << endl);
282  return 1;
283  }
284  // Normal end
285  return 0;
286 }
287 
288 // =====================================================================
289 // ---------------------------------------------------------------------
290 // ---------------------------------------------------------------------
291 // =====================================================================
292 
294 {
295  #ifdef CASTOR_DEBUG
296  if (!m_initialized)
297  {
298  Cerr("***** vImageConvolver::CopyToPaddedImage() -> Called while not initialized !" << endl);
299  Exit(EXIT_DEBUG);
300  }
301  #endif
302 
303  // Copy the image into the padded buffer, centered with respect to the pad
304  INTNB z;
305  #pragma omp parallel for private(z) schedule(guided)
307  {
308  // Pad coordinate Z
309  INTNB padz = z + m_offsetZ;
311  {
312  // Pad coordinate Y
313  INTNB pady = y + m_offsetY;
315  {
316  // Pad coordinate X
317  INTNB padx = x + m_offsetX;
318  // Global original coordinate
320  // Global pad coordinate
321  INTNB coord_pad = padz*m_dimPadXY + pady*m_dimPadX + padx;
322  // Affect buffer
323  mp_paddedImage[coord_pad] = ap_inputImage[coord_orig];
324  // Zero image
325  ap_inputImage[coord_orig] = 0.;
326  }
327  }
328  }
329 }
330 
331 // =====================================================================
332 // ---------------------------------------------------------------------
333 // ---------------------------------------------------------------------
334 // =====================================================================
335 
336 int vImageConvolver::Convolve(FLTNB* ap_outputImage)
337 {
338  #ifdef CASTOR_DEBUG
339  if (!m_initialized)
340  {
341  Cerr("***** vImageConvolver::Convolve() -> Called while not initialized !" << endl);
342  Exit(EXIT_DEBUG);
343  }
344  #endif
345 
346  // This function is designed to work universally and exclusively on a stationary kernel.
347  // Note that we suppose that a stationary kernel is also symmetric (but not necessarily isotropic).
348  // For asymetric and non-stationary kernels, have to overload this function in the child convolver.
349  // Note also that we choose the out-to-in implementation (outer loop on the output image) so that the
350  // parallelism is operated on the output and is thus thread-safe.
351  // Finally note that transaxially, we do not implement an image intensity preservation policy as we suppose that
352  // the distance between the object and the image border is taller than the kernel half-width (it should be so).
353  if (m_stationary)
354  {
355  INTNB stationary_kernel = 0;
356  // Convolve (OpenMP on Z dimension)
357  INTNB z;
358  #pragma omp parallel for private(z) schedule(guided)
360  {
361  // As a first step, we virtually go through the kernel along Z to compute the actual kernel integral that will
362  // be really used in the convolution. For each virtual slice contributing to the convolved slice, we test if it
363  // is inside or outside the image; in the former case we count it into the integral and in the latter we don't.
364  FLTNB kernel_integral = 0.;
365  for (INTNB zk=0; zk<mp_dimKernelZ[stationary_kernel]; zk++)
366  {
367  // Index of the virtual slice which will (or not) contribute to the convolved slice
368  INTNB virtualZ = z + zk - m_offsetZ;
369  // Test if it is not in the image, then we move onto the next virtual slice
370  if (virtualZ<0 || virtualZ>=mp_ImageDimensionsAndQuantification->GetNbVoxZ()) continue;
371  // Otherwise, we update the kernel integral
372  INTNB kernZ_base = zk * mp_dimKernelX[stationary_kernel] * mp_dimKernelY[stationary_kernel];
373  for (INTNB xyk=0; xyk<mp_dimKernelX[stationary_kernel]*mp_dimKernelY[stationary_kernel]; xyk++)
374  kernel_integral += m2p_kernel[stationary_kernel][kernZ_base+xyk];
375  }
376  // Some precomputation for maximum efficiency
379  {
380  // Some precomputation for maximum efficiency
381  INTNB indexZY_base = indexZ_base + y*mp_ImageDimensionsAndQuantification->GetNbVoxX();
383  {
384  // Output image index
385  INTNB index_img = indexZY_base + x;
386  // Inner loops on convolution kernel dimensions
387  for (INTNB zk=0, index_kern=0; zk<mp_dimKernelZ[stationary_kernel]; zk++)
388  {
389  // Some precomputation for maximum efficiency
390  INTNB indexZ_pad = (z + zk) * m_dimPadXY;
391  for (INTNB yk=0; yk<mp_dimKernelY[stationary_kernel]; yk++)
392  {
393  // Some precomputation for maximum efficiency
394  INTNB indexZY_pad = indexZ_pad + (y + yk) * m_dimPadX;
395  for (INTNB xk=0; xk<mp_dimKernelX[stationary_kernel]; xk++, index_kern++)
396  {
397  // Padded image index
398  INTNB index_pad = indexZY_pad + x + xk;
399  // Apply contribution
400  ap_outputImage[index_img] += mp_paddedImage[index_pad] * m2p_kernel[stationary_kernel][index_kern] / kernel_integral;
401  }
402  }
403  }
404  }
405  }
406  }
407  // Normal end
408  return 0;
409  }
410  // Otherwise the convolve function has to be implemented by the child non-stationary convolver
411  else
412  {
413  Cerr("***** vImageConvolver::Convolve() -> To use a non-stationary kernel, you should overload this function to implement your own in your child convolver !" << endl);
414  return 1;
415  }
416 }
417 
418 // =====================================================================
419 // ---------------------------------------------------------------------
420 // ---------------------------------------------------------------------
421 // =====================================================================
422 
424 {
425  #ifdef CASTOR_DEBUG
426  if (!m_initialized)
427  {
428  Cerr("***** vImageConvolver::ConvolveTranspose() -> Called while not initialized !" << endl);
429  Exit(EXIT_DEBUG);
430  }
431  #endif
432 
433  // This function is designed to work universally on a stationary kernel.
434  // Note that we suppose a stationary kernel is also symmetric (otherwise it is absurd)
435  if (m_stationary)
436  {
437  return Convolve(ap_outputImage);
438  }
439  // Otherwise the convolve function has to be implemented by the child non-stationary convolver
440  else
441  {
442  Cerr("***** vImageConvolver::ConvolveTranspose() -> To use a non-stationary kernel, you should overload this function to implement your own in your child convolver !" << endl);
443  return 1;
444  }
445 }
446 
447 // =====================================================================
448 // ---------------------------------------------------------------------
449 // ---------------------------------------------------------------------
450 // =====================================================================
Declaration of class oImageDimensionsAndQuantification.
#define FLTNB
Definition: gVariables.hh:81
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:92
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:97
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.