CASToR  3.1
Tomographic Reconstruction (PET/SPECT/CT)
oDynamicDataManager.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-2020 all CASToR contributors listed below:
18 
19  --> Didier BENOIT, Claude COMTAT, Marina FILIPOVIC, Thibaut MERLIN, Mael MILLARDET, Simon STUTE, Valentin VIELZEUF, Zacharias CHALAMPALAKIS
20 
21 This is CASToR version 3.1.
22 */
23 
31 #include "oDynamicDataManager.hh"
33 
34 // =====================================================================
35 // ---------------------------------------------------------------------
36 // ---------------------------------------------------------------------
37 // =====================================================================
38 
40 {
41  mp_ID = NULL;
42  m_verbose = -1;
43  m_nbTimeFrames = -1;
44  mp_currentFrameIndex = NULL;
45  m_respGatingFlag = false;
46  m_rMotionCorrFlag = false;
47  m_nbRespGates = -1;
51  m_cardGatingFlag = false;
52  m_cMotionCorrFlag = false;
53  m_nbCardGates = -1;
58  m2p_durationPerGate = NULL;
59  m_pMotionCorrFlag = false;
68 }
69 
70 // =====================================================================
71 // ---------------------------------------------------------------------
72 // ---------------------------------------------------------------------
73 // =====================================================================
74 
76 {
77  for (int fr=0; fr<m_nbTimeFrames; fr++)
78  {
80  delete m2p_nbEventsPerRespGate[fr];
82  delete m2p_nbEventsPerCardGate[fr];
84  delete m2p_durationPerGate[fr];
86  delete m2p_indexLastEventRespGate[fr];
88  delete m2p_indexLastEventCardGate[fr];
91  }
92 
95  if (m2p_durationPerGate!=NULL) delete m2p_durationPerGate;
107 }
108 
109 // =====================================================================
110 // ---------------------------------------------------------------------
111 // ---------------------------------------------------------------------
112 // =====================================================================
113 
114 int oDynamicDataManager::InitDynamicData( int a_nbRespGates, int a_nbCardGates, const string& a_pathTo4DDataFile,
115  int a_rmCorrFlag, int a_cmCorrFlag, int a_pmCorrFlag)
116 {
117  // Verbose
119  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicData() -> Initialize dynamic data management" << endl);
120 
121  // Initialization
123  m_nbRespGates = a_nbRespGates;
124  m_nbCardGates = a_nbCardGates;
126 
127  if (m_nbRespGates > 1) m_respGatingFlag = true;
128  if (m_nbCardGates > 1) m_cardGatingFlag = true;
129  m_rMotionCorrFlag = a_rmCorrFlag;
130  m_cMotionCorrFlag = a_cmCorrFlag;
131  m_pMotionCorrFlag = a_pmCorrFlag;
132 
133  // Instanciation & initialization for current indices (used during the loop on events to know which frame/gate the event belongs to)
138  for (int th=0 ; th<mp_ID->GetNbThreadsForProjection() ; th++)
139  {
140  mp_currentFrameIndex [0] = 0;
141  mp_currentRespGateIndex [0] = 0;
142  mp_currentCardGateIndex [0] = 0;
143  mp_currentPMotionIndex[0] = 0;
144  }
145 
146  // Instanciation & initialization of number of events per frame for gated reconstructions
147  m2p_nbEventsPerRespGate = new int64_t*[m_nbTimeFrames];
148  m2p_nbEventsPerCardGate = new int64_t*[m_nbTimeFrames];
152 
153  for (int fr=0; fr<m_nbTimeFrames; fr++)
154  {
155  m2p_nbEventsPerRespGate[fr] = new int64_t[m_nbRespGates];
158  m2p_indexLastEventRespGate[fr] = new int64_t[m_nbRespGates];
160 
161  for (int rg=0; rg<m_nbRespGates; rg++)
162  {
163  m2p_nbEventsPerRespGate[fr][rg] = -1;
164  m2p_indexLastEventRespGate[fr][rg] = -1;
165 
166  for (int cg=0; cg<m_nbCardGates; cg++)
167  {
168  m2p_durationPerGate[ fr ][ rg*m_nbCardGates + cg ] = -1;
169  m2p_nbEventsPerCardGate[ fr ][ rg*m_nbCardGates + cg ] = -1;
170  m2p_indexLastEventCardGate[ fr ][ rg*m_nbCardGates + cg ] = -1;
171  }
172  }
173  }
174 
175  // Check coherence of motion initialization
176  // Throw error if cardiac gating is enabled alongside respiratory motion correction (and dual-gating motion correction disabled)
177  // TODO : warning in the documentation that Respiratory motion correction is not supported for cardiac-gated data in the current implementation
179  {
180  Cerr("***** oDynamicDataManager::InitDynamicData() -> Respiratory motion correction is enabled for cardiac-gated data. This is not supported in the current implementation !" << endl);
181  return 1;
182  }
183 
184  // Check iPat motion vs physiological motion
185  if (a_pmCorrFlag && (m_rMotionCorrFlag || m_cMotionCorrFlag) )
186  {
187  Cerr("***** oDynamicDataManager::InitDynamicData() -> Involuntary patient image-based motion correction cannot be used along with respiratory or cardiac motion correction !" << endl);
188  return 1;
189  }
190 
191  // Initialization for respiratory/cardiac gated reconstruction
192  // Note : for Analytic Projection, gating flag could be enabled in order to project an image containing several gates, but no description file is required
193  // InitDynamicDataGating() is restricted to reconstruction
194  // Check on the existence of a_pathTo4DDataFile during reconstruction is performed onnly in Grecon
195  if ((m_respGatingFlag || m_cardGatingFlag) && !a_pathTo4DDataFile.empty() )
196  {
197  if(m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicData() -> Initializing data for gated reconstruction... " << endl);
198 
199  if (InitDynamicDataGating(a_pathTo4DDataFile))
200  {
201  Cerr("***** oDynamicDataManager::InitDynamicData() -> A problem occurred during the dynamic gating initialization !" << endl);
202  return 1;
203  }
204  }
205 
206  // Initialization with involuntary patient motion corrected reconstruction
207  if (m_pMotionCorrFlag && InitDynamicDataPatientMotion(a_pathTo4DDataFile) )
208  {
209  Cerr("***** oDynamicDataManager::InitDynamicData() -> A problem occurred during the patient involuntary motion correction initialization !" << endl);
210  return 1;
211  }
212 
213  // Some feedback
215  {
216  if (m_respGatingFlag) Cout("oDynamicDataManager::InitDynamicData() -> Respiratory gating enabled" << endl);
217  if (m_rMotionCorrFlag) Cout("oDynamicDataManager::InitDynamicData() -> Respiratory image-based motion correction enabled" << endl);
218  if (m_cardGatingFlag) Cout("oDynamicDataManager::InitDynamicData() -> Cardiac gating enabled" << endl);
219  if (m_cMotionCorrFlag) Cout("oDynamicDataManager::InitDynamicData() -> Cardiac image-based motion correction enabled" << endl);
220  if (m_pMotionCorrFlag) Cout("oDynamicDataManager::InitDynamicData() -> Involuntary image-based patient motion correction enabled" << endl);
221  }
222 
223  // End
224  return 0;
225 }
226 
227 // =====================================================================
228 // ---------------------------------------------------------------------
229 // ---------------------------------------------------------------------
230 // =====================================================================
231 
232 int oDynamicDataManager::InitDynamicDataGating(const string& a_pathToFile)
233 {
234  // Verbose
236  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataGating() ... " << endl);
237 
238  // Respiratory gating enabled
239  if (m_respGatingFlag)
240  {
241  // Read information about respiratory gating (number of events per respiratory gate per frame)
242  if ( ReadDataASCIIFile(a_pathToFile,
243  "nb_events_respiratory_gates",
245  m_nbRespGates,
248  {
249  Cerr("***** oDynamicDataManager::InitDynamicDataGating() -> Error while trying to read 'nb_events_respiratory_gates' in file '" << a_pathToFile << "' !" << endl);
250  return 1;
251  }
252 
253  // Get the index of the last event of each respiratory gate using the number of events inside each gate
254  uint64_t event_number_sum = 0;
255  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataGating() :" << endl);
256 
257  for (int fr=0; fr<m_nbTimeFrames; fr++)
258  for (int rg=0; rg<m_nbRespGates; rg++)
259  {
260  m2p_indexLastEventRespGate[fr][rg] += m2p_nbEventsPerRespGate[fr][rg] + event_number_sum;
261  event_number_sum += m2p_nbEventsPerRespGate[fr][rg];
262  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> Number of events for frame #" << fr << ", respiratory gate #" << rg << " = " << m2p_nbEventsPerRespGate[fr][rg] << endl);
263  }
264  }
265 
266  // Cardiac gating enabled
267  if (m_cardGatingFlag)
268  {
269  // Read information about cardiac gating (number of events per cardiac gate per respiratory gates times frames)
270  if ( ReadDataASCIIFile(a_pathToFile,
271  "nb_events_cardiac_gates",
276  {
277  Cerr("***** oDynamicDataManager::InitDynamicDataGating() -> Error while trying to read 'nb_events_cardiac_gates' in file '" << a_pathToFile << "' !" << endl);
278  return 1;
279  }
280  // Get the index of the last event of each cardiac gate using the number of events inside each gate
281  uint64_t event_number_sum = 0;
282  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataGating() :" << endl);
283 
284  for (int fr=0; fr<m_nbTimeFrames; fr++)
285  for (int g=0; g<m_nbRespGates*m_nbCardGates; g++)
286  {
287  m2p_indexLastEventCardGate[fr][g] += m2p_nbEventsPerCardGate[fr][g] + event_number_sum;
288  event_number_sum += m2p_nbEventsPerCardGate[fr][g];
289  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> Number of events for frame #" << fr << ", cardiac gate #" << g << " = " << m2p_nbEventsPerCardGate[fr][g] << endl);
290  }
291  }
292 
293  // Read optional information about respiratory gate duration (if provided)
294  if ( ReadDataASCIIFile(a_pathToFile,
295  "duration_gate",
300  {
301  Cerr("***** oDynamicDataManager::InitDynamicDataGating() -> Error while trying to read 'nb_events_respiratory_gates' in file '" << a_pathToFile << "' !" << endl);
302  return 1;
303  }
304 
305  // Flag indicating that gate duration have been provided
306  if (m2p_durationPerGate[0][0]>0)
307  {
310  for (int fr=0; fr<m_nbTimeFrames; fr++)
311  for (int g=0; g<m_nbRespGates*m_nbCardGates; g++)
312  Cout(" --> Provided gate duration, frame #" << fr << ", gate #" << g << ": " << m2p_durationPerGate[fr][g] << endl);
313 
314  }
315 
316  // End
317  return 0;
318 }
319 
320 // =====================================================================
321 // ---------------------------------------------------------------------
322 // ---------------------------------------------------------------------
323 // =====================================================================
324 
326 {
327  // Verbose
329  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataPatientMotion() ... " << endl);
330 
331  // First get the number of time triggers into the dynamic file
332  if (ReadDataASCIIFile(a_pathToFile,
333  "nb_motion_triggers",
335  1,
337  {
338  Cerr("***** oDynamicDataManager::InitDynamicDataPatientMotion() -> Involuntary motion correction enabled but no dynamic configuration file has been provided with the -im option," << endl
339  << "***** or the number of triggers could not be found in the dynamic file '" << a_pathToFile << "' !" << endl);
340  return 1;
341  }
342  // Allocate time triggers and read them from the dynamic file
343  mp_listPMotionTriggers = (uint32_t*)malloc(m_nbPMotionTriggers*sizeof(uint32_t));
344  mp_frameNbPMotionIndexes = (uint16_t*)malloc(m_nbTimeFrames*sizeof(uint16_t));
345  mp_framePMotionFirstIndex = (uint16_t*)malloc(m_nbTimeFrames*sizeof(uint16_t));
346  mp_framePMotionLastIndex = (uint16_t*)malloc(m_nbTimeFrames*sizeof(uint16_t));
347  mp_MotionTriggersIndexInFrame = (int**) malloc(m_nbTimeFrames * sizeof(int*));
349 
350 
351  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
352  {
353  // mp_frameNbPMotionIndexes initialised with 1
354  mp_frameNbPMotionIndexes[fr] = 1;
355  mp_framePMotionLastIndex[fr] = 0;
357  }
358 
359  // Get timestamp of motion trigger
360  if (ReadDataASCIIFile(a_pathToFile,
361  "timestamp_motion_triggers",
365  {
366  Cerr("***** oDynamicDataManager::InitDynamicDataPatientMotion() -> Involuntary motion correction enabled but list of triggers"
367  << " not found in the dynamic file '" << a_pathToFile << "' !" << endl);
368  return 1;
369  }
370 
371  // Convert motion trigger in ms as for timestamp
372  for (int pm=0 ; pm<m_nbPMotionTriggers ; pm++)
373  mp_listPMotionTriggers [ pm ] *= 1000.;
374 
375 
376  // remove motion triggers taking place after the end of the last frame
377  // scan from the end to the first motion trigger and adapt total number of triggers accordingly
378  // we assume that the tags are provided in ascending order ( as required )
379  int new_m_nbPMotionTriggers=m_nbPMotionTriggers;
380  for (int pm=(m_nbPMotionTriggers-1) ; pm>0 ; pm--)
381  if (mp_listPMotionTriggers[pm] >= mp_ID->GetFrameTimeStopInMs(0, m_nbTimeFrames-1))
382  new_m_nbPMotionTriggers--;
383  m_nbPMotionTriggers = new_m_nbPMotionTriggers;
384 
385 
386  // --- Recover the number of transformation involved during each frame --- //
387  // The following lines loop on the motion trigger in order to recover,
388  // for each frame, the first and last patient motion trigger
389 
390  // First check that the first patient motion trigger is 0s (référence)
391  if(mp_listPMotionTriggers[ 0 ] > 0.)
392  {
393  Cerr("***** oDynamicDataManager::InitDynamicDataPatientMotion() -> "
394  <<" Error, the first motion trigger must be 0 ms"
395  <<" (the corresponding transformation parameters are usually set to 0,"
396  <<" as the acquisition start in the reference position!" << endl);
397  return 1;
398  }
399 
400  // First we need to find the number and index of motion triggers within frames ( for intra-frame motion )
401  //
402  // Loop over all frames
403  for (int fr = 0; fr < m_nbTimeFrames; fr++)
404  {
405  // Loop on all motion triggers (MT) and get the number motion index within each frame
406  for (int t = 0; t < m_nbPMotionTriggers; t++)
407  {
410  }
411  // allocate memory for the motion trigger indexes in each frame for mp_frameNbPMotionIndexes
412  mp_MotionTriggersIndexInFrame[fr] = (int*) malloc((mp_frameNbPMotionIndexes[fr] - 1) * sizeof(int));
413  // Populate each mp_MotionTriggersIndexInFrame with the motion trigger indexes
414  int MT_in_frame = 0;
415  for (int t = 0; t < m_nbPMotionTriggers; t++)
416  {
418  {
419  mp_MotionTriggersIndexInFrame[fr][MT_in_frame] = t;
420  MT_in_frame++;
421  }
422  }
423  }
424 
425  // Then we set the first and last motion index for each frame
426  // in this loop we also need to consider motion tags that coincide with frame start times.
427  //
428  // Loop over all frames
429  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
430  {
431  // Loop on all motion triggers (MT) and get the first and last motion index for each frame
432  for (int t = 0; t < m_nbPMotionTriggers; t++)
433  {
434  // If motion trigger t <= starting time of the current frame, we set it as the first motion index
436  {
438  mp_framePMotionLastIndex[fr] = t;
439  }
440  // if higher than frame start time and less that stop time, we update the last motion trigger
441  else if (mp_listPMotionTriggers[t] < mp_ID->GetFrameTimeStopInMs(0, fr))
442  mp_framePMotionLastIndex[fr] = t;
443  // if higher than frame stop time we break and process the next frame
444  else if (mp_listPMotionTriggers[t] >= mp_ID->GetFrameTimeStopInMs(0, fr))
445  break;
446  }
447  }
448 
449  // Recover number of MT for each frame
450  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataPatientMotion() -> First / Last motion trigger index for each dynamic frame: " << endl);
451  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
452  {
454  if (m_verbose>=VERBOSE_DETAIL) Cout("Frm" << fr+1 << " : " << mp_framePMotionFirstIndex[fr] << " / " << mp_framePMotionLastIndex[fr] << endl);
455  }
456 
457  // ===================================================================
458  // Compute the weight of each pm sub-frame within each frame
459  // (ratio subset duration/total frame duration)
460  // Adjust the decay quantification factors for each taking into account each sub-frame
461  // This is only used for list-mode sensitivity image generation where we need to apply the factor to each sub-frame
462  // including application of decay effects
463 
464  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
465  {
467  // Initialise sub-frame weights with one
468  for (int ipm = 0; ipm < mp_frameNbPMotionIndexes[fr]; ipm++)
469  m2p_listPMotionWeightInFrame[fr][ipm]=1;
470  }
471 
472  // Loop on frames, recover subset start/stop and compute weights
473  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
474  {
475  if (mp_frameNbPMotionIndexes[fr] > 1)
476  {
477  // for first sub-frame set start time equal to frame start time
478  uint32_t motion_subset_start_time_ms = mp_ID->GetFrameTimeStartInMs(0, fr);
479  uint32_t motion_subset_stop_time_ms;
480  // loop over motion triggers in frame
481  for (int ipm = 0; ipm < mp_frameNbPMotionIndexes[fr]-1; ipm++)
482  {
483  // set the subset time stop from the stored triggers in Frame
484  motion_subset_stop_time_ms = mp_listPMotionTriggers[ mp_MotionTriggersIndexInFrame[fr][ipm] ] ;
485  // calculate the weight based on duration
487  FLTNB(motion_subset_stop_time_ms - motion_subset_start_time_ms) / mp_ID->GetFrameDurationInMs(0, fr);
488 
489  // update motion_subset_start_time_ms for next sub-frame
490  motion_subset_start_time_ms = mp_listPMotionTriggers[ mp_MotionTriggersIndexInFrame[fr][ipm] ] ;
491  }
492  // Treat the last sub-frame in this frame
493  motion_subset_stop_time_ms = mp_ID->GetFrameTimeStopInMs(0, fr);
494  m2p_listPMotionWeightInFrame[fr][mp_frameNbPMotionIndexes[fr]-1] =
495  FLTNB(motion_subset_stop_time_ms - motion_subset_start_time_ms) / mp_ID->GetFrameDurationInMs(0, fr);
496  }
497  }
498 
499  // Check if an isotope has been provided for the reconstruction
500  // Loop on frames, and uncorrect for application of frame decay and duration
501  if (mp_ID->GetLambda() > 0)
502  {
503  for (int fr = 0; fr < m_nbTimeFrames; fr++)
504  if (mp_frameNbPMotionIndexes[fr] > 1)
505  {
506  long double lambda = mp_ID->GetLambda();
507  long double dstart = lambda * mp_ID->GetFrameTimeStartInSec(0, fr);
508  long double dacq = lambda * mp_ID->GetFrameDurationInSec(0, fr);
509  for (int ipm = 0; ipm < mp_frameNbPMotionIndexes[fr]; ipm++)
510  {
511  // Uncorrect for time start decay ( by multiplying with the decay correction factor )
512  m2p_listPMotionWeightInFrame[fr][ipm] *= exp(dstart);
513  // Uncorrect for frame duration decay ( by multiplying with the decay duration factor )
514  m2p_listPMotionWeightInFrame[fr][ipm] *= dacq / (1.0 - exp(-dacq));
515  }
516  }
517 
518  // Loop on frames and sub-frames, recover subset start/stop and apply sub-frame decay factors
519  for (int fr = 0; fr < m_nbTimeFrames; fr++)
520  if (mp_frameNbPMotionIndexes[fr] > 1)
521  {
522  uint32_t motion_subset_start_time_ms = mp_ID->GetFrameTimeStartInMs(0, fr);
523  uint32_t motion_subset_stop_time_ms;
524  long double lambda = mp_ID->GetLambda();
525  long double dstart ;
526  long double dacq ;
527  for (int ipm = 0; ipm < mp_frameNbPMotionIndexes[fr] - 1; ipm++)
528  {
529  // set the subset time stop from the stored triggers in Frame
530  motion_subset_stop_time_ms = mp_listPMotionTriggers[mp_MotionTriggersIndexInFrame[fr][ipm]];
531  // calculate the decay factors for the subframe
532  dstart = lambda
533  * (long double) motion_subset_start_time_ms
534  * 0.001;
535  dacq = lambda
536  * (long double) (motion_subset_stop_time_ms - motion_subset_start_time_ms)
537  * 0.001;
538  // Then, apply the correct sub-frame factor the pm subset
539  // Time start decay factor
540  m2p_listPMotionWeightInFrame[fr][ipm] /= exp(dstart);
541  // Frame duration decay factor
542  m2p_listPMotionWeightInFrame[fr][ipm] /= dacq / (1.0 - exp(-dacq));
543  // update motion_subset_start_time_ms for next sub-frame
544  motion_subset_start_time_ms = mp_listPMotionTriggers[mp_MotionTriggersIndexInFrame[fr][ipm]];
545  }
546  // Treat the last sub-frame in this frame
547  motion_subset_stop_time_ms = mp_ID->GetFrameTimeStopInMs(0, fr);
548  dstart = lambda
549  * (long double)motion_subset_start_time_ms
550  * 0.001;
551  dacq = lambda
552  * (long double)(motion_subset_stop_time_ms - motion_subset_start_time_ms)
553  * 0.001 ;
554 
555  // Time start decay correction
556  m2p_listPMotionWeightInFrame[fr][mp_frameNbPMotionIndexes[fr]-1] /= exp(dstart);
557  // Frame duration decay correction
558  m2p_listPMotionWeightInFrame[fr][mp_frameNbPMotionIndexes[fr]-1] /= dacq / (1.0 - exp(-dacq));
559 
560  }
561  }
562 
564  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
565  for( int ipm=0 ; ipm<mp_frameNbPMotionIndexes[fr] ; ipm++ )
566  cout << "m2p_listPMotionWeightInFrame[" <<fr+1 << "] [" <<ipm+1 << "] : " << m2p_listPMotionWeightInFrame[fr][ipm] << endl;
567 
568 
569 
570  return 0;
571 }
572 
573 // =====================================================================
574 // ---------------------------------------------------------------------
575 // ---------------------------------------------------------------------
576 // =====================================================================
577 
579 {
581  if (m_verbose>=VERBOSE_NORMAL) Cout("oDynamicDataManager::SetDynamicSpecificQuantificationFactors() ... " << endl);
582 
583  // COMPUTE GATE-SPECIFIC QUANTITATIVE FACTORS
584  if (m_nbRespGates>1 || m_nbCardGates>1)
585  {
586  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
587  {
588  // We have cardiac gates and don't correct for motion -> quantification factors required for cardiac gates
590  {
591  // If duration have been provided (value >0, then normalize according to the duration)
592  if (m2p_durationPerGate[fr][0]>0)
593  {
594  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++)
595  a2p_quantificationFactors[fr][g] /= m2p_durationPerGate[fr][g];
596  }
597  else // Default normalization according to the number of events
598  {
599  uint64_t total_events_in_frame = 0;
600  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++) total_events_in_frame += m2p_nbEventsPerCardGate[fr][g];
601 
603 
604  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++)
605  a2p_quantificationFactors[fr][g] *= (FLTNB)total_events_in_frame/(FLTNB)m2p_nbEventsPerCardGate[fr][g];
606  }
607 
609  {
610  Cout(" --> Cardiac gating correction factors :" << endl);
611  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++)
612  Cout(" Frame #" << fr << ", cardiac gate #" << g << " = " << a2p_quantificationFactors[fr][g] << endl);
613  }
614  }
615 
616  // We have resp gates and don't correct for resp motion (and no independent cardiac gate reconstruction) -> quantification factors required for cardiac gates
618  {
619  // If duration have been provided (value >0, then normalize according to the duration)
620  if (m2p_durationPerGate[fr][0]>0)
621  {
622  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++)
623  {
624  // remove pre-applied frame duration form the quantification factors
625  a2p_quantificationFactors[fr][g] *= ((FLTNB)(mp_ID->GetFrameDurationInSec(0,fr)));
626  // use the gate duration as the duration of the acquisition for the quantification factor
627  a2p_quantificationFactors[fr][g] /= m2p_durationPerGate[fr][g];
628  }
629  }
630  else // Default normalization according to the number of events
631  {
632  uint64_t total_events_in_frame = 0;
633  for(int rg=0 ; rg<m_nbRespGates ; rg++) total_events_in_frame += m2p_nbEventsPerRespGate[fr][rg];
634 
636 
637  for(int rg=0 ; rg<m_nbRespGates ; rg++)
638  a2p_quantificationFactors[fr][rg] *= (FLTNB)total_events_in_frame/(FLTNB)m2p_nbEventsPerRespGate[fr][rg];
639  }
640 
642  {
643  Cout(" --> Respiratory gating correction factors :" << endl);
644  for(int rg=0 ; rg<m_nbRespGates ; rg++)
645  Cout(" Frame #" << fr << ", gate #" << rg << " = " << a2p_quantificationFactors[fr][rg] << endl);
646  }
647  }
648  }
649  }
650 
651  // End
652  return 0;
653 }
654 
655 // =====================================================================
656 // ---------------------------------------------------------------------
657 // ---------------------------------------------------------------------
658 // =====================================================================
659 
661 {
662  // Verbose
664  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::CheckParameters() -> Check parameters for dynamic data settings" << endl);
665  // Check image dimensions
666  if (mp_ID==NULL)
667  {
668  Cerr("***** oDynamicDataManager::CheckParameters() -> No image dimensions provided !" << endl);
669  return 1;
670  }
671  // Check time frames
672  if (m_nbTimeFrames<0)
673  {
674  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong number of time frames !" << endl);
675  return 1;
676  }
677  // Check resp gates
678  if (m_nbRespGates<0)
679  {
680  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong number of respiratory gates !" << endl);
681  return 1;
682  }
683  // Check card gates
684  if (m_nbCardGates<0)
685  {
686  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong number of respiratory gates !" << endl);
687  return 1;
688  }
689  // Check involuntary motion triggers
690  if (m_nbPMotionTriggers<0)
691  {
692  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong number of involuntary motion subsets provided !" << endl);
693  return 1;
694  }
695  // Check verbosity
696  if (m_verbose<0)
697  {
698  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong verbosity level provided !" << endl);
699  return 1;
700  }
701 
702  // Feedback regarding the enabled/disabled options for the reconstruction
703  if (m_verbose>=2)
704  {
705  if (m_respGatingFlag) Cout("oDynamicDataManager::CheckParameters() -> Respiratory gating is enabled" << endl);
706  if (m_rMotionCorrFlag) Cout("oDynamicDataManager::CheckParameters() -> Respiratory motion correction enabled" << endl);
707  if (m_cardGatingFlag) Cout("oDynamicDataManager::CheckParameters() -> Cardiac gating is enabled" << endl);
708  if (m_cMotionCorrFlag) Cout("oDynamicDataManager::CheckParameters() -> Cardiac motion correction is enabled" << endl);
709  if (m_pMotionCorrFlag) Cout("oDynamicDataManager::CheckParameters() -> Involuntary motion correction is enabled" << endl);
710  }
711 
712  // Check consistency between number of events in the datafile and the potential splitting of the dynamic data into respiratory or cardiac gates (if any gating is enabled)
713  if (m_respGatingFlag)
714  {
715  int last_fr = m_nbTimeFrames-1;
716  int last_rg = m_nbRespGates-1;
717  if (m2p_indexLastEventRespGate[last_fr][last_rg]+1 != a_nbEvents)
718  {
719  Cerr("***** oDynamicDataManager::CheckParameters() -> Problem while checking consistency of dynamic data !" << endl
720  << " The number of events in the datafile (" << a_nbEvents
721  << ") is different from the total number of events in respiratory gates (" << m2p_indexLastEventRespGate[last_fr][last_rg]+1 << ") as initialized in the gating file !" << endl);
722  return 1;
723  }
724  }
725  if (m_cardGatingFlag)
726  {
727  int last_fr = m_nbTimeFrames-1;
728  int last_cg = (m_nbRespGates*m_nbCardGates) - 1;
729  if (m2p_indexLastEventCardGate[last_fr][last_cg]+1 != a_nbEvents)
730  {
731  Cerr("***** oDynamicDataManager::CheckParameters() -> Problem while checking consistency of dynamic data !" << endl
732  << " The number of events in the datafile (" << a_nbEvents
733  << ") is different to the total number of events in cardiac gates (" << m2p_indexLastEventCardGate[last_fr][last_cg]+1 << ") as initialized in the gating file !" << endl);
734  return 1;
735  }
736  }
737  // Normal end
738  return 0;
739 }
740 
741 // =====================================================================
742 // ---------------------------------------------------------------------
743 // ---------------------------------------------------------------------
744 // =====================================================================
745 
747 {
749  // Reset indices for each thread (this is a thread-safe implementation)
750  for (int th=0; th<mp_ID->GetNbThreadsForProjection(); th++)
751  {
752  // We initialize the frame index to -1 because at the beginning of the events loop, we don't know yet
753  // if we are in the first frame (the user can reconstruct only a part of the whole datafile).
754  mp_currentFrameIndex[th] = -1;
755  mp_currentPMotionIndex[th] = 0;
756  mp_currentRespGateIndex[th] = 0;
757  mp_currentCardGateIndex[th] = 0;
758  }
759 }
760 
761 // =====================================================================
762 // ---------------------------------------------------------------------
763 // ---------------------------------------------------------------------
764 // =====================================================================
765 
766 int oDynamicDataManager::DynamicSwitch(int64_t a_currentEventIndex, uint32_t a_currentTime, int a_bed, int a_th)
767 {
769 
770  // The value that will be returned
771  int return_value = DYNAMIC_SWITCH_NOTHING;
772 
773  // -------------------------------------------------------------------------------------------------
774  // Step 1: involuntary motion management
775  // -------------------------------------------------------------------------------------------------
776 
777  // Only do this if the involuntary motion correction is enabled (meaning we must proceed to a deformation)
778  if (m_pMotionCorrFlag)
779  {
780  // Search if we passed one of the next motion triggers (starting at current index)
781  for (int mt=mp_currentPMotionIndex[a_th]; mt<m_nbPMotionTriggers; mt++)
782  {
783  // If we passed this trigger, set the return value to DEFORMATION. However, we continue the loop
784  // in the case where we passed multiple triggers.
785  if (a_currentTime >= mp_listPMotionTriggers[ mt ] // Current timestamp is posterior to the trigger timestamp
786  && mt > mp_currentPMotionIndex[a_th] ) // The trigger timestamp changed (posterior to the current trigger)
787  {
788  mp_currentPMotionIndex[a_th] = mt;
789 
790  #ifdef CASTOR_DEBUG
791  if (m_verbose>=VERBOSE_DEBUG_EVENT) Cout("oDynamicDataManager::DynamicSwitch() -> Thread " << a_th << ", gate for patient motion correction switched to " << mp_currentPMotionIndex[a_th] << endl
792  << " at event #" << a_currentEventIndex << ", timestamp = " << a_currentTime << endl);
793  #endif
794  return_value = DYNAMIC_SWITCH_DEFORMATION;
795  }
796  }
797  }
798 
799  // -------------------------------------------------------------------------------------------------
800  // Step 2: frame management
801  // -------------------------------------------------------------------------------------------------
802 
803  // Special case if the frame number is still -1
804  if (mp_currentFrameIndex[a_th]==-1)
805  {
806  // We check if we are before the first frame, in this case we directly return a CONTINUE signal to go to the next event
807  if (a_currentTime<mp_ID->GetFrameTimeStartInMs(a_bed,0)) return DYNAMIC_SWITCH_CONTINUE;
808  // Otherwise, we now are at least in the first frame (which will be updated right after this 'if' section)
809  else mp_currentFrameIndex[a_th] = 0;
810  }
811 
812  // A boolean to know later if the frame index has changed
813  bool frame_has_changed = false;
814 
815 
816  // Now we search for the first frame index in which the event belongs, starting from the current frame index. Note that we do that
817  // this way (not simply incrementing the frame index) because we want to deal with the case where the next event managed by this
818  // thread could possibly skip multiple frames at once.
819  for (int fr=mp_currentFrameIndex[a_th]; fr<m_nbTimeFrames; fr++)
820  {
821  // If the current time is less than the time stop of the frame 'fr', then the event belongs to this frame
822  if (a_currentTime<mp_ID->GetFrameTimeStopInMs(a_bed,fr))
823  {
824  // Test if the frame has actually changed
825  if (mp_currentFrameIndex[a_th]!=fr)
826  {
827  // Set the boolean to true
828  frame_has_changed = true;
829  // Update the current frame index
830  mp_currentFrameIndex[a_th] = fr;
831  }
832  break;
833  }
834  // Otherwise, if in the last frame, then it means that this event is outside of frame definitions.
835  // In this case, we return a special value so that this event and the next ones will be ignored.
836  else if (fr==m_nbTimeFrames-1)
837  {
839  }
840  }
841 
842  #ifdef CASTOR_DEBUG
843  if (frame_has_changed && m_verbose >=VERBOSE_DEBUG_EVENT)
844  Cout("oDynamicDataManager::DynamicSwitch() -> Thread " << a_th << ", frame switched to " << mp_currentFrameIndex[a_th] << endl);
845  #endif
846 
847 
848  // -------------------------------------------------------------------------------------------------
849  // Step 3: respiratory gating management
850  // -------------------------------------------------------------------------------------------------
851 
852  // A boolean to know later if the respiratory index has changed
853  bool resp_gate_has_changed = false;
854 
855  // Do this only if respiratory gating is enabled
856  if (m_respGatingFlag)
857  {
858  // Test if the frame index has changed
859  if (frame_has_changed)
860  {
861  // Reset the respiratory gate index
862  mp_currentRespGateIndex[a_th] = 0;
863  }
864 
865  // For this frame, search the first gate (from the current gate index) for which the provided event index is below the event-index-stop
866  bool resp_gate_found = false;
867  for (int rg=mp_currentRespGateIndex[a_th]; rg<m_nbRespGates; rg++)
868  {
869  // If the current event index is below the last event of this gate, then the event belongs to this gate
870  // (We won't enter again in the if statement due to the flag setting to true)
871  if (a_currentEventIndex<=m2p_indexLastEventRespGate[mp_currentFrameIndex[a_th]][rg] && resp_gate_found == false)
872  {
873  // Test if the gate has actually changed
874  if (mp_currentRespGateIndex[a_th]!=rg)
875  {
876  // Update the current gate index
877  mp_currentRespGateIndex[a_th] = rg;
878  // Verbose
879  #ifdef CASTOR_DEBUG
880  if (m_verbose>=2) Cout("oDynamicDataManager::DynamicSwitch() -> Thread " << a_th << ", respiratory gate switch to " << mp_currentRespGateIndex[a_th] << endl
881  << " on event " << a_currentEventIndex << endl
882  << " current frame : " << mp_currentFrameIndex[a_th] << endl
883  << " current respiratory gate index " << mp_currentRespGateIndex[a_th] << endl);
884  #endif
885  // If motion correction is enabled, then we should return a DEFORMATION signal
886  if (m_rMotionCorrFlag) return_value = DYNAMIC_SWITCH_DEFORMATION;
887 
888  // Set the boolean to true
889  resp_gate_has_changed = true;
890  }
891  // Set the boolean to true
892  resp_gate_found = true;
893  }
894  }
895 
896 
897  }
898 
899  // -------------------------------------------------------------------------------------------------
900  // Step 4: cardiac gating management
901  // -------------------------------------------------------------------------------------------------
902 
903  // A boolean to know later if the respiratory index has changed
904  //bool card_gate_has_changed = false;
905 
906  // Do this only if cardiac gating is enabled
907  if (m_cardGatingFlag)
908  {
909  // Test if the frame or respiratory index have changed
910  if (frame_has_changed || resp_gate_has_changed)
911  {
912  // Reset the cardiac gate index
913  mp_currentCardGateIndex[a_th] = 0;
914  }
915  // For this frame and respiratory gate, search the first gate (from the current gate index) for which the provided event index is below the event-index-stop
916  bool card_gate_found = false;
917  for (int cg=mp_currentCardGateIndex[a_th]; cg<m_nbCardGates; cg++)
918  {
919  // If the current event index is below the event-index-stop of this gate, then the event belongs to this gate
920  if (a_currentEventIndex<m2p_indexLastEventCardGate[mp_currentFrameIndex[a_th]][mp_currentRespGateIndex[a_th]*m_nbCardGates+cg] && card_gate_found == false)
921  {
922  // Test if the gate has actually changed
923  if (mp_currentCardGateIndex[a_th]!=cg)
924  {
925  // Update the current gate index
926  mp_currentCardGateIndex[a_th] = cg;
927  // Verbose
928  #ifdef CASTOR_DEBUG
929  if (m_verbose>=3) Cout("oDynamicDataManager::DynamicSwitch() -> Thread " << a_th << ", cardiac gate switch to " << mp_currentCardGateIndex[a_th] << endl
930  << " on event " << a_currentEventIndex << endl
931  << " current frame : " << mp_currentFrameIndex[a_th] << endl);
932  #endif
933  // If motion correction is enabled, then we should return a DEFORMATION signal
934  if (m_cMotionCorrFlag) return_value = DYNAMIC_SWITCH_DEFORMATION;
935 
936  // Set the boolean to true
937  //card_gate_has_changed = true;
938  }
939  // Set the boolean to true
940  card_gate_found = true;
941  }
942  }
943  }
944 
945  // -------------------------------------------------------------------------------------------------
946  // Step 5: just return the value !
947  // -------------------------------------------------------------------------------------------------
948 
949  // Return the status of the dynamic switch
950  return return_value;
951 }
952 
953 // =====================================================================
954 // ---------------------------------------------------------------------
955 // ---------------------------------------------------------------------
956 // =====================================================================
int InitDynamicDataGating(const string &a_pathToGateFile)
Initialisation of arrays containing informations about the data splitting and respiratory/cardiac gat...
int CheckParameters(int64_t a_nbEvents)
Check all mandatory parameters.
HPFLTNB ** m2p_listPMotionWeightInFrame
Declaration of class oImageDimensionsAndQuantification.
#define VERBOSE_DEBUG_EVENT
FLTNB GetFrameTimeStartInSec(int a_bed, int a_frame)
Get the frame time start for the given bed, in seconds as a FLTNB.
#define FLTNB
Definition: gVariables.hh:81
#define DYNAMIC_SWITCH_NOTHING
#define DYNAMIC_SWITCH_CONTINUE
void ResetCurrentDynamicIndices()
Reset to 0 the multithreaded dynamic arrays gathering the indices of current frame, gates and involuntary motion.
#define HPFLTNB
Definition: gVariables.hh:83
#define VERBOSE_DETAIL
uint32_t GetFrameTimeStartInMs(int a_bed, int a_frame)
Get the frame time start for the given bed, in milliseconds as a uint32_t.
#define DYNAMIC_SWITCH_END_LOOP
uint16_t * mp_framePMotionFirstIndex
int InitDynamicData(int a_nbRespGates, int a_nbCardGates, const string &a_pathTo4DDataSplittingFile, int a_rmMCorrFlag, int a_cMmCorrFlag, int a_pMotionCorrFlag)
Main function for instanciation and initialization of the member variables and arrays. Call the specific initialization function depending of the type of dataset.
int DynamicSwitch(int64_t a_index, uint32_t a_time, int a_bed, int a_th)
This function is called in the reconstruction event loop. It is used to check if the current event be...
#define Cerr(MESSAGE)
#define VERBOSE_DEBUG_LIGHT
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
Declaration of class oDynamicDataManager.
~oDynamicDataManager()
oDynamicDataManager destructor.
oDynamicDataManager()
oDynamicDataManager constructor. Initialize the member variables to their default values...
#define VERBOSE_NORMAL
oImageDimensionsAndQuantification * mp_ID
int SetDynamicSpecificQuantificationFactors(FLTNB **a2p_quantificationFactors)
Compute gate-specific quantificative factors using the number of events within each gate...
int64_t ** m2p_indexLastEventCardGate
#define KEYWORD_MANDATORY
Definition: gOptions.hh:47
#define KEYWORD_OPTIONAL
Definition: gOptions.hh:49
int InitDynamicDataPatientMotion(const string &a_pathToFile)
Initialisation of involuntary patient motion correction information, if any.
int GetNbTimeFrames()
Get the number of time frames.
int64_t ** m2p_indexLastEventRespGate
int GetNbThreadsForProjection()
Get the number of threads used for projections.
uint32_t GetFrameDurationInMs(int a_bed, int a_frame)
Get the frame duration for the given bed, in milliseconds as a uint32_t.
#define DEBUG_VERBOSE(IGNORED1, IGNORED2)
#define Cout(MESSAGE)
FLTNB GetFrameDurationInSec(int a_bed, int a_frame)
Get the frame duration for the given bed, in seconds as a FLTNB.
#define VERBOSE_DEBUG_NORMAL
#define KEYWORD_OPTIONAL_ERROR
Definition: gOptions.hh:55
#define DYNAMIC_SWITCH_DEFORMATION
uint32_t GetFrameTimeStopInMs(int a_bed, int a_frame)
Get the frame time stop for the given bed, in milliseconds as a uint32_t.