CASToR  3.2
Tomographic Reconstruction (PET/SPECT/CT)
src/scanner/iScannerPET.cc
Go to the documentation of this file.
1 
8 #include "iScannerPET.hh"
9 #include "sOutputManager.hh"
10 #include "sScannerManager.hh"
11 
12 // =====================================================================
13 // ---------------------------------------------------------------------
14 // ---------------------------------------------------------------------
15 // =====================================================================
16 
18 {
19  // Set member variables to default values
21  m_nbLayers = -1;
22  m_nbCrystals = -1;
24  mp_nbCrystalsInLayer = NULL;
32  mp_sizeCrystalTrans = NULL;
33  mp_sizeCrystalAxial = NULL;
34  mp_sizeCrystalDepth = NULL;
36  mp_positionMatrix_ref = NULL;
37  mp_positionMatrix_out = NULL;
38  mp_rotationMatrix = NULL;
39  // Variables depending on the acquisition
40  m_maxAxialDiffmm = -1.;
41 }
42 
43 // =====================================================================
44 // ---------------------------------------------------------------------
45 // ---------------------------------------------------------------------
46 // =====================================================================
47 
49 {
50  if (mp_sizeCrystalTrans != NULL) delete mp_sizeCrystalTrans;
51  if (mp_sizeCrystalAxial != NULL) delete mp_sizeCrystalAxial;
52  if (mp_sizeCrystalDepth != NULL) delete mp_sizeCrystalDepth;
60  if (mp_nbCrystalsInLayer != NULL) delete mp_nbCrystalsInLayer;
61  if (mp_positionMatrix_ref != NULL) delete mp_positionMatrix_ref;
62  if (mp_positionMatrix_out != NULL) delete mp_positionMatrix_out;
63  if (mp_rotationMatrix != NULL) delete mp_rotationMatrix;
64 }
65 
66 
67 
68 
69 // =====================================================================
70 // ---------------------------------------------------------------------
71 // ---------------------------------------------------------------------
72 // =====================================================================
78 {
80  if (m_verbose==0) return;
81 
82  // Describe the scanner
83  Cout("iScannerPET::DescribeSpecific() -> Here is some specific content of the PET scanner" << endl);
84  Cout(" --> Number of layers: " << m_nbLayers << endl);
85  Cout(" --> Total number of crystals: " << m_nbCrystals << endl);
86  for (int l=0 ; l<m_nbLayers ; l++)
87  if( mp_nbCrystalsInLayer ) Cout(" --> Nb crystals layer ["<<l+1<<"]: " << mp_nbCrystalsInLayer[l] << endl);
88 
89  for (int l=0 ; l<m_nbLayers ; l++)
90  {
91  if(l>0) Cout(" --> Layer "<<l<<":"<< endl);
92  if( mp_sizeCrystalTrans ) Cout(" --> Crystal transaxial dimension (mm): " << mp_sizeCrystalTrans[l] << endl);
93  if( mp_sizeCrystalAxial ) Cout(" --> Crystal axial dimension (mm): " << mp_sizeCrystalAxial[l] << endl);
94  if( mp_sizeCrystalDepth ) Cout(" --> Crystal depth dimension (mm): " << mp_sizeCrystalDepth[l] << endl);
95  }
97  for (int l=0 ; l<m_nbLayers ; l++)
98  Cout(" --> Mean depth of interaction for layer ["<<l+1<<"]: " << mp_meanDepthOfInteraction[l] << endl);
99 
100  Cout(" --> (Transaxial) minimum angle difference (radian): " << m_minAngleDifference << endl);
101  if(m_maxAxialDiffmm > 0.)Cout(" --> Maximum axial difference for LORs (mm): " << m_maxAxialDiffmm << endl);
102 }
103 
104 
105 
106 
107 
108 
109 
110 // =====================================================================
111 // ---------------------------------------------------------------------
112 // ---------------------------------------------------------------------
113 // =====================================================================
114 /*
115  \fn int iScannerPET::Instantiate()
116  \param a_scannerFileIsLUT : boolean indicating if the file describing
117  the system is a generic file (0) or custom Look-up-table (1)
118  \brief Get mandatory informations from the scanner file
119  and allocate memory for the member variables
120  \return 0 if success, positive value otherwise
121 */
122 int iScannerPET::Instantiate(bool a_scannerFileIsLUT)
123 {
125 
126  // Verbose
127  if (m_verbose>=VERBOSE_NORMAL) Cout("iScannerPET::Instantiate() -> Create scanner structure and read parameters from configuration file"<< endl);
128 
129  // Get scanner manager
130  sScannerManager* p_scannerManager;
131  p_scannerManager = sScannerManager::GetInstance();
132 
133  // Get the number of layers in the scanner
134  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of layers", &m_nbLayers, 1, KEYWORD_MANDATORY))
135  {
136  Cerr("***** iScannerPET::Instantiate() -> An error occurred while trying to read the number of layers in the scanner header file !" << endl);
137  return 1;
138  }
139  // Check the correct initialization of the number of layers
140  if (m_nbLayers<=0)
141  {
142  Cerr("***** iScannerPET::Instantiate() -> Incorrect value for the number of layer (must be >0) !" << endl);
143  return 1;
144  }
145  // Get the number of elements in the system
146  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of elements", &m_nbCrystals, 1, KEYWORD_MANDATORY))
147  {
148  Cerr("***** iScannerPET::Instantiate() -> An error occurred while trying to read the number of elements in the scanner header file !" << endl);
149  return 1;
150  }
151 
152  // Instanciation of layer dependent member variables
153  mp_nbCrystalsInLayer = new int[m_nbLayers];
158 
159  // Instanciation of number of crystals dependent member variables
166 
167  // Initialize layer size with default value (=0);
168  for (int l=0 ; l<m_nbLayers ; l++)
169  {
170  mp_sizeCrystalTrans[l] = 0.;
171  mp_sizeCrystalAxial[l] = 0.;
172  mp_sizeCrystalDepth[l] = 0.;
173  }
174 
175  // Size of crystals
176  if (a_scannerFileIsLUT)
177  {
178  // For the moment, only the depth is mandatory as the others are not yet implemented
179  if ( ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "crystals size trans", mp_sizeCrystalTrans, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
181  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "crystals size depth", mp_sizeCrystalDepth, m_nbLayers, KEYWORD_MANDATORY) )
182  {
183  Cerr("***** iScannerPET::Instantiate() -> An error occurred while trying to read the crystals size in the scanner header file !" << endl);
184  return 1;
185  }
186  }
187  else
188  {
189  // Mandatory parameter
190  if ( ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "crystals size trans", mp_sizeCrystalTrans, m_nbLayers, KEYWORD_MANDATORY) ||
191  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "crystals size axial", mp_sizeCrystalAxial, m_nbLayers, KEYWORD_MANDATORY) ||
192  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "crystals size depth", mp_sizeCrystalDepth, m_nbLayers, KEYWORD_MANDATORY) )
193  {
194  Cerr("***** iScannerPET::Instantiate() -> An error occurred while trying to read the crystals size in the scanner header file !" << endl);
195  return 1;
196  }
197  }
198 
199  // ----------------------------------------------
200  // Optional parameters
201  // ----------------------------------------------
202 
203  // Initialize with default values
204  for (int l=0 ; l<m_nbLayers ; l++) mp_meanDepthOfInteraction[l] = -1;
207 
208  // Check if value is provided in the scanner conf file
210  {
211  Cerr("***** iScannerPET::Instantiate() -> An error occurred while trying to read the mean depth of interaction in the scanner header file !" << endl);
212  return 1;
213  }
214  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "min angle difference", &m_minAngleDifference, 1, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR)
215  {
216  Cerr("***** iScannerPET::Instantiate() -> An error occurred while trying to read the min angle difference in the scanner header file !" << endl);
217  return 1;
218  }
219  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "multiple bed displacement", &m_defaultBedDisplacementInMm, 1, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR)
220  {
221  Cerr("***** iScannerPET::Instantiate() -> An error occurred while trying to read the multiple bed displacement in the scanner header file !" << endl);
222  return 1;
223  }
224 
225  // Instanciation of objects for matrix calculation during reconstruction
226  mp_positionMatrix_ref = new oMatrix(3,1);
227  mp_positionMatrix_out = new oMatrix(3,1);
228  mp_rotationMatrix = new oMatrix(3,3);
229 
230  // End
231  return 0;
232 }
233 
234 // =====================================================================
235 // ---------------------------------------------------------------------
236 // ---------------------------------------------------------------------
237 // =====================================================================
238 /*
239  \fn BuildLUT
240  \param a_scannerFileIsLUT : boolean indicating if the file describing
241  the SPECT camera is a generic file (0) or custom Look-up-table (1)
242  \brief Call the functions to generate the LUT or read the user-made LUT depending on the user choice
243  \return 0 if success, positive value otherwise
244  \todo default values to max ring diff
245 */
246 int iScannerPET::BuildLUT(bool a_scannerFileIsLUT)
247 {
249 
250  // Verbose
251  if (m_verbose>=VERBOSE_NORMAL) Cout("iScannerPET::BuildLUT() -> Build LUT for scanner elements coordinates and orientations"<< endl);
252 
253  // Either generate the LUT from a generic file, or load the user precomputed LUT
254  if (!a_scannerFileIsLUT)
255  {
256  if (ComputeLUT())
257  {
258  Cerr("***** iScannerPET::BuildLUT() -> A problem occurred while generating scanner LUT !" << endl);
259  return 1;
260  }
261  }
262  else
263  {
264  if (LoadLUT())
265  {
266  Cerr("***** iScannerPET::BuildLUT() -> A problem occurred while loading scanner LUT !" << endl);
267  return 1;
268  }
269  }
270 
271  // End
272  return 0;
273 }
274 
275 // =====================================================================
276 // ---------------------------------------------------------------------
277 // ---------------------------------------------------------------------
278 // =====================================================================
279 /*
280  \fn CheckParameters
281  \brief Check if all parameters have been correctly initialized
282  \return 0 if success, positive value otherwise
283 */
285 {
287 
288  // Check if all parameters have been correctly initialized. Return error otherwise
289  if (m_scannerType == -1)
290  {
291  Cerr("***** iScannerPET::CheckParameters() -> Scanner type not initialized !" << endl);
292  return 1;
293  }
294  if (m_verbose == -1)
295  {
296  Cerr("***** iScannerPET::CheckParameters() -> Verbosity not initialized !" << endl);
297  return 1;
298  }
299  if (m_nbLayers <= 0)
300  {
301  Cerr("***** iScannerPET::CheckParameters() -> Incorrect value for the number of layer (must be >0) !" << endl);
302  return 1;
303  }
304  if (m_nbCrystals <0)
305  {
306  Cerr("***** iScannerPET::CheckParameters() -> Number of crystals not initialized !" << endl);
307  return 1;
308  }
309  if (mp_nbCrystalsInLayer == NULL)
310  {
311  Cerr("***** iScannerPET::CheckParameters() -> Number of crystals in layer(s) not initialized !" << endl);
312  return 1;
313  }
315  {
316  Cerr("***** iScannerPET::CheckParameters() -> Crystals central positions not initialized !" << endl);
317  return 1;
318  }
320  {
321  Cerr("***** iScannerPET::CheckParameters() -> Crystals orientations not initialized !" << endl);
322  return 1;
323  }
324  if (mp_sizeCrystalTrans==NULL || mp_sizeCrystalAxial==NULL || mp_sizeCrystalDepth==NULL)
325  {
326  Cerr("***** iScannerPET::CheckParameters() -> Crystals dimensions not initialized !" << endl);
327  return 1;
328  }
329  if (mp_meanDepthOfInteraction == NULL)
330  {
331  Cout("***** iScannerPET::CheckParameters() -> Mean depth of interaction not initialized !" << endl);
332  return 1;
333  }
334  if (m_minAngleDifference < 0)
335  {
336  Cerr("***** iScannerPET::CheckParameters() -> Minimum angle difference not initialized !" << endl);
337  return 1;
338  }
339  if (m_scannerType == -1)
340  {
341  Cerr("***** iScannerPET::CheckParameters() -> Scanner type not initialized !" << endl);
342  return 1;
343  }
344  if (m_scannerType == -1)
345  {
346  Cerr("***** iScannerPET::CheckParameters() -> Scanner type not initialized !" << endl);
347  return 1;
348  }
349  // End
350  m_allParametersChecked = true;
351  return 0;
352 }
353 
354 // =====================================================================
355 // ---------------------------------------------------------------------
356 // ---------------------------------------------------------------------
357 // =====================================================================
358 /*
359  \fn Initialize
360  \brief Check general initialization and set several parameters to their default value
361  \return 0 if success, positive value otherwise
362 */
364 {
366 
367  // Verbose
368  if (m_verbose>=VERBOSE_NORMAL) Cout("iScannerPET::Initialize() -> Initialize remaining stuff for scanner to be ready"<< endl);
369 
370  // Parameters checked ?
372  {
373  Cerr("***** iScannerPET::Initialize() -> Parameters have not been checked !" << endl);
374  return 1;
375  }
376 
377  // Set mean depth of interaction to the center of crystal if not initialized by default.
378  for(int l=0 ; l<m_nbLayers ; l++)
380 
381  // Conversion of the minimal angle difference into radians
382  m_minAngleDifference *= M_PI/180;
383 
384  return 0;
385 }
386 
387 // =====================================================================
388 // ---------------------------------------------------------------------
389 // ---------------------------------------------------------------------
390 // =====================================================================
391 /*
392  \fn LoadLUT
393  \brief Load a precomputed scanner LUT
394  \details Read mandatory data from the header of the LUT.
395  Then load the LUT elements for each crystal
396  \return 0 if success, positive value otherwise
397 */
399 {
401 
402  // Verbose
403  if (m_verbose>=VERBOSE_DETAIL) Cout("iScannerPET::LoadLUT() -> Start loading LUT from user-provided file" << endl);
404 
405  // Get scanner manager
406  sScannerManager* p_scannerManager;
407  p_scannerManager = sScannerManager::GetInstance();
408 
409  if(ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of crystals in layer", mp_nbCrystalsInLayer, m_nbLayers, 1) )
410  {
411  Cerr("***** iScannerPET::LoadLUT() -> An error occurred while trying to read a mandatory parameter in the scanner header file !" << endl);
412  return 1;
413  }
414 
415  // Open the scanner LUT file
416  string scanner_lut_file = p_scannerManager->GetPathToScannerFile();
417  //todo : maybe more practical to directly read the path from the header.
418  // Do not put restrictions on the file extension (.lut)
419  scanner_lut_file = scanner_lut_file.substr(0, scanner_lut_file.find_last_of(".")).append(".lut");
420 
421  // Open file
422  FILE* LUT_file = fopen(scanner_lut_file.c_str(), "rb");
423  if (LUT_file==NULL)
424  {
425  Cerr("***** iScannerPET::LoadLUT() -> Input LUT file '" << scanner_lut_file << "' is missing or corrupted !" << endl);
426  return 1;
427  }
428 
429  // Read data for each index
430  int nb_data_read = 0;
431  for (int i=0; i<m_nbCrystals; i++)
432  {
433  FLTNBLUT buffer;
434  // Read central crystal position X, then Y and Z
435  nb_data_read += fread(&buffer,sizeof(FLTNBLUT),1,LUT_file);
436  mp_crystalCentralPositionX[i] = ((FLTNB)buffer);
437  nb_data_read += fread(&buffer,sizeof(FLTNBLUT),1,LUT_file);
438  mp_crystalCentralPositionY[i] = ((FLTNB)buffer);
439  nb_data_read += fread(&buffer,sizeof(FLTNBLUT),1,LUT_file);
440  mp_crystalCentralPositionZ[i] = ((FLTNB)buffer);
441  // Read crystal orientation X, then Y and Z
442  nb_data_read += fread(&buffer,sizeof(FLTNBLUT),1,LUT_file);
443  mp_crystalOrientationX[i] = ((FLTNB)buffer);
444  nb_data_read += fread(&buffer,sizeof(FLTNBLUT),1,LUT_file);
445  mp_crystalOrientationY[i] = ((FLTNB)buffer);
446  nb_data_read += fread(&buffer,sizeof(FLTNBLUT),1,LUT_file);
447  mp_crystalOrientationZ[i] = ((FLTNB)buffer);
448  }
449 
450  // Close file
451  fclose(LUT_file);
452 
453  // Check reading
454  if (nb_data_read!=m_nbCrystals*6)
455  {
456  Cerr("***** iScannerPET::LoadLUT() -> Failed to read all data in input LUT file '" << scanner_lut_file << "' !" << endl);
457  return 1;
458  }
459  // Verbose
460  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> LUT successfully loaded" << endl);
461  // End
462  return 0;
463 }
464 
465 // =====================================================================
466 // ---------------------------------------------------------------------
467 // ---------------------------------------------------------------------
468 // =====================================================================
469 /*
470  \fn ComputeLUT
471  \brief Compute the LUT of the scanner from a generic (.geom) file
472  \details Read mandatory data from the geom file. Then compute the LUT elements for each crystal from the geometry described in the file
473  \details Compute the look-up-tables of the system containing the locations of the scanner elements center in space and their orientations
474  \todo Add option to inverse rsector if transaxial orientation is counter-clockwise.?
475  \todo rotation for non-perfectly cylindric scanner (angles along the Z axis)
476  \return 0 if success, positive value otherwise
477 */
479 {
481 
482  if (m_verbose>=VERBOSE_DETAIL) Cout("iScannerPET::ComputeLUT() -> Start LUT generation" << endl);
483 
484  // ============================================================================================================
485  // Init local variables to recover data from scanner file
486  // ============================================================================================================
487 
488  // Layer dependent variables
489  int *nb_ang_rsector_lyr, // nb of angular position for the rsectors
490  *nb_axl_rsector_lyr, // nb of axial subdivision of the rsector for a same angular position
491  *nb_trs_mod_lyr,
492  *nb_axl_mod_lyr,
493  *nb_trs_submod_lyr,
494  *nb_axl_submod_lyr,
495  *nb_trs_crystal_lyr,
496  *nb_axl_crystal_lyr;
497 
498  FLTNBLUT *radius_lyr,
499  *rsector_first_angle_lyr,
500  *rsector_angular_span_lyr,
501  *gap_axl_rsector_lyr,
502  *gap_trs_mod_lyr,
503  *gap_axl_mod_lyr,
504  *gap_trs_submod_lyr,
505  *gap_axl_submod_lyr,
506  *gap_trs_crystal_lyr,
507  *gap_axl_crystal_lyr;
508 
509  nb_ang_rsector_lyr = new int[m_nbLayers];
510  nb_axl_rsector_lyr = new int[m_nbLayers];
511  nb_trs_mod_lyr = new int[m_nbLayers];
512  nb_axl_mod_lyr = new int[m_nbLayers];
513  nb_trs_submod_lyr = new int[m_nbLayers];
514  nb_axl_submod_lyr = new int[m_nbLayers];
515  nb_trs_crystal_lyr = new int[m_nbLayers];
516  nb_axl_crystal_lyr = new int[m_nbLayers];
517 
518  radius_lyr = new FLTNBLUT[m_nbLayers];
519  gap_axl_rsector_lyr = new FLTNBLUT[m_nbLayers];
520  gap_trs_mod_lyr = new FLTNBLUT[m_nbLayers];
521  gap_axl_mod_lyr = new FLTNBLUT[m_nbLayers];
522  gap_trs_submod_lyr = new FLTNBLUT[m_nbLayers];
523  gap_axl_submod_lyr = new FLTNBLUT[m_nbLayers];
524  gap_trs_crystal_lyr = new FLTNBLUT[m_nbLayers];
525  gap_axl_crystal_lyr = new FLTNBLUT[m_nbLayers];
526  rsector_first_angle_lyr = new FLTNBLUT[m_nbLayers];
527  rsector_angular_span_lyr = new FLTNBLUT[m_nbLayers];
528 
529  string rotation_direction = "";
530 
531  // -------------------------------------------------------------------
532  // Recover mandatory data from scanner file
533  sScannerManager* p_scannerManager;
534  p_scannerManager = sScannerManager::GetInstance();
535 
536  if( ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of rsectors", nb_ang_rsector_lyr, m_nbLayers, KEYWORD_MANDATORY) ||
537  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of crystals transaxial", nb_trs_crystal_lyr, m_nbLayers, KEYWORD_MANDATORY) ||
538  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of crystals axial", nb_axl_crystal_lyr, m_nbLayers, KEYWORD_MANDATORY) ||
539  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "scanner radius", radius_lyr, m_nbLayers, KEYWORD_MANDATORY) )
540  {
541  Cerr("***** iScannerPET::ComputeLUT() -> An error occurred while trying to read a mandatory parameter for scanner LUT generation in the scanner header file !" << endl);
542  return 1;
543  }
544 
545  // -------------------------------------------------------------------
546  // Recover optionnal data from scanner file
547 
548  // Initialize defaulf values
549  for(int l=0 ; l<m_nbLayers ; l++)
550  {
551  nb_axl_rsector_lyr[ l ] =1;
552  nb_trs_mod_lyr[ l ] =1;
553  nb_axl_mod_lyr[ l ] =1;
554  nb_trs_submod_lyr[ l ] = 1;
555  nb_axl_submod_lyr[ l ] = 1;
556  gap_axl_rsector_lyr[ l ] = 0.;
557  gap_trs_mod_lyr[ l ] = 0.;
558  gap_axl_mod_lyr[ l ] = 0.;
559  gap_trs_submod_lyr[ l ] = 0.;
560  gap_axl_submod_lyr[ l ] = 0.;
561  gap_trs_crystal_lyr[ l ] = 0.;
562  gap_axl_crystal_lyr[ l ] = 0.;
563  rsector_first_angle_lyr[ l ] = 0;
564  rsector_angular_span_lyr[ l ] = 360.;
565  }
566 
567  if( ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of rsectors axial", nb_axl_rsector_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
568  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of modules transaxial", nb_trs_mod_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
569  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of modules axial", nb_axl_mod_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
570  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of submodules transaxial", nb_trs_submod_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
571  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of submodules axial", nb_axl_submod_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
572  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "rsector gap axial", gap_axl_rsector_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
573  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "module gap transaxial", gap_trs_mod_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
574  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "module gap axial", gap_axl_mod_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
575  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "submodule gap transaxial", gap_trs_submod_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
576  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "submodule gap axial", gap_axl_submod_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
577  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "crystal gap transaxial", gap_trs_crystal_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
578  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "crystal gap axial", gap_axl_crystal_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
579  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "rsectors first angle", rsector_first_angle_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
580  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "rsectors angular span", rsector_angular_span_lyr, m_nbLayers, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR ||
581  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "rotation direction", &rotation_direction, 1, KEYWORD_OPTIONAL) == KEYWORD_OPTIONAL_ERROR)
582  {
583  Cerr("***** iScannerPET::ComputeLUT() -> An error occurred while trying to read an optionnal parameter for scanner LUT generation in the scanner header file !" << endl);
584  return 1;
585  }
586 
587  // Compute nb rings per layer
588  uint16_t *p_nbRings = new uint16_t[m_nbLayers];
589  for(int lyr=0 ; lyr<m_nbLayers ; lyr++)
590  p_nbRings[lyr] = nb_axl_rsector_lyr[ lyr ]
591  * nb_axl_mod_lyr[ lyr ]
592  * nb_axl_submod_lyr[ lyr ]
593  * nb_axl_crystal_lyr[ lyr ];
594 
595  // Set rotation direction for geometry generation
596  if (SetRotDirection(rotation_direction) )
597  {
598  Cerr("***** iScannerPET::ComputeLUT() ->Error occurred while trying to initialize head rotation orientation " << endl);
599  return 1;
600  }
601 
602  // Z-SHIFT MANAGEMENT
603 
604  // Variables
605  int rsector_nb_zshift;
606  FLTNBLUT *zshift;
607 
608  int rvalue = ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "rsectors nbZShift", &rsector_nb_zshift, 1, KEYWORD_OPTIONAL);
609 
610  // reading error
611  if( rvalue == KEYWORD_OPTIONAL_ERROR )
612  {
613  Cerr("***** iScannerPET::ComputeLUT() -> Error occurred when trying to read z-shift nb !" << endl);
614  return 1;
615  }
616  // z-shift not found, or == 0
617  else if( rvalue > 1 || rsector_nb_zshift==0)
618  {
619  rsector_nb_zshift = 1; // set to default value (=1)
620  zshift = new FLTNBLUT[rsector_nb_zshift];
621  zshift[0] = 0;
622  }
623  // (==0, zhift value provided)
624  else
625  {
626  zshift = new FLTNBLUT[rsector_nb_zshift];
627 
628  if(ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "rsectors ZShift", zshift, rsector_nb_zshift, KEYWORD_MANDATORY) )
629  {
630  Cerr("***** iScannerPET::ComputeLUT() -> No data found about modules z-shift in the scanner header file, whereas z-shift is enabled !" << endl);
631  return 1;
632  }
633  }
634 
635  // keep track of the number of crystals already indexed between layer
636  int nb_crystals_cur = 0;
637 
638  // Loop on layer, crystals indexed according to the layer they belong to
639  for(int lyr=0 ; lyr<m_nbLayers ; lyr++)
640  {
641  if (lyr>0)
642  nb_crystals_cur+=mp_nbCrystalsInLayer[lyr-1];
643 
644  int nb_ang_rsector = nb_ang_rsector_lyr[lyr],
645  nb_axl_rsector = nb_axl_rsector_lyr[lyr],
646  nb_trs_mod = nb_trs_mod_lyr[lyr],
647  nb_axl_mod = nb_axl_mod_lyr[lyr],
648  nb_trs_submod = nb_trs_submod_lyr[lyr],
649  nb_axl_submod = nb_axl_submod_lyr[lyr],
650  nb_trs_crystal = nb_trs_crystal_lyr[lyr],
651  nb_axl_crystal = nb_axl_crystal_lyr[lyr];
652 
653  FLTNBLUT radius = radius_lyr[lyr],
654  rsector_first_angle = rsector_first_angle_lyr[lyr],
655  angular_span = rsector_angular_span_lyr[lyr],
656  gap_axl_rsector = gap_axl_rsector_lyr[lyr],
657  gap_trs_mod = gap_trs_mod_lyr[lyr],
658  gap_axl_mod = gap_axl_mod_lyr[lyr],
659  gap_trs_submod = gap_trs_submod_lyr[lyr],
660  gap_axl_submod = gap_axl_submod_lyr[lyr],
661  gap_trs_crystal = gap_trs_crystal_lyr[lyr],
662  gap_axl_crystal = gap_axl_crystal_lyr[lyr];
663 
664  FLTNBLUT size_trs_submod = nb_trs_crystal*mp_sizeCrystalTrans[lyr] + (nb_trs_crystal-1)*gap_trs_crystal;
665  FLTNBLUT size_axl_submod = nb_axl_crystal*mp_sizeCrystalAxial[lyr] + (nb_axl_crystal-1)*gap_axl_crystal;
666  FLTNBLUT size_trs_mod = nb_trs_submod*size_trs_submod + (nb_trs_submod-1)*gap_trs_submod;
667  FLTNBLUT size_axl_mod = nb_axl_submod*size_axl_submod + (nb_axl_submod-1)*gap_axl_submod;
668  FLTNBLUT size_trs_rsector = nb_trs_mod*size_trs_mod + (nb_trs_mod-1)*gap_trs_mod;
669  FLTNBLUT size_axl_rsector = nb_axl_mod*size_axl_mod + (nb_axl_mod-1)*gap_axl_mod;
670 
671 
672  int nb_rsectors = nb_ang_rsector * nb_axl_rsector;
673  int nb_mod = nb_axl_mod * nb_trs_mod;
674  int nb_submod = nb_axl_submod * nb_trs_submod;
675  int nb_crystal = nb_axl_crystal * nb_trs_crystal;
676 
677  // Check the number of crystals < nb elements provided by the scanner configuration file
678  int nb_crystals = nb_rsectors*nb_mod*nb_submod*nb_crystal + nb_crystals_cur;
679  if(m_nbCrystals<nb_crystals)
680  {
681  Cerr("***** iScannerPET::ComputeLUT() -> Computed number of crystals computed from the geom file ("<< nb_crystals
682  <<") > not consistent with the total number of crystals (including potential layers) provided in the geom file ("<< m_nbCrystals <<") !" << endl);
683  return 1;
684  }
685 
686  mp_nbCrystalsInLayer[lyr] = nb_rsectors*nb_mod*nb_submod*nb_crystal;
687  int number_crystals_in_ring = mp_nbCrystalsInLayer[lyr]/p_nbRings[lyr];
688 
689  // ============================================================================================================
690  // Main part of the function: Generate the LUT
691  // ============================================================================================================
692 
693  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> Generate positions for each crystal"<< endl);
694 
695  // loop to nb_ang_rsector+1. crystal_center[0] will be used to gather position of the reference rsector angular position (directly above isocenter)
696  oMatrix ******crystal_center = new oMatrix *****[nb_ang_rsector+1];
697 
698  for(int rsa = 0; rsa < nb_ang_rsector+1 ; rsa++)
699  {
700  crystal_center[rsa] = new oMatrix ****[ nb_axl_rsector ];
701 
702  for (int i = 0; i<nb_axl_rsector ; i++)
703  {
704  crystal_center[rsa][i] = new oMatrix ***[ nb_mod ];
705 
706  for (int j = 0; j<nb_mod ; j++)
707  {
708  crystal_center[rsa][i][j] = new oMatrix **[ nb_submod ];
709 
710  for (int k = 0; k<nb_submod; k++)
711  {
712  crystal_center[rsa][i][j][k] = new oMatrix*[ nb_crystal ];
713 
714  for (int l = 0; l<nb_crystal; l++)
715  {
716  crystal_center[rsa][i][j][k][l] = new oMatrix(3,1);
717  }
718  }
719  }
720  }
721  }
722 
723 
724 
725  // Generation of the rotation matrix allowing to compute the position of all the rsectors.
726  oMatrix** rotation_mtx = new oMatrix*[nb_rsectors];
727 
728  for(int i=0; i<nb_ang_rsector; i++)
729  rotation_mtx[i] = new oMatrix(3,3);
730 
731  // convert first angle and angular span in radians
732  FLTNBLUT rsector_first_angle_rad = rsector_first_angle*M_PI/180.;
733  FLTNBLUT angular_span_rad = angular_span*M_PI/180.;
734 
735 
736  int dir = (m_rotDirection == GEO_ROT_CCW) ? -1 : 1;
737 
738 
739  for (int i = 0; i<nb_ang_rsector; i++)
740  {
741  FLTNBLUT angle = remainderf(rsector_first_angle_rad + ((FLTNBLUT)i)*angular_span_rad/((FLTNBLUT)(nb_ang_rsector)), 2.*M_PI);
742 
743  rotation_mtx[i]->SetMatriceElt(0,0,cos(angle) );
744  rotation_mtx[i]->SetMatriceElt(1,0,-dir*sin(angle) );
745  rotation_mtx[i]->SetMatriceElt(2,0,0);
746  rotation_mtx[i]->SetMatriceElt(0,1,dir*sin(angle) );
747  rotation_mtx[i]->SetMatriceElt(1,1,cos(angle) );
748  rotation_mtx[i]->SetMatriceElt(2,1,0);
749  rotation_mtx[i]->SetMatriceElt(0,2,0);
750  rotation_mtx[i]->SetMatriceElt(1,2,0);
751  rotation_mtx[i]->SetMatriceElt(2,2,1);
752  }
753 
754  // Compute scanner elements positions for the first rsector
755  for (int r=0; r < nb_axl_rsector ; r++)
756  {
757  // Define the transaxial and axial edge start positions for the global rsector
758  FLTNBLUT x_start_r = (-dir*size_trs_rsector) / 2.;
759  FLTNBLUT z_start_r = -(nb_axl_rsector*size_axl_rsector + (nb_axl_rsector-1)*gap_axl_rsector) / 2.;
760 
761  z_start_r += r * (size_axl_rsector + gap_axl_rsector);
762 
763  for (int i=0; i < nb_mod ; i++)
764  {
765  // Define the transaxial and axial edge start positions for the rsector
766  FLTNBLUT x_start_m = x_start_r;//-dir*(nb_trs_mod*size_trs_mod + (nb_trs_mod-1)*gap_trs_mod) / 2.;
767  FLTNBLUT z_start_m = z_start_r;//-(nb_axl_mod*size_axl_mod + (nb_axl_mod-1)*gap_axl_mod) / 2.;
768 
769  // Define the transaxial and axial edge start positions for the i-Module in the rsector.
770  // Enumeration starting with the transaxial modules.
771  x_start_m += dir*(i%nb_trs_mod) * (size_trs_mod + gap_trs_mod);
772  z_start_m += int(i/nb_trs_mod) * (size_axl_mod + gap_axl_mod);
773 
774  for (int j=0 ; j < nb_submod ; j++)
775  {
776  FLTNBLUT x_start_sm = x_start_m;
777  FLTNBLUT z_start_sm = z_start_m;
778 
779  x_start_sm += dir*(j%nb_trs_submod) * (size_trs_submod + gap_trs_submod);
780  z_start_sm += int(j/nb_trs_submod) * (size_axl_submod + gap_axl_submod);
781 
782  for (int k=0 ; k < nb_crystal ; k++)
783  {
784  // Define the transaxial and axial center positions for the j-SubModule (crystal) i-Module of the rsector.
785  // Enumeration starting with the transaxial submodules.
786  FLTNBLUT Xcrist = x_start_sm + dir* ( (k%nb_trs_crystal) * (mp_sizeCrystalTrans[lyr] + gap_trs_crystal) + mp_sizeCrystalTrans[lyr]/2 );
787  FLTNBLUT Ycrist = radius + mp_sizeCrystalDepth[lyr]/2;
788  FLTNBLUT Zcrist = z_start_sm + int(k/nb_trs_crystal) * (mp_sizeCrystalAxial[lyr] + gap_axl_crystal) + mp_sizeCrystalAxial[lyr]/2;
789 
790  crystal_center[0][r][i][j][k]->SetMatriceElt(0,0,Xcrist);
791  crystal_center[0][r][i][j][k]->SetMatriceElt(1,0,Ycrist);
792  crystal_center[0][r][i][j][k]->SetMatriceElt(2,0,Zcrist);
793  }
794  }
795  }
796  }
797 
798 
799  // ============================================================================================================
800  // Loop over all the other rsectors.
801  // Positions of the scanner elements are progressively stored in the LUT file.
802  // ============================================================================================================
803  for (int rsa=0 ; rsa<nb_ang_rsector ; rsa++)
804  for (int rs=0 ; rs<nb_axl_rsector ; rs++)
805  for (int m=0 ; m<nb_mod ; m++)
806  for (int sm=0 ; sm<nb_submod ; sm++)
807  for (int c=0 ; c<nb_crystal ; c++)
808  {
809  // crystal indexation
810  int cryID = rs * nb_axl_mod * nb_axl_submod * nb_axl_crystal * number_crystals_in_ring // nb indexed crystals in the rings covered by the previous (axial) rsectors
811  + int(m/nb_trs_mod) * nb_axl_submod * nb_axl_crystal * number_crystals_in_ring // nb indexed crystals in the rings covered by the previous (axial) modules
812  + int(sm/nb_trs_submod) * nb_axl_crystal * number_crystals_in_ring // nb indexed crystals in the rings covered by the previous (axial) submodules
813  + int(c/nb_trs_crystal) * number_crystals_in_ring // nb indexed crystals in the rings covered by the previous (axial) crystals
814  + rsa * nb_trs_mod * nb_trs_submod * nb_trs_crystal // nb indexed crystals in the previous (transaxial) rsectors
815  + m%nb_trs_mod * nb_trs_submod * nb_trs_crystal // nb indexed crystals in the previous (transaxial) modules
816  + sm%nb_trs_submod * nb_trs_crystal // nb indexed crystals in the previous (transaxial) submodules
817  + c%nb_trs_crystal // previous crystals in the submodule
818  + nb_crystals_cur; // nb indexed crystals in the previous layer(s)
819 
820 
821  rotation_mtx[rsa]->Multiplication(crystal_center[0][rs][m][sm][c], crystal_center[rsa+1][rs][m][sm][c]);
822 
823  mp_crystalCentralPositionX[cryID] = (FLTNB)crystal_center[rsa+1][rs][m][sm][c]->GetMatriceElt(0,0);
824  mp_crystalCentralPositionY[cryID] = (FLTNB)crystal_center[rsa+1][rs][m][sm][c]->GetMatriceElt(1,0);
825  mp_crystalCentralPositionZ[cryID] = (FLTNB)crystal_center[rsa+1][rs][m][sm][c]->GetMatriceElt(2,0);
826  mp_crystalCentralPositionZ[cryID] += (FLTNB)zshift[rsa%rsector_nb_zshift];
827 
828  // TODO Rotation for non-perfectly cylindric scanner (angles along the Z axis)
829  mp_crystalOrientationX[cryID] = (FLTNB)rotation_mtx[rsa]->GetMatriceElt(0,1);
830  mp_crystalOrientationY[cryID] = (FLTNB)rotation_mtx[rsa]->GetMatriceElt(0,0);
831  mp_crystalOrientationZ[cryID] = 0.;
832  }
833 
834  // ============================================================================================================
835  // End
836  // ============================================================================================================
837 
838  // Delete objects
839  for (int rsa = 0; rsa<nb_ang_rsector+1 ; rsa++)
840  for (int i = 0; i<nb_axl_rsector ; i++)
841  for (int j = 0; j<nb_mod; j++)
842  for (int k = 0; k<nb_submod; k++)
843  for (int l = 0; l<nb_crystal; l++)
844  {
845  delete crystal_center[rsa][i][j][k][l];
846  }
847 
848 
849  for(int rsa = 0; rsa < nb_ang_rsector+1 ; rsa++)
850  for(int i = 0; i < nb_axl_rsector ; i++)
851  for (int j = 0; j<nb_mod; j++)
852  for (int k = 0; k<nb_submod; k++)
853  {
854  delete[] crystal_center[rsa][i][j][k];
855  }
856 
857  for(int rsa = 0; rsa < nb_ang_rsector+1 ; rsa++)
858  for(int i = 0; i < nb_axl_rsector ; i++)
859  for (int j = 0; j<nb_mod; j++)
860  {
861  delete[] crystal_center[rsa][i][j];
862  }
863 
864  for(int rsa = 0; rsa < nb_ang_rsector+1 ; rsa++)
865  for(int i = 0; i < nb_axl_rsector ; i++)
866  {
867  delete[] crystal_center[rsa][i];
868  }
869 
870  for(int rsa = 0; rsa < nb_ang_rsector+1 ; rsa++)
871  delete[] crystal_center[rsa];
872 
873 
874  for(int i = 0; i < nb_ang_rsector ; i++)
875  delete rotation_mtx[i];
876 
877  delete[] crystal_center;
878  delete[] rotation_mtx;
879 
880  }
881 
882  // ============================================================================================================
883  // Save LUT if required
884  // ============================================================================================================
885 
886  if (p_scannerManager->SaveLUTFlag())
887  {
888  // Make file names
889  string path_to_geom_file = p_scannerManager->GetPathToScannerFile();
890  string path_to_LUT = path_to_geom_file.substr(0, path_to_geom_file.find_last_of("."));
891  string path_to_header_LUT = path_to_LUT + ".ghscan";
892  path_to_LUT.append(".glut");
893  // Verbose
894  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> Save LUT into file with extension '.glut'" << endl);
895  // Open file
896  ofstream LUT_file, header_LUT_file;
897  LUT_file.open(path_to_LUT.c_str(), ios::binary | ios::out);
898  // Write the corresponding crystal parameters in the binary LUT
899  for (int i=0 ; i<m_nbCrystals ; i++)
900  {
901  LUT_file.write(reinterpret_cast<char*>(&mp_crystalCentralPositionX[i]), sizeof(FLTNB));
902  LUT_file.write(reinterpret_cast<char*>(&mp_crystalCentralPositionY[i]), sizeof(FLTNB));
903  LUT_file.write(reinterpret_cast<char*>(&mp_crystalCentralPositionZ[i]), sizeof(FLTNB));
904  LUT_file.write(reinterpret_cast<char*>(&mp_crystalOrientationX[i]), sizeof(FLTNB));
905  LUT_file.write(reinterpret_cast<char*>(&mp_crystalOrientationY[i]), sizeof(FLTNB));
906  LUT_file.write(reinterpret_cast<char*>(&mp_crystalOrientationZ[i]), sizeof(FLTNB));
907  }
908  // Close file
909  LUT_file.close();
910  // ---------------------
911  // --> Write header file
912  // ---------------------
913  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> Save header LUT file with extension '.ghscan'" << endl);
914  // Open file
915  header_LUT_file.open(path_to_header_LUT.c_str(), ios::out);
916  // Start writing
917  string scanner_name = path_to_geom_file.substr(0, path_to_geom_file.find_last_of("."));
918  header_LUT_file << "scanner name:" << " " << GetFileFromPath(scanner_name) << endl;
919  header_LUT_file << "modality:" << " " << "PET" << endl;
920 
921  header_LUT_file << "scanner radius:" << " " << radius_lyr[0];
922  for (int lyr=1 ; lyr<m_nbLayers ; lyr++)
923  header_LUT_file << "," << radius_lyr[lyr] ;
924  header_LUT_file << endl;
925 
926  header_LUT_file << "number of elements:" << " " << m_nbCrystals << endl;
927  header_LUT_file << "number of layers:" << " " << m_nbLayers << endl;
928  header_LUT_file << "number of crystals in layer(s):" << " " << mp_nbCrystalsInLayer[0]; // TODO nb_cry_in_layer
929  for (int lyr=1 ; lyr<m_nbLayers ; lyr++)
930  header_LUT_file << ","<< mp_nbCrystalsInLayer[lyr] ;
931  header_LUT_file << endl;
932 
933  header_LUT_file << "crystals size depth:" << " " << mp_sizeCrystalDepth[0];
934  for (int lyr=1 ; lyr<m_nbLayers ; lyr++)
935  header_LUT_file << ","<< mp_sizeCrystalDepth[lyr] ;
936  header_LUT_file << endl;
937 
938  header_LUT_file << "crystals size transaxial:" << " " << mp_sizeCrystalTrans[0];
939  for (int lyr=1 ; lyr<m_nbLayers ; lyr++)
940  header_LUT_file << ","<< mp_sizeCrystalTrans[lyr] ;
941  header_LUT_file << endl;
942 
943  header_LUT_file << "crystals size axial:" << " " << mp_sizeCrystalAxial[0];
944  for (int lyr=1 ; lyr<m_nbLayers ; lyr++)
945  header_LUT_file << ","<< mp_sizeCrystalAxial[lyr] ;
946  header_LUT_file << endl;
947 
948  // Default reconstruction parameters
949  uint32_t def_dim_trs = 0, def_dim_axl = 0; // default number of voxels
950  FLTNB def_FOV_trs = -1., def_FOV_axl = -1; // computed from modules width and gaps of the 1st layer
951 
952  if( ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "voxels number transaxial", &def_dim_trs, 1, KEYWORD_MANDATORY) ||
953  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "voxels number axial", &def_dim_axl, 1, KEYWORD_MANDATORY) ||
954  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "field of view transaxial", &def_FOV_trs, 1, KEYWORD_MANDATORY) ||
955  ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "field of view axial", &def_FOV_axl, 1, KEYWORD_MANDATORY) )
956  {
957  Cerr("***** iScannerPET::ComputeLUT() -> Error occurred when trying to read transaxial/axial dimensions and voxel sizes from scanner geom file !" << endl);
958  return 1;
959  }
960 
961  header_LUT_file << "voxels number transaxial:" << " " << def_dim_trs << endl;
962  header_LUT_file << "voxels number axial:" << " " << def_dim_axl << endl;
963 
964  header_LUT_file << "field of view transaxial:" << " " << def_FOV_trs << endl;
965  header_LUT_file << "field of view axial:" << " " << def_FOV_axl << endl;
966 
967  header_LUT_file << "min angle difference:" << " " << m_minAngleDifference << " #deg" << endl;
968 
969  header_LUT_file << "mean depth of interaction:" << " " << mp_meanDepthOfInteraction[0];
970  for (int lyr=1 ; lyr<m_nbLayers ; lyr++)
971  header_LUT_file << "," << mp_meanDepthOfInteraction[lyr] ;
972  header_LUT_file << " #optional (default value : center of crystal ). Input value must correspond to the distance from the crystal surface, or negative value if default" << endl;
973 
974  header_LUT_file << "description:" << " " << "LUT generated from geom file: " << GetFileFromPath(p_scannerManager->GetPathToScannerFile()) << endl;
975 
976  if(m_verbose>=2) Cout("iScannerPET::ComputeLUT() -> Header LUT file writing completed" << endl);
977  }
978 
979  delete[] p_nbRings;
980  delete[] nb_ang_rsector_lyr;
981  delete[] nb_axl_rsector_lyr;
982  delete[] nb_trs_mod_lyr;
983  delete[] nb_axl_mod_lyr;
984  delete[] nb_trs_submod_lyr;
985  delete[] nb_axl_submod_lyr;
986  delete[] nb_trs_crystal_lyr;
987  delete[] nb_axl_crystal_lyr;
988 
989  delete[] radius_lyr;
990  delete[] rsector_angular_span_lyr;
991  delete[] rsector_first_angle_lyr;
992  delete[] zshift;
993  delete[] gap_axl_rsector_lyr;
994  delete[] gap_trs_mod_lyr;
995  delete[] gap_axl_mod_lyr;
996  delete[] gap_trs_submod_lyr;
997  delete[] gap_axl_submod_lyr;
998  delete[] gap_trs_crystal_lyr;
999  delete[] gap_axl_crystal_lyr;
1000 
1001  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> LUT generation completed" << endl);
1002 
1003  return 0;
1004 }
1005 
1006 // =====================================================================
1007 // ---------------------------------------------------------------------
1008 // ---------------------------------------------------------------------
1009 // =====================================================================
1010 /*
1011  \fn GetPositionsAndOrientations
1012  \param a_index1 : index of the 1st crystal
1013  \param a_index2 : index of the 2nd crystal
1014  \param ap_Position1[3] : x,y,z cartesian position of center of the 1st crystal
1015  \param ap_Position2[3] : x,y,z cartesian position of center of the 2nd crystal
1016  \param ap_Orientation1[3] : x,y,z components of the orientation vector related to the 1st crystal
1017  \param ap_Orientation2[3] : x,y,z components of the orientation vector related to the 2nd crystal
1018  \param ap_POI1 : x,y,z components of the Point Of Interation related to the 1st crystal
1019  \param ap_POI2 : x,y,z components of the Point Of Interation related to the 2nd crystal
1020  \brief Get the central positions and orientations of the scanner elements from their indices.
1021  \details This method is very general and is used by the vProjector.
1022  From these positions and orientations, other methods can be used by specific projectors to get specific positions.
1023  Position calculations include POI and mean depth of interaction
1024  \todo some cases depending on POI are not implemented
1025  \return 0 if success, positive value otherwise
1026 */
1027 int iScannerPET::GetPositionsAndOrientations( int a_index1, int a_index2,
1028  FLTNB ap_Position1[3], FLTNB ap_Position2[3],
1029  FLTNB ap_Orientation1[3], FLTNB ap_Orientation2[3],
1030  FLTNB* ap_POI1, FLTNB* ap_POI2 )
1031 {
1033 
1034  // First check crystals existency
1035  if (a_index1<0 || a_index1>=m_nbCrystals)
1036  {
1037  Cerr("***** iScannerPET::GetPositionsAndOrientations() -> Crystal index 1 (" << a_index1 << ") out of range [0:" << m_nbCrystals-1 << "] !" << endl);
1038  return 1;
1039  }
1040  if (a_index2<0 || a_index2>=m_nbCrystals)
1041  {
1042  Cerr("***** iScannerPET::GetPositionsAndOrientations() -> Crystal index 2 (" << a_index2 << ") out of range [0:" << m_nbCrystals-1 << "] !" << endl);
1043  return 1;
1044  }
1045 
1046  // The POI specification is as follows:
1047  // - POI[0] and POI[1] express the local x and y coordinates: x is the tangential axis, and y is colinear to the global z axis.
1048  // Their origins are in the middle of the scanner elements so they range from minus half to half the element dimensions.
1049  // - POI[2] express the DOI along the local z axis which is colinear to the crystal orientation (which itself is oriented from
1050  // the center of the scanner to the exterior).
1051  // Its origin is at the entrance of the element and thus ranges from 0 to the local z size of the element (crystal depth).
1052  // - If POI[2] has a negative value, this means that the mean depth of interaction should be considered instead, while still
1053  // using POI[0] and POI[1].
1054  // Remember here that the crystal position is at its center.
1055 
1056  // Case when POI is not provided (we use the mean depth of interaction)
1057  if (ap_POI1==NULL)
1058  {
1059  FLTNB depth = mp_meanDepthOfInteraction[GetLayer(a_index1)] - mp_sizeCrystalDepth[GetLayer(a_index1)]/2.;
1060  ap_Position1[0] = mp_crystalCentralPositionX[a_index1] + depth*mp_crystalOrientationX[a_index1];
1061  ap_Position1[1] = mp_crystalCentralPositionY[a_index1] + depth*mp_crystalOrientationY[a_index1];
1062  ap_Position1[2] = mp_crystalCentralPositionZ[a_index1] + depth*mp_crystalOrientationZ[a_index1];
1063  }
1064  // Case when POI[2] is negative (meaning we only have POI[0] or POI[1] specified and to be taken into account)
1065  else if (ap_POI1[2]<0.)
1066  {
1067  // TODO
1068  }
1069  // Case when only the DOI is provided
1070  else if (ap_POI1[0]==0. && ap_POI1[1]==0.)
1071  {
1072  FLTNB depth = ap_POI1[2] - mp_sizeCrystalDepth[GetLayer(a_index1)]/2.;
1073  ap_Position1[0] = mp_crystalCentralPositionX[a_index1] + depth*mp_crystalOrientationX[a_index1];
1074  ap_Position1[1] = mp_crystalCentralPositionY[a_index1] + depth*mp_crystalOrientationY[a_index1];
1075  ap_Position1[2] = mp_crystalCentralPositionZ[a_index1] + depth*mp_crystalOrientationZ[a_index1];
1076  }
1077  // Case when the full POI is taken into account
1078  else
1079  {
1080  // TODO
1081  }
1082 
1083  // Case when POI is not provided (we use the mean depth of interaction)
1084  if (ap_POI2==NULL)
1085  {
1086  FLTNB depth = mp_meanDepthOfInteraction[GetLayer(a_index2)] - mp_sizeCrystalDepth[GetLayer(a_index2)]/2.;
1087  ap_Position2[0] = mp_crystalCentralPositionX[a_index2] + depth*mp_crystalOrientationX[a_index2];
1088  ap_Position2[1] = mp_crystalCentralPositionY[a_index2] + depth*mp_crystalOrientationY[a_index2];
1089  ap_Position2[2] = mp_crystalCentralPositionZ[a_index2] + depth*mp_crystalOrientationZ[a_index2];
1090  }
1091  // Case when POI[2] is negative (meaning we only have POI[0] or POI[1] specified and to be taken into account)
1092  else if (ap_POI2[2]<0.)
1093  {
1094  // TODO
1095  }
1096  // Case when only the DOI is provided
1097  else if (ap_POI2[0]==0. && ap_POI2[1]==0.)
1098  {
1099  FLTNB depth = ap_POI2[2] - mp_sizeCrystalDepth[GetLayer(a_index2)]/2.;
1100  ap_Position2[0] = mp_crystalCentralPositionX[a_index2] + depth*mp_crystalOrientationX[a_index2];
1101  ap_Position2[1] = mp_crystalCentralPositionY[a_index2] + depth*mp_crystalOrientationY[a_index2];
1102  ap_Position2[2] = mp_crystalCentralPositionZ[a_index2] + depth*mp_crystalOrientationZ[a_index2];
1103  }
1104  // Case when the full POI is taken into account
1105  else
1106  {
1107  // TODO
1108  }
1109 
1110  // Get orientations
1111  ap_Orientation1[0] = mp_crystalOrientationX[a_index1];
1112  ap_Orientation1[1] = mp_crystalOrientationY[a_index1];
1113  ap_Orientation1[2] = mp_crystalOrientationZ[a_index1];
1114  ap_Orientation2[0] = mp_crystalOrientationX[a_index2];
1115  ap_Orientation2[1] = mp_crystalOrientationY[a_index2];
1116  ap_Orientation2[2] = mp_crystalOrientationZ[a_index2];
1117 
1118  // End
1119  return 0;
1120 }
1121 
1122 // =====================================================================
1123 // ---------------------------------------------------------------------
1124 // ---------------------------------------------------------------------
1125 // =====================================================================
1126 /*
1127  \fn GetRdmPositionsAndOrientations
1128  \param a_index1 : index of the 1st crystal
1129  \param a_index2 : index of the 2nd crystal
1130  \param ap_Position1[3] : x,y,z cartesian position of center of the 1st crystal
1131  \param ap_Position2[3] : x,y,z cartesian position of center of the 2nd crystal
1132  \param ap_Orientation1[3] : x,y,z components of the orientation vector related to the 1st crystal
1133  \param ap_Orientation2[3] : x,y,z components of the orientation vector related to the 2nd crystal
1134  \brief Get random positions of the scanner elements and their orientations from their indices.
1135  \details - Find the scanner elements described by the two indexes passed in parameters.
1136  \details - Compute random positions inside the elements described by the indexes passed in parameters
1137  \details - Find the scanner elements described by the two indexes passed in parameters.
1138  \details - Write the corresponding random cartesian coordinates in the positions parameters.
1139  \details Position calculations include POI and mean depth of interaction
1140  \todo fix the possibility to draw LOR outside the actual crystal volume (if mp_meanDepthOfInteraction != 0.5)
1141  \todo some cases depending on POI are not implemented
1142  \return 0 if success, positive value otherwise
1143 */
1144 int iScannerPET::GetRdmPositionsAndOrientations(int a_index1, int a_index2,
1145  FLTNB ap_Position1[3], FLTNB ap_Position2[3],
1146  FLTNB ap_Orientation1[3], FLTNB ap_Orientation2[3] )
1147 {
1149 
1150  // First check crystals existency
1151  if (a_index1<0 || a_index1>=m_nbCrystals)
1152  {
1153  Cerr("***** iScannerPET::GetRdmPositionsAndOrientations() -> Crystal index 1 (" << a_index1 << ") out of range [0:" << m_nbCrystals-1 << "] !" << endl);
1154  return 1;
1155  }
1156  if (a_index2<0 || a_index2>=m_nbCrystals)
1157  {
1158  Cerr("***** iScannerPET::GetRdmPositionsAndOrientations() -> Crystal index 2 (" << a_index2 << ") out of range [0:" << m_nbCrystals-1 << "] !" << endl);
1159  return 1;
1160  }
1161 
1162  if (mp_sizeCrystalTrans[GetLayer(a_index1)]<=0. || mp_sizeCrystalAxial[GetLayer(a_index1)]<=0. ||
1163  mp_sizeCrystalTrans[GetLayer(a_index2)]<=0. || mp_sizeCrystalAxial[GetLayer(a_index2)]<=0. )
1164  {
1165  Cerr("***** iScannerPET::GetRdmPositionsAndOrientations() -> Crystal sizes are unknown or equal to 0. Crystal dimensions are mandatory for this function !" << endl);
1166  return 1;
1167  }
1168 
1170 
1171  // --- 1st detector --- //
1172 
1173  // Get random numbers for the first crystal
1174  FLTNB rdm_axl1 = p_RNG->GenerateRdmNber();
1175  FLTNB rdm_trs1 = p_RNG->GenerateRdmNber();
1176  FLTNB rdm_depth1 = p_RNG->GenerateRdmNber();
1177 
1178  // Compute average positions on each axis according to detector dimensions
1179  FLTNB size_crystalTrans1 = mp_sizeCrystalTrans[GetLayer(a_index1)];
1180  FLTNB size_crystalAxial1 = mp_sizeCrystalAxial[GetLayer(a_index1)];
1181  FLTNB size_crystalDepth1 = mp_sizeCrystalDepth[GetLayer(a_index1)];
1182  FLTNB axial1 = (rdm_axl1-0.5) * size_crystalAxial1;
1183  FLTNB trans1 = (rdm_trs1-0.5) * size_crystalTrans1;
1184  FLTNB depth1 = (rdm_depth1-0.5) * size_crystalDepth1;
1185 
1186 
1187 
1188  // If a mean depth of interaction has been provided, we shoot inside
1189  // a segment centered on the mean depth of interaction
1190  //
1191  // --> crystal depth
1192  // _________________
1193  // |___X_____________| <- X = mean depth of interaction
1194  // |-------| <- length where to shoot
1195  //
1196  FLTNB mDOI1 = mp_meanDepthOfInteraction[ GetLayer(a_index1) ];
1197  if( mDOI1>0 )
1198  {
1199  depth1 = -size_crystalDepth1*0.5 + mDOI1;
1200  depth1 += mDOI1 < size_crystalDepth1/2 ?
1201  (rdm_depth1-0.5)*mDOI1*2 :
1202  (rdm_depth1-0.5)*(size_crystalDepth1-mDOI1)*2 ;
1203  }
1204 
1205  // Recover orientations in local variables a.w.a function arguments
1206  FLTNB uX = mp_crystalOrientationX[a_index1];
1207  FLTNB uY = mp_crystalOrientationY[a_index1];
1208  FLTNB uZ = mp_crystalOrientationZ[a_index1];
1209  ap_Orientation1[0] = uX;
1210  ap_Orientation1[1] = uY;
1211  ap_Orientation1[2] = uZ;
1212 
1213  // Transaxial vector definition only work if x and y are not zero.
1214  FLTNB trans_VecNorm;
1215  // Only the z part of the axial vector is not simple.
1216  FLTNB axial_z;
1217  FLTNB axial_VecNorm;
1218 
1219  // Arbitrary threshold for non-null uZ
1220  if(abs(uZ) < 1e-6)
1221  {
1222  trans_VecNorm = 1.0;
1223  axial_z = 0.0;
1224  axial_VecNorm = sqrt( pow(uX, 2) + pow(uY, 2));
1225  }
1226  else
1227  {
1228  // Third componnent is zero, do not need to add it.
1229  trans_VecNorm = sqrt( pow(uX, 2) + pow(uY, 2));
1230  axial_z = (pow(uX, 2) + pow(uY, 2)) / uZ;
1231  axial_VecNorm = sqrt( pow(uX, 2) + pow(uY, 2) + pow(axial_z, 2));
1232  }
1233 
1234  ap_Position1[0] = mp_crystalCentralPositionX[a_index1]
1235  + depth1 * uX
1236  - trans1 * uY / trans_VecNorm
1237  - axial1 * uX / axial_VecNorm;
1238 
1239  ap_Position1[1] = mp_crystalCentralPositionY[a_index1]
1240  + depth1 * uY
1241  + trans1 * uX / trans_VecNorm
1242  - axial1 * uY / axial_VecNorm;
1243 
1244  ap_Position1[2] = mp_crystalCentralPositionZ[a_index1]
1245  + depth1 * uZ
1246  + trans1 * 0.0
1247  + axial1 * axial_z / axial_VecNorm;
1248 
1249 
1250  // --- 2nd detector --- //
1251 
1252  // Get random numbers for the second crystal
1253  FLTNB rdm_axl2 = p_RNG->GenerateRdmNber();
1254  FLTNB rdm_trs2 = p_RNG->GenerateRdmNber();
1255  FLTNB rdm_depth2 = p_RNG->GenerateRdmNber();
1256 
1257  // Compute average positions on each axis according to detector dimensions
1258  FLTNB size_crystalTrans2 = mp_sizeCrystalTrans[GetLayer(a_index2)];
1259  FLTNB size_crystalAxial2 = mp_sizeCrystalAxial[GetLayer(a_index2)];
1260  FLTNB size_crystalDepth2 = mp_sizeCrystalDepth[GetLayer(a_index2)];
1261  FLTNB axial2 = (rdm_axl2-0.5) * size_crystalAxial2;
1262  FLTNB trans2 = (rdm_trs2-0.5) * size_crystalTrans2;
1263  FLTNB depth2 = (rdm_depth2-0.5) * size_crystalDepth2;
1264 
1265  // If a mean depth of interaction has been provided, we shoot inside
1266  // a segment centered on the mean depth of interaction
1267  //
1268  // --> crystal depth
1269  // _________________
1270  // |___X_____________| <- X = mean depth of interaction
1271  // |-------| <- length where to shoot
1272  //
1273  FLTNB mDOI2 = mp_meanDepthOfInteraction[ GetLayer(a_index2) ];
1274  if( mDOI2>0 )
1275  {
1276  depth2 = -size_crystalDepth2*0.5 + mDOI2;
1277  depth2 += mDOI2 < size_crystalDepth2*0.5 ?
1278  (rdm_depth2-0.5)*mDOI2*2 :
1279  (rdm_depth2-0.5)*(size_crystalDepth2-mDOI2)*2 ;
1280  }
1281 
1282  // Recover orientations in local variables a.w.a function arguments
1283  uX = mp_crystalOrientationX[a_index2];
1284  uY = mp_crystalOrientationY[a_index2];
1285  uZ = mp_crystalOrientationZ[a_index2];
1286  ap_Orientation2[0] = uX;
1287  ap_Orientation2[1] = uY;
1288  ap_Orientation2[2] = uZ;
1289 
1290  // Arbitrary threshold for non-null uZ
1291  if(abs(uZ) < 1e-6)
1292  {
1293  trans_VecNorm = 1.0;
1294  axial_z = 0.0;
1295  axial_VecNorm = sqrt( pow(uX, 2) + pow(uY, 2));
1296  }
1297  else
1298  {
1299  // Third componnent is zero, do not need to add it.
1300  trans_VecNorm = sqrt( pow(uX, 2) + pow(uY, 2));
1301  axial_z = (pow(uX, 2) + pow(uY, 2)) / uZ;
1302  axial_VecNorm = sqrt( pow(uX, 2) + pow(uY, 2) + pow(axial_z, 2));
1303  }
1304 
1305  ap_Position2[0] = mp_crystalCentralPositionX[a_index2]
1306  + depth2 * uX
1307  - trans2 * uY / trans_VecNorm
1308  - axial2 * uX / axial_VecNorm;
1309 
1310  ap_Position2[1] = mp_crystalCentralPositionY[a_index2]
1311  + depth2 * uY
1312  + trans2 * uX / trans_VecNorm
1313  - axial2 * uY / axial_VecNorm;
1314 
1315  ap_Position2[2] = mp_crystalCentralPositionZ[a_index2]
1316  + depth2 * uZ
1317  + trans2 * 0.0
1318  + axial2 * axial_z / axial_VecNorm;
1319 
1320  // End
1321  return 0;
1322 }
1323 
1324 // =====================================================================
1325 // ---------------------------------------------------------------------
1326 // ---------------------------------------------------------------------
1327 // =====================================================================
1328 /*
1329  \fn GetPositionWithRandomDepth
1330  \param a_index1 : index of the 1st crystal
1331  \param a_index2 : index of the 2nd crystal
1332  \param ap_Position1[3] : x,y,z cartesian position of the point related to the 1st index (see child function for more details)
1333  \param ap_Position2[3] : x,y,z cartesian position of the point related to the 2st index (see child function for more details)
1334  \brief Get the positions and orientations of scanner elements from their indices, with a random depth.
1335  \details Method for testing purposes. Does not include POI and mean depth of interaction
1336  \return 0 if success, positive value otherwise
1337 */
1338 int iScannerPET::GetPositionWithRandomDepth(int a_index1, int a_index2, FLTNB ap_Position1[3], FLTNB ap_Position2[3])
1339 {
1341 
1342  // Get instance of random number generator
1344 
1345  // Shoot first random number
1346  FLTNB shoot1 = p_RNG->GenerateRdmNber();
1347  // Compute associated depth
1348  FLTNB depth1 = (shoot1-0.5) * mp_sizeCrystalDepth[GetLayer(a_index1)];
1349 
1350  // Compute associated position
1351  ap_Position1[0] = mp_crystalCentralPositionX[a_index1] + depth1*mp_crystalOrientationX[a_index1];
1352  ap_Position1[1] = mp_crystalCentralPositionY[a_index1] + depth1*mp_crystalOrientationY[a_index1];
1353  ap_Position1[2] = mp_crystalCentralPositionZ[a_index1] + depth1*mp_crystalOrientationZ[a_index1];
1354 
1355  // Shoot second random number
1356  FLTNB shoot2 = p_RNG->GenerateRdmNber();
1357  // Compute associated depth
1358  FLTNB depth2 = (shoot2-0.5) * mp_sizeCrystalDepth[GetLayer(a_index2)];
1359  // Compute associated position
1360  ap_Position2[0] = mp_crystalCentralPositionX[a_index2] + depth2*mp_crystalOrientationX[a_index2];
1361  ap_Position2[1] = mp_crystalCentralPositionY[a_index2] + depth2*mp_crystalOrientationY[a_index2];
1362  ap_Position2[2] = mp_crystalCentralPositionZ[a_index2] + depth2*mp_crystalOrientationZ[a_index2];
1363  // End
1364  return 0;
1365 }
1366 
1367 // =====================================================================
1368 // ---------------------------------------------------------------------
1369 // ---------------------------------------------------------------------
1370 // =====================================================================
1371 /*
1372  \fn GetTwoCorners
1373  \param a_index1 : index of the 1st crystal
1374  \param a_index2 : index of the 2nd crystal
1375  \param ap_CornerInf1[3]
1376  \param ap_CornerSup1[3]
1377  \param ap_CornerInf2[3]
1378  \param ap_CornerSup2[3]
1379  \brief Get the cartesian coordinaters of the two opposite corners of a scanner element.
1380  \todo Not implemented yet
1381  \return 0 if success, positive value otherwise
1382 */
1383 int iScannerPET::GetTwoCorners(int a_index1, int a_index2,
1384  FLTNB ap_CornerInf1[3], FLTNB ap_CornerSup1[3],
1385  FLTNB ap_CornerInf2[3], FLTNB ap_CornerSup2[3])
1386 {
1388 
1389  if (a_index1<0 || a_index1>=m_nbCrystals || a_index2<0 || a_index2>=m_nbCrystals)
1390  {
1391  Cerr("***** iScannerPET::GetTwoCorners() -> Crystal index out of range !" << endl);
1392  return 1;
1393  }
1394 
1395  if (mp_sizeCrystalTrans[GetLayer(a_index1)]<=0. || mp_sizeCrystalAxial[GetLayer(a_index1)]<=0. ||
1396  mp_sizeCrystalTrans[GetLayer(a_index2)]<=0. || mp_sizeCrystalAxial[GetLayer(a_index2)]<=0. )
1397  {
1398  Cerr("***** iScannerPET::GetRdmPositionsAndOrientations() -> Crystal sizes are unknown or equal to 0. Crystal dimensions are mandatory for this function !" << endl);
1399  return 1;
1400  }
1401 
1402  Cerr("***** iScannerPET::GetTwoCorners() -> Not implemented yet !" << endl);
1403  return 1;
1404 }
1405 
1406 // =====================================================================
1407 // ---------------------------------------------------------------------
1408 // ---------------------------------------------------------------------
1409 // =====================================================================
1410 
1411 int iScannerPET::GetEdgesCenterPositions( int a_index1, int a_index2,
1412  FLTNB ap_pos_line_point1[3], FLTNB ap_pos_line_point2[3],
1413  FLTNB ap_pos_point1_x[4], FLTNB ap_pos_point1_y[4], FLTNB ap_pos_point1_z[4],
1414  FLTNB ap_pos_point2_x[4], FLTNB ap_pos_point2_y[4], FLTNB ap_pos_point2_z[4]
1415 )
1416 {
1418 
1419  if( a_index1 < 0 || a_index1 >= m_nbCrystals
1420  || a_index2 < 0 || a_index2 >= m_nbCrystals )
1421  {
1422  Cerr("***** iScannerPET::GetEdgesCenterPositions() -> Crystal indices out of range !" << endl);
1423  return 1;
1424  }
1425 
1426  // Getting the half size of axial/trans crystal 1 and 2 depending on the layer
1427  FLTNB half_crystal_trans_1 = mp_sizeCrystalTrans[GetLayer(a_index1)] / 2.0;
1428  FLTNB half_crystal_axial_1 = mp_sizeCrystalAxial[GetLayer(a_index1)] / 2.0;
1429  FLTNB half_crystal_trans_2 = mp_sizeCrystalTrans[GetLayer(a_index2)] / 2.0;
1430  FLTNB half_crystal_axial_2 = mp_sizeCrystalAxial[GetLayer(a_index2)] / 2.0;
1431 
1433  // Computing coordinates depending on the orientation for point1
1434  // X-axis
1435  ap_pos_point1_x[ 0 ] = ap_pos_line_point1[ 0 ] - half_crystal_trans_1 * mp_crystalOrientationY[ a_index1 ];
1436  ap_pos_point1_x[ 1 ] = ap_pos_line_point1[ 0 ] + half_crystal_trans_1 * mp_crystalOrientationY[ a_index1 ];
1437  ap_pos_point1_x[ 2 ] = ap_pos_line_point1[ 0 ];
1438  ap_pos_point1_x[ 3 ] = ap_pos_line_point1[ 0 ];
1439 
1440  // Y-axis
1441  ap_pos_point1_y[ 0 ] = ap_pos_line_point1[ 1 ] + half_crystal_trans_1 * mp_crystalOrientationX[ a_index1 ];
1442  ap_pos_point1_y[ 1 ] = ap_pos_line_point1[ 1 ] - half_crystal_trans_1 * mp_crystalOrientationX[ a_index1 ];
1443  ap_pos_point1_y[ 2 ] = ap_pos_line_point1[ 1 ];
1444  ap_pos_point1_y[ 3 ] = ap_pos_line_point1[ 1 ];
1445 
1446  // Z-axis
1447  ap_pos_point1_z[ 0 ] = ap_pos_line_point1[ 2 ];
1448  ap_pos_point1_z[ 1 ] = ap_pos_line_point1[ 2 ];
1449  ap_pos_point1_z[ 2 ] = ap_pos_line_point1[ 2 ] - half_crystal_axial_1;
1450  ap_pos_point1_z[ 3 ] = ap_pos_line_point1[ 2 ] + half_crystal_axial_1;
1451 
1453  // Computing coordinates depending on the orientation for point2
1454  // X-axis
1455  ap_pos_point2_x[ 0 ] = ap_pos_line_point2[ 0 ] + half_crystal_trans_2 * mp_crystalOrientationY[ a_index2 ];
1456  ap_pos_point2_x[ 1 ] = ap_pos_line_point2[ 0 ] - half_crystal_trans_2 * mp_crystalOrientationY[ a_index2 ];
1457  ap_pos_point2_x[ 2 ] = ap_pos_line_point2[ 0 ];
1458  ap_pos_point2_x[ 3 ] = ap_pos_line_point2[ 0 ];
1459 
1460  // Y-axis
1461  ap_pos_point2_y[ 0 ] = ap_pos_line_point2[ 1 ] - half_crystal_trans_2 * mp_crystalOrientationX[ a_index2 ];
1462  ap_pos_point2_y[ 1 ] = ap_pos_line_point2[ 1 ] + half_crystal_trans_2 * mp_crystalOrientationX[ a_index2 ];
1463  ap_pos_point2_y[ 2 ] = ap_pos_line_point2[ 1 ];
1464  ap_pos_point2_y[ 3 ] = ap_pos_line_point2[ 1 ];
1465 
1466  // Z-axis
1467  ap_pos_point2_z[ 0 ] = ap_pos_line_point2[ 2 ];
1468  ap_pos_point2_z[ 1 ] = ap_pos_line_point2[ 2 ];
1469  ap_pos_point2_z[ 2 ] = ap_pos_line_point2[ 2 ] - half_crystal_axial_2;
1470  ap_pos_point2_z[ 3 ] = ap_pos_line_point2[ 2 ] + half_crystal_axial_2;
1471 
1472  // Normal end
1473  return 0;
1474 }
1475 
1476 // =====================================================================
1477 // ---------------------------------------------------------------------
1478 // ---------------------------------------------------------------------
1479 // =====================================================================
1480 /*
1481  \fn GetLayer
1482  \param a_idx : index of the crystal in the loaded LUT
1483  \brief Get the layer from which the 'a_idx' crystal belongs to
1484  \return layer index
1485 */
1486 int iScannerPET::GetLayer(int a_idx)
1487 {
1489 
1490  int layer=0;
1491  int sum_crystals = mp_nbCrystalsInLayer[layer];
1492  // Get the layer which the crystal belongs to
1493  while (a_idx >= sum_crystals)
1494  {
1495  layer++;
1496  sum_crystals += mp_nbCrystalsInLayer[layer];
1497  }
1498  return layer;
1499 }
1500 
1501 
1502 
1503 
1504 // =====================================================================
1505 // ---------------------------------------------------------------------
1506 // ---------------------------------------------------------------------
1507 // =====================================================================
1508 /*
1509  \fn IsAvailableLOR
1510  \param a_elt1 : index of the 1st crystal
1511  \param a_elt2 : index of the 2nd crystal
1512  \brief Check if the LOR formed by the crystalf whose indices are passed
1513  in parameters is available according to the scanner restrictions
1514  \details This function is dedicated to analytic projection and list-mode sensitivity image generation
1515  The PET implementation checks the LOR availability according to the minimal (transaxial) angle difference
1516  and the maximal ring difference between two crystals
1517  \todo min angle difference (currently just system dependent) may be estimated from the FOV requested by the user ?
1518  \todo Restriction for ring_ID NOT safe for user using their own LUT !!!
1519  Perhaps throw warning in this case
1520  \return 1 if the LOR is available, 0 otherwise
1521 */
1522 int iScannerPET::IsAvailableLOR(int a_elt1, int a_elt2)
1523 {
1525 
1526  // Get absolute angle between the two crystals
1527  FLTNB abs_angle = mp_crystalOrientationX[a_elt1]*mp_crystalOrientationX[a_elt2]
1528  + mp_crystalOrientationY[a_elt1]*mp_crystalOrientationY[a_elt2];
1529 
1530  // Handle boundaries (arg of acos() outside range [-1. ; +1])
1531  abs_angle = (abs_angle>1.) ? 1 : abs_angle;
1532  abs_angle = (abs_angle<-1.) ? -1 : abs_angle;
1533 
1534  FLTNB angle_diff = acos(abs_angle);
1535 
1536  // Check restrictions (Axial constraint in mm and Transaxial constraint on min angle difference)
1537  // TODO : min angle difference (currently just system dependent) may be estimated from the FOV requested by the user ?
1538  if ( ( m_maxAxialDiffmm <0. || abs(mp_crystalCentralPositionZ[a_elt1]-mp_crystalCentralPositionZ[a_elt2]) <= m_maxAxialDiffmm ) // a max axial diff restriction
1539  && ( angle_diff>=m_minAngleDifference || FLTNBIsEqual(angle_diff,m_minAngleDifference,.0001)) ) // Returns 1 if computed angle diff ~ min angle diff (E-4)
1540  {
1541  return 1;
1542  }
1543 
1544  return 0;
1545 }
1546 
1547 // =====================================================================
1548 // ---------------------------------------------------------------------
1549 // ---------------------------------------------------------------------
1550 // =====================================================================
1551 /*
1552  \fn GetGeometricInfoFromDataFile
1553  \brief Retrieve PET geometric informations from the datafile
1554  \details Recover the (axial) max ring difference
1555  \return 0 if success, positive value otherwise
1556 */
1557 int iScannerPET::GetGeometricInfoFromDataFile(string a_pathToDF)
1558 {
1560 
1561  if (m_verbose>=VERBOSE_DETAIL) Cout("iScannerPET::GetGeometricInfoFromDataFile() -> Specific to PET" << endl);
1562 
1563  // This function is intended to be called after the scanner initialization, so that any field present in the datafile header, similar to
1564  // one in the scanner configuration file, may overload the scanner value.
1565 
1566  // Get maximum axial difference in mm
1567  if (ReadDataASCIIFile(a_pathToDF, "Maximum axial difference mm", &m_maxAxialDiffmm, 1, KEYWORD_OPTIONAL)==1)
1568  {
1569  Cerr("***** iScannerPET::GetGeometricInfoFromDataFile() -> Error while reading max number of ring difference in the header data file '" << endl);
1570  return 1;
1571  }
1572 
1573  return 0;
1574 }
1575 
1576 // =====================================================================
1577 // ---------------------------------------------------------------------
1578 // ---------------------------------------------------------------------
1579 // =====================================================================
1580 /*
1581  \fn PROJ_GetPETSpecificParameters
1582  \param ap_maxAxialDiffmm
1583  \brief Set pointers passed in argument with the related PET specific variables
1584  This function is used to recover these values in the datafile object
1585  \return 0 if success, positive value otherwise
1586 */
1587 int iScannerPET::PROJ_GetPETSpecificParameters(FLTNB* ap_maxAxialDiffmm)
1588 {
1590 
1591  if (m_verbose>=VERBOSE_NORMAL) Cout("iScannerPET::PROJ_GetPETSpecificParameters ..." << endl);
1592 
1593  // Verify that all parameters have been correctly checked
1595  {
1596  Cerr("***** iScannerPET::PROJ_GetPETSpecificParameters() -> Parameters have not been checked !" << endl);
1597  return 1;
1598  }
1599 
1600  *ap_maxAxialDiffmm = m_maxAxialDiffmm;
1601  return 0;
1602 }
1603 
1604 // =====================================================================
1605 // ---------------------------------------------------------------------
1606 // ---------------------------------------------------------------------
1607 // =====================================================================
1608 /*
1609  \fn ShowHelp
1610  \brief Display help
1611  \todo Provide informations about PET system initialization ?
1612 */
1613 void iScannerPET::ShowHelp()
1614 {
1616  cout << "This scanner class is dedicated to the description of PET systems." << endl;
1617 }
int GetPositionWithRandomDepth(int a_index1, int a_index2, FLTNB ap_Position1[3], FLTNB ap_Position2[3])
int CheckParameters()
Check if all parameters have been correctly initialized.
static sScannerManager * GetInstance()
Instanciate the singleton object and Initialize member variables if not already done, return a pointer to this object otherwise.
static sRandomNumberGenerator * GetInstance()
Instanciate the singleton object and Initialize member variables if not already done, return a pointer to this object otherwise.
int Instantiate(bool a_scannerFileIsLUT)
int IsAvailableLOR(int a_elt1, int a_elt2)
#define Cerr(MESSAGE)
int Initialize()
Check general initialization and set several parameters to their default value.
int GetTwoCorners(int a_index1, int a_index2, FLTNB ap_CornerInf1[3], FLTNB ap_CornerSup1[3], FLTNB ap_CornerInf2[3], FLTNB ap_CornerSup2[3])
#define GEO_ROT_CCW
int GetRdmPositionsAndOrientations(int a_index1, int a_index2, FLTNB ap_Position1[3], FLTNB ap_Position2[3], FLTNB ap_Orientation1[3], FLTNB ap_Orientation2[3])
bool FLTNBIsEqual(FLTNB a, FLTNB b, FLTNB a_eps)
Comparison of FLTNB numbers.
int GetEdgesCenterPositions(int a_index1, int a_index2, FLTNB ap_pos_line_point1[3], FLTNB ap_pos_line_point2[3], FLTNB ap_pos_point1_x[4], FLTNB ap_pos_point1_y[4], FLTNB ap_pos_point1_z[4], FLTNB ap_pos_point2_x[4], FLTNB ap_pos_point2_y[4], FLTNB ap_pos_point2_z[4])
int GetGeometricInfoFromDataFile(string a_pathToDataFilename)
virtual int SetRotDirection(string a_rotDirection)
#define DEBUG_VERBOSE(IGNORED1, IGNORED2)
Singleton class that Instantiate and initialize the scanner object.
int GetPositionsAndOrientations(int a_index1, int a_index2, FLTNB ap_Position1[3], FLTNB ap_Position2[3], FLTNB ap_Orientation1[3], FLTNB ap_Orientation2[3], FLTNB *ap_POI1=NULL, FLTNB *ap_POI2=NULL)
bool SaveLUTFlag()
Get flag indicating a LUT generated by a geom file should be written on disk or not.
int PROJ_GetPETSpecificParameters(FLTNB *ap_maxAxialDiffmm)
HPFLTNB GenerateRdmNber()
Generate a random number for the thread which index is recovered from the OpenMP function.
#define KEYWORD_MANDATORY
#define KEYWORD_OPTIONAL
Singleton class that generate a thread-safe random generator number for openMP As singleton...
int LoadLUT()
Load a precomputed scanner LUT.
iScannerPET()
iScannerPET constructor. Initialize the member variables to their default values. ...
void ShowHelp()
Display help.
Structure designed for basic matrices operations.
~iScannerPET()
iScannerPET destructor.
string GetFileFromPath(const string &a_pathToFile)
Simply return the file from a path string passed in parameter.
int BuildLUT(bool a_scannerFileIsLUT)
int Multiplication(oMatrix *ap_Mtx, oMatrix *ap_MtxResult)
void DescribeSpecific()
Implementation of the pure virtual eponym function that simply prints info about the scanner...
int ReadDataASCIIFile(const string &a_file, const string &a_keyword, T *ap_return, int a_nbElts, bool a_mandatoryFlag)
Look for "a_nbElts" elts in the "a_file" file matching the "a_keyword" string passed as parameter a...
int SetMatriceElt(uint16_t l, uint16_t c, HPFLTNB a_val)
#define Cout(MESSAGE)
#define GEO_ROT_CW
Generic class for scanner objects.
#define KEYWORD_OPTIONAL_ERROR
int ComputeLUT()
Compute the LUT of the scanner from a generic (.geom) file.