CASToR  3.2
Tomographic Reconstruction (PET/SPECT/CT)
iOptimizerADMMLim.cc
Go to the documentation of this file.
1 
8 #include "iOptimizerADMMLim.hh"
9 #include "sOutputManager.hh"
10 
11 // =====================================================================
12 // ---------------------------------------------------------------------
13 // ---------------------------------------------------------------------
14 // =====================================================================
15 
16 
18 {
19  // ---------------------------
20  // Mandatory member parameters
21  // ---------------------------
22 
23  // Initial value at 1
24  m_initialValue = 1.;
25  // Two additional backward images in addition to default one
27  // ADMMLim accepts penalties
29  // ADMMLim is compatible with histogram data
32  // ADMMLim is specific to emission data
35 
36  // ADMMLim needs pre process loop to initialize v (compute v^0)
37  m_needPreIteration = true;
38  // ADMMLim needs 3 sub steps: gradient computation, image update using the gradient, v and u update
39  m_nbSubSteps = 3;
40 
41  // --------------------------
42  // Specific member parameters
43  // --------------------------
44 
46  mp_outputIterations = NULL;
47 
48  m2p_rBackgroundEvents = NULL;
49  m2p_yData = NULL;
50 
51  m_alpha = -1.;
52  m_previousAlpha = -1.;
53  m_tau = -1.;
54  m_mu = -1.;
55  m_tauMax = -1.;
56  m_xi = -1.;
57 
58  m4p_currentGrad = NULL;
60  m2p_toWrite_uk = NULL;
61  m2p_toWrite_vk = NULL;
62  m2p_vectorAx = NULL;
63 
64  mp_gradSquareNorm = NULL;
65  mp_projGradSquareNorm = NULL;
69  mp_primalSquareNorm = NULL;
70  mp_squareNorm_Ax = NULL;
71  mp_squareNorm_v = NULL;
72 
75  m_saveSinogramsUAndV = false;
78  m_firstIteration = -1;
80 }
81 
82 // =====================================================================
83 // ---------------------------------------------------------------------
84 // ---------------------------------------------------------------------
85 // =====================================================================
86 
88 {
89  // Note: there is no need to deallocate the images themselves as they are allocate using the
90  // miscellaneous image function from the image space, which automatically deals with
91  // memory deallocations.
92  // Delete the first order derivative penalty image and other variables indexed on basis functions
94  {
95  // Loop over time basis functions
97  {
99  {
100  // Loop over respiratory basis functions
101  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
102  {
103  if (m4p_firstDerivativePenaltyImage[tbf][rbf])
104  {
105  free(m4p_firstDerivativePenaltyImage[tbf][rbf]);
106  }
107  }
109  }
110  }
112  }
113  // Deallocate all pointers
115  {
117  {
118  if (m2p_rBackgroundEvents[th])
119  {
120  free(m2p_rBackgroundEvents[th]);
121  }
122  }
123  free(m2p_rBackgroundEvents);
124  }
125  if (m2p_yData)
126  {
128  {
129  if (m2p_yData[th])
130  {
131  free(m2p_yData[th]);
132  }
133  }
134  free(m2p_yData);
135  }
136  if (m4p_currentGrad)
137  {
138  // Loop over time basis functions
139  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
140  {
141  if (m4p_currentGrad[tbf])
142  {
143  // Loop over respiratory basis functions
144  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
145  {
146  if (m4p_currentGrad[tbf][rbf])
147  {
148  free(m4p_currentGrad[tbf][rbf]);
149  }
150  }
151  free(m4p_currentGrad[tbf]);
152  }
153  }
154  free(m4p_currentGrad);
155  }
158  {
159  // Loop over time frames
160  for (int fr=0; fr<mp_ImageDimensionsAndQuantification->GetNbTimeFrames(); fr++)
161  {
163  {
164  // Loop over respiratory gates
165  for (int rg=0; rg<mp_ImageDimensionsAndQuantification->GetNbRespGates(); rg++)
166  {
167  if (m4p_currentGradFrameGates[fr][rg])
168  {
169  free(m4p_currentGradFrameGates[fr][rg]);
170  }
171  }
172  free(m4p_currentGradFrameGates[fr]);
173  }
174  }
176  }
177  if (m2p_toWrite_uk)
178  {
179  free(m2p_toWrite_uk);
180  }
181  if (m2p_toWrite_vk)
182  {
183  free(m2p_toWrite_vk);
184  }
185  if (m2p_vectorAx)
186  {
187  free(m2p_vectorAx);
188  }
189  if (mp_gradSquareNorm)
190  {
191  free(mp_gradSquareNorm);
192  }
194  {
195  free(mp_projGradSquareNorm);
196  }
198  {
200  }
202  {
203  free(mp_primalSquareNorm);
204  }
205  if (mp_squareNorm_Ax)
206  {
207  free(mp_squareNorm_Ax);
208  }
209  if (mp_squareNorm_v)
210  {
211  free(mp_squareNorm_v);
212  }
213 }
214 
215 // =====================================================================
216 // ---------------------------------------------------------------------
217 // ---------------------------------------------------------------------
218 // =====================================================================
219 
221 {
222  cout << "This optimizer is the ADMM with non-negativity constraint on projection space." << endl;
223  cout << "It was proposed by H. Lim, Y. K. Dewaraja, and J. A. Fessler, Physics " << endl;
224  cout << "in Medicine & Biology, 2018. It is for now developed without subsets" << endl;
225  cout << "It is implemented using the same equations as in the original paper, also with one inner iteration." << endl;
226  cout << "The algorithm can be used without penalty, with quadratic penalty or with Markov Random Field (MRF) penalty with quadratic potential." << endl;
227  cout << "The following options can be used according to the paper by B. Wohlberg in 2017 on residual balancing:" << endl;
228  cout << " - alpha: to set the convergence speed of the ADMM with penalty parameter alpha." << endl;
229  cout << " - adaptive : to set which parameters are adaptive (must be set to nothing, alpha, or tau (which means alpha and tau))." << endl;
230  cout << " - mu: factor to balance primal and dual residual in adaptive alpha computation." << endl;
231  cout << " - tau: Factor to multiply alpha in adaptive alpha computation." << endl;
232  cout << " - xi: Factor to balance primal and dual residual convergence speed in adaptive tau computation." << endl;
233  cout << " - tau_max: Maximum value of the tau factor to multiply alpha in adaptive alpha and tau computation." << endl;
234  cout << " - stoppingCriterionValue: Error value for a stopping criterion based on small residuals. 0 means no stopping criterion." << endl;
235  cout << " - saveSinogramsUAndV: to save or not the computed sinograms u and v for same iterations as image " << endl;
236  cout << " (sinograms u and v can only be saved for non TOF data)" << endl;
237  cout << "Adviced practice is to use adaptive tau with xi to 1 or to use adaptive alpha with mu to 10 and tau to 2." << endl;
238  cout << "A stopping criterion can be used (both residuals smaller than 1e-4 for instance)." << endl;
239  cout << "This optimizer was extended from Lim et al. paper to dynamic frame by frame reconstruction, and TOF reconstruction. However, " << endl;
240  cout << "only one alpha value can be derived from ADMM as the likelihood is maximized over all the frames/TOF bins at the same time." << endl;
241  cout << "Adviced practice is to launch a frame by frame reconstruction with one CASToR command line per frame, so that alpha is adapted to the frame." << endl;
242  cout << "Moreover, the use of time basis functions may not make sense as, for some of them, the image can have negative voxels (as the optimizer" << endl;
243  cout << "allows for it), which can be meaningless from a physiological or physical point of view." << endl;
244  cout << "TOF reconstruction is not expected to converge quicker than non-TOF reconstruction as alpha is related to the constraint Ax=v" << endl;
245  cout << "which is harder to satisfy for several TOF bins." << endl;
246  cout << "N.B.: The first iteration will not update the image if no penalty is used or if a uniform initialization image is used with MRF penalty." << endl;
247  cout << "In these cases, it will only change sinograms u and v." << endl;}
248 
249 // =====================================================================
250 // ---------------------------------------------------------------------
251 // ---------------------------------------------------------------------
252 // =====================================================================
253 
254 int iOptimizerADMMLim::ReadConfigurationFile(const string& a_configurationFile)
255 {
256  string key_word = "";
257  string buffer = "";
258  // Read the penalty parameter alpha
259  key_word = "alpha";
260  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_alpha, 1, KEYWORD_MANDATORY))
261  {
262  Cerr("***** iOptimizerADMMLim::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
263  return 1;
264  }
265  // Read which parameters are adaptive
266  key_word = "adaptive";
267  if (ReadDataASCIIFile(a_configurationFile, key_word, &buffer, 1, KEYWORD_MANDATORY))
268  {
269  Cerr("***** iOptimizerADMMLim::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
270  return 1;
271  }
272  if (buffer == "nothing")
273  {
275  }
276  else if (buffer == "alpha")
277  {
279  }
280  else if (buffer == "both")
281  {
283  }
284  // Read the factor mu
285  key_word = "mu";
286  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_mu, 1, KEYWORD_MANDATORY))
287  {
288  Cerr("***** iOptimizerADMMLim::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
289  return 1;
290  }
291  // Read the factor tau
292  key_word = "tau";
293  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_tau, 1, KEYWORD_MANDATORY))
294  {
295  Cerr("***** iOptimizerADMMLim::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
296  return 1;
297  }
298  // Read the factor xi
299  key_word = "xi";
300  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_xi, 1, KEYWORD_MANDATORY))
301  {
302  Cerr("***** iOptimizerADMMLim::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
303  return 1;
304  }
305  // Read the factor tau_max
306  key_word = "tau_max";
307  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_tauMax, 1, KEYWORD_MANDATORY))
308  {
309  Cerr("***** iOptimizerADMMLim::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
310  return 1;
311  }
312  // Read the value of the stopping criterion
313  key_word = "stoppingCriterionValue";
314  if (ReadDataASCIIFile(a_configurationFile, key_word, &m_stoppingCriterionValue, 1, KEYWORD_MANDATORY))
315  {
316  Cerr("***** iOptimizerADMMLim::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
317  return 1;
318  }
319  // Read if the sinograms u and v must be saved
320  key_word = "saveSinogramsUAndV";
321  if (ReadDataASCIIFile(a_configurationFile, key_word, &buffer, 1, KEYWORD_MANDATORY))
322  {
323  Cerr("***** iOptimizerADMMLim::ReadConfigurationFile() -> Failed to get the '" << key_word << "' keyword !" << endl);
324  return 1;
325  }
326  if (buffer == "true")
327  {
329  }
330  else if (buffer == "false")
331  {
333  }
334  // Normal end
335  return 0;
336 }
337 
338 // =====================================================================
339 // ---------------------------------------------------------------------
340 // ---------------------------------------------------------------------
341 // =====================================================================
342 
343 int iOptimizerADMMLim::ReadOptionsList(const string& a_optionsList)
344 {
345  // There are 8 floating point variables as option
346  const int nb_options = 8;
347  FLTNB options[nb_options];
348  // Read them
349  if (ReadStringOption(a_optionsList, options, nb_options, ",", "ADMMLim configuration"))
350  {
351  Cerr("***** iOptimizerADMMLim::ReadOptionsList() -> Failed to correctly read the list of options !" << endl);
352  return 1;
353  }
354  // Affect options
355  m_alpha = options[0];
356  m_adaptiveParameters = options[1];
357  m_mu = options[2];
358  m_tau = options[3];
359  m_xi = options[4];
360  m_tauMax = options[5];
361  m_stoppingCriterionValue = options[6];
362  m_saveSinogramsUAndV = options[7];
363  // Normal end
364  return 0;
365 }
366 
367 // =====================================================================
368 // ---------------------------------------------------------------------
369 // ---------------------------------------------------------------------
370 // =====================================================================
371 
373 {
374  // Check adaptive parameters
376  {
377  Cerr("***** iOptimizerADMMLim::CheckSpecificParameters() -> Should provide which parameters are adaptive (nothing, alpha or tau)" << endl);
378  return 1;
379  }
380  // Check the user provided a boolean to save sinograms or not
382  {
383  Cerr("***** iOptimizerADMMLim::CheckSpecificParameters() -> Please specify if sinograms u and v need to be stored (put 0 or 1)" << endl);
384  return 1;
385  }
386  //
388  {
389  Cerr("!!!!! iOptimizerADMMLim::CheckSpecificParameters() -> There are several frames or gates, thus sinograms u and v will not be stored even if asked by the user" << endl);
390  }
391  // Check that penalty parameter alpha is positive
392  if (m_alpha<=0.)
393  {
394  Cerr("***** iOptimizerADMMLim->CheckSpecificParameters() -> Provided alpha (" << m_alpha << ") must be strictly positive !" << endl);
395  return 1;
396  }
397  // Check that factor mu is positive
398  if (m_mu<=0.)
399  {
400  Cerr("***** iOptimizerADMMLim->CheckSpecificParameters() -> Provided mu (" << m_mu << ") must be strictly positive !" << endl);
401  return 1;
402  }
403  // Check that factor tau is greater than 1
404  if (m_tau<=1.)
405  {
406  Cerr("***** iOptimizerADMMLim->CheckSpecificParameters() -> Provided tau (" << m_tau << ") must be strictly greater than 1 !" << endl);
407  return 1;
408  }
409  // Check that factor xi is positive
410  if (m_xi<=0.)
411  {
412  Cerr("***** iOptimizerADMMLim->CheckSpecificParameters() -> Provided xi (" << m_xi << ") must be strictly positive !" << endl);
413  return 1;
414  }
415  // Check that factor tau_max is greater than 1
416  if (m_tauMax<=1.)
417  {
418  Cerr("***** iOptimizerADMMLim->CheckSpecificParameters() -> Provided tau (" << m_tauMax << ") must be strictly greater than 1 !" << endl);
419  return 1;
420  }
421  // Check that value of the stopping criterion is positive
423  {
424  Cerr("***** iOptimizerADMMLim->CheckSpecificParameters() -> Provided value for the stopping criterion (" << m_stoppingCriterionValue << ") must be positive !" << endl);
425  return 1;
426  }
427  // Check that number of additional data is 0 (if no initialization of v and u) or 2 (if user initialized v and u).
429  {
430  Cerr("!!!!! iOptimizerADMMLim::CheckSpecificParameters() -> ADMMLim does not require additional data, except if you want to initialize u^k and v^k !" << endl);
431  }
433  {
434  Cerr("***** iOptimizerADMMLim::CheckSpecificParameters() -> ADMMLim requires 2 additional data for u^k and v^k if they need to be initialized, or nothing !" << endl);
435  return 1;
436  }
437  // Cannot deal with list-mode or transmission data
439  {
440  Cerr("***** iOptimizerADMMLim->CheckSpecificParameters() -> Cannot reconstruct list-mode or transmission data !" << endl);
441  return 1;
442  }
443  // Normal end
444  return 0;
445 }
446 
447 // =====================================================================
448 // ---------------------------------------------------------------------
449 // ---------------------------------------------------------------------
450 // =====================================================================
451 
453 {
454  // -------------------------------------------------------------------
455  // First, check if a Penalty has been initialized
456  if (mp_Penalty != NULL)
457  {
458  // Check that Penalty is MRF
459  if (mp_Penalty->GetPenaltyID() == "MRF")
460  {
461  // If Penalty is MRF, now check potential function is quadratic
462  if ((dynamic_cast<iPenaltyMarkovRandomField*>(mp_Penalty))->GetPotentialType() != MRF_POTENTIAL_QUADRATIC)
463  {
464  Cerr("***** iOptimizerADMMLim->InitializeSpecific() -> This optimizer is only compatible with Quadratic potential function for the Markov Random Field penalty (MRF) penalty !" << endl);
465  Cerr(" Please use 'potential function: quadratic' in the penalty initialization file!" << endl);
466  return 1;
467  }
468  }
469  else if (mp_Penalty->GetPenaltyID() != "QUAD")
470  {
471  Cerr("***** iOptimizerADMMLim->InitializeSpecific() -> This optimizer is only compatible with the Markov Random Field (MRF) penalty, with quadratic penalty, or without any penalty !" << endl);
472  return 1;
473  }
474  }
475  // Allocate and create the penalty image
477  // Loop over time basis functions
478  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
479  {
481  // Loop over respiratory basis functions
482  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
483  {
485  // Loop over cardiac basis functions
486  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
487  {
488  // Get a pointer to a newly allocated image
489  m4p_firstDerivativePenaltyImage[tbf][rbf][cbf] = mp_ImageSpace -> AllocateMiscellaneousImage();
490  // Initialize to 0, in case the penalty is not used
491  for (INTNB v=0; v<mp_ImageDimensionsAndQuantification->GetNbVoxXYZ(); v++) m4p_firstDerivativePenaltyImage[tbf][rbf][cbf][v] = 0.;
492  }
493  }
494  }
495 
496  // Allocate and initialize useful variables for u and v computation (as many as threads and as many as TOF bins)
500  {
501  m2p_rBackgroundEvents[th] = (FLTNB*)malloc(m_nbTOFBins*sizeof(FLTNB));
502  m2p_yData[th] = (FLTNB*)malloc(m_nbTOFBins*sizeof(FLTNB));
503  for (int b=0; b<m_nbTOFBins; b++)
504  {
505  m2p_rBackgroundEvents[th][b] = 0.;
506  m2p_yData[th][b] = 0.;
507  }
508  }
509 
510  // Allocate norms related to gradient and penalty strength (as many as threads)
517 
518  // Allocate and create the whole gradient at previous and current iteration, which need to be stored to compute forward projection and norms of them
520  // Loop over time basis functions
521  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
522  {
524  // Loop over respiratory basis functions
525  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
526  {
528  // Loop over cardiac basis functions
529  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
530  {
532  // Get a pointer to a newly allocated image
533  m4p_currentGrad[tbf][rbf][cbf] = mp_ImageSpace -> AllocateMiscellaneousImage();
534  // Initialize to 0
535  for (INTNB v=0; v<mp_ImageDimensionsAndQuantification->GetNbVoxXYZ(); v++) m4p_currentGrad[tbf][rbf][cbf][v] = 0.;
536  }
537  }
538  }
539 
541  // Allocate gradient indexed on frames and gates
543  // Loop over time frames
544  for (int fr=0; fr<mp_ImageDimensionsAndQuantification->GetNbTimeFrames(); fr++)
545  {
547  // Loop over respiratory gates
548  for (int rg=0; rg<mp_ImageDimensionsAndQuantification->GetNbRespGates(); rg++)
549  {
551  // Loop over cardiac gates
552  for (int cg=0; cg<mp_ImageDimensionsAndQuantification->GetNbCardGates(); cg++)
553  {
554  // Get a pointer to a newly allocated image
555  m4p_currentGradFrameGates[fr][rg][cg] = mp_ImageSpace -> AllocateMiscellaneousImage();
556  }
557  }
558  }
559  //*/
560 
561  // Allocate and create the sinograms u and v if no are provided. Otherwise use pointers to additional data memory space
562  m2p_toWrite_uk = (FLTNB**)malloc(m_nbTOFBins*sizeof(FLTNB*));
563  m2p_toWrite_vk = (FLTNB**)malloc(m_nbTOFBins*sizeof(FLTNB*));
564  // Check if sinograms u and v were given by user
566  else u_and_v_from_user = false;
567  // Loop on TOF bins
568  for (int b=0; b<m_nbTOFBins; b++)
569  {
570  if (u_and_v_from_user)
571  {
574  }
575  else
576  {
579  }
580  }
581  // Allocate and create the gradient projection for each TOF bin
582  m2p_vectorAx = (FLTNB**)malloc(m_nbTOFBins*sizeof(FLTNB*));
583  for (int b=0; b<m_nbTOFBins; b++)
584  {
586  }
587 
588  // Verbose
590  {
591  Cout("iOptimizerADMMLim::InitializeSpecific() -> Use the ADMMLim optimizer" << endl);
593  {
594  Cout(" --> Initial image value: " << m_initialValue << endl);
596  {
597  Cout(" --> Adaptive mode: " << "not adaptive" << endl);
598  }
600  {
601  Cout(" --> Adaptive mode: " << "adaptive alpha" << endl);
602  }
604  {
605  Cout(" --> Adaptive mode: " << "adaptive alpha and tau" << endl);
606  }
607  Cout(" --> Initial penalty parameter alpha: " << m_alpha << endl);
608  Cout(" --> Initial penalty parameter tau: " << m_tau << endl);
610  {
611  Cout(" --> Factor mu: " << m_mu << endl);
612  Cout(" --> Factor xi: " << m_xi << endl);
613  }
615  {
616  Cout(" --> Maximum tau value (if adaptive alpha and tau) tau_max: " << m_tau << endl);
617  }
618  if (m_stoppingCriterionValue > 0.)
619  {
620  Cout(" --> Stopping criterion is used" << endl);
621  }
622  }
623  }
624  // Normal end
625  return 0;
626 }
627 
628 // =====================================================================
629 // ---------------------------------------------------------------------
630 // ---------------------------------------------------------------------
631 // =====================================================================
632 
634 {
635  // Check the user asked only for one subset (check only first subset of mp_nbSubsets for the sake of simplicity)
636  // CASToR's structure does not enable to access the number of subsets in CheckSpecificParameters(), so we do it here
637  if (mp_nbSubsets[0] != 1)
638  {
639  Cerr("***** iOptimizerADMMLim::PreDataUpdateSpecificStep() -> Should provide only one subset !" << endl);
640  // Leave computation
641  return 1;
642  }
643  // Initialize previous value of alpha to alpha
645  // Initialize norm of gradient
646  if (m_currentSubStep == 0)
647  {
649  {
650  mp_gradSquareNorm[th] = 0.;
651  }
652  }
653  // Initialize norms for relative primal residual computation
655  if (m_currentSubStep == 2)
656  {
658  {
659  mp_primalSquareNorm[th] = 0.;
660  mp_squareNorm_Ax[th] = 0.;
661  mp_squareNorm_v[th] = 0.;
662  }
663  }
664 
666  // Check if the stopping criterion is reached
668  {
671  }
672  // Write file with residuals and iteration and stop the computation if stopping criterion is reached
674  {
675  // Get the path for the file for the stopping criterion
676  sOutputManager* p_outputManager = sOutputManager::GetInstance();
677  string temps_ss_stop = p_outputManager->GetPathName() + p_outputManager->GetBaseName();
678  // Show both relative residuals and the final iteration
679  temps_ss_stop += "_adaptive_stopping_criteria.log";
680  ofstream outfile;
681  outfile.open(temps_ss_stop);
682  outfile << setprecision(numeric_limits<FLTNB>::digits10 + 1);
683  outfile << "final outer iteration : " << endl;
684  outfile << m_currentIteration << endl;
685  outfile << "relPrimal : " << endl;
686  outfile << sqrt(m_relativePrimalSquareNorm) << endl;
687  outfile << "relDual : " << endl;
688  outfile << sqrt(m_relativeDualSquareNorm) << endl;
689  outfile.close();
690  // Leave computation
691  Cerr("!!!!! Stopping criterion reached ! Leaving computation." << endl);
692  return 1;
693  }
694  // Check if we are in first computed iteration, will be used to initialize sinogram u and v if user provieded some
696  {
699  }
700 
701  if (m_currentSubStep == 1)
702  {
703  // Loop over time frames
704  for (int fr=0; fr<mp_ImageDimensionsAndQuantification->GetNbTimeFrames(); fr++)
705  {
706  // Loop over respiratory gates
707  for (int rg=0; rg<mp_ImageDimensionsAndQuantification->GetNbRespGates(); rg++)
708  {
709  // Loop over cardiac gates
710  for (int cg=0; cg<mp_ImageDimensionsAndQuantification->GetNbCardGates(); cg++)
711  {
712  // Voxel index
713  INTNB v;
714  // Multi-threading over voxels
715  #pragma omp parallel for private(v) schedule(guided)
717  {
718  // Clean buffer (gradient over frames and gates)
719  m4p_currentGradFrameGates[fr][rg][cg][v] = 0.;
720  }
721  }
722  }
723  }
724  }
725 
726  if (m_currentSubStep == 1)
727  {
728  // Initialize norm of gradient projection before x update
730  {
731  mp_projGradSquareNorm[th] = 0.;
732  }
733  // Apply convolver to gradient image because image-based PSF not inside ForwardProject function
734  // Loop over time basis functions
735  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
736  {
737  // Loop over respiratory basis functions
738  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
739  {
740  // Loop over cardiac basis functions
741  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
742  {
744  {
745  Cerr("***** iOptimizerADMMLim::PreDataUpdateSpecificStep() -> A problem occurred while applying image convolver to gradient image !" << endl);
746  return 1;
747  }
748  }
749  }
750  }
751 
752  // Loop over time frames
753  for (int fr=0; fr<mp_ImageDimensionsAndQuantification->GetNbTimeFrames(); fr++)
754  {
755  // Loop over respiratory gates
756  for (int rg=0; rg<mp_ImageDimensionsAndQuantification->GetNbRespGates(); rg++)
757  {
758  // Loop over cardiac gates
759  for (int cg=0; cg<mp_ImageDimensionsAndQuantification->GetNbCardGates(); cg++)
760  {
761  // Loop over time basis functions
762  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
763  {
764  // Get frame/basis coefficient and continue if null
766  if (time_basis_coef==0.) continue;
767  // Loop over respiratory basis functions
768  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
769  {
770  // Get resp_gate/basis coefficient and continue if null
772  if (resp_basis_coef==0.) continue;
773  // Loop over cardiac basis functions
774  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
775  {
776  // Get card_gate_basis coefficient and continue if null
778  if (card_basis_coef==0.) continue;
779  // Compute global coefficient
780  FLTNB global_basis_coef = time_basis_coef * resp_basis_coef * card_basis_coef;
781 
782  // Voxel index
783  INTNB v;
784  // Multi-threading over voxels
785  #pragma omp parallel for private(v) schedule(guided)
787  {
788  // Apply dynamic coefficient conversion
789  m4p_currentGradFrameGates[fr][rg][cg][v] += m4p_currentGrad[tbf][rbf][cbf][v] * global_basis_coef;
790  }
791  }
792  }
793  }
794  }
795  }
796  }
797 
798  // If penalty is MRF with quadratic potential, compute finite difference matrix times gradient vector : Cg
799  // Its norm is needed for stepsize computation to update image x with quadratic MRF penalty
800  if (mp_Penalty != NULL)
801  {
802  if (mp_Penalty->GetPenaltyID() == "MRF")
803  {
804  // Initialize norm of penalty gradient
806  {
807  mp_penaltyGradSquareNorm[th] = 0.;
808  }
809  // Loop over time frames
810  for (int fr=0; fr<mp_ImageDimensionsAndQuantification->GetNbTimeFrames(); fr++)
811  {
812  // Loop over respiratory gates
813  for (int rg=0; rg<mp_ImageDimensionsAndQuantification->GetNbRespGates(); rg++)
814  {
815  // Loop over cardiac gates
816  for (int cg=0; cg<mp_ImageDimensionsAndQuantification->GetNbCardGates(); cg++)
817  {
818  // Loop over time basis functions
819  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
820  {
821  // Get frame/basis coefficient and continue if null
823  if (time_basis_coef==0.) continue;
824  // Loop over respiratory basis functions
825  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
826  {
827  // Get resp_gate/basis coefficient and continue if null
829  if (resp_basis_coef==0.) continue;
830  // Loop over cardiac basis functions
831  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
832  {
833  // Get card_gate_basis coefficient and continue if null
835  if (card_basis_coef==0.) continue;
836  // Compute global coefficient
837  FLTNB global_basis_coef = time_basis_coef * resp_basis_coef * card_basis_coef;
838  // In order to detect problems in the multi-threaded loop
839  bool problem = false;
840  // Voxel index
841  INTNB v;
842  // Multi-threading over voxels
843  #pragma omp parallel for private(v) schedule(guided)
845  {
846  // Get the thread index
847  int th = 0;
848  #ifdef CASTOR_OMP
849  th = omp_get_thread_num();
850  #endif
851  // Local precomputation step if needed by the penalty
852  if (mp_Penalty->LocalPreProcessingStep(tbf,rbf,cbf,v,th))
853  {
854  Cerr("***** iOptimizerADMMLim::PreDataUpdateSpecificStep() -> A problem occurred while computing the penalty local pre-processing step for voxel " << v << " !" << endl);
855  problem = true;
856  }
857  // Compute finite difference matrix times gradient vector Cg (its norm will be used in the image update step).
858  // We need to loop over basis functions to do the local pre processing step in MRF penalty
859  // Apply dynamic coefficient conversion
860  mp_penaltyGradSquareNorm[th] += 2. * (HPFLTNB)mp_Penalty->ComputePenaltyValue(m4p_currentGrad[tbf][rbf][cbf],v,th) * global_basis_coef;
861  //mp_penaltyGradSquareNorm[fr][rg][cg] += 2. * (HPFLTNB)mp_Penalty->ComputePenaltyValue(m4p_currentGradFrameGates[fr][rg][cg],v,th) * global_basis_coef;
862  }
863  // Check for problems
864  if (problem)
865  {
866  Cerr("***** iOptimizerADMMLim::PreImageUpdateSpecificStep() -> A problem occurred inside the multi-threaded loop, stop now !" << endl);
867  return 1;
868  }
869  }
870  }
871  }
872  }
873  }
874  }
875  }
876  }
877  }
878  // Normal end
879  return 0;
880 }
881 
882 // =====================================================================
883 // ---------------------------------------------------------------------
884 // ---------------------------------------------------------------------
885 // =====================================================================
886 
888  int a_bed, int a_timeFrame, int a_respGate, int a_cardGate,
889  int a_th )
890 {
891  // Loop on TOF bins
892  for (int b=0; b<ap_Line->GetNbTOFBins(); b++)
893  {
894  // Set the current TOF bin of the projection-line
895  ap_Line->SetCurrentTOFBin(b);
896  // Scale u if alpha has just changed (cf Boyd et al. 2011 paper on ADMM, Foundations and Trends in Machine Learning, paragraph 3.4.1)
897  if (m_currentSubStep == 0)
898  {
899  FLTNB coeff_alpha = m_alpha / m_previousAlpha;
900  m2p_toWrite_uk[b][ap_Line->GetEventIndex()] /= coeff_alpha;
901  }
902  }
903  // End
904  return 0;
905 }
906 
907 // =====================================================================
908 // ---------------------------------------------------------------------
909 // ---------------------------------------------------------------------
910 // =====================================================================
911 
912 int iOptimizerADMMLim::SensitivitySpecificOperations( FLTNB a_data, FLTNB a_forwardModel, FLTNB* ap_weight,
913  FLTNB a_multiplicativeCorrections, FLTNB a_additiveCorrections, FLTNB a_blankValue,
914  FLTNB a_quantificationFactor, oProjectionLine* ap_Line )
915 {
916  // Line weight here is simply 1
917  *ap_weight = 1;
918  // That's all
919  return 0;
920 }
921 
922 // =====================================================================
923 // ---------------------------------------------------------------------
924 // ---------------------------------------------------------------------
925 // =====================================================================
926 
928  int a_bed, int a_timeFrame, int a_respGate, int a_cardGate,
929  int a_th )
930 {
931  // Override dataStep5 to get Ax, y, r_bar (depending on thread) for u and v computation
932  // Loop on TOF bins
933  for (int b=0; b<ap_Line->GetNbTOFBins(); b++)
934  {
935  // Set the current TOF bin of the projection-line
936  ap_Line->SetCurrentTOFBin(b);
937  // Compute Ax = (Ax+r_bar) - r_bar after x update, and store data and background events without backprojection for v computation
939  {
940  m2p_yData[a_th][b] = (FLTNB)ap_Event->GetEventValue(b);
941  // Truncate data to 0 if negative
942  if (m2p_yData[a_th][b]<0.) m2p_yData[a_th][b] = 0.;
944  m2p_vectorAx[b][ap_Line->GetEventIndex()] = (HPFLTNB)m2p_forwardValues[a_th][b] - (HPFLTNB)m2p_rBackgroundEvents[a_th][b];
945  }
946 
947  // Backward project (Ax - v + u) to get the gradient
948  if (m_currentSubStep == 0 && !m_isInPreIteration) m3p_backwardValues[a_th][b][0] = m2p_vectorAx[b][ap_Line->GetEventIndex()] - (HPFLTNB)m2p_toWrite_vk[b][ap_Line->GetEventIndex()] + (HPFLTNB)m2p_toWrite_uk[b][ap_Line->GetEventIndex()];
949  }
950  // End
951  return 0;
952 }
953 
954 // =====================================================================
955 // ---------------------------------------------------------------------
956 // ---------------------------------------------------------------------
957 // =====================================================================
958 
959 int iOptimizerADMMLim::DataSpaceSpecificOperations( FLTNB a_data, FLTNB a_forwardModel, FLTNB* ap_backwardValues,
960  FLTNB a_multiplicativeCorrections, FLTNB a_additiveCorrections, FLTNB a_blankValue,
961  FLTNB a_quantificationFactor, oProjectionLine* ap_Line )
962 {
963  // Not useful in this optimizer, this function is directly written in DataStep5ComputeCorrections function as used variables depend on the current thread
964  // Still need to define it as it is a pure virtual function in vOptimizer
965  return 0;
966 }
967 
968 // =====================================================================
969 // ---------------------------------------------------------------------
970 // ---------------------------------------------------------------------
971 // =====================================================================
972 
974  int a_bed, int a_timeFrame, int a_respGate, int a_cardGate,
975  int a_th )
976 {
977  // Initialize v^0 and u^0 if optimizer is at pre iteration and no additional data was given (using Ax computed in dataStep5)
978  // Loop on TOF bins
979  for (int b=0; b<ap_Line->GetNbTOFBins(); b++)
980  {
981  // Set the current TOF bin of the projection-line
982  ap_Line->SetCurrentTOFBin(b);
983  // Initialize v^0 and u^0 if optimizer is at pre iteration and no additional data was given (using Ax computed in dataStep5)
985  {
986  m2p_toWrite_vk[b][ap_Line->GetEventIndex()] = (FLTNB)m2p_vectorAx[b][ap_Line->GetEventIndex()];
987  m2p_toWrite_uk[b][ap_Line->GetEventIndex()] = 0.;
988  }
989  }
990  // Compute norm of projection of gradient for stepsize in x update
991  HPFLTNB proj_grad_i_b = 0.;
992  // Loop over TOF bins
993  for (int b=0; b<ap_Line->GetNbTOFBins(); b++)
994  {
995  // Set the current TOF bin
996  ap_Line->SetCurrentTOFBin(b);
997  // Project the gradient and apply dynamic coefficient conversion
998  if (m_currentSubStep == 1)
999  {
1000  // Compute norm of projection of gradient for stepsize in x update
1001  proj_grad_i_b = (HPFLTNB)ForwardProject(ap_Line,m4p_currentGradFrameGates[a_timeFrame][a_respGate][a_cardGate]);
1002  mp_projGradSquareNorm[a_th] += proj_grad_i_b * proj_grad_i_b;
1003  }
1004  // Compute v and u after x update
1005  else if (m_currentSubStep == 2 && !m_isInPostIteration)
1006  {
1008  FLTNB previous_vk_current_event = m2p_toWrite_vk[b][ap_Line->GetEventIndex()];
1009 
1010  FLTNB beta = 0.5 * (1 / m_alpha + m2p_rBackgroundEvents[a_th][b] - m2p_toWrite_uk[b][ap_Line->GetEventIndex()] - (FLTNB)m2p_vectorAx[b][ap_Line->GetEventIndex()]);
1011  FLTNB gamma = m2p_rBackgroundEvents[a_th][b] * (m2p_toWrite_uk[b][ap_Line->GetEventIndex()] + (FLTNB)m2p_vectorAx[b][ap_Line->GetEventIndex()]) - (m2p_rBackgroundEvents[a_th][b] - m2p_yData[a_th][b]) / m_alpha;
1012  FLTNB v_hat = 0.;
1013  FLTNB beta_square_gamma = beta * beta + gamma;
1014  // Compute v_hat solution given the value of y
1015  if (m2p_yData[a_th][b] == 0.)
1016  {
1017  v_hat = (FLTNB)m2p_vectorAx[b][ap_Line->GetEventIndex()] + m2p_toWrite_uk[b][ap_Line->GetEventIndex()] - 1 / m_alpha;
1018  }
1019  else
1020  {
1021  // Check for numerical instabilities. beta*beta+gamma should theoretically be positive. Set it to 0 otherwise
1022  if (beta_square_gamma < 0.)
1023  {
1024  Cerr("!!!!! beta * beta + gamma = " << beta_square_gamma << " , should not be negative ! Perhaps it is a case of divergence." << endl);
1025  beta_square_gamma = 0.;
1026  }
1027  // Compute v_hat using formula proposed in Lim et al. to avoid numerical instability of the quadratic solution
1028  v_hat = (beta < 0.) ? sqrt(beta_square_gamma) - beta :gamma / (sqrt(beta_square_gamma) + beta);
1029  }
1030  // Correct v if it does not respect the constraint (Ax+r>0) and store its value in sinogram
1031  m2p_toWrite_vk[b][ap_Line->GetEventIndex()] = ((v_hat + m2p_rBackgroundEvents[a_th][b]) > 0.) ? v_hat : -m2p_rBackgroundEvents[a_th][b];
1032 
1034  // Update sinogram u (u^{k+1} := u^k + Ax^{k+1} - v^{k+1}) and store it
1035  m2p_toWrite_uk[b][ap_Line->GetEventIndex()] = m2p_toWrite_uk[b][ap_Line->GetEventIndex()] + (FLTNB)m2p_vectorAx[b][ap_Line->GetEventIndex()] - m2p_toWrite_vk[b][ap_Line->GetEventIndex()];
1036 
1038  // Store sinograms u^{k+1} and v^{k+1}-v^k to be backward projected for residuals computation
1039  m3p_backwardValues[a_th][b][1] = m2p_toWrite_uk[b][ap_Line->GetEventIndex()];
1040  m3p_backwardValues[a_th][b][2] = m2p_toWrite_vk[b][ap_Line->GetEventIndex()] - previous_vk_current_event;
1041 
1043  HPFLTNB diff = (HPFLTNB)m2p_vectorAx[b][ap_Line->GetEventIndex()] - (HPFLTNB)m2p_toWrite_vk[b][ap_Line->GetEventIndex()];
1044  mp_primalSquareNorm[a_th] += diff * diff;
1045  mp_squareNorm_Ax[a_th] += (HPFLTNB)m2p_vectorAx[b][ap_Line->GetEventIndex()]*(HPFLTNB)m2p_vectorAx[b][ap_Line->GetEventIndex()];
1046  mp_squareNorm_v[a_th] += (HPFLTNB)m2p_toWrite_vk[b][ap_Line->GetEventIndex()]*(HPFLTNB)m2p_toWrite_vk[b][ap_Line->GetEventIndex()];
1047  }
1048  }
1049  // End
1050  return 0;
1051 }
1052 
1053 // =====================================================================
1054 // ---------------------------------------------------------------------
1055 // ---------------------------------------------------------------------
1056 // =====================================================================
1057 
1059 {
1060  // Merge multi-threads results to get final norms
1061  if (m_currentSubStep == 1)
1062  {
1064  {
1068  }
1069  }
1070  else if (m_currentSubStep == 2)
1071  {
1073  {
1076  mp_squareNorm_v[0] += mp_squareNorm_v[th];
1077  }
1078  }
1079 
1080  // Residuals computation
1081  if (m_currentSubStep == 2)
1082  {
1084  int no_thread = 0;
1085  // Find the biggest norm between the 2 previously computed
1086  FLTNB primalMax = (mp_squareNorm_Ax[no_thread] > mp_squareNorm_v[no_thread]) ? mp_squareNorm_Ax[no_thread] : mp_squareNorm_v[no_thread];
1087  // Norm of relative primal residual
1088  m_relativePrimalSquareNorm = mp_primalSquareNorm[no_thread] / primalMax;
1090  FLTNB dual_norm = 0.;
1091  FLTNB AtuSquareNorm = 0.;
1092  FLTNB AtvvSquareNorm = 0.;
1093  // Loop over time basis functions
1094  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
1095  {
1096  // Loop over respiratory basis functions
1097  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
1098  {
1099  // Loop over cardiac basis functions
1100  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
1101  {
1102  int no_thread = 0.;
1103  // Loop over voxels
1104  for (int v=0; v<mp_ImageDimensionsAndQuantification->GetNbVoxXYZ(); v++)
1105  {
1106  // Calculate norm of Atu and Atvv
1107  AtuSquareNorm += mp_ImageSpace->m6p_backwardImage[1][no_thread][tbf][rbf][cbf][v]*mp_ImageSpace->m6p_backwardImage[1][no_thread][tbf][rbf][cbf][v];
1108  AtvvSquareNorm += mp_ImageSpace->m6p_backwardImage[2][no_thread][tbf][rbf][cbf][v]*mp_ImageSpace->m6p_backwardImage[2][no_thread][tbf][rbf][cbf][v];
1109  }
1110  }
1111  }
1112  }
1113  // Compute norm of dual residual and relative one
1114  dual_norm = m_alpha * sqrt(AtvvSquareNorm);
1115  m_relativeDualSquareNorm = AtvvSquareNorm / AtuSquareNorm;
1116 
1118  // Define and compute relative dual residual which will be computed for current frame and gates
1120  {
1122  {
1123  // Compute adaptive tau (if asked for) to compute adaptive alpha for next iteration
1124  FLTNB adaptiveTau = sqrt((1/m_xi)*sqrt(sqrt(m_relativePrimalSquareNorm)/ sqrt(m_relativeDualSquareNorm)));
1125  if (adaptiveTau >= 1 && adaptiveTau < m_tauMax)
1126  {
1127  m_tau = adaptiveTau;
1128  }
1129  else if (adaptiveTau >= (1/m_tauMax) && adaptiveTau < 1)
1130  {
1131  m_tau = 1/adaptiveTau;
1132  }
1133  else
1134  {
1135  m_tau = m_tauMax;
1136  }
1137  }
1138  // Store current alpha value
1140  // Compute adaptive alpha for next iteration (if asked for)
1142  {
1144  {
1145  m_alpha *= m_tau;
1146  }
1147  else if (sqrt(m_relativeDualSquareNorm) > (1/m_xi)*m_mu*sqrt(m_relativePrimalSquareNorm))
1148  {
1149  m_alpha /= m_tau;
1150  }
1151  else
1152  {
1153  m_alpha = m_alpha;
1154  }
1155  }
1156  }
1157 
1159  if (m_optimizerFOMFlag)
1160  {
1161  // Get the path for the FOMs file
1162  sOutputManager* p_outputManager = sOutputManager::GetInstance();
1163  string temps_ss_alpha = p_outputManager->GetPathName() + p_outputManager->GetBaseName();
1164  // Show both alpha and the obtained adaptive alpha for next iteration, and residuals
1165  temps_ss_alpha += "_adaptive_it" + std::to_string(m_currentIteration + 1) + ".log";
1166  ofstream outfile;
1167  outfile.open(temps_ss_alpha);
1168  outfile << setprecision(numeric_limits<FLTNB>::digits10 + 1);
1169  outfile << "adaptive alpha : " << endl;
1170  outfile << m_alpha << endl;
1171  outfile << "adaptive tau" << endl;
1172  outfile << m_tau << endl;
1173  outfile << "relPrimal" << endl;
1174  outfile << sqrt(m_relativePrimalSquareNorm) << endl;
1175  outfile << "relDual" << endl;
1176  outfile << sqrt(m_relativeDualSquareNorm) << endl;
1177  outfile << "primal residual" << endl;
1178  int no_thread = 0;
1179  outfile << sqrt(mp_primalSquareNorm[no_thread]) << endl;
1180  outfile << "dual residual" << endl;
1181  outfile << dual_norm << endl;
1182  outfile.close();
1183  }
1184 
1186  // Compute true iteration (without subiterations)
1187  sOutputManager* p_outputManager = sOutputManager::GetInstance();
1190  {
1192  {
1193  int first_TOF_bin = 0;
1194  if (m2p_toWrite_vk)
1195  {
1196  // Write sinogram v^{k+1} with provided directory and filename
1197  stringstream temp_ss_v;
1198  temp_ss_v << p_outputManager->GetPathName() << p_outputManager->GetBaseName() << "_v_it" << m_currentIteration+1 << ".img";
1199  string a_pathToImg_v = temp_ss_v.str();
1200  IntfWriteImage(a_pathToImg_v, m2p_toWrite_vk[first_TOF_bin], mp_DataFile->GetNbEvents(), m_verbose);
1201  }
1202  if (m2p_toWrite_uk)
1203  {
1204  // Write sinogram u^{k+1} with provided directory and filename, to be used in next Python iteration
1205  stringstream temp_ss_u;
1206  temp_ss_u << p_outputManager->GetPathName() << p_outputManager->GetBaseName() << "_u_it" << m_currentIteration+1 << ".img";
1207  string a_pathToImg_u = temp_ss_u.str();
1208  IntfWriteImage(a_pathToImg_u, m2p_toWrite_uk[first_TOF_bin], mp_DataFile->GetNbEvents(), m_verbose);
1209  }
1210  }
1211  }
1212 
1213 
1214  }
1215 
1217  // ==========================================================================================
1218  // If no penalty, then exit (the penalty image term has been initialized to 0)
1219  if (mp_Penalty==NULL) return 0;
1220  // Set the number of threads
1221  #ifdef CASTOR_OMP
1223  #endif
1224  // Verbose
1225  if (m_verbose>=1) Cout("iOptimizerADMMLim::PreImageUpdateSpecificStep() -> Compute penalty term" << endl);
1226  // ==========================================================================================
1227  // Global precomputation step if needed by the penalty
1229  {
1230  Cerr("***** iOptimizerADMMLim::PreImageUpdateSpecificStep() -> A problem occurred while computing the penalty pre-processing step !" << endl);
1231  return 1;
1232  }
1233  // ==========================================================================================
1234  // Loop over time basis functions
1235  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
1236  {
1237  // Loop over respiratory basis functions
1238  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
1239  {
1240  // Loop over cardiac basis functions
1241  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
1242  {
1243  // In order to detect problems in the multi-threaded loop
1244  bool problem = false;
1245  // Voxel index
1246  INTNB v;
1247  // Multi-threading over voxels
1248  #pragma omp parallel for private(v) schedule(guided)
1250  {
1251  // Get the thread index
1252  int th = 0;
1253  #ifdef CASTOR_OMP
1254  th = omp_get_thread_num();
1255  #endif
1256  // Local precomputation step if needed by the penalty
1257  if (mp_Penalty->LocalPreProcessingStep(tbf,rbf,cbf,v,th))
1258  {
1259  Cerr("***** iOptimizerADMMLim::PreImageUpdateSpecificStep() -> A problem occurred while computing the penalty local pre-processing step for voxel " << v << " !" << endl);
1260  problem = true;
1261  }
1262  // Compute first derivative order penalty terms
1264  }
1265  // Check for problems
1266  if (problem)
1267  {
1268  Cerr("***** iOptimizerADMMLim::PreImageUpdateSpecificStep() -> A problem occurred inside the multi-threaded loop, stop now !" << endl);
1269  return 1;
1270  }
1271  }
1272  }
1273  }
1274  // Normal end
1275  return 0;
1276 }
1277 
1278 // =====================================================================
1279 // ---------------------------------------------------------------------
1280 // ---------------------------------------------------------------------
1281 // =====================================================================
1282 
1284 {
1285  // Verbose
1286  if (m_verbose>=2 || m_optimizerImageStatFlag) Cout("iOptimizerADMMLim::ImageUpdateStep() -> Start image update" << endl);
1287  // Number of spaces for nice printing
1288  string spaces_tbf = ""; if (mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions()>1) spaces_tbf += " ";
1289  string spaces_rbf = ""; if (mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions()>1) spaces_rbf += " ";
1290  string spaces_cbf = ""; if (mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions()>1) spaces_cbf += " ";
1291  // Loop over time basis functions
1292  for (int tbf=0; tbf<mp_ImageDimensionsAndQuantification->GetNbTimeBasisFunctions(); tbf++)
1293  {
1294  // Verbose
1296  {
1297  if (mp_ImageDimensionsAndQuantification->GetTimeStaticFlag()) Cout(spaces_tbf << "--> Time frame " << tbf+1 << endl);
1298  else Cout(spaces_tbf << "--> Time basis function: " << tbf+1 << endl);
1299  }
1300  // Loop over respiratory basis functions
1301  for (int rbf=0; rbf<mp_ImageDimensionsAndQuantification->GetNbRespBasisFunctions(); rbf++)
1302  {
1303  // Verbose
1305  {
1306  if (mp_ImageDimensionsAndQuantification->GetRespStaticFlag()) Cout(spaces_tbf << spaces_rbf << "--> Respiratory gate " << rbf+1 << endl);
1307  else Cout(spaces_tbf << spaces_rbf << "--> Respiratory basis function: " << rbf+1 << endl);
1308  }
1309  // Loop over cardiac basis functions
1310  for (int cbf=0; cbf<mp_ImageDimensionsAndQuantification->GetNbCardBasisFunctions(); cbf++)
1311  {
1312  // Verbose
1314  {
1315  if (mp_ImageDimensionsAndQuantification->GetCardStaticFlag()) Cout(spaces_tbf << spaces_rbf << spaces_cbf << "--> Cardiac gate " << cbf+1 << endl);
1316  else Cout(spaces_tbf << spaces_rbf << spaces_cbf << "--> Cardiac basis function: " << cbf+1 << endl);
1317  }
1318  // Set the number of threads for image computation
1319  #ifdef CASTOR_OMP
1321  #endif
1322  // Reset counter for image stats
1324  {
1326  {
1327  mp_imageStatNbVox[th] = 0;
1328  mp_imageStatMin[th] = mp_ImageSpace->m4p_image[tbf][rbf][cbf][0];
1329  mp_imageStatMax[th] = mp_ImageSpace->m4p_image[tbf][rbf][cbf][0];
1330  mp_imageStatMean[th] = 0.;
1331  mp_imageStatVariance[th] = 0.;
1332  mp_correctionStatMean[th] = 0.;
1333  mp_correctionStatVariance[th] = 0.;
1334  }
1335  }
1336  // OMP loop over voxels
1337  INTNB v;
1338  bool error = false;
1339  #pragma omp parallel for private(v) schedule(guided)
1340  for (v=0 ; v<mp_ImageDimensionsAndQuantification->GetNbVoxXYZ() ; v++)
1341  {
1342  // Get the thread index
1343  int th = 0;
1344  #ifdef CASTOR_OMP
1345  th = omp_get_thread_num();
1346  #endif
1347  // Compute sensitivity
1348  FLTNB sensitivity = ComputeSensitivity(mp_ImageSpace->m5p_sensitivity[0],tbf,rbf,cbf,v);
1349  // If the sensitivity is negative or null, we skip this voxel
1350  if (sensitivity<=0.) continue;
1351  // Keep the current image value in a buffer for update statistics
1352  FLTNB old_image_value = mp_ImageSpace->m4p_image[tbf][rbf][cbf][v];
1353  // Fill class-local buffer of backward values (thread-safe)
1354  for (int nb=0; nb<m_nbBackwardImages; nb++)
1355  m3p_backwardValues[th][0][nb] = mp_ImageSpace->m6p_backwardImage[nb][0][tbf][rbf][cbf][v];
1356 
1358  FLTNB a_currentImageValue = mp_ImageSpace->m4p_image[tbf][rbf][cbf][v];
1359  FLTNB* apImageValue = &mp_ImageSpace->m4p_image[tbf][rbf][cbf][v];
1360  //FLTNB a_sensitivity = sensitivity;
1361  FLTNB* ap_correctionValues = m3p_backwardValues[th][0];
1362  INTNB a_voxel = v;
1363  int a_tbf = tbf;
1364  int a_rbf = rbf;
1365  int a_cbf = cbf;
1366  int a_th = th;
1367 
1368  // Store gradient for next sub step
1369  int no_thread = 0;
1370  if (m_currentSubStep == 0 && !m_isInPreIteration)
1371  {
1372  // Scale penalty with respect to the number of subsets to get correct balance between likelihood and penalty
1373  HPFLTNB penalty = ((HPFLTNB)(m4p_firstDerivativePenaltyImage[a_tbf][a_rbf][a_cbf][a_voxel])) / ((HPFLTNB)(mp_nbSubsets[m_currentTotalSubIteration]));
1374  // Compute gradient
1375  m4p_currentGrad[a_tbf][a_rbf][a_cbf][a_voxel] = -m_alpha * *ap_correctionValues - penalty;
1376  mp_gradSquareNorm[a_th] += (HPFLTNB)m4p_currentGrad[a_tbf][a_rbf][a_cbf][a_voxel]*(HPFLTNB)m4p_currentGrad[a_tbf][a_rbf][a_cbf][a_voxel];
1377  }
1378  // Compute x update as gradient was updated
1379  if (m_currentSubStep == 1)
1380  {
1381  // Compute part of stepsize depending on penalty type
1382  HPFLTNB added_term = 0.;
1383  // If no penalty, then exit (the penalty image term has been initialized to 0)
1384  if (mp_Penalty==NULL)
1385  {
1386  added_term = 0.;
1387  }
1388  // If penalty is quadratic MRF
1389  else if (mp_Penalty->GetPenaltyID() == "MRF")
1390  {
1391  added_term = mp_penaltyGradSquareNorm[no_thread];
1392  }
1393  else if (mp_Penalty->GetPenaltyID() == "QUAD") // If penalty is quadratic
1394  {
1395  added_term = ((HPFLTNB)mp_Penalty->GetPenaltyStrength()*mp_gradSquareNorm[no_thread]);
1396  }
1397  // Compute stepsize avoiding division by 0
1398  HPFLTNB stepsize = (((HPFLTNB)m_alpha * mp_projGradSquareNorm[no_thread]) + added_term == 0.) ? 0. : mp_gradSquareNorm[no_thread] / (((HPFLTNB)m_alpha * mp_projGradSquareNorm[no_thread]) + added_term);
1399  //stepsize = 0.0001;
1400  // Compute additive image update factor
1401  HPFLTNB additive_image_update_factor = stepsize * (HPFLTNB)m4p_currentGrad[a_tbf][a_rbf][a_cbf][a_voxel];
1402  // Update image value
1403  *apImageValue = (HPFLTNB)a_currentImageValue + additive_image_update_factor;
1404  }
1405 
1406  // Do some image statistics
1408  {
1409  if (mp_imageStatMin[th]>mp_ImageSpace->m4p_image[tbf][rbf][cbf][v]) mp_imageStatMin[th] = mp_ImageSpace->m4p_image[tbf][rbf][cbf][v];
1410  if (mp_imageStatMax[th]<mp_ImageSpace->m4p_image[tbf][rbf][cbf][v]) mp_imageStatMax[th] = mp_ImageSpace->m4p_image[tbf][rbf][cbf][v];
1411  // The one pass algorithm is used to compute the variance here for both the image and correction step.
1412  // Do not change anyhting to the order of the operations !
1413  mp_imageStatNbVox[th]++;
1414  HPFLTNB sample = ((HPFLTNB)(mp_ImageSpace->m4p_image[tbf][rbf][cbf][v]));
1415  HPFLTNB delta = sample - mp_imageStatMean[th];
1416  mp_imageStatMean[th] += delta / ((HPFLTNB)(mp_imageStatNbVox[th]));
1417  mp_imageStatVariance[th] += delta * (sample - mp_imageStatMean[th]);
1418  // Same one-pass variance algorithm for the correction step
1419  sample = ((HPFLTNB)(mp_ImageSpace->m4p_image[tbf][rbf][cbf][v] - old_image_value));
1420  delta = sample - mp_correctionStatMean[th];
1421  mp_correctionStatMean[th] += delta / ((HPFLTNB)(mp_imageStatNbVox[th]));
1422  mp_correctionStatVariance[th] += delta * (sample - mp_correctionStatMean[th]);
1423  }
1424  }
1425  // Check for errors (we cannot stop the multi-threaded loop so we do it here)
1426  if (error) return 1;
1427  // Finish image update statistics computations: these operations are specific to the merging of the
1428  // multi-threaded one-pass variance estimations used above.
1430  {
1432  {
1433  // Image minimum and maximum
1436  // Cast the counts into HPFLTNB
1437  HPFLTNB count1 = ((HPFLTNB)(mp_imageStatNbVox[0]));
1438  HPFLTNB count2 = ((HPFLTNB)(mp_imageStatNbVox[th]));
1439  // Update the count
1441  HPFLTNB count12 = ((HPFLTNB)(mp_imageStatNbVox[0]));
1442  // Compute the delta mean
1443  HPFLTNB delta = mp_imageStatMean[0] - mp_imageStatMean[th];
1444  // Update the variance
1445  mp_imageStatVariance[0] += mp_imageStatVariance[th] + delta * delta * count1 * count2 / count12;
1446  // Update the mean
1447  mp_imageStatMean[0] = (count1*mp_imageStatMean[0] + count2*mp_imageStatMean[th]) / count12;
1448  // Do the same for the correction step
1450  mp_correctionStatVariance[0] += mp_correctionStatVariance[th] + delta * delta * count1 * count2 / count12;
1451  mp_correctionStatMean[0] = (count1*mp_correctionStatMean[0] + count2*mp_correctionStatMean[th]) / count12;
1452  }
1453  // Finish variance computation
1456  // Print out
1457  Cout(spaces_tbf << spaces_rbf << spaces_cbf << " --> Image update step "
1458  << " | mean: " << mp_correctionStatMean[0]
1459  << " | stdv: " << sqrt(mp_correctionStatVariance[0]) << endl);
1460  Cout(spaces_tbf << spaces_rbf << spaces_cbf << " --> New image estimate"
1461  << " | mean: " << mp_imageStatMean[0]
1462  << " | stdv: " << sqrt(mp_imageStatVariance[0])
1463  << " | min/max: " << mp_imageStatMin[0] << "/" << mp_imageStatMax[0] << endl);
1464  }
1465  }
1466  }
1467  }
1468 
1469  // End
1470  return 0;
1471 }
1472 
1473 // =====================================================================
1474 // ---------------------------------------------------------------------
1475 // ---------------------------------------------------------------------
1476 // =====================================================================
1477 
1478 int iOptimizerADMMLim::ImageSpaceSpecificOperations( FLTNB a_currentImageValue, FLTNB* apImageValue,
1479  FLTNB a_sensitivity, FLTNB* ap_correctionValues,
1480  INTNB a_voxel, int a_tbf, int a_rbf, int a_cbf )
1481 {
1482  // Do not call pure virtual function for specific data space operations ImageSpaceSpecificOperations because we need current thread for gradient norm computation
1483  return 0;
1484 }
1485 
1486 // =====================================================================
1487 // ---------------------------------------------------------------------
1488 // ---------------------------------------------------------------------
1489 // =====================================================================
int64_t GetNbEvents()
Get the total number of events in the datafile.
#define ADMMLIM_SAVE_SINOGRAMS
HPFLTNB * mp_projGradSquareNorm
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.
int GetNbCardBasisFunctions()
Get the number of cardiac basis functions.
#define ADMMLIM_NOT_ADAPTIVE
int CheckSpecificParameters()
A private function used to check the parameters settings specific to the child optimizer.
#define Cerr(MESSAGE)
FLTNB GetTimeBasisCoefficient(int a_timeBasisFunction, int a_timeFrame)
oImageDimensionsAndQuantification * mp_ImageDimensionsAndQuantification
FLTNB ComputeSensitivity(FLTNB ****a4p_sensitivityImage, int a_timeBasisFunction, int a_respBasisFunction, int a_cardBasisFunction, int a_voxel)
virtual FLTNB GetAdditiveCorrections(int a_bin)=0
const string & GetPenaltyID()
int ApplyConvolution(FLTNB *ap_image)
A function that simply calls the eponym function from the vImageConvolver.
iOptimizerADMMLim()
The constructor of iOptimizerADMMLim.
virtual int LocalPreProcessingStep(int a_tbf, int a_rbf, int a_cbf, INTNB a_voxel, int a_th)
virtual int DataStep4Optional(oProjectionLine *ap_Line, vEvent *ap_Event, int a_bed, int a_timeFrame, int a_respGate, int a_cardGate, int a_thread)
int GetNbTimeBasisFunctions()
Get the number of time basis functions.
#define ADMMLIM_ADAPTIVE_ALPHA_AND_TAU
HPFLTNB * mp_penaltyGradSquareNorm
FLTNB ForwardProject(oProjectionLine *ap_Line, FLTNB *ap_image=NULL)
static sOutputManager * GetInstance()
Instanciate the singleton object and Initialize member variables if not already done, return a pointer to this object otherwise.
virtual int DataStep6Optional(oProjectionLine *ap_Line, vEvent *ap_Event, int a_bed, int a_timeFrame, int a_respGate, int a_cardGate, int a_thread)
A public function to compute the analytical solution of ADMM equation on v and update u...
FLTNB GetPenaltyStrength()
Get the penalty strength.
bool GetRespStaticFlag()
Get the respiratory static flag that says if the reconstruction has only one respiratory gate or not...
FLTNB * GetNewAdditionalDataMatrix(INTNB a_nbDataPerEvent)
Allocate the memory for this additional data matrix and return the pointer to the matrix...
int ReadConfigurationFile(const string &a_configurationFile)
A function used to read options from a configuration file.
void ShowHelpSpecific()
A function used to show help about the child optimizer.
#define ADMMLIM_NOT_DEFINED
bool GetCardStaticFlag()
Get the cardiac static flag that says if the reconstruction has only one cardiac gate or not...
#define ADMMLIM_DO_NOT_SAVE_SINOGRAMS
FLTNB GetRespBasisCoefficient(int a_respBasisFunction, int a_respGate)
Singleton class that manages output writing on disk (images, sinograms, etc). It also manages loggi...
virtual int GlobalPreProcessingStep()
A public function computing a global pre-processing step for the penalty.
int64_t GetEventIndex()
Get current index associated to the event.
FLTNB GetCardBasisCoefficient(int a_cardBasisFunction, int a_cardGate)
Declaration of class iOptimizerADMMLim.
virtual int DataStep5ComputeCorrections(oProjectionLine *ap_Line, vEvent *ap_Event, int a_bed, int a_timeFrame, int a_respGate, int a_cardGate, int a_thread)
A public function used to compute the correction terms in the data space, for the provided event...
bool GetTimeStaticFlag()
Get the time static flag that says if the reconstruction has only one frame or not.
FLTNB **** m4p_firstDerivativePenaltyImage
#define KEYWORD_MANDATORY
~iOptimizerADMMLim()
The destructor of iOptimizerADMMLim.
int GetNbAdditionalData()
Get the number of additional data.
#define SPEC_TRANSMISSION
FLTNB **** m4p_currentGradFrameGates
virtual FLTNB GetEventValue(int a_bin)=0
This class is designed to generically described any iterative optimizer.
This class is designed to manage and store system matrix elements associated to a vEvent...
int ReadOptionsList(const string &a_optionsList)
A function used to read options from a list of options.
virtual int PreDataUpdateSpecificStep()
A private function used to compute some norms for the image update.
Mother class for the Event objects.
int GetNbThreadsForImageComputation()
Get the number of threads used for image operations.
int PreImageUpdateSpecificStep()
A private function used to compute the penalty term update adaptive alpha of the ADMM algorithm...
int GetNbTOFBins()
This function is used to get the number of TOF bins in use.
virtual int ImageUpdateStep()
A public function used to perform the image update step of the optimizer.
int GetNbThreadsForProjection()
Get the number of threads used for projections.
#define ADMMLIM_ADAPTIVE_ALPHA
virtual FLTNB ComputeFirstDerivative(int a_tbf, int a_rbf, int a_cbf, INTNB a_voxel, int a_th)=0
int InitializeSpecific()
This function is used to initialize specific stuff to the child optimizer.
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 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...
int IntfWriteImage(const string &a_pathToImg, FLTNB *ap_outImgMtx, uint32_t a_dim, int vb)
Write Interfile raw data whose path is provided in parameter, using image matrix provided in paramete...
#define Cout(MESSAGE)
int ImageSpaceSpecificOperations(FLTNB a_currentImageValue, FLTNB *apImageValue, 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 GetNbRespBasisFunctions()
Get the number of respiratory basis functions.
oImageConvolverManager * mp_ImageConvolverManager
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) ...
virtual FLTNB ComputePenaltyValue(int a_tbf, int a_rbf, int a_cbf, INTNB a_voxel, int a_th)=0