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