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