CASToR  3.0
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-2019 all CASToR contributors listed below:
18 
19  --> Didier BENOIT, Claude COMTAT, Marina FILIPOVIC, Thibaut MERLIN, Mael MILLARDET, Simon STUTE, Valentin VIELZEUF
20 
21 This is CASToR version 3.0.
22 */
23 
31 #include "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;
67 }
68 
69 // =====================================================================
70 // ---------------------------------------------------------------------
71 // ---------------------------------------------------------------------
72 // =====================================================================
73 
75 {
76  for (int fr=0; fr<m_nbTimeFrames; fr++)
77  {
79  delete m2p_nbEventsPerRespGate[fr];
81  delete m2p_nbEventsPerCardGate[fr];
83  delete m2p_durationPerGate[fr];
85  delete m2p_indexLastEventRespGate[fr];
87  delete m2p_indexLastEventCardGate[fr];
90  }
91 
94  if (m2p_durationPerGate!=NULL) delete m2p_durationPerGate;
106 }
107 
108 // =====================================================================
109 // ---------------------------------------------------------------------
110 // ---------------------------------------------------------------------
111 // =====================================================================
112 
113 int oDynamicDataManager::InitDynamicData( int a_nbRespGates, int a_nbCardGates, const string& a_pathTo4DDataFile,
114  int a_rmCorrFlag, int a_cmCorrFlag, int a_pmCorrFlag)
115 {
116  // Verbose
118  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicData() -> Initialize dynamic data management" << endl);
119 
120  // Initialization
122  m_nbRespGates = a_nbRespGates;
123  m_nbCardGates = a_nbCardGates;
125 
126  if (m_nbRespGates > 1) m_respGatingFlag = true;
127  if (m_nbCardGates > 1) m_cardGatingFlag = true;
128  m_rMotionCorrFlag = a_rmCorrFlag;
129  m_cMotionCorrFlag = a_cmCorrFlag;
130  m_pMotionCorrFlag = a_pmCorrFlag;
131 
132  // Instanciation & initialization for current indices (used during the loop on events to know which frame/gate the event belongs to)
137  for (int th=0 ; th<mp_ID->GetNbThreadsForProjection() ; th++)
138  {
139  mp_currentFrameIndex [0] = 0;
140  mp_currentRespGateIndex [0] = 0;
141  mp_currentCardGateIndex [0] = 0;
142  mp_currentPMotionIndex[0] = 0;
143  }
144 
145  // Instanciation & initialization of number of events per frame for gated reconstructions
146  m2p_nbEventsPerRespGate = new int64_t*[m_nbTimeFrames];
147  m2p_nbEventsPerCardGate = new int64_t*[m_nbTimeFrames];
151 
152  for (int fr=0; fr<m_nbTimeFrames; fr++)
153  {
154  m2p_nbEventsPerRespGate[fr] = new int64_t[m_nbRespGates];
157  m2p_indexLastEventRespGate[fr] = new int64_t[m_nbRespGates];
159 
160  for (int rg=0; rg<m_nbRespGates; rg++)
161  {
162  m2p_nbEventsPerRespGate[fr][rg] = -1;
163  m2p_indexLastEventRespGate[fr][rg] = -1;
164 
165  for (int cg=0; cg<m_nbCardGates; cg++)
166  {
167  m2p_durationPerGate[ fr ][ rg*m_nbCardGates + cg ] = -1;
168  m2p_nbEventsPerCardGate[ fr ][ rg*m_nbCardGates + cg ] = -1;
169  m2p_indexLastEventCardGate[ fr ][ rg*m_nbCardGates + cg ] = -1;
170  }
171  }
172  }
173 
174  // Check coherence of motion initialization
175  // Throw error if cardiac gating is enabled alongside respiratory motion correction (and dual-gating motion correction disabled)
176  // TODO : warning in the documentation that Respiratory motion correction is not supported for cardiac-gated data in the current implementation
178  {
179  Cerr("***** oDynamicDataManager::InitDynamicData() -> Respiratory motion correction is enabled for cardiac-gated data. This is not supported in the current implementation !" << endl);
180  return 1;
181  }
182 
183  // Check iPat motion vs physiological motion
184  if (a_pmCorrFlag && (m_rMotionCorrFlag || m_cMotionCorrFlag) )
185  {
186  Cerr("***** oDynamicDataManager::InitDynamicData() -> Involuntary patient image-based motion correction cannot be used along with respiratory or cardiac motion correction !" << endl);
187  return 1;
188  }
189 
190  // Initialization for respiratory/cardiac gated reconstruction
191  // Note : for Analytic Projection, gating flag could be enabled in order to project an image containing several gates, but no description file is required
192  // InitDynamicDataGating() is restricted to reconstruction
193  // Check on the existence of a_pathTo4DDataFile during reconstruction is performed onnly in Grecon
194  if ((m_respGatingFlag || m_cardGatingFlag) && !a_pathTo4DDataFile.empty() )
195  {
196  if(m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicData() -> Initializing data for gated reconstruction... " << endl);
197 
198  if (InitDynamicDataGating(a_pathTo4DDataFile))
199  {
200  Cerr("***** oDynamicDataManager::InitDynamicData() -> A problem occurred during the dynamic gating initialization !" << endl);
201  return 1;
202  }
203  }
204 
205  // Initialization with involuntary patient motion corrected reconstruction
206  if (m_pMotionCorrFlag && InitDynamicDataPatientMotion(a_pathTo4DDataFile) )
207  {
208  Cerr("***** oDynamicDataManager::InitDynamicData() -> A problem occurred during the patient involuntary motion correction initialization !" << endl);
209  return 1;
210  }
211 
212  // Some feedback
214  {
215  if (m_respGatingFlag) Cout("oDynamicDataManager::InitDynamicData() -> Respiratory gating enabled" << endl);
216  if (m_rMotionCorrFlag) Cout("oDynamicDataManager::InitDynamicData() -> Respiratory image-based motion correction enabled" << endl);
217  if (m_cardGatingFlag) Cout("oDynamicDataManager::InitDynamicData() -> Cardiac gating enabled" << endl);
218  if (m_cMotionCorrFlag) Cout("oDynamicDataManager::InitDynamicData() -> Cardiac image-based motion correction enabled" << endl);
219  if (m_pMotionCorrFlag) Cout("oDynamicDataManager::InitDynamicData() -> Involuntary image-based patient motion correction enabled" << endl);
220  }
221 
222  // End
223  return 0;
224 }
225 
226 // =====================================================================
227 // ---------------------------------------------------------------------
228 // ---------------------------------------------------------------------
229 // =====================================================================
230 
231 int oDynamicDataManager::InitDynamicDataGating(const string& a_pathToFile)
232 {
233  // Verbose
235  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataGating() ... " << endl);
236 
237  // Respiratory gating enabled
238  if (m_respGatingFlag)
239  {
240  // Read information about respiratory gating (number of events per respiratory gate per frame)
241  if ( ReadDataASCIIFile(a_pathToFile,
242  "nb_events_respiratory_gates",
244  m_nbRespGates,
247  {
248  Cerr("***** oDynamicDataManager::InitDynamicDataGating() -> Error while trying to read 'nb_events_respiratory_gates' in file '" << a_pathToFile << "' !" << endl);
249  return 1;
250  }
251 
252  // Get the index of the last event of each respiratory gate using the number of events inside each gate
253  uint64_t event_number_sum = 0;
254  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataGating() :" << endl);
255 
256  for (int fr=0; fr<m_nbTimeFrames; fr++)
257  for (int rg=0; rg<m_nbRespGates; rg++)
258  {
259  m2p_indexLastEventRespGate[fr][rg] += m2p_nbEventsPerRespGate[fr][rg] + event_number_sum;
260  event_number_sum += m2p_nbEventsPerRespGate[fr][rg];
261  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> Number of events for frame #" << fr << ", respiratory gate #" << rg << " = " << m2p_nbEventsPerRespGate[fr][rg] << endl);
262  }
263  }
264 
265  // Cardiac gating enabled
266  if (m_cardGatingFlag)
267  {
268  // Read information about cardiac gating (number of events per cardiac gate per respiratory gates times frames)
269  if ( ReadDataASCIIFile(a_pathToFile,
270  "nb_events_cardiac_gates",
275  {
276  Cerr("***** oDynamicDataManager::InitDynamicDataGating() -> Error while trying to read 'nb_events_cardiac_gates' in file '" << a_pathToFile << "' !" << endl);
277  return 1;
278  }
279  // Get the index of the last event of each cardiac gate using the number of events inside each gate
280  uint64_t event_number_sum = 0;
281  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataGating() :" << endl);
282 
283  for (int fr=0; fr<m_nbTimeFrames; fr++)
284  for (int g=0; g<m_nbRespGates*m_nbCardGates; g++)
285  {
286  m2p_indexLastEventCardGate[fr][g] += m2p_nbEventsPerCardGate[fr][g] + event_number_sum;
287  event_number_sum += m2p_nbEventsPerCardGate[fr][g];
288  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> Number of events for frame #" << fr << ", cardiac gate #" << g << " = " << m2p_nbEventsPerCardGate[fr][g] << endl);
289  }
290  }
291 
292  // Read optional information about respiratory gate duration (if provided)
293  if ( ReadDataASCIIFile(a_pathToFile,
294  "duration_gate",
299  {
300  Cerr("***** oDynamicDataManager::InitDynamicDataGating() -> Error while trying to read 'nb_events_respiratory_gates' in file '" << a_pathToFile << "' !" << endl);
301  return 1;
302  }
303 
304  // Flag indicating that gate duration have been provided
305  if (m2p_durationPerGate[0][0]>0)
306  {
309  for (int fr=0; fr<m_nbTimeFrames; fr++)
310  for (int g=0; g<m_nbRespGates*m_nbCardGates; g++)
311  Cout(" --> Provided gate duration, frame #" << fr << ", gate #" << g << ": " << m2p_durationPerGate[fr][g] << endl);
312 
313  }
314 
315  // End
316  return 0;
317 }
318 
319 // =====================================================================
320 // ---------------------------------------------------------------------
321 // ---------------------------------------------------------------------
322 // =====================================================================
323 
325 {
326  // Verbose
328  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataPatientMotion() ... " << endl);
329 
330  // First get the number of time triggers into the dynamic file
331  if (ReadDataASCIIFile(a_pathToFile,
332  "nb_motion_triggers",
334  1,
336  {
337  Cerr("***** oDynamicDataManager::InitDynamicDataPatientMotion() -> Involuntary motion correction enabled but no dynamic configuration file has been provided with the -im option," << endl
338  << "***** or the number of triggers could not be found in the dynamic file '" << a_pathToFile << "' !" << endl);
339  return 1;
340  }
341  // Allocate time triggers and read them from the dynamic file
342  mp_listPMotionTriggers = (uint32_t*)malloc(m_nbPMotionTriggers*sizeof(uint32_t));
343  mp_frameNbPMotionTriggers = (uint16_t*)malloc(m_nbTimeFrames*sizeof(uint16_t));
344  mp_framePMotionFirstIndex = (uint16_t*)malloc(m_nbTimeFrames*sizeof(uint16_t));
345  mp_framePMotionLastIndex = (uint16_t*)malloc(m_nbTimeFrames*sizeof(uint16_t));
347 
348 
349  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
350  {
352  mp_framePMotionLastIndex[fr] = 0;
354  }
355 
356  // Get timestamp of motion trigger
357  if (ReadDataASCIIFile(a_pathToFile,
358  "timestamp_motion_triggers",
362  {
363  Cerr("***** oDynamicDataManager::InitDynamicDataPatientMotion() -> Involuntary motion correction enabled but list of triggers"
364  << " not found in the dynamic file '" << a_pathToFile << "' !" << endl);
365  return 1;
366  }
367 
368  // Convert motion trigger in ms as for timestamp
369  for (int pm=0 ; pm<m_nbPMotionTriggers ; pm++)
370  mp_listPMotionTriggers [ pm ] *= 1000.;
371 
372 
373  // --- Recover the number of transformation involved during each frame --- //
374  // The following lines loop on the motion trigger in order to recover,
375  // for each frame, the first and last patient motion trigger
376 
377  // First check that the first patient motion trigger is 0s (référence)
378  if(mp_listPMotionTriggers[ 0 ] > 0.)
379  {
380  Cerr("***** oDynamicDataManager::InitDynamicDataPatientMotion() -> "
381  <<" Error, the first motion trigger must be 0 ms"
382  <<" (the corresponding transformation parameters are usually set to 0,"
383  <<" as the acquisition start in the reference position!" << endl);
384  return 1;
385  }
386 
387 
388  // Init a frame index
389  uint16_t fr=0;
390 
391  // Loop on all motion triggers (MT) and get the first and last motion index for each frame
392  for(int t=0 ; t<m_nbPMotionTriggers ; t++)
393  {
394  // If motion trigger t <= starting time of the current frame,
396  // set t as 1st trigger for all remaining frames
397  for(int f=fr ; f<m_nbTimeFrames ; f++)
398  {
399  mp_framePMotionFirstIndex[ f ] = t ;
400  mp_framePMotionLastIndex[ f ] = t ;
401  }
402  else
403  {
404  // Check if the current MT timestamp >= the current frame (fr index) timestop.
405  // In this case : Check the next frame, init the first and last MT index for the next frame as the current MT and MT+1
406  // Otherwise : Out of while loop, set the indices for the subsequent frames, and go to the next MT
408  {
409  fr++;
410  // Throw error if there is a motion trigger after the last frame TimeStop)
411  if(fr >= m_nbTimeFrames)
412  {
413  Cerr("***** oDynamicDataManager::InitDynamicDataPatientMotion() -> Error, the "<<t+1<<"th motion trigger timestamp: "<< mp_listPMotionTriggers[t]
414  <<" occurs after the end of the last frame (frame "<<fr<<", timestop :"<< mp_ID->GetFrameTimeStopInMs(0,fr-1) << " !" << endl);
415  return 1;
416  }
417  // Special case if the motion trigger timestamp match the frame start timestamp
419  mp_framePMotionFirstIndex[ fr ] = t ;
420  }
421 
422  // set t as last motion trigger for the current frame
423  mp_framePMotionLastIndex[ fr ] = t;
424 
425  // set t as 1st and last motion trigger for all remaining frames (if any)
426  for(int f=fr+1 ; f<m_nbTimeFrames ; f++)
427  {
428  mp_framePMotionFirstIndex[ f ] = t ;
429  mp_framePMotionLastIndex[ f ] = t ;
430  }
431  }
432 
433  }
434 
435 
436  // Set the indexes of any remaining frames
437  if(fr<m_nbTimeFrames-1)
438  for(int f=fr ; f<m_nbTimeFrames ; f++)
439  {
442  }
443 
444  // Recover number of MT for each frame
445  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::InitDynamicDataPatientMotion() -> First / Last motion trigger index for each dynamic frame: " << endl);
446  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
447  {
449  if (m_verbose>=VERBOSE_DETAIL) Cout("Frm" << fr << " : " << mp_framePMotionFirstIndex[fr] << " / " << mp_framePMotionLastIndex[fr] << endl);
450  }
451 
452  // ===================================================================
453  // Compute the weight of each pm subsets within each frame
454  // (ratio subset duration/total frame duration)
455  // Correct for decay quantification factors
456  // This is only used for list-mode sensitivity image generation
457 
458  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
460 
461  // Loop on frames, recover subset start/stop and compute weights
462  int t=1; //(first timestamp is always 0)
463  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
464  {
465  uint32_t motion_subset_start_time_ms = mp_ID->GetFrameTimeStartInMs(0,fr);
466  uint32_t motion_subset_stop_time_ms = 0;
467 
468  for( int ipm=0 ; ipm<mp_frameNbPMotionTriggers[fr] ; ipm++ )
469  {
470  if (t<m_nbPMotionTriggers // Not the last motion trigger. Then check if the next stop is the end of the frame or if another transformation occurs before.
471  && mp_listPMotionTriggers[t]>mp_ID->GetFrameTimeStartInMs(0,fr)) // Trigger timestamp occurs after the start of the frame
472  {
473  motion_subset_stop_time_ms = (mp_listPMotionTriggers[t] < mp_ID->GetFrameTimeStopInMs(0,fr) )?
476  }
477  else // No more motion trigger. Then the next stop is the end of the frame
478  motion_subset_stop_time_ms = mp_ID->GetFrameTimeStopInMs(0,fr) ;
479 
480  m2p_listPMotionWeightInFrame[fr][ipm] = FLTNB(motion_subset_stop_time_ms - motion_subset_start_time_ms ) / mp_ID->GetFrameDurationInMs(0,fr) ;
481 
482  // Check if an isotope has been provided
483  if (mp_ID->GetLambda() > 0)
484  {
485  // Correct quantification factors for decay TODO TM TEST !
486  // Decay correction is applied in mp_ID->SetPETIsotope() by default
487  // Those correction are erroneous for patient motion subset though,
488  // so we will manage them here
489 
490  // First, we need to "uncorrect" for the decay correction applied in
491  // this function...
492  long double lambda = mp_ID->GetLambda();
493  long double dstart = lambda*mp_ID->GetFrameTimeStartInSec(0,fr);
494  long double dacq = lambda*mp_ID->GetFrameDurationInSec(0,fr);
495  // Uncorrect for time start decay
496  m2p_listPMotionWeightInFrame[ fr ][ ipm ] /= exp(dstart);
497  // Uncorrect for frame duration decay
498  m2p_listPMotionWeightInFrame[ fr ][ ipm ] /= dacq/(1.0-exp(-dacq));
499 
500  // Then, apply the correct corrections for the pm subset
501  dstart = lambda
502  * (long double)motion_subset_start_time_ms
503  * 0.001;
504  dacq = lambda
505  * (long double)(motion_subset_stop_time_ms - motion_subset_start_time_ms)
506  * 0.001 ;
507 
508  // Time start decay correction
509  m2p_listPMotionWeightInFrame[ fr ][ ipm ] *= exp(dstart);
510  // Frame duration decay correction
511  m2p_listPMotionWeightInFrame[ fr ][ ipm ] *= dacq/(1.0-exp(-dacq));
512  }
513 
514  // Set the motion subset start time for the next trigger
515  motion_subset_start_time_ms = mp_listPMotionTriggers[t];
516 
517  t++;
518  } // end of loop on patient motion subsets
519 
520  t--;
521  } // end of loop on frames
522 
523  // End
524  return 0;
525 }
526 
527 // =====================================================================
528 // ---------------------------------------------------------------------
529 // ---------------------------------------------------------------------
530 // =====================================================================
531 
533 {
535  if (m_verbose>=VERBOSE_NORMAL) Cout("oDynamicDataManager::SetDynamicSpecificQuantificationFactors() ... " << endl);
536 
537  // COMPUTE GATE-SPECIFIC QUANTITATIVE FACTORS
538  if (m_nbRespGates>1 || m_nbCardGates>1)
539  {
540  for(int fr=0 ; fr<m_nbTimeFrames ; fr++)
541  {
542  // We have cardiac gates and don't correct for motion -> quantification factors required for cardiac gates
544  {
545  // If duration have been provided (value >0, then normalize according to the duration)
546  if (m2p_durationPerGate[fr][0]>0)
547  {
548  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++)
549  a2p_quantificationFactors[fr][g] /= m2p_durationPerGate[fr][g];
550  }
551  else // Default normalization according to the number of events
552  {
553  uint64_t total_events_in_frame = 0;
554  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++) total_events_in_frame += m2p_nbEventsPerCardGate[fr][g];
555 
557 
558  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++)
559  a2p_quantificationFactors[fr][g] *= (FLTNB)total_events_in_frame/(FLTNB)m2p_nbEventsPerCardGate[fr][g];
560  }
561 
563  {
564  Cout(" --> Cardiac gating correction factors :" << endl);
565  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++)
566  Cout(" Frame #" << fr << ", cardiac gate #" << g << " = " << a2p_quantificationFactors[fr][g] << endl);
567  }
568  }
569 
570  // We have resp gates and don't correct for resp motion (and no independent cardiac gate reconstruction) -> quantification factors required for cardiac gates
572  {
573  // If duration have been provided (value >0, then normalize according to the duration)
574  if (m2p_durationPerGate[fr][0]>0)
575  {
576  for(int g=0 ; g<m_nbRespGates*m_nbCardGates ; g++)
577  a2p_quantificationFactors[fr][g] /= m2p_durationPerGate[fr][g];
578  }
579  else // Default normalization according to the number of events
580  {
581  uint64_t total_events_in_frame = 0;
582  for(int rg=0 ; rg<m_nbRespGates ; rg++) total_events_in_frame += m2p_nbEventsPerRespGate[fr][rg];
583 
585 
586  for(int rg=0 ; rg<m_nbRespGates ; rg++)
587  a2p_quantificationFactors[fr][rg] *= (FLTNB)total_events_in_frame/(FLTNB)m2p_nbEventsPerRespGate[fr][rg];
588  }
589 
591  {
592  Cout(" --> Respiratory gating correction factors :" << endl);
593  for(int rg=0 ; rg<m_nbRespGates ; rg++)
594  Cout(" Frame #" << fr << ", gate #" << rg << " = " << a2p_quantificationFactors[fr][rg] << endl);
595  }
596  }
597  }
598  }
599 
600  // End
601  return 0;
602 }
603 
604 // =====================================================================
605 // ---------------------------------------------------------------------
606 // ---------------------------------------------------------------------
607 // =====================================================================
608 
610 {
611  // Verbose
613  if (m_verbose>=VERBOSE_DETAIL) Cout("oDynamicDataManager::CheckParameters() -> Check parameters for dynamic data settings" << endl);
614  // Check image dimensions
615  if (mp_ID==NULL)
616  {
617  Cerr("***** oDynamicDataManager::CheckParameters() -> No image dimensions provided !" << endl);
618  return 1;
619  }
620  // Check time frames
621  if (m_nbTimeFrames<0)
622  {
623  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong number of time frames !" << endl);
624  return 1;
625  }
626  // Check resp gates
627  if (m_nbRespGates<0)
628  {
629  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong number of respiratory gates !" << endl);
630  return 1;
631  }
632  // Check card gates
633  if (m_nbCardGates<0)
634  {
635  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong number of respiratory gates !" << endl);
636  return 1;
637  }
638  // Check involuntary motion triggers
639  if (m_nbPMotionTriggers<0)
640  {
641  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong number of involuntary motion subsets provided !" << endl);
642  return 1;
643  }
644  // Check verbosity
645  if (m_verbose<0)
646  {
647  Cerr("***** oDynamicDataManager::CheckParameters() -> Wrong verbosity level provided !" << endl);
648  return 1;
649  }
650 
651  // Feedback regarding the enabled/disabled options for the reconstruction
652  if (m_verbose>=2)
653  {
654  if (m_respGatingFlag) Cout("oDynamicDataManager::CheckParameters() -> Respiratory gating is enabled" << endl);
655  if (m_rMotionCorrFlag) Cout("oDynamicDataManager::CheckParameters() -> Respiratory motion correction enabled" << endl);
656  if (m_cardGatingFlag) Cout("oDynamicDataManager::CheckParameters() -> Cardiac gating is enabled" << endl);
657  if (m_cMotionCorrFlag) Cout("oDynamicDataManager::CheckParameters() -> Cardiac motion correction is enabled" << endl);
658  if (m_pMotionCorrFlag) Cout("oDynamicDataManager::CheckParameters() -> Involuntary motion correction is enabled" << endl);
659  }
660 
661  // 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)
662  if (m_respGatingFlag)
663  {
664  int last_fr = m_nbTimeFrames-1;
665  int last_rg = m_nbRespGates-1;
666  if (m2p_indexLastEventRespGate[last_fr][last_rg]+1 != a_nbEvents)
667  {
668  Cerr("***** oDynamicDataManager::CheckParameters() -> Problem while checking consistency of dynamic data !" << endl
669  << " The number of events in the datafile (" << a_nbEvents
670  << ") 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);
671  return 1;
672  }
673  }
674  if (m_cardGatingFlag)
675  {
676  int last_fr = m_nbTimeFrames-1;
677  int last_cg = (m_nbRespGates*m_nbCardGates) - 1;
678  if (m2p_indexLastEventCardGate[last_fr][last_cg]+1 != a_nbEvents)
679  {
680  Cerr("***** oDynamicDataManager::CheckParameters() -> Problem while checking consistency of dynamic data !" << endl
681  << " The number of events in the datafile (" << a_nbEvents
682  << ") 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);
683  return 1;
684  }
685  }
686  // Normal end
687  return 0;
688 }
689 
690 // =====================================================================
691 // ---------------------------------------------------------------------
692 // ---------------------------------------------------------------------
693 // =====================================================================
694 
696 {
698  // Reset indices for each thread (this is a thread-safe implementation)
699  for (int th=0; th<mp_ID->GetNbThreadsForProjection(); th++)
700  {
701  // We initialize the frame index to -1 because at the beginning of the events loop, we don't know yet
702  // if we are in the first frame (the user can reconstruct only a part of the whole datafile).
703  mp_currentFrameIndex[th] = -1;
704  mp_currentPMotionIndex[th] = 0;
705  mp_currentRespGateIndex[th] = 0;
706  mp_currentCardGateIndex[th] = 0;
707  }
708 }
709 
710 // =====================================================================
711 // ---------------------------------------------------------------------
712 // ---------------------------------------------------------------------
713 // =====================================================================
714 
715 int oDynamicDataManager::DynamicSwitch(int64_t a_currentEventIndex, uint32_t a_currentTime, int a_bed, int a_th)
716 {
718 
719  // The value that will be returned
720  int return_value = DYNAMIC_SWITCH_NOTHING;
721 
722  // -------------------------------------------------------------------------------------------------
723  // Step 1: involuntary motion management
724  // -------------------------------------------------------------------------------------------------
725 
726  // Only do this if the involuntary motion correction is enabled (meaning we must proceed to a deformation)
727  if (m_pMotionCorrFlag)
728  {
729  // Search if we passed one of the next motion triggers (starting at current index)
730  for (int mt=mp_currentPMotionIndex[a_th]; mt<m_nbPMotionTriggers; mt++)
731  {
732  // If we passed this trigger, set the return value to DEFORMATION. However, we continue the loop
733  // in the case where we passed multiple triggers.
734  if (a_currentTime >= mp_listPMotionTriggers[ mt ] // Current timestamp is posterior to the trigger timestamp
735  && mt > mp_currentPMotionIndex[a_th] ) // The trigger timestamp changed (posterior to the current trigger)
736  {
737  mp_currentPMotionIndex[a_th] = mt;
738 
739  #ifdef CASTOR_DEBUG
740  if (m_verbose>=VERBOSE_DEBUG_EVENT) Cout("oDynamicDataManager::DynamicSwitch() -> Thread " << a_th << ", gate for patient motion correction switched to " << mp_currentPMotionIndex[a_th] << endl
741  << " at event #" << a_currentEventIndex << ", timestamp = " << a_currentTime << endl);
742  #endif
743  return_value = DYNAMIC_SWITCH_DEFORMATION;
744  }
745  }
746  }
747 
748  // -------------------------------------------------------------------------------------------------
749  // Step 2: frame management
750  // -------------------------------------------------------------------------------------------------
751 
752  // Special case if the frame number is still -1
753  if (mp_currentFrameIndex[a_th]==-1)
754  {
755  // We check if we are before the first frame, in this case we directly return a CONTINUE signal to go to the next event
756  if (a_currentTime<mp_ID->GetFrameTimeStartInMs(a_bed,0)) return DYNAMIC_SWITCH_CONTINUE;
757  // Otherwise, we now are at least in the first frame (which will be updated right after this 'if' section)
758  else mp_currentFrameIndex[a_th] = 0;
759  }
760 
761  // A boolean to know later if the frame index has changed
762  bool frame_has_changed = false;
763 
764 
765  // Now we search for the first frame index in which the event belongs, starting from the current frame index. Note that we do that
766  // this way (not simply incrementing the frame index) because we want to deal with the case where the next event managed by this
767  // thread could possibly skip multiple frames at once.
768  for (int fr=mp_currentFrameIndex[a_th]; fr<m_nbTimeFrames; fr++)
769  {
770  // If the current time is less than the time stop of the frame 'fr', then the event belongs to this frame
771  if (a_currentTime<mp_ID->GetFrameTimeStopInMs(a_bed,fr))
772  {
773  // Test if the frame has actually changed
774  if (mp_currentFrameIndex[a_th]!=fr)
775  {
776  // Set the boolean to true
777  frame_has_changed = true;
778  // Update the current frame index
779  mp_currentFrameIndex[a_th] = fr;
780  }
781  break;
782  }
783  // Otherwise, if in the last frame, then it means that this event is outside of frame definitions.
784  // In this case, we return a special value so that this event and the next ones will be ignored.
785  else if (fr==m_nbTimeFrames-1)
786  {
788  }
789  }
790 
791  #ifdef CASTOR_DEBUG
792  if (frame_has_changed && m_verbose >=VERBOSE_DEBUG_EVENT)
793  Cout("oDynamicDataManager::DynamicSwitch() -> Thread " << a_th << ", frame switched to " << mp_currentFrameIndex[a_th] << endl);
794  #endif
795 
796 
797  // -------------------------------------------------------------------------------------------------
798  // Step 3: respiratory gating management
799  // -------------------------------------------------------------------------------------------------
800 
801  // A boolean to know later if the respiratory index has changed
802  bool resp_gate_has_changed = false;
803 
804  // Do this only if respiratory gating is enabled
805  if (m_respGatingFlag)
806  {
807  // Test if the frame index has changed
808  if (frame_has_changed)
809  {
810  // Reset the respiratory gate index
811  mp_currentRespGateIndex[a_th] = 0;
812  }
813 
814  // For this frame, search the first gate (from the current gate index) for which the provided event index is below the event-index-stop
815  bool resp_gate_found = false;
816  for (int rg=mp_currentRespGateIndex[a_th]; rg<m_nbRespGates; rg++)
817  {
818  // If the current event index is below the last event of this gate, then the event belongs to this gate
819  // (We won't enter again in the if statement due to the flag setting to true)
820  if (a_currentEventIndex<=m2p_indexLastEventRespGate[mp_currentFrameIndex[a_th]][rg] && resp_gate_found == false)
821  {
822  // Test if the gate has actually changed
823  if (mp_currentRespGateIndex[a_th]!=rg)
824  {
825  // Update the current gate index
826  mp_currentRespGateIndex[a_th] = rg;
827  // Verbose
828  #ifdef CASTOR_DEBUG
829  if (m_verbose>=2) Cout("oDynamicDataManager::DynamicSwitch() -> Thread " << a_th << ", respiratory gate switch to " << mp_currentRespGateIndex[a_th] << endl
830  << " on event " << a_currentEventIndex << endl
831  << " current frame : " << mp_currentFrameIndex[a_th] << endl
832  << " current respiratory gate index " << mp_currentRespGateIndex[a_th] << endl);
833  #endif
834  // If motion correction is enabled, then we should return a DEFORMATION signal
835  if (m_rMotionCorrFlag) return_value = DYNAMIC_SWITCH_DEFORMATION;
836 
837  // Set the boolean to true
838  resp_gate_has_changed = true;
839  }
840  // Set the boolean to true
841  resp_gate_found = true;
842  }
843  }
844 
845 
846  }
847 
848  // -------------------------------------------------------------------------------------------------
849  // Step 4: cardiac gating management
850  // -------------------------------------------------------------------------------------------------
851 
852  // A boolean to know later if the respiratory index has changed
853  //bool card_gate_has_changed = false;
854 
855  // Do this only if cardiac gating is enabled
856  if (m_cardGatingFlag)
857  {
858  // Test if the frame or respiratory index have changed
859  if (frame_has_changed || resp_gate_has_changed)
860  {
861  // Reset the cardiac gate index
862  mp_currentCardGateIndex[a_th] = 0;
863  }
864  // 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
865  bool card_gate_found = false;
866  for (int cg=mp_currentCardGateIndex[a_th]; cg<m_nbCardGates; cg++)
867  {
868  // If the current event index is below the event-index-stop of this gate, then the event belongs to this gate
869  if (a_currentEventIndex<m2p_indexLastEventCardGate[mp_currentFrameIndex[a_th]][mp_currentRespGateIndex[a_th]*m_nbCardGates+cg] && card_gate_found == false)
870  {
871  // Test if the gate has actually changed
872  if (mp_currentCardGateIndex[a_th]!=cg)
873  {
874  // Update the current gate index
875  mp_currentCardGateIndex[a_th] = cg;
876  // Verbose
877  #ifdef CASTOR_DEBUG
878  if (m_verbose>=3) Cout("oDynamicDataManager::DynamicSwitch() -> Thread " << a_th << ", cardiac gate switch to " << mp_currentCardGateIndex[a_th] << endl
879  << " on event " << a_currentEventIndex << endl
880  << " current frame : " << mp_currentFrameIndex[a_th] << endl);
881  #endif
882  // If motion correction is enabled, then we should return a DEFORMATION signal
883  if (m_cMotionCorrFlag) return_value = DYNAMIC_SWITCH_DEFORMATION;
884 
885  // Set the boolean to true
886  //card_gate_has_changed = true;
887  }
888  // Set the boolean to true
889  card_gate_found = true;
890  }
891  }
892  }
893 
894  // -------------------------------------------------------------------------------------------------
895  // Step 5: just return the value !
896  // -------------------------------------------------------------------------------------------------
897 
898  // Return the status of the dynamic switch
899  return return_value;
900 }
901 
902 // =====================================================================
903 // ---------------------------------------------------------------------
904 // ---------------------------------------------------------------------
905 // =====================================================================
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
uint16_t * mp_frameNbPMotionTriggers
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 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.