CASToR  3.0
Tomographic Reconstruction (PET/SPECT/CT)
iOptimizerPenalizedPreconditionedGradientML.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 
31 #include "sOutputManager.hh"
32 
33 // =====================================================================
34 // ---------------------------------------------------------------------
35 // ---------------------------------------------------------------------
36 // =====================================================================
37 
39 {
40  // ---------------------------
41  // Mandatory member parameters
42  // ---------------------------
43 
44  // Initial value at 1
45  m_initialValue = 1.;
46  // Only one backward image
48  // This algorithm accepts penalty and requires the first and second derivatives
50  // PreconditionedGradientMAP is only compatible with histogram data
53  // PreconditionedGradientMAP is only compatible with emission data
56 
57  // --------------------------
58  // Specific member parameters
59  // --------------------------
60 
66 }
67 
68 // =====================================================================
69 // ---------------------------------------------------------------------
70 // ---------------------------------------------------------------------
71 // =====================================================================
72 
74 {
75  // Note: there is no need to deallocate the images themselves as they are allocate using the
76  // miscellaneous image function from the image space, which automatically deals with
77  // memory deallocations.
78  // Delete the first order derivative penalty image
80  {
81  // Loop over time basis functions
83  {
85  {
86  // Loop over respiratory basis functions
88  {
89  if (m4p_firstDerivativePenaltyImage[tbf][rbf])
90  {
91  free(m4p_firstDerivativePenaltyImage[tbf][rbf]);
92  }
93  }
95  }
96  }
98  }
99  // Delete the second order derivative penalty image
101  {
102  // Loop over time basis functions
103  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
104  {
106  {
107  // Loop over respiratory basis functions
108  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
109  {
110  if (m4p_secondDerivativePenaltyImage[tbf][rbf])
111  {
112  free(m4p_secondDerivativePenaltyImage[tbf][rbf]);
113  }
114  }
116  }
117  }
119  }
120 }
121 
122 // =====================================================================
123 // ---------------------------------------------------------------------
124 // ---------------------------------------------------------------------
125 // =====================================================================
126 
128 {
129  cout << "This optimizer is the Penalized Preconditioned Gradient algorithm from J. Nuyts et al, IEEE TNS, Feb 2002, vol. 49, pp. 56-60." << endl;
130  cout << "As usually described by its inventor, it is a heuristic but effective gradient ascent algorithm" << endl;
131  cout << "for penalized maximum-likelihood reconstruction. It addresses the shortcoming of One-Step-Late when large" << endl;
132  cout << "penalty strengths can create numerical problems. Penalty terms must have a derivative order of at least two." << endl;
133  cout << "Subsets can be used as for OSEM. Without penalty, it is equivalent to the gradient ascent form of the MLEM algorithm." << endl;
134  cout << "Based on likelihood gradient and penalty, a multiplicative update factor is computed and its range is limited by provided parameters." << endl;
135  cout << "Thus, negative values cannot occur and voxels cannot be trapped into 0 values, providing a first positive estimate." << endl;
136  cout << "The following options can be used (in this particular order when provided as a list):" << endl;
137  cout << " initial image value: to set the uniform voxel value for the initial image" << endl;
138  cout << " denominator threshold: to set the threshold of the data space denominator under which the ratio is set to 1" << endl;
139  cout << " minimum image update: to set the minimum of the image update factor under which it stays constant (0 or a negative value" << endl;
140  cout << " means no minimum thus allowing a 0 update)" << endl;
141  cout << " maximum image update: to set the maximum of the image update factor over which it stays constant (0 or a negative value means" << endl;
142  cout << " no maximum)" << endl;
143 }
144 
145 // =====================================================================
146 // ---------------------------------------------------------------------
147 // ---------------------------------------------------------------------
148 // =====================================================================
149 
151 {
152  string key_word = "";
153  string buffer = "";
154  // Read the initial image value option
155  key_word = "initial image value";
156  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_initialValue, 1, KEYWORD_MANDATORY))
157  {
158  Cerr("***** iOptimizerPenalizedPreconditionedGradientML::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
159  return 1;
160  }
161  // Read the denominator threshold option
162  key_word = "denominator threshold";
163  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_dataSpaceDenominatorThreshold, 1, KEYWORD_MANDATORY))
164  {
165  Cerr("***** iOptimizerPenalizedPreconditionedGradientML::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
166  return 1;
167  }
168  // Read the minimum image update option
169  key_word = "minimum image update";
170  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_minimumImageUpdateFactor, 1, KEYWORD_MANDATORY))
171  {
172  Cerr("***** iOptimizerPenalizedPreconditionedGradientML::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
173  return 1;
174  }
175  // Read the maximum image update option
176  key_word = "maximum image update";
177  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_maximumImageUpdateFactor, 1, KEYWORD_MANDATORY))
178  {
179  Cerr("***** iOptimizerPenalizedPreconditionedGradientML::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
180  return 1;
181  }
182  // Normal end
183  return 0;
184 }
185 
186 // =====================================================================
187 // ---------------------------------------------------------------------
188 // ---------------------------------------------------------------------
189 // =====================================================================
190 
192 {
193  // There are 4 floating point variables as options
194  const int nb_options = 4;
195  FLTNB options[nb_options];
196  // Read them
197  if (ReadStringOption(a_optionsList, options, nb_options, ",", "PenalizedPreconditionedGradient configuration"))
198  {
199  Cerr("***** iOptimizerPenalizedPreconditionedGradientML::ReadOptionsList() -> Failed to correctly read the list of options !" << endl);
200  return 1;
201  }
202  // Affect options
203  m_initialValue = options[0];
204  m_dataSpaceDenominatorThreshold = options[1];
205  m_minimumImageUpdateFactor = options[2];
206  m_maximumImageUpdateFactor = options[3];
207  // Normal end
208  return 0;
209 }
210 
211 // =====================================================================
212 // ---------------------------------------------------------------------
213 // ---------------------------------------------------------------------
214 // =====================================================================
215 
217 {
218  // Check that initial image value is strictly positive
219  if (m_initialValue<=0.)
220  {
221  Cerr("***** iOptimizerPenalizedPreconditionedGradientML->CheckSpecificParameters() -> Provided initial image value (" << m_initialValue << ") must be strictly positive !" << endl);
222  return 1;
223  }
224  // Check that denominator threshold value is strictly positive
226  {
227  Cerr("***** iOptimizerPenalizedPreconditionedGradientML->CheckSpecificParameters() -> Provided data space denominator threshold (" << m_dataSpaceDenominatorThreshold << ") must be strictly positive !" << endl);
228  return 1;
229  }
230  // Check that maximum image update factor is higher than the minimum
232  {
233  Cerr("***** iOptimizerPenalizedPreconditionedGradientML->CheckSpecificParameters() -> Provided minimum/maximum (" << m_minimumImageUpdateFactor << "/" << m_maximumImageUpdateFactor << " are inconsistent !" << endl);
234  return 1;
235  }
236  // Normal end
237  return 0;
238 }
239 
240 // =====================================================================
241 // ---------------------------------------------------------------------
242 // ---------------------------------------------------------------------
243 // =====================================================================
244 
246 {
247  // Allocate and create the penalty image
249  // Loop over time basis functions
250  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
251  {
253  // Loop over respiratory basis functions
254  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
255  {
257  // Loop over cardiac basis functions
258  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
259  {
260  // Get a pointer to a newly allocated image
261  m4p_firstDerivativePenaltyImage[tbf][rbf][cbf] = mp_ImageSpace -> AllocateMiscellaneousImage();
262  // Initialize to 0, in case the penalty is not used
263  for (INTNB v=0; v<mp_ImageDimensionsAndQuantification->GetNbVoxXYZ(); v++) m4p_firstDerivativePenaltyImage[tbf][rbf][cbf][v] = 0.;
264  }
265  }
266  }
267  // Allocate and create the second order derivative penalty image
269  // Loop over time basis functions
270  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
271  {
273  // Loop over respiratory basis functions
274  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
275  {
277  // Loop over cardiac basis functions
278  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
279  {
280  // Get a pointer to a newly allocated image
281  m4p_secondDerivativePenaltyImage[tbf][rbf][cbf] = mp_ImageSpace -> AllocateMiscellaneousImage();
282  // Initialize to 0, in case the penalty is not used
284  }
285  }
286  }
287  // Verbose
288  if (m_verbose>=2)
289  {
290  Cout("iOptimizerPenalizedPreconditionedGradientML::InitializeSpecific() -> Use the Penalized Preconditioned Gradient optimizer" << endl);
291  if (m_verbose>=3)
292  {
293  Cout(" --> Initial image value: " << m_initialValue << endl);
294  Cout(" --> Data space denominator threshold: " << m_dataSpaceDenominatorThreshold << endl);
295  if (m_minimumImageUpdateFactor>0.) Cout(" --> Minimum image update factor: " << m_minimumImageUpdateFactor << endl);
296  else Cerr("!!!!! The minimum update value is not set, if using subsets, voxels could be trapped in 0 value causing some negative bias !" << endl);
297  if (m_maximumImageUpdateFactor>0.) Cout(" --> Maximum image update factor: " << m_maximumImageUpdateFactor << endl);
298  }
299  }
300  // Normal end
301  return 0;
302 }
303 
304 // =====================================================================
305 // ---------------------------------------------------------------------
306 // ---------------------------------------------------------------------
307 // =====================================================================
308 
310 {
311  // ==========================================================================================
312  // If no penalty, then exit (the penalty image term has been initialized to 0)
313  if (mp_Penalty==NULL) return 0;
314  // Set the number of threads
315  #ifdef CASTOR_OMP
317  #endif
318  // Verbose
319  if (m_verbose>=1) Cout("iOptimizerPenalizedPreconditionedGradientML::PreImageUpdateSpecificStep() -> Compute penalty term" << endl);
320  // ==========================================================================================
321  // Global precomputation step if needed by the penalty
323  {
324  Cerr("***** iOptimizerPenalizedPreconditionedGradientML::PreImageUpdateSpecificStep() -> A problem occurred while computing the penalty pre-processing step !" << endl);
325  return 1;
326  }
327  // ==========================================================================================
328  // Loop over time basis functions
329  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
330  {
331  // Loop over respiratory basis functions
332  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
333  {
334  // Loop over cardiac basis functions
335  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
336  {
337  // In order to detect problems in the multi-threaded loop
338  bool problem = false;
339  // Voxel index
340  INTNB v;
341  // Multi-threading over voxels
342  #pragma omp parallel for private(v) schedule(guided)
344  {
345  // Get the thread index
346  int th = 0;
347  #ifdef CASTOR_OMP
348  th = omp_get_thread_num();
349  #endif
350  // Local precomputation step if needed by the penalty
351  if (mp_Penalty->LocalPreProcessingStep(tbf,rbf,cbf,v,th))
352  {
353  Cerr("***** iOptimizerPenalizedPreconditionedGradientML::PreImageUpdateSpecificStep() -> A problem occurred while computing the penalty local pre-processing step for voxel " << v << " !" << endl);
354  problem = true;
355  }
356  // Compute first and second derivative order penalty terms
357  m4p_firstDerivativePenaltyImage[tbf][rbf][cbf][v] = mp_Penalty->ComputeFirstDerivative(tbf,rbf,cbf,v,th);
358  m4p_secondDerivativePenaltyImage[tbf][rbf][cbf][v] = mp_Penalty->ComputeSecondDerivative(tbf,rbf,cbf,v,th);
359  }
360  // Check for problems
361  if (problem)
362  {
363  Cerr("***** iOptimizerPenalizedPreconditionedGradientML::PreImageUpdateSpecificStep() -> A problem occurred inside the multi-threaded loop, stop now !" << endl);
364  return 1;
365  }
366  }
367  }
368  }
369  // Normal end
370  return 0;
371 }
372 
373 // =====================================================================
374 // ---------------------------------------------------------------------
375 // ---------------------------------------------------------------------
376 // =====================================================================
377 
379  FLTNB a_multiplicativeCorrections, FLTNB a_additiveCorrections, FLTNB a_blankValue,
380  FLTNB a_quantificationFactor, oProjectionLine* ap_Line )
381 {
382  // Line weight here is simply 1
383  *ap_weight = 1.;
384  // That's all
385  return 0;
386 }
387 
388 // =====================================================================
389 // ---------------------------------------------------------------------
390 // ---------------------------------------------------------------------
391 // =====================================================================
392 
394  FLTNB a_multiplicativeCorrections, FLTNB a_additiveCorrections, FLTNB a_blankValue,
395  FLTNB a_quantificationFactor, oProjectionLine* ap_Line )
396 {
397  // Truncate data to 0 if negative
398  if (a_data<0.) a_data = 0.;
399  // Compute numerator
400  FLTNB numerator = a_data - a_forwardModel;
401 
402  // If the foward model is too close to zero, then ignore this data (set to 0 as the update is additive)
403  if (a_forwardModel>m_dataSpaceDenominatorThreshold) *ap_backwardValues = numerator / a_forwardModel;
404  else *ap_backwardValues = 0.;
405 /*
406  // Compute denominator that will be strictly positive
407  FLTNB denominator = max(a_forwardModel,m_dataSpaceDenominatorThreshold);
408  // Update backward values
409  *ap_backwardValues = numerator / denominator;
410 */
411  // That's all
412  return 0;
413 
414 }
415 
416 // =====================================================================
417 // ---------------------------------------------------------------------
418 // ---------------------------------------------------------------------
419 // =====================================================================
420 
422  FLTNB a_sensitivity, FLTNB* ap_correctionValues,
423  INTNB a_voxel, int a_tbf, int a_rbf, int a_cbf )
424 {
425  // Note 1: the penalty terms are divided by the current number of subsets, for balance.
426  // Note 2: we did not deal with negative values as suggest by original Johan's paper, because it traps voxels
427  // into 0 value. We rather compute a multiplicative update factor and limit its range as for MLEM.
428  // Compute numerator
429  FLTNB numerator = *ap_correctionValues - m4p_firstDerivativePenaltyImage[a_tbf][a_rbf][a_cbf][a_voxel] / (FLTNB)mp_nbSubsets[m_currentIteration];
430  // Compute denominator
431  FLTNB denominator = a_sensitivity + a_currentImageValue * m4p_secondDerivativePenaltyImage[a_tbf][a_rbf][a_cbf][a_voxel] / (FLTNB)mp_nbSubsets[m_currentIteration];
432  // Compute multiplicative image update factor
433  FLTNB image_update_factor = 1. + numerator / denominator;
434  // Apply minimum image update factor
435  if ( m_minimumImageUpdateFactor > 0. && image_update_factor < m_minimumImageUpdateFactor ) image_update_factor = m_minimumImageUpdateFactor;
436  // Apply maximum image update factor
437  if ( m_maximumImageUpdateFactor > 0. && image_update_factor > m_maximumImageUpdateFactor ) image_update_factor = m_maximumImageUpdateFactor;
438  // Update image
439  *ap_newImageValue = a_currentImageValue * image_update_factor;
440  // Check if it is a number, if not, keep the value unchanged
441  if (!isfinite(*ap_newImageValue)) *ap_newImageValue = a_currentImageValue;
442  // End
443  return 0;
444 }
445 
446 // =====================================================================
447 // ---------------------------------------------------------------------
448 // ---------------------------------------------------------------------
449 // =====================================================================
int m_requiredPenaltyDerivativesOrder
Definition: vOptimizer.hh:697
int GetNbCardBasisFunctions()
Get the number of cardiac basis functions.
#define FLTNB
Definition: gVariables.hh:81
FLTNB m_initialValue
Definition: vOptimizer.hh:663
Declaration of class iOptimizerPenalizedPreconditionedGradient.
bool m_listmodeCompatibility
Definition: vOptimizer.hh:664
int ReadOptionsList(const string &a_optionsList)
A function used to read options from a list of options.
void ShowHelpSpecific()
A function used to show help about the child optimizer.
oImageDimensionsAndQuantification * mp_ImageDimensionsAndQuantification
Definition: vOptimizer.hh:669
~iOptimizerPenalizedPreconditionedGradientML()
The destructor of iOptimizerPenalizedPreconditionedGradientML.
virtual int LocalPreProcessingStep(int a_tbf, int a_rbf, int a_cbf, INTNB a_voxel, int a_th)
A public function computing a local pre-processing step for the penalty.
Definition: vPenalty.cc:149
int GetNbTimeBasisFunctions()
Get the number of time basis functions.
virtual FLTNB ComputeSecondDerivative(int a_tbf, int a_rbf, int a_cbf, INTNB a_voxel, int a_th)=0
A public function computing the second derivative of the penalty (the two derivatives are according t...
#define Cerr(MESSAGE)
int CheckSpecificParameters()
A private function used to check the parameters settings specific to the child optimizer.
bool m_emissionCompatibility
Definition: vOptimizer.hh:666
iOptimizerPenalizedPreconditionedGradientML()
The constructor of iOptimizerPenalizedPreconditionedGradientML.
int SensitivitySpecificOperations(FLTNB a_data, FLTNB a_forwardModel, FLTNB *ap_weight, FLTNB a_multiplicativeCorrections, FLTNB a_additiveCorrections, FLTNB a_blankValue, FLTNB a_quantificationFactor, oProjectionLine *ap_Line)
This function compute the weight associated to the provided event (for sensitivity computation) ...
int m_nbBackwardImages
Definition: vOptimizer.hh:659
virtual int GlobalPreProcessingStep()
A public function computing a global pre-processing step for the penalty.
Definition: vPenalty.cc:138
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...
Definition: gOptions.cc:129
bool m_histogramCompatibility
Definition: vOptimizer.hh:665
int ImageSpaceSpecificOperations(FLTNB a_currentImageValue, FLTNB *ap_newImageValue, FLTNB a_sensitivity, FLTNB *ap_correctionValues, INTNB a_voxel, int a_tbf=-1, int a_rbf=-1, int a_cbf=-1)
This function perform the image update step specific to the optimizer.
int ReadConfigurationFile(const string &a_configurationFile)
A function used to read options from a configuration file.
#define KEYWORD_MANDATORY
Definition: gOptions.hh:47
#define INTNB
Definition: gVariables.hh:92
bool m_transmissionCompatibility
Definition: vOptimizer.hh:667
This class is designed to generically described any iterative optimizer.
Definition: vOptimizer.hh:59
This class is designed to manage and store system matrix elements associated to a vEvent...
vPenalty * mp_Penalty
Definition: vOptimizer.hh:698
int m_currentIteration
Definition: vOptimizer.hh:678
Declaration of class sOutputManager.
oImageSpace * mp_ImageSpace
Definition: vOptimizer.hh:670
int GetNbThreadsForImageComputation()
Get the number of threads used for image operations.
INTNB GetNbVoxXYZ()
Get the total number of voxels.
int DataSpaceSpecificOperations(FLTNB a_data, FLTNB a_forwardModel, FLTNB *ap_backwardValues, FLTNB a_multiplicativeCorrections, FLTNB a_additiveCorrections, FLTNB a_blankValue, FLTNB a_quantificationFactor, oProjectionLine *ap_Line)
This function performs the data space operations specific to the optimizer (computes the values to be...
int * mp_nbSubsets
Definition: vOptimizer.hh:677
#define Cout(MESSAGE)
int PreImageUpdateSpecificStep()
A private function used to compute the penalty term of the PreconditionedGradientMAP algorithm...
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.
Definition: gOptions.cc:50
int GetNbRespBasisFunctions()
Get the number of respiratory basis functions.
virtual FLTNB ComputeFirstDerivative(int a_tbf, int a_rbf, int a_cbf, INTNB a_voxel, int a_th)=0
A public function computing the derivative of the penalty.
int InitializeSpecific()
This function is used to initialize specific stuff to the child optimizer.