gVirtualXRay  2.0.10
VirtualX-RayImagingLibraryonGPU
Sinogram.inl
Go to the documentation of this file.
1 /*
2 
3 Copyright (c) 2016-2023, Dr Franck P. Vidal, Bangor University, All rights reserved.
4 Copyright (c) 2023-present, Prof Franck P. Vidal (franck.vidal@stfc.ac.uk),
5 UK Research and Innovation, All rights reserved.
6 
7 
8 Redistribution and use in source and binary forms, with or without modification,
9 are permitted provided that the following conditions are met:
10 
11 1. Redistributions of source code must retain the above copyright notice,
12 this list of conditions and the following disclaimer.
13 
14 2. Redistributions in binary form must reproduce the above copyright notice,
15 this list of conditions and the following disclaimer in the documentation and/or
16 other materials provided with the distribution.
17 
18 3. Neither the name of Bangor University, UK Research and Innovation nor the
19 names of its contributors may be used to endorse or promote products derived
20 from this software without specific prior written permission.
21 
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
23 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
24 THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
31 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 
33 */
34 
35 
61 //******************************************************************************
62 // Define
63 //******************************************************************************
64 
65 
66 //******************************************************************************
67 // Include
68 //******************************************************************************
69 #ifndef __FFT_h
70 #include "gVirtualXRay/FFT.h"
71 #endif
72 
73 
74 //******************************************************************************
75 // namespace
76 //******************************************************************************
77 namespace gVirtualXRay {
78 
79 
80 template<typename T> Sinogram<T>::Sinogram():
81  Image<T>()
82 {}
83 
84 
85 template<typename T> Sinogram<T>::Sinogram(const Image<T> anImage):
86  Image<T>(anImage)
87 {}
88 
89 
90 template<typename T> Sinogram<T>::Sinogram(unsigned int aWidth,
91  unsigned int aHeight,
92  unsigned int aNumberOfSlices):
93  Image<T>(aWidth, aHeight, aNumberOfSlices)
94 {}
95 
96 
97 template<typename T> Image<T> Sinogram<T>::getProjectionSet() const
98 {
99  Image<T> projection_set(this->m_width, this->m_number_of_slices, this->m_height);
100 
101 #ifdef NDEBUG
102 #ifdef WIN32
103  #pragma omp parallel for
104 #else
105  #pragma omp parallel for collapse(2)
106 #endif
107 #endif
108  for (int angle_id = 0; angle_id < this->m_height; ++angle_id)
109  {
110  for (int row_projection_id = 0; row_projection_id < this->m_number_of_slices; ++row_projection_id)
111  {
112  const T* p_input(this->getRawData() + angle_id * this->m_width + row_projection_id * this->m_width * this->m_height);
113  T* p_output(projection_set.getRawData() + angle_id * this->m_width * this->m_number_of_slices + this->m_width * row_projection_id);
114 
115  std::copy(p_input, p_input + this->m_width, p_output);
116  }
117  }
118  return (projection_set);
119 }
120 
121 
122 //------------------------------------------------------------------------------
123 template<typename T> Image<T> Sinogram<T>::art(double aFirstAngle,
124  double anAngleStep,
125  unsigned int aNumberOfIterations,
126  const char* aDirectory,
127  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
128 //------------------------------------------------------------------------------
129 {
130  unsigned int image_width(2 * floor (this->m_width / (2.0 * std::sqrt(2.0))));
131 
132  // Start with a homogenous image
133  Image<T> new_estimated_image(image_width, image_width, this->m_number_of_slices, this->getSum() / (image_width * image_width));
134  Image<T> old_estimated_image(image_width, image_width, this->m_number_of_slices, this->getSum() / (image_width * image_width));
135 
136  Image<T> error_back_projection;
137 
138  double old_ncc(0.0);
139 
140  unsigned int iteration(1);
141  bool stop(false);
142  while (!stop)
143  {
144  old_estimated_image = new_estimated_image;
145 
146  // Process every angle
147  double angle(aFirstAngle);
148  for (unsigned int angle_id(0); angle_id < this->m_height; ++angle_id, angle += anAngleStep)
149  {
150  // Compute the projections
151  Image<T> estimated_projection(new_estimated_image.radonTransform(angle, 0.0, angle).setCanvasSize(this->m_width, 1, this->m_number_of_slices));
152  //double sum_estimated(estimated_projection.getSum());
153 
154  // Real image
155  Image<T> real_projection(this->getROI(0, angle_id, 0, this->m_width, 1, this->m_number_of_slices));
156 
157  // Compute the error
158  Sinogram<T> error_in_projections = (real_projection - estimated_projection);
159 
160  // Correct for errors
161  error_back_projection = error_in_projections.simpleBackProjection(angle, angle);
162 
163  new_estimated_image += error_back_projection.setCanvasSize(image_width, image_width, this->m_number_of_slices);
164 
165  T* p_temp(new_estimated_image.getRawData());
166  for (unsigned int i(0); i < new_estimated_image.getWidth() * new_estimated_image.getHeight() * new_estimated_image.getDepth(); ++i, ++p_temp)
167  {
168  *p_temp = std::max(0.0f, *p_temp);
169  }
170 
171  if (aDirectory)
172  {
173  std::stringstream filename;
174  filename << aDirectory << "/iteration_" << iteration << "_" << angle_id << ".mha";
175  (new_estimated_image.setCanvasSize(image_width, image_width, this->m_number_of_slices).normalised() * 255).savePGM(filename.str());
176  }
177 
178  if (aCallback)
179  {
180  (*aCallback)(&new_estimated_image, &estimated_projection, &error_back_projection);
181  }
182  }
183 
184  // Compute the projection
185  Image<T> estimated_projections(new_estimated_image.radonTransform(aFirstAngle, anAngleStep, this->m_height).setCanvasSize(this->m_width, this->m_height, this->m_number_of_slices));
186 
187  // Stopping criteria
188  double ncc_projections(100.0 * this->computeNCC(estimated_projections));
189  double ncc_reconstructions(100.0 * new_estimated_image.computeNCC(old_estimated_image));
190 
191  if ((ncc_projections > 99.50 || ncc_reconstructions > 99.50) /*&& std::abs(old_ncc - ncc_reconstructions) < EPSILON*/)
192  {
193  stop = true;
194  }
195  else if (aNumberOfIterations == iteration)
196  {
197  stop = true;
198  }
199 
200  old_ncc = ncc_reconstructions;
201 
202  iteration++;
203 
204 
205  if (aCallback)
206  {
207  (*aCallback)(&new_estimated_image, &estimated_projections, &error_back_projection);
208  }
209  }
210 
211  return (new_estimated_image.setCanvasSize(image_width, image_width, this->m_number_of_slices));
212 }
213 
214 
215 //-------------------------------------------------------------------------------
216 template<typename T> Image<T> Sinogram<T>::mart(double aFirstAngle,
217  double anAngleStep,
218  unsigned int aNumberOfIterations,
219  const char* aDirectory,
220  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
221 //-------------------------------------------------------------------------------
222 {
223  unsigned int image_width(2 * floor (this->m_width / (2.0 * std::sqrt(2.0))));
224 
225  // Start with a homogenous image
226  Image<T> new_estimated_image(image_width, image_width, this->m_number_of_slices, this->getSum() / (image_width * image_width));
227  Image<T> old_estimated_image(image_width, image_width, this->m_number_of_slices, this->getSum() / (image_width * image_width));
228 
229  Image<T> error_back_projection;
230 
231  double old_ncc(0.0);
232 
233  unsigned int iteration(1);
234  bool stop(false);
235  while (!stop)
236  {
237  old_estimated_image = new_estimated_image;
238 
239  // Process every angle
240  double angle(aFirstAngle);
241  for (unsigned int angle_id(0); angle_id < this->m_height; ++angle_id, angle += anAngleStep)
242  {
243  // Compute the projections
244  Image<T> estimated_projection(new_estimated_image.radonTransform(angle, 0.0, angle).setCanvasSize(this->m_width, 1, this->m_number_of_slices));
245  //double sum_estimated(estimated_projection.getSum());
246 
247  // Real image
248  Image<T> real_projection(this->getROI(0, angle_id, 0, this->m_width, 1, this->m_number_of_slices));
249 
250  // Compute the error
251  Sinogram<T> error_in_projections(real_projection - estimated_projection);
252 
253  // Correct for errors
254  error_back_projection = Image<T>(error_in_projections.simpleBackProjection(angle, angle));
255 
256  new_estimated_image *= error_back_projection.setCanvasSize(image_width, image_width, this->m_number_of_slices);
257 
258  T* p_temp(new_estimated_image.getRawData());
259  for (unsigned int i(0); i < new_estimated_image.getWidth() * new_estimated_image.getHeight() * new_estimated_image.getDepth(); ++i, ++p_temp)
260  {
261  *p_temp = std::max(0.0f, *p_temp);
262  }
263 
264  if (aDirectory)
265  {
266  std::stringstream filename;
267  filename << aDirectory << "/iteration_" << iteration << "_" << angle_id << ".mha";
268  (new_estimated_image.setCanvasSize(image_width, image_width, this->m_number_of_slices).normalised() * 255).savePGM(filename.str());
269  }
270 
271  if (aCallback)
272  {
273  (*aCallback)(&new_estimated_image, &estimated_projection, &error_back_projection);
274  }
275  }
276 
277  // Compute the projection
278  Image<T> estimated_projections(new_estimated_image.radonTransform(aFirstAngle, anAngleStep, this->m_height).setCanvasSize(this->m_width, this->m_height, this->m_number_of_slices));
279 
280  // Stopping criteria
281  double ncc_projections(100.0 * this->computeNCC(estimated_projections));
282  double ncc_reconstructions(100.0 * new_estimated_image.computeNCC(old_estimated_image));
283 
284  if ((ncc_projections > 99.50 || ncc_reconstructions > 99.50) /*&& std::abs(old_ncc - ncc_reconstructions) < EPSILON*/)
285  {
286  stop = true;
287  }
288  else if (aNumberOfIterations == iteration)
289  {
290  stop = true;
291  }
292 
293  old_ncc = ncc_reconstructions;
294 
295  iteration++;
296 
297 
298  if (aCallback)
299  {
300  (*aCallback)(&new_estimated_image, &estimated_projections, &error_back_projection);
301  }
302  }
303 
304  return (new_estimated_image.setCanvasSize(image_width, image_width, this->m_number_of_slices));
305 }
306 
307 
308 //-------------------------------------------------------------------------------
309 template<typename T> Image<T> Sinogram<T>::sirt(double aFirstAngle,
310  double anAngleStep,
311  unsigned int aNumberOfIterations,
312  const char* aDirectory,
313  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
314 //-------------------------------------------------------------------------------
315 {
316  unsigned int image_width(2 * floor (this->m_width / (2.0 * std::sqrt(2.0))));
317 
318  // Start with a homogenous image
319  Image<T> new_estimated_image(image_width, image_width, this->m_number_of_slices, this->getSum() / (image_width * image_width));
320  Image<T> old_estimated_image(image_width, image_width, this->m_number_of_slices, this->getSum() / (image_width * image_width));
321 
322  //std::cout << this->getSum() << "\t" << new_estimated_image.getSum() << std::endl;
323  double old_ncc(0.0);
324 
325  unsigned int iteration(1);
326  bool stop(false);
327  while (!stop)
328  {
329  old_estimated_image = new_estimated_image;
330 
331  Image<T> estimated_projection(new_estimated_image.radonTransform(aFirstAngle, anAngleStep, this->m_height));
332 
333  // Compute the error
334  Sinogram<T> error_in_projections(*this - estimated_projection);
335 
336  this->saveASCII("real_projection.mat");
337  estimated_projection.saveASCII("estimated_projection.mat");
338  error_in_projections.saveASCII("error_in_projections.mat");
339 
340  Image<T> error_back_projection(error_in_projections.simpleBackProjection(aFirstAngle, anAngleStep));
341  error_back_projection.saveASCII("error_back_projection.mat");
342 
343  // Correct for errors
344  new_estimated_image += error_back_projection.setCanvasSize(image_width, image_width, this->m_number_of_slices);
345  new_estimated_image.saveASCII("new_estimated_image.mat");
346  old_estimated_image.saveASCII("old_estimated_image.mat");
347 
348  T* p_temp(new_estimated_image.getRawData());
349  for (unsigned int i(0); i < new_estimated_image.getWidth() * new_estimated_image.getHeight() * new_estimated_image.getDepth(); ++i, ++p_temp)
350  {
351  *p_temp = std::max(0.0f, *p_temp);
352  }
353 
354  if (aDirectory)
355  {
356  std::stringstream filename;
357  filename << aDirectory << "/iteration_" << iteration << ".mha";
358  (new_estimated_image.setCanvasSize(image_width, image_width, this->m_number_of_slices).normalised() * 255).savePGM(filename.str());
359  }
360 
361 
362  if (aCallback)
363  {
364  (*aCallback)(&new_estimated_image, &estimated_projection, &error_back_projection);
365  }
366 
367  // Compute the projection
368  Image<T> estimated_projections(new_estimated_image.radonTransform(aFirstAngle, anAngleStep, this->m_height).setCanvasSize(this->m_width, this->m_height, this->m_number_of_slices));
369  estimated_projections.saveASCII("simulated_projection.mat");
370 
371  // Stopping criteria
372  double ncc_projections(100.0 * this->computeNCC(estimated_projections));
373  double ncc_reconstructions(100.0 * new_estimated_image.computeNCC(old_estimated_image));
374 
375  if ((ncc_projections > 99.50 || ncc_reconstructions > 99.50) /*&& std::abs(old_ncc - ncc_reconstructions) < EPSILON*/)
376  {
377  stop = true;
378  }
379  else if (aNumberOfIterations == iteration)
380  {
381  stop = true;
382  }
383 
384  old_ncc = ncc_reconstructions;
385 
386  iteration++;
387 
388  }
389 
390  return (new_estimated_image.setCanvasSize(image_width, image_width, this->m_number_of_slices));
391 }
392 
393 
394 //-------------------------------------------------------------------------------------------
395 template<typename T> Image<T> Sinogram<T>::simpleBackProjection(double aFirstAngle,
396  double anAngleStep,
397  const char* aDirectory,
398  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
399 //-------------------------------------------------------------------------------------------
400 {
401  Image<T> sinogram(this->resize(this->m_width * 2, this->m_height, this->m_number_of_slices));
402  sinogram.saveASCII("resized.mat");
403  unsigned int image_width(2 * floor (sinogram.getWidth() / (2.0 * std::sqrt(2.0))));
404 
405  Image<T> reconstruction(sinogram.getWidth(), sinogram.getWidth(), sinogram.getDepth());
406 
407  double angle(aFirstAngle);
408  for (unsigned int angle_id(0);
409  angle_id < this->m_height;
410  ++angle_id, angle += anAngleStep)
411  {
412  Image<T> back_projection(sinogram.getWidth(), sinogram.getWidth(), sinogram.getDepth());
413 
414 #ifdef NDEBUG
415 #ifdef WIN32
416  #pragma omp parallel for
417 #else
418  #pragma omp parallel for collapse(3)
419 #endif
420 #endif
421  for (int k = 0; k < reconstruction.getDepth(); ++k)
422  {
423  for (int j = 0; j < reconstruction.getHeight(); ++j)
424  {
425  for (int i = 0; i < reconstruction.getWidth(); ++i)
426  {
427  back_projection(i, j, k) += sinogram.getPixel(i, angle_id, k);
428  }
429  }
430  }
431 
432  reconstruction += back_projection.rotate(-angle) / reconstruction.getHeight();
433 
434  if (aDirectory)
435  {
436  std::stringstream filename;
437  filename << aDirectory << "/angle_" << angle_id << ".mha";
438  (reconstruction.setCanvasSize(image_width, image_width, reconstruction.getDepth()).resize(image_width / 2, image_width / 2, reconstruction.getDepth()).normalised() * 255).savePGM(filename.str());
439  }
440 
441 
442  if (aCallback)
443  {
444  (*aCallback)(&reconstruction, &back_projection, this);
445  }
446  }
447 
448  //reconstruction = reconstruction.rotate(-angle);
449 
450  reconstruction = reconstruction.setCanvasSize(image_width, image_width, this->m_number_of_slices);
451  reconstruction *= Pi / (/*2.0 * */ this->getHeight());
452 
453  return (reconstruction.resize(image_width / 2, image_width / 2, this->getDepth()));
454 }
455 
456 
457 //---------------------------------------------------------------------------------------------
458 template<typename T> Image<T> Sinogram<T>::filteredBackProjection(double aFirstAngle,
459  double anAngleStep,
460  const char* aFilterType,
461  float aScalingFactor,
462  const char* aDirectory,
463  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
464 //---------------------------------------------------------------------------------------------
465 {
466  typedef FFT<T> FAST_FOURIER_TRANSFORM;
467 
468  // Compute the FFTs
469  std::vector<FAST_FOURIER_TRANSFORM> p_fft_set;
470  for (unsigned int j(0); j < this->m_height; ++j)
471  {
472  p_fft_set.push_back(this->getROI(0, j, 0, this->m_width, 1, this->m_number_of_slices).getFFT());
473  }
474 
475  // Compute the rho filter
476  for (typename std::vector<FAST_FOURIER_TRANSFORM>::iterator ite(p_fft_set.begin());
477  ite != p_fft_set.end();
478  ++ite)
479  {
480  *ite = ite->rhoFilter(aFilterType, aScalingFactor);
481  }
482 
483  // Compute the iFFTs
484  std::vector<Image<T> > p_ifft_set(this->m_height);
485  for (unsigned int j(0); j < this->m_height; ++j)
486  {
487  p_ifft_set[j] = p_fft_set[j].getInverseFFT();
488  }
489 
490  // Store the results
491  Sinogram<T> filtered_sinogram(this->m_width, this->m_height, this->m_number_of_slices);
492 
493  for (unsigned int k(0); k < this->m_number_of_slices; ++k)
494  {
495  for (unsigned int j(0); j < this->m_height; ++j)
496  {
497  for (unsigned int i(0); i < this->m_width; ++i)
498  {
499  filtered_sinogram(i, j, k) = p_ifft_set[j](i, 0, k);
500  }
501  }
502  }
503 
504  //std::cout << getAverage() << " " << filtered_sinogram.getAverage() << std::endl;
505  //filtered_sinogram *= filtered_sinogram.getAverage() / getAverage();
506  //filtered_sinogram.saveASCII("filtered_sinogram.mat");
507  return (filtered_sinogram.simpleBackProjection(aFirstAngle,
508  anAngleStep,
509  aDirectory,
510  aCallback));
511 }
512 
513 
514 //---------------------------------------------------------------------------------
515 template<typename T> Image<T> Sinogram<T>::art(double aFirstAngle,
516  double anAngleStep,
517  unsigned int aNumberOfIterations,
518  const std::string& aDirectory,
519  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
520 //---------------------------------------------------------------------------------
521 {
522  return (art(aFirstAngle,
523  anAngleStep,
524  aNumberOfIterations,
525  aDirectory.data(),
526  aCallback));
527 }
528 
529 
530 //----------------------------------------------------------------------------------
531 template<typename T> Image<T> Sinogram<T>::mart(double aFirstAngle,
532  double anAngleStep,
533  unsigned int aNumberOfIterations,
534  const std::string& aDirectory,
535  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
536 //----------------------------------------------------------------------------------
537 {
538  return (mart(aFirstAngle,
539  anAngleStep,
540  aNumberOfIterations,
541  aDirectory.data(),
542  aCallback));
543 }
544 
545 
546 //----------------------------------------------------------------------------------
547 template<typename T> Image<T> Sinogram<T>::sirt(double aFirstAngle,
548  double anAngleStep,
549  unsigned int aNumberOfIterations,
550  const std::string& aDirectory,
551  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
552 //----------------------------------------------------------------------------------
553 {
554  return (sirt(aFirstAngle,
555  anAngleStep,
556  aNumberOfIterations,
557  aDirectory.data(),
558  aCallback));
559 }
560 
561 
562 //--------------------------------------------------------------------------------------------------
563 template<typename T> Image<T> Sinogram<T>::simpleBackProjection(double aFirstAngle,
564  double anAngleStep,
565  const std::string& aDirectory,
566  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
567 //--------------------------------------------------------------------------------------------------
568 {
569  return (simpleBackProjection(aFirstAngle,
570  anAngleStep,
571  aDirectory.data(),
572  aCallback));
573 }
574 
575 
576 //----------------------------------------------------------------------------------------------------
577 template<typename T> Image<T> Sinogram<T>::filteredBackProjection(double aFirstAngle,
578  double anAngleStep,
579  const std::string& aFilterType,
580  float aScalingFactor,
581  const std::string& aDirectory,
582  void (*aCallback)(const Image<T>*, const Image<T>*, const Image<T>*)) const
583 //----------------------------------------------------------------------------------------------------
584 {
585  return (filteredBackProjection(aFirstAngle,
586  anAngleStep,
587  aFilterType.data(),
588  aScalingFactor,
589  aDirectory.data(),
590  aCallback));
591 }
592 
593 
594 } // namespace gVirtualXRay
Image< T > simpleBackProjection(double aFirstAngle, double anAngleStep, const char *aDirectorye=0, void(*aCallback)(const Image< T > *, const Image< T > *, const Image< T > *)=0) const
Definition: Sinogram.inl:395
void saveASCII(const char *aFileName) const
Save the image in an ASCII file.
Definition: Image.inl:2805
T & getPixel(unsigned int i, unsigned int j, unsigned int k=0)
Accessor on a pixel value.
Definition: Image.inl:582
Sinogram is a class to reconstruct images from a sinogram.
Definition: Image.h:94
T * getRawData()
Accessor on the raw data.
Definition: Image.inl:717
gVirtualXRay::Sinogram< T > radonTransform(double aFirstAngle, double anAngleStep, double aLastAngle) const
Definition: Image.inl:4422
Image is a class to manage a greyscale image.
Definition: Image.h:92
unsigned int getHeight() const
Number of pixels along the vertical axis.
Definition: Image.inl:792
unsigned int getDepth() const
Number of pixels along the Z axis.
Definition: Image.inl:800
Class to compute the FFT of a greyscale image.
Image< T > setCanvasSize(unsigned int aNewWidth, unsigned int aNewHeight, unsigned int aNewNumberOfSlices) const
Definition: Image.inl:4194
unsigned int getWidth() const
Number of pixels along the horizontal axis.
Definition: Image.inl:784
const double Pi
Pi.
FFT is a class to compute the FFT of a greyscale image.
Definition: FFT.h:78
double computeNCC(const Image< T > &anImage) const
Definition: Image.inl:3694