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