CASToR  1.1
Tomographic Reconstruction (PET/SPECT)
 All Classes Files Functions Variables Typedefs Macros Groups Pages
iScannerSPECTConv.cc
Go to the documentation of this file.
1 
2 /*
3  Implementation of class iScannerSPECTConv
4 
5  - separators: X
6  - doxygen: X
7  - default initialization: X
8  - CASTOR_DEBUG: none
9  - CASTOR_VERBOSE: X
10 */
11 
18 #include "iScannerSPECTConv.hh"
19 #include "sOutputManager.hh"
20 #include "sScannerManager.hh"
21 
22 
23 
24 
25 
26 // =====================================================================
27 // ---------------------------------------------------------------------
28 // ---------------------------------------------------------------------
29 // =====================================================================
30 
32 {
33  // Set member variables to default values
35  m_nbCrystals = -1;
36  m_nbHeads = -1;
37  m_nbPixelsTrans = 0;
38  m_pixelsSizeTrans = -1.;
39  m_gapSizeTrans = -1.;
40  m_nbPixelsAxial = 0;
41  m_pixelsSizeAxial = -1.;
42  m_gapSizeAxial = -1.;
43  m_vPixelsSizeTrans = -1.;
44  m_vPixelsSizeAxial = -1.;
45  m_vNbPixelsTrans = 0;
46  m_vNbPixelsAxial = 0;
47  mp_nbOfBins[0] = 0;
48  mp_nbOfBins[1] = 0;
49  m_crystalDepth = -1.;
50  mp_focalModelTrans = NULL;
51  mp_nbCoefModelTrans = NULL;
53  mp_focalModelAxial = NULL;
54  mp_nbCoefModelAxial = NULL;
57  mp_projectionAngles = NULL;
58  mp_radius = NULL;
64  mp_crystalOrientationX = NULL;
65  mp_crystalOrientationY = NULL;
66  mp_crystalOrientationZ = NULL;
70  mp_positionMatrix_ref = NULL;
71  mp_positionMatrix_out = NULL;
72  mp_rotationMatrix = NULL;
73 }
74 
75 // =====================================================================
76 // ---------------------------------------------------------------------
77 // ---------------------------------------------------------------------
78 // =====================================================================
79 
81 {
84 
86  {
87  for ( int i = 0; i < m_nbHeads; ++i )
88  delete m2p_transFocalParameters[ i ];
89  delete[] m2p_transFocalParameters;
90  }
91 
94 
96  {
97  for ( int i = 0; i < m_nbHeads; ++i )
98  delete m2p_axialFocalParameters[ i ];
99  delete[] m2p_axialFocalParameters;
100  }
101 
103  if (mp_radius) delete[] mp_radius;
105 
109 
113 
117 
121 }
122 
123 // =====================================================================
124 // ---------------------------------------------------------------------
125 // ---------------------------------------------------------------------
126 // =====================================================================
127 
128 int iScannerSPECTConv::Instantiate(bool a_scannerFileIsLUT)
129 {
131 
132  // Verbose
133  if (m_verbose>=VERBOSE_NORMAL) Cout("iScannerSPECTConv::Instantiate() -> Create scanner structure and read parameters from configuration file"<< endl);
134 
135  // Get scanner manager
136  sScannerManager* p_scannerManager;
137  p_scannerManager = sScannerManager::GetInstance();
138 
139  // Get the number of heads in the scanner
140  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "number of detector heads", &m_nbHeads, 1, KEYWORD_MANDATORY))
141  {
142  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read the number of SPECT heads !" << endl);
143  return 1;
144  }
145 
146  // Allocating memories that depend on number of heads
147  mp_radius = new FLTNB[m_nbHeads];
148  mp_focalModelTrans = new string[ m_nbHeads ];
149  mp_focalModelAxial = new string[ m_nbHeads ];
150  mp_nbCoefModelTrans = new uint8_t[ m_nbHeads ];
151  mp_nbCoefModelAxial = new uint8_t[ m_nbHeads ];
154 
155  // Get default radius
156  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "scanner radius", mp_radius, m_nbHeads, KEYWORD_MANDATORY))
157  {
158  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read the number of SPECT heads !" << endl);
159  return 1;
160  }
161  for (int i = 0; i < m_nbHeads; i++) if (mp_radius[i]<=0.)
162  {
163  Cerr("***** iScannerSPECTConv::Instantiate() -> Scanner radius <= 0. ? really ? :) ... " << endl);
164  return 1;
165  }
166  // Detector dimensions
167  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "trans number of pixels", &m_nbPixelsTrans, 1, KEYWORD_MANDATORY))
168  {
169  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read the transaxial number of pixels !" << endl);
170  return 1;
171  }
172  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "trans pixel size", &m_pixelsSizeTrans, 1, KEYWORD_MANDATORY))
173  {
174  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read the transaxial pixel size !" << endl);
175  return 1;
176  }
177  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "axial number of pixels", &m_nbPixelsAxial, 1, KEYWORD_MANDATORY))
178  {
179  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read the axial number of pixels !" << endl);
180  return 1;
181  }
182  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "axial pixel size", &m_pixelsSizeAxial, 1, KEYWORD_MANDATORY))
183  {
184  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read the axial pixel size !" << endl);
185  return 1;
186  }
187  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "detector depth", &m_crystalDepth, 1, KEYWORD_MANDATORY))
188  {
189  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read detector depth !" << endl);
190  return 1;
191  }
192 
193  // Head strings to get information from scanner configuration file
194  string *head_name = new string[ m_nbHeads + 1 ];
195  for ( int i = 0; i < m_nbHeads; i++ )
196  {
197  ostringstream oss( ostringstream::out );
198  oss << "head" << i+1;
199  head_name[ i ] = oss.str();
200  }
201  head_name[ m_nbHeads ] = "eof";
202 
203  // Read informations about SPECT heads
204  for ( int i = 0; i < m_nbHeads; i++ )
205  {
206  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "trans focal model", &mp_focalModelTrans[ i ], 1, KEYWORD_MANDATORY, head_name[ i ], head_name[ i + 1 ]))
207  {
208  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read transaxial focal model of head " << i << " !" << endl);
209  return 1;
210  }
211  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "trans number of coef model", &mp_nbCoefModelTrans[ i ], 1, KEYWORD_MANDATORY, head_name[ i ], head_name[ i + 1 ]))
212  {
213  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read transaxial number of coef model of head " << i << " !" << endl);
214  return 1;
215  }
216  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "axial focal model", &mp_focalModelAxial[ i ], 1, KEYWORD_MANDATORY, head_name[ i ], head_name[ i + 1 ]))
217  {
218  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read axial focal model of head " << i << " !" << endl);
219  return 1;
220  }
221  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "axial number of coef model", &mp_nbCoefModelAxial[ i ], 1, KEYWORD_MANDATORY, head_name[ i ], head_name[ i + 1 ]))
222  {
223  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read axial number of coef model of head " << i << " !" << endl);
224  return 1;
225  }
228  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "trans parameters", m2p_transFocalParameters[i], mp_nbCoefModelTrans[ i ], KEYWORD_MANDATORY, head_name[ i ], head_name[ i + 1 ]))
229  {
230  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read transaxial model parameters of head " << i << " !" << endl);
231  return 1;
232  }
233  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "axial parameters", m2p_axialFocalParameters[i], mp_nbCoefModelAxial[ i ], KEYWORD_MANDATORY, head_name[ i ], head_name[ i + 1 ]))
234  {
235  Cerr("***** iScannerSPECTConv::Instantiate() -> An error occurred while trying to read axial model parameters of head " << i << " !" << endl);
236  return 1;
237  }
238  }
239 
240  // Bed displacement
242  if (ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "multiple bed displacement", &m_multiBedDisplacementInMm, 1, KEYWORD_OPTIONAL) == 1)
243  {
244  Cerr("***** iScannerSPECTConv::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
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  // Delete heads strings
254  delete[] head_name;
255 
256  // End
257  return 0;
258 }
259 
260 // =====================================================================
261 // ---------------------------------------------------------------------
262 // ---------------------------------------------------------------------
263 // =====================================================================
264 /*
265  \fn BuildLUT
266  \param a_scannerFileIsLUT : boolean indicating if the file describing
267  the SPECT camera is a generic file (0) or custom Look-up-table (1)
268  \brief Call the functions to generate the LUT or read the user-made LUT depending on the user choice
269  \return 0 if success, positive value otherwise
270 */
271 int iScannerSPECTConv::BuildLUT(bool a_scannerFileIsLUT)
272 {
274 
275  // Verbose
276  if (m_verbose>=VERBOSE_NORMAL) Cout("iScannerSPECTConv::BuildLUT -> Build LUT for scanner elements coordinates and orientations"<< endl);
277 
278  // Initialize nb_crystals
279  if (mp_nbOfBins[0] == 0 && mp_nbOfBins[1] == 0) // Number of bins not initialized in the datafile, then we read the number of crystals from the geom file
280  {
281  if (m_nbPixelsTrans==0 || m_nbPixelsAxial==0)
282  {
283  Cerr("***** iScannerSPECTConv::BuildLUT() -> Transaxial or axial number of pixels in the geometric file should be >0 !" << endl);
284  return 1;
285  }
287  }
288  else // Initialization with number of bins
289  {
291  }
292 
302 
303  // Either generate the LUT from a generic file, or load the user precomputed LUT
304  if (!a_scannerFileIsLUT)
305  {
306  if (ComputeLUT())
307  {
308  Cerr("***** iScannerSPECTConv::BuildLUT() -> A problem occured while generating scanner LUT !" << endl);
309  return 1;
310  }
311  }
312  else
313  {
314  if (LoadLUT())
315  {
316  Cerr("***** iScannerSPECTConv::BuildLUT() -> A problem occured while loading scanner LUT !" << endl);
317  return 1;
318  }
319  }
320 
321  // End
322  return 0;
323 }
324 
325 // =====================================================================
326 // ---------------------------------------------------------------------
327 // ---------------------------------------------------------------------
328 // =====================================================================
329 /*
330  \fn CheckParameters
331  \brief Check that all parameters have been correctly initialized.
332  \return 0 if success, positive value otherwise
333  \todo Keep the check on crystal depth ?
334 */
336 {
338 
339  // Check if all parameters have been correctly initialized. Return error otherwise
340  if (m_nbCrystals<0)
341  {
342  Cerr("***** iScannerSPECTConv::CheckParameters()-> Number of crystals has not been initialized !" <<endl);
343  return 1;
344  }
345  if (m_nbHeads<0)
346  {
347  Cerr("***** iScannerSPECTConv::CheckParameters()-> Number of heads in the SPECT system has not been initialized !" <<endl);
348  return 1;
349  }
350  if (m_nbOfProjections<=0)
351  {
352  Cerr("***** iScannerSPECTConv::CheckParameters()-> Number of projection angles has not been initialized !" <<endl);
353  return 1;
354  }
355  if (m_nbPixelsTrans<=0 || m_nbPixelsAxial<=0 )
356  {
357  Cerr("***** iScannerSPECTConv::CheckParameters()-> Number of transaxial/axial pixels have not correctly been initialized ! (should be >0)" <<endl);
358  return 1;
359  }
360  if (m_pixelsSizeTrans<=0 || m_pixelsSizeAxial<=0 )
361  {
362  Cerr("***** iScannerSPECTConv::CheckParameters()-> Transaxial/axial pixel sizes have not correctly been initialized ! (should be >0)" <<endl);
363  return 1;
364  }
365  if (m_vNbPixelsTrans<=0 || m_vNbPixelsAxial<=0 )
366  {
367  Cerr("***** iScannerSPECTConv::CheckParameters()-> Transaxial/axial number of virtual pixels have not correctly been initialized ! (should be >0)" <<endl);
368  return 1;
369  }
371  {
372  Cerr("***** iScannerSPECTConv::CheckParameters()-> Transaxial/axial virtual pixel sizes have not correctly been initialized ! (should be >0)" <<endl);
373  return 1;
374  }
375  if (m_gapSizeTrans<0 || m_gapSizeAxial<0 )
376  {
377  Cerr("***** iScannerSPECTConv::CheckParameters()-> Transaxial/axial pixel gap sizes have not correctly been initialized ! (should be >0)" <<endl);
378  return 1;
379  }
380  // todo : maybe delete this as we don't used it
381  if (m_crystalDepth<=0)
382  {
383  Cerr("***** iScannerSPECTConv::CheckParameters()-> Crystal depth has not correctly been initialized ! (should be >0)" <<endl);
384  return 1;
385  }
386  if (mp_focalModelTrans == NULL || mp_focalModelAxial == NULL)
387  {
388  Cerr("***** iScannerSPECTConv::CheckParameters()-> Transaxial/axial focal models have not correctly been initialized !" <<endl);
389  return 1;
390  }
391  if (mp_nbCoefModelTrans == NULL || mp_nbCoefModelAxial == NULL)
392  {
393  Cerr("***** iScannerSPECTConv::CheckParameters()-> Number of coefficients for the transaxial/axial focal models have not correctly been initialized !" <<endl);
394  return 1;
395  }
396  if (m2p_transFocalParameters == NULL || m2p_axialFocalParameters == NULL)
397  {
398  Cerr("***** iScannerSPECTConv::CheckParameters()-> Parameters for the transaxial/axial focal models have not correctly been initialized !" <<endl);
399  return 1;
400  }
401  if (mp_projectionAngles == NULL)
402  {
403  Cerr("***** iScannerSPECTConv::CheckParameters()-> Projection angles have not correctly been initialized !" <<endl);
404  return 1;
405  }
406  if (mp_radius == NULL)
407  {
408  Cerr("***** iScannerSPECTConv::CheckParameters()-> Default scanner radius for each detector heads (scanner file) not correctly initialized !" <<endl);
409  return 1;
410  }
411  if (mp_CORtoDetectorDistance == NULL)
412  {
413  Cerr("***** iScannerSPECTConv::CheckParameters()-> Distances between center of rotation and each detector heads have not correctly been initialized !" <<endl);
414  return 1;
415  }
416  if (mp_crystalCentralPositionX == NULL ||
417  mp_crystalCentralPositionY == NULL ||
419  {
420  Cerr("***** iScannerSPECTConv::CheckParameters()-> LUT elements (crystal central positions) have not correctly been initialized !" <<endl);
421  return 1;
422  }
423  if (mp_crystalOrientationX == NULL ||
424  mp_crystalOrientationY == NULL ||
425  mp_crystalOrientationZ == NULL )
426  {
427  Cerr("***** iScannerSPECTConv::CheckParameters()-> LUT elements (crystal orientations) have not correctly been initialized !" <<endl);
428  return 1;
429  }
430  if (mp_crystalFocalPositionX == NULL ||
431  mp_crystalFocalPositionY == NULL ||
432  mp_crystalFocalPositionZ == NULL )
433  {
434  Cerr("***** iScannerSPECTConv::CheckParameters()-> LUT elements (crystal focal positions) have not correctly been initialized !" <<endl);
435  return 1;
436  }
437  // No need to check mp_nbOfBins, Default values = 0
438 
439  // End
440  m_allParametersChecked = true;
441  return 0;
442 }
443 
444 // =====================================================================
445 // ---------------------------------------------------------------------
446 // ---------------------------------------------------------------------
447 // =====================================================================
448 /*
449  \fn Initialize
450  \brief Check general initialization and set several parameters to their default value
451  \return 0 if success, positive value otherwise
452 */
454 {
456 
457  // Verbose
458  if (m_verbose>=VERBOSE_NORMAL) Cout("iScannerSPECTConv::Initialize() -> Initialize remaining stuff for scanner to be ready"<< endl);
459 
460  // Parameters checked ?
462  {
463  Cerr("***** iScannerSPECTConv::Initialize() -> Parameters have not been checked !" << endl);
464  return 1;
465  }
466 
467  // Any initialization
468 
469  return 0;
470 }
471 
472 // =====================================================================
473 // ---------------------------------------------------------------------
474 // ---------------------------------------------------------------------
475 // =====================================================================
476 /*
477  \fn LoadLUT
478  \brief Load a precomputed scanner LUT
479  \details Read mandatory data from the header of the LUT. Then load the LUT elements for each crystal
480  \todo Not yet implemented
481  \return 0 if success, positive value otherwise
482 */
484 {
486  // This has not been designed yet
487  Cerr("iScannerSPECTConv::LoadLUT() -> Not yet implemented !" << endl);
488  return 1;
489 }
490 
491 // =====================================================================
492 // ---------------------------------------------------------------------
493 // ---------------------------------------------------------------------
494 // =====================================================================
495 /*
496  \fn ComputeLUT
497  \brief Computes the LUT of the scanner from a generic (.geom) file.
498  \details Read mandatory data from the geom file. Then compute the LUT elements for each crystal from the geometry described in the file
499  Compute the look-up-tables of the system containing the locations of the scanner elements center in space and their orientations
500  \todo center of rotation & head first angles : get this from acquisition header file
501  \return 0 if success, positive value otherwise
502 */
504 {
506 
507  if(m_verbose>=VERBOSE_DETAIL) Cout("iScannerSPECTConv::ComputeLUT() -> Start LUT generation" << endl);
508 
509  // Compute LUT according to SPECT camera geom files. Check the Castor presentation file for more details about the related mandatory/optional fields to be read
510 
511  // ============================================================================================================
512  // Parameters declarations
513  // ============================================================================================================
514 
515  // todo center of rotation & head first angles : get this from acquisition header file
516  FLTNB *CORx, *CORy, *CORz;
517  FLTNB *head_angleX, *head_angleY, *head_angleZ;
518 
519  CORx = new FLTNB[m_nbHeads];
520  CORy = new FLTNB[m_nbHeads];
521  CORz = new FLTNB[m_nbHeads];
522  head_angleX = new FLTNB[m_nbHeads];
523  head_angleY = new FLTNB[m_nbHeads];
524  head_angleZ = new FLTNB[m_nbHeads];
525 
526  // for(int hId=0 ; hId<m_nbHeads ; hId++)
527  // todo Initialisation COR and head_angle
528 
529  // ============================================================================================================
530  // Generate the LUT
531  // ============================================================================================================
532 
533  // oMatrix crystal_center_ref will be used to gather cartesian positions of the crystals center (on the surface of the crystals)
534  // in the reference head (directly above isocenter)
535  oMatrix** crystal_center_ref = new oMatrix *[m_nbCrystals];
536  // oMatrix crystal_center_out will be used to recover positions of each crystal after each rotation (each projection angles)
537  oMatrix* crystal_center_out = new oMatrix(3,1);
538 
539  // same method for matrices recovering the positions of focal point
540  oMatrix* focal_projection_position_mtx = new oMatrix(3,3);
541  oMatrix* focal_projection_position_mtx_output = new oMatrix(3,3);
542 
543  for (int c = 0; c<m_nbCrystals; c++)
544  crystal_center_ref[c] = new oMatrix(3,1);
545 
546 
547  for(int i=0 ; i<3 ; i++)
548  {
549  crystal_center_out->SetMatriceElt(i,0,0);
550 
551  for(int j=0 ; j<3 ; j++)
552  {
553  focal_projection_position_mtx->SetMatriceElt(i,j,0);
554  focal_projection_position_mtx_output->SetMatriceElt(i,j,0);
555  }
556  }
557 
558  // Generation of the rotation matrix allowing to compute the position of all the rsectors.
559  oMatrix** rotation_mtx = new oMatrix*[m_nbOfProjections];
560 
561  for(int i=0; i<m_nbOfProjections; i++)
562  {
563  rotation_mtx[i] = new oMatrix(3,3);
564 
565  int dir = (m_rotDirection == GEO_ROT_CCW) ? -1 : 1;
566 
567  rotation_mtx[i]->SetMatriceElt(0,0, cos(mp_projectionAngles[i] * M_PI/180.) );
568  rotation_mtx[i]->SetMatriceElt(1,0, -dir*sin(mp_projectionAngles[i] * M_PI/180.) );
569  rotation_mtx[i]->SetMatriceElt(2,0,0);
570  rotation_mtx[i]->SetMatriceElt(0,1, dir*sin(mp_projectionAngles[i] * M_PI/180.) );
571  rotation_mtx[i]->SetMatriceElt(1,1, cos(mp_projectionAngles[i] * M_PI/180.) );
572  rotation_mtx[i]->SetMatriceElt(2,1,0);
573  rotation_mtx[i]->SetMatriceElt(0,2,0);
574  rotation_mtx[i]->SetMatriceElt(1,2,0);
575  rotation_mtx[i]->SetMatriceElt(2,2,1);
576  }
577 
578  // Initialization of pixel size
579  FLTNB size_pixel_trans = 0., size_pixel_axial = 0.;
580  int nb_pixel_trans=0, nb_pixel_axial=0;
581 
582  // nb of bins have been recovered from the datafile (>0)
583  if(mp_nbOfBins[0]>0 && mp_nbOfBins[1]>0)
584  {
585  size_pixel_trans = m_pixelsSizeTrans/mp_nbOfBins[0];
586  size_pixel_axial = m_pixelsSizeAxial/mp_nbOfBins[1];
587 
588  // Initialize pixel value
589  nb_pixel_trans = mp_nbOfBins[0];
590  nb_pixel_axial = mp_nbOfBins[1];
591 
592  // Set the member variable recovering the pixel value
595  }
596  else // Otherwise, we recover these informations from the geom file
597  {
598  size_pixel_trans = m_pixelsSizeTrans;
599  size_pixel_axial = m_pixelsSizeAxial;
600  nb_pixel_trans = m_nbPixelsTrans;
601  nb_pixel_axial = m_nbPixelsAxial;
602 
603  // Set the member variable recovering the pixel value
606  }
607 
608  // Recover the actual trans/axial size of the pixels in member variable, in case we need them during reconstruction
609  m_vPixelsSizeTrans = size_pixel_trans;
610  m_vPixelsSizeAxial = size_pixel_axial;
611 
612 
613  // Recover the trans/axial gap size from the geom file
614  sScannerManager* p_scannerManager;
615  p_scannerManager = sScannerManager::GetInstance();
616 
617  if(ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "trans gap size", &m_gapSizeTrans, 1, KEYWORD_MANDATORY))
618  {
619  Cerr("***** iScannerSPECTConv::ComputeLUT() -> An error occurred while trying to read the transaxial gap size !" << endl);
620  return 1;
621  }
622 
623  if(ReadDataASCIIFile(p_scannerManager->GetPathToScannerFile(), "axial gap size", &m_gapSizeAxial, 1, KEYWORD_MANDATORY))
624  {
625  Cerr("***** iScannerSPECTConv::ComputeLUT() -> An error occurred while trying to read the axial gap size !" << endl);
626  return 1;
627  }
628 
629 
630  // Generate cartesian coordinates of the crystal centers for the SPECT at the reference position (directly above isocenter)
631  // Starting position of the SPECT camera for all axis
632  FLTNB x_start = -(nb_pixel_trans*size_pixel_trans + (nb_pixel_trans-1)*m_gapSizeTrans) / 2 + (size_pixel_trans/2);
633  FLTNB z_start = -(nb_pixel_axial*size_pixel_axial + (nb_pixel_axial-1)*m_gapSizeAxial) / 2 + (size_pixel_axial/2);
634 
635  for( int jj = 0; jj < nb_pixel_axial; ++jj )
636  {
637  FLTNB Zcrist = z_start + jj * (size_pixel_axial + m_gapSizeAxial);
638  for( int ii = 0; ii < nb_pixel_trans; ++ ii )
639  {
640  FLTNB Xcrist = x_start + ii * (size_pixel_trans + m_gapSizeTrans);
641 
642  crystal_center_ref[ii + nb_pixel_trans * jj]->SetMatriceElt(0,0,Xcrist);
643  crystal_center_ref[ii + nb_pixel_trans * jj]->SetMatriceElt(1,0, 0); // This value will be set for each projection angles
644  crystal_center_ref[ii + nb_pixel_trans * jj]->SetMatriceElt(2,0,Zcrist);
645  }
646  }
647 
648  // ============================================================================================================
649  // Loop over all the projection angles
650  // Positions of the scanner elements are progressively stored in the LUT file.
651  // ============================================================================================================
652  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> Generate positions for each projection angle"<< endl);
653 
654  for (int a=0 ; a<m_nbOfProjections ; a++)
655  {
656  // Set y-position of the crystal reference matrix according to the distance between SPECT camera and center of rotation of the specific head.(mp_CORtoDetectorDistance)
657  int hId = a*m_nbHeads/m_nbOfProjections;
658 
659  // Get surface crystal position (and not center crystal positions for SPECT), and set the reference crystal matrix
660  FLTNB Ycrist = mp_CORtoDetectorDistance[hId];
661 
662  for (int c=0 ; c < m_nbCrystals ; c++)
663  crystal_center_ref[c]->SetMatriceElt(1,0,Ycrist);
664 
665  for (int c=0 ; c<m_nbCrystals ; c++)
666  {
667  // crystal indexation
668  int cryID = a*m_nbCrystals + c;
669 
670  // SET CARTESIAN COORDINATES OF THE CRYSTAL SURFACE CENTER POSITIONS
671  // Rotation
672  rotation_mtx[a]->Multiplication(crystal_center_ref[c], crystal_center_out);
673 
674  // Get crystal surface positions
675  mp_crystalCentralPositionX[cryID] = crystal_center_out->GetMatriceElt(0,0);
676  mp_crystalCentralPositionY[cryID] = crystal_center_out->GetMatriceElt(1,0);
677  mp_crystalCentralPositionZ[cryID] = crystal_center_out->GetMatriceElt(2,0);
678 
679  // COMPUTE FOCAL POSITIONS AND SET CARTESIAN COORDINATES OF THE CRYSTAL FOCAL POSITIONS
680  if(ComputeFocalPositions(crystal_center_ref[c]->GetMatriceElt(0,0),
681  crystal_center_ref[c]->GetMatriceElt(1,0),
682  crystal_center_ref[c]->GetMatriceElt(2,0),
683  hId,
684  cryID) )
685  {
686  Cerr("***** iScannerSPECTConv::ComputeLUT() -> An error occurred while computing the focal positions ! " << endl);
687  return 1;
688  }
689 
690  // Set elements of the focal matrix
691  focal_projection_position_mtx->SetMatriceElt(0,0,mp_crystalFocalPositionX[cryID] );
692  focal_projection_position_mtx->SetMatriceElt(1,0,mp_crystalFocalPositionY[cryID] );
693  focal_projection_position_mtx->SetMatriceElt(2,0,mp_crystalFocalPositionZ[cryID] );
694  focal_projection_position_mtx->SetMatriceElt(0,1,0);
695  focal_projection_position_mtx->SetMatriceElt(1,1,0);
696  focal_projection_position_mtx->SetMatriceElt(2,1,0);
697  focal_projection_position_mtx->SetMatriceElt(0,2,0);
698  focal_projection_position_mtx->SetMatriceElt(1,2,0);
699  focal_projection_position_mtx->SetMatriceElt(2,2,1);
700 
701  // Rotate and compute projections of focal position for this crystal
702  rotation_mtx[a]->Multiplication(focal_projection_position_mtx, focal_projection_position_mtx_output);
703  mp_crystalFocalPositionX[cryID] = focal_projection_position_mtx_output->GetMatriceElt(0,0);
704  mp_crystalFocalPositionY[cryID] = focal_projection_position_mtx_output->GetMatriceElt(1,0);
705  mp_crystalFocalPositionZ[cryID] = focal_projection_position_mtx_output->GetMatriceElt(2,0);
706 
707  // GET CRYSTAL ORIENTATIONS
708  mp_crystalOrientationX[cryID] = rotation_mtx[a]->GetMatriceElt(0,0);
709  mp_crystalOrientationY[cryID] = rotation_mtx[a]->GetMatriceElt(0,1);
710  mp_crystalOrientationZ[cryID] = 0;
711  }
712  }
713 
714  for(int i=0; i<m_nbOfProjections; i++)
715  delete rotation_mtx[i];
716 
717  delete[] rotation_mtx;
718 
719 
720  for (int c=0; c<m_nbCrystals; c++)
721  delete crystal_center_ref[c];
722 
723  delete[] crystal_center_ref;
724  delete crystal_center_out;
725  delete focal_projection_position_mtx;
726  delete focal_projection_position_mtx_output;
727 
728  delete[] head_angleX;
729  delete[] head_angleY;
730  delete[] head_angleZ;
731  delete[] CORx;
732  delete[] CORy;
733  delete[] CORz;
734 
735  if (m_verbose>=VERBOSE_DETAIL) Cout(" --> LUT generation completed" << endl);
736 
737  return 0;
738 }
739 
740 // =====================================================================
741 // ---------------------------------------------------------------------
742 // ---------------------------------------------------------------------
743 // =====================================================================
744 /*
745  \fn ComputeFocalPositions
746  \param a_posX : cartesian position of the crystal on the x-axis
747  \param a_posY : cartesian position of the crystal on the y-axis
748  \param a_posZ : cartesian position of the crystal on the z-axis
749  \param a_headID : head index of the crystal
750  (required to select the related focal model parameters
751  and distance to center of rotation)
752  \param a_cryID : crystal index in the LUT
753  \brief Compute focal positions for a specific crystal ID
754  \details Compute the focal positions using the implemented "constant",
755  "polynomial", "slanthole" focal models related to the gamma cameras
756  The "custom" focal model is dedicated to user-made focal model
757  and should be implemented by the user
758  \return 0 if success, positive value otherwise
759 */
760 int iScannerSPECTConv::ComputeFocalPositions(FLTNB a_posX, FLTNB a_posY, FLTNB a_posZ, int a_headID, int a_cryID)
761 {
763 
764  mp_crystalFocalPositionY[a_cryID] = -a_posY;
765 
766  // ===================================================================
767  // AXIAL FOCAL POSITIONS
768  // ===================================================================
769  // constant
770  if(mp_focalModelAxial[a_headID] == "constant")
771  {
772  mp_crystalFocalPositionZ[a_cryID] = a_posZ;
773  }
774 
775  // polynomial
776  else if(mp_focalModelAxial[a_headID] == "polynomial" )
777  {
778  if (mp_nbCoefModelAxial[a_headID] >= 1 && mp_nbCoefModelAxial[a_headID] <= 3)
779  {
780  FLTNB focal_posZ = 0;
781 
782  for(int coef=0 ; coef<mp_nbCoefModelAxial[a_headID] ; coef++)
783  focal_posZ += m2p_axialFocalParameters[a_headID][coef]*abs(pow(a_posZ,coef));
784 
785  // Compute the projection of the focal position on the Y plane (thales) in order to be sure that the focal positions are not in the field of view (LOR would not go through the whole FOV)
786  mp_crystalFocalPositionZ[a_cryID] = -(2*mp_CORtoDetectorDistance[a_headID]-focal_posZ)*a_posZ/focal_posZ;
787 
788  }
789  else if (mp_nbCoefModelAxial[a_headID] > 3) // Error (nb parameters >3)
790  {
791  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Error, the number of coeffs for the axial model should be <4 (max : polynom order 2 = 3 coeffs) ! " << endl);
792  return 1;
793  }
794  else // Error (nb parameters == 0)
795  {
796  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Error, the number of coeffs for the axial model should be >0 ('axial number of coef model' in geom file) ! " << endl);
797  return 1;
798  }
799  }
800 
801  // slanthole
802  else if(mp_focalModelAxial[a_headID] == "slanthole" )
803  {
804  if (mp_nbCoefModelAxial[a_headID] == 1)
805  {
806  // Compute the projection of the focal position on the Y plane
807  mp_crystalFocalPositionZ[a_cryID] = -2.*m2p_axialFocalParameters[a_headID][0]*mp_CORtoDetectorDistance[a_headID] + a_posZ;
808  }
809  else // Error (nb parameters != 1)
810  {
811  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Error, the number of coeffs for a slanthole model should be equal to 1 (slope) : 'axial number of coef model' in geom file ! " << endl);
812  return 1;
813  }
814  }
815 
816  // custom
817  else if(mp_focalModelAxial[a_headID] == "custom" )
818  {
819  if (mp_nbCoefModelAxial[a_headID] == 1) // Uni-Parameter
820  {
821  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Custom model should be implemented by the user (in iScannerSPECTConv::ComputeFocalPositions()) ! " << endl);
822  return 1;
823  }
824  else if (mp_nbCoefModelAxial[a_headID] > 1) // Multi-Parameters
825  {
826  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Custom model should be implemented by the user (in iScannerSPECTConv::ComputeFocalPositions()) ! " << endl);
827  return 1;
828  }
829  else // Error (nb parameters == 0)
830  {
831  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Custom model should be implemented by the user (in iScannerSPECTConv::ComputeFocalPositions()) ! " << endl);
832  return 1;
833  }
834  }
835  else // Error, unknown model
836  {
837  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Error, current model " << mp_focalModelAxial[a_headID] << " is unknown !" << endl);
838  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Should be either 'constant' (parallel), 'polynomial', 'hyperbolic, or 'custom'" << endl);
839  return 1;
840  }
841 
842  // ===================================================================
843  // TRANSAXIAL FOCAL POSITIONS
844  // ===================================================================
845  // constant
846  if(mp_focalModelTrans[a_headID] == "constant")
847  {
848  mp_crystalFocalPositionX[a_cryID] = a_posX;
849  }
850 
851  // polynomial
852  else if(mp_focalModelTrans[a_headID] == "polynomial" )
853  {
854  if (mp_nbCoefModelTrans[a_headID] >= 1 && mp_nbCoefModelTrans[a_headID] <= 3)
855  {
856  FLTNB focal_posX = 0;
857 
858  for(int coef=0 ; coef<mp_nbCoefModelTrans[a_headID] ; coef++)
859  focal_posX += m2p_transFocalParameters[a_headID][coef]*abs(pow(a_posX,coef));
860 
861  // Compute the projection of the focal position on the Y plane (thales) in order to be sure that the focal positions are not in the field of view (LOR would not go through the whole FOV)
862  mp_crystalFocalPositionX[a_cryID] = -(2*mp_CORtoDetectorDistance[a_headID]-focal_posX)*a_posX/focal_posX;
863 
864  }
865  else if (mp_nbCoefModelTrans[a_headID] > 3) // Error (nb parameters >3)
866  {
867  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Error, the number of coeffs for the trans model should be <4 (max : polynom order 2 = 3 coeffs) ! " << endl);
868  return 1;
869  }
870  else // Error (nb parameters == 0)
871  {
872  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Error, the number of coeffs for the axial model should be >0 ('trans number of coef model' in geom file) ! " << endl);
873  return 1;
874  }
875  }
876 
877  // slanthole
878  else if(mp_focalModelTrans[a_headID] == "slanthole" )
879  {
880  if (mp_nbCoefModelTrans[a_headID] == 1)
881  {
882  // Compute the projection of the focal position on the Y plane
883  mp_crystalFocalPositionX[a_cryID] = -2.*m2p_transFocalParameters[a_headID][0]*mp_CORtoDetectorDistance[a_headID] + a_posX;
884  }
885  else // Error (nb parameters != 1
886  {
887  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Error, the number of coeffs for a slanthole model should be equal to 1 (slope) : 'trans number of coef model' in geom file ! " << endl);
888  return 1;
889  }
890  }
891 
892  // custom
893  else if(mp_focalModelTrans[a_headID] == "custom" )
894  {
895  if (mp_nbCoefModelTrans[a_headID] == 1) // Uni-Parameter
896  {
897  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Custom model should be implemented by the user (in iScannerSPECTConv::ComputeFocalPositions()) ! " << endl);
898  return 1;
899  }
900  else if (mp_nbCoefModelTrans[a_headID] > 1) // Multi-Parameters
901  {
902  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Custom model should be implemented by the user (in iScannerSPECTConv::ComputeFocalPositions()) ! " << endl);
903  return 1;
904  }
905  else // Error (nb parameters == 0)
906  {
907  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Custom model should be implemented by the user (in iScannerSPECTConv::ComputeFocalPositions()) ! " << endl);
908  return 1;
909  }
910  }
911  else // Error, unknown model
912  {
913  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Error, current model " << mp_focalModelTrans[a_headID] << " is unknown !" << endl);
914  Cerr("***** iScannerSPECTConv::ComputeFocalPositions() -> Should be either 'constant' (parallel), 'polynomial', 'hyperbolic, or 'custom'" << endl);
915  return 1;
916  }
917 
918  return 0;
919 }
920 
921 // =====================================================================
922 // ---------------------------------------------------------------------
923 // ---------------------------------------------------------------------
924 // =====================================================================
925 /*
926  \fn GetPositionsAndOrientations
927  \param a_index1 : 1st index of the event (projection angle)
928  \param a_index2 : 2nd index of the event (crystal index in the gamma camera)
929  \param ap_Position1[3] : x,y,z cartesian position of the focal point
930  \param ap_Position2[3] : x,y,z cartesian position of the crystal
931  \param ap_Orientation1[3] : return -1 by default (no orientation components required for the focal point)
932  \param ap_Orientation2[3] : x,y,z components of the orientation vector related to the crystal
933  \param ap_POI1 : ignored in SPECT (no POI component required for the focal point)
934  \param ap_POI2 : x,y,z components of the Point Of Interation related to the crystal.
935  Currently ignored (POI management not implemented in SPECT)
936  \brief Get the central positions and orientations of the scanner elements from their indices.
937  \todo : Point Of Interactions management is NOT implemented and ignored
938  \return 0 if success, positive value otherwise
939 */
940 int iScannerSPECTConv::GetPositionsAndOrientations( int a_index1, int a_index2,
941  FLTNB ap_Position1[3], FLTNB ap_Position2[3],
942  FLTNB ap_Orientation1[3], FLTNB ap_Orientation2[3],
943  FLTNB* ap_POI1, FLTNB* ap_POI2 )
944 {
946 
947  // SPECT indexes : 1st for the projection, 2nd for the crystal
948 
949  // First check projection angle existency
950  if (a_index1<0 || a_index1>=m_nbOfProjections)
951  {
952  Cerr("***** iScannerSPECTConv::GetPositionsAndOrientations() -> Projection index (" << a_index1 << ") out of range [0:" << m_nbOfProjections-1 << "] !" << endl);
953  return 1;
954  }
955 
956  // Second check crystals existency
957  if (a_index2<0 || a_index2>=m_nbCrystals)
958  {
959  Cerr("***** iScannerSPECTConv::GetPositionsAndOrientations() -> Crystal index (" << a_index2 << ") out of range [0:" << m_nbCrystals-1 << "] !" << endl);
960  return 1;
961  }
962 
963  // Get crystal index related to the projection
964  int index = a_index1*m_nbCrystals + a_index2;
965 
966  // Get position of the focal point related to the crystal
967  ap_Position1[0] = mp_crystalFocalPositionX[index];
968  ap_Position1[1] = mp_crystalFocalPositionY[index];
969  ap_Position1[2] = mp_crystalFocalPositionZ[index];
970 
971  // todo : Due to the current implementation of SPECT projection, POI
972  // and DOI are not handled and ignored.
973  // An error is returned if POI are provided
974 
975  // Case when POI is not provided
976  if (ap_POI2==NULL)
977  {
978  //FLTNB depth = mp_meanDepthOfInteraction[GetLayer(a_index2)] - mp_sizeCrystalDepth[GetLayer(a_index2)]/2;
979  //ap_Position2[0] = mp_crystalCentralPositionX[a_index2] + depth*mp_crystalOrientationX[a_index2];
980  //ap_Position2[1] = mp_crystalCentralPositionY[a_index2] + depth*mp_crystalOrientationY[a_index2];
981  //ap_Position2[2] = mp_crystalCentralPositionZ[a_index2] + depth*mp_crystalOrientationZ[a_index2];
982  ap_Position2[0] = mp_crystalCentralPositionX[index];
983  ap_Position2[1] = mp_crystalCentralPositionY[index];
984  ap_Position2[2] = mp_crystalCentralPositionZ[index];
985  }
986  // Case when POI[2] is negative (meaning we only have POI[0] or POI[1] specified and to be taken into account)
987  else if (ap_POI2[2]<0.)
988  {
989  Cerr("***** iScannerSPECTConv::GetPositionsAndOrientations() -> POI management not implemented yet for SPECT !" << endl);
990  return 1;
991  }
992  // Case when only the DOI is provided
993  else if (ap_POI2[0]==0. && ap_POI2[1]==0.)
994  {
995  Cerr("***** iScannerSPECTConv::GetPositionsAndOrientations() -> POI management not implemented yet for SPECT !" << endl);
996  return 1;
997  }
998  // Case when the full POI is taken into account
999  else
1000  {
1001  Cerr("***** iScannerSPECTConv::GetPositionsAndOrientations() -> POI management not implemented yet for SPECT !" << endl);
1002  return 1;
1003  }
1004 
1005  // Get orientations
1006  ap_Orientation1[0] = -mp_crystalOrientationX[a_index2];
1007  ap_Orientation1[1] = -mp_crystalOrientationY[a_index2];
1008  ap_Orientation1[2] = -mp_crystalOrientationZ[a_index2];
1009  ap_Orientation2[0] = mp_crystalOrientationX[a_index2];
1010  ap_Orientation2[1] = mp_crystalOrientationY[a_index2];
1011  ap_Orientation2[2] = mp_crystalOrientationZ[a_index2];
1012 
1013  return 0;
1014 }
1015 
1016 // =====================================================================
1017 // ---------------------------------------------------------------------
1018 // ---------------------------------------------------------------------
1019 // =====================================================================
1020 /*
1021  \fn GetRdmPositionsAndOrientations()
1022  \param a_index1 : 1st index of the event (projection angle)
1023  \param a_index2 : 2nd index of the event (crystal index in the gamma camera)
1024  \param ap_Position1[3] : x,y,z cartesian position of the focal point
1025  \param ap_Position2[3] : x,y,z cartesian position of the crystal
1026  \param ap_Orientation1[3] : return -1 by default (no orientation components required for the focal point)
1027  \param ap_Orientation2[3] : x,y,z components of the orientation vector related to the crystal
1028  \brief Get the focal point and random positions on the crystal surface and its orientations from the event indices.
1029  \details - Computed the LUT index described by the projection angle and crystal index passed in parameters.
1030  - Compute random positions on the surface of the crystal
1031  - Write the corresponding random cartesian coordinates in the positions parameters.
1032  \todo This implementation has to be checked and adapted,
1033  as the current implementation of SPECT projection assumes crystal position located on the center of the crystal surface
1034  \return 0 if success, positive value otherwise
1035 */
1037  FLTNB ap_Position1[3], FLTNB ap_Position2[3],
1038  FLTNB ap_Orientation1[3], FLTNB ap_Orientation2[3] )
1039 {
1041 
1042  // SPECT indexes : 1st for the projection, 2nd for the crystal
1043 
1044  // First check projection angle existency
1045  if (a_index1<0 || a_index1>=m_nbOfProjections)
1046  {
1047  Cerr("***** iScannerSPECTConv::GetPositionsAndOrientations() -> Projection index (" << a_index1 << ") out of range [0:" << m_nbOfProjections-1 << "] !" << endl);
1048  return 1;
1049  }
1050 
1051  // Second check crystals existency
1052  if (a_index2<0 || a_index2>=m_nbCrystals)
1053  {
1054  Cerr("***** iScannerSPECTConv::GetPositionsAndOrientations() -> Crystal index (" << a_index2 << ") out of range [0:" << m_nbCrystals-1 << "] !" << endl);
1055  return 1;
1056  }
1057 
1058  // Get crystal index related to the projection
1059  int index = a_index1*m_nbCrystals + a_index2;
1060 
1061  // Get position of the focal point related to the crystal
1062  ap_Position1[0] = mp_crystalFocalPositionX[index];
1063  ap_Position1[1] = mp_crystalFocalPositionY[index];
1064  ap_Position1[2] = mp_crystalFocalPositionZ[index];
1065 
1066  // Get instance of random number generator
1068 
1069  // Get random numbers for the first crystal
1070  FLTNB axial = (p_RNG->GenerateRdmNber()-0.5) * m_pixelsSizeAxial;
1071  FLTNB trans = (p_RNG->GenerateRdmNber()-0.5) * m_pixelsSizeTrans;
1072  // Do not consider random depth (position on the surface of the crystal)
1073  //FLTNB depth = (p_RNG->GenerateRdmNber()-0.5) * m_crystalDepth;
1074 
1075  ap_Position2[0] = mp_crystalCentralPositionX[index] + trans*mp_crystalOrientationY[index] + axial*mp_crystalOrientationX[index]*mp_crystalOrientationZ[index];
1076  ap_Position2[1] = mp_crystalCentralPositionY[index] + trans*mp_crystalOrientationX[index] + axial*mp_crystalOrientationY[index]*mp_crystalOrientationZ[index];
1077  ap_Position2[2] = mp_crystalCentralPositionZ[index] + axial*sqrt(1-mp_crystalOrientationZ[index]*mp_crystalOrientationZ[index]);
1078 
1079  // Get orientations
1080  ap_Orientation1[0] = -1.;
1081  ap_Orientation1[1] = -1.;
1082  ap_Orientation1[2] = -1.;
1083  ap_Orientation2[0] = mp_crystalOrientationX[a_index2];
1084  ap_Orientation2[1] = mp_crystalOrientationY[a_index2];
1085  ap_Orientation2[2] = mp_crystalOrientationZ[a_index2];
1086 
1087  return 0;
1088 }
1089 
1090 // =====================================================================
1091 // ---------------------------------------------------------------------
1092 // ---------------------------------------------------------------------
1093 // =====================================================================
1094 /*
1095  \fn GetPositionWithRandomDepth
1096  \param a_index1 :
1097  \param a_index2 : index of the crystal
1098  \param ap_Position1[3] :
1099  \param ap_Position2[3] : x,y,z cartesian position of the point related to the crystal
1100  \brief Get the positions and orientations of scanner elements from their indices, with a random depth.
1101  \todo Not yet implemented
1102  \return 0 if success, positive value otherwise
1103 */
1104 int iScannerSPECTConv::GetPositionWithRandomDepth(int a_index1, int a_index2, FLTNB ap_Position1[3], FLTNB ap_Position2[3])
1105 {
1107  // This function was first implemented for PET testing purpose. Not implemented yet.
1108  Cerr("***** iScannerSPECTConv::GetPositionWithRandomDepth() -> This function was implemented for PET testing purpose. Not implemented for SPECT !" << endl);
1109  return 1;
1110 }
1111 
1112 // =====================================================================
1113 // ---------------------------------------------------------------------
1114 // ---------------------------------------------------------------------
1115 // =====================================================================
1116 /*
1117  \fn GetTwoCorners
1118  \param a_index1 : index of the projection angle
1119  \param a_index2 : index of the crystal
1120  \param ap_CornerInf1[3]
1121  \param ap_CornerSup1[3]
1122  \param ap_CornerInf2[3]
1123  \param ap_CornerSup2[3]
1124  \brief Get the cartesian coordinaters of the two opposite corners of a scanner element.
1125  \todo Not yet implemented
1126  \return 0 if success, positive value otherwise
1127 */
1128 int iScannerSPECTConv::GetTwoCorners(int a_index1, int a_index2,
1129  FLTNB ap_CornerInf1[3], FLTNB ap_CornerSup1[3],
1130  FLTNB ap_CornerInf2[3], FLTNB ap_CornerSup2[3])
1131 {
1133  Cerr("***** iScannerSPECTConv::GetTwoCorners() -> Not implemented yet !" << endl);
1134  return 1;
1135 }
1136 
1137 // =====================================================================
1138 // ---------------------------------------------------------------------
1139 // ---------------------------------------------------------------------
1140 // =====================================================================
1141 /*
1142  \fn GetGeometricInfoFromDatafile
1143  \param a_pathToDF : string containing the path to datafile header
1144  \brief Recover geometric informations specific to the scanner class from the datafile header
1145  \details -Recover nb of bins and projections
1146  -Recover the projection angles from the header
1147  (directly read from the datafile, or extrapolated from a first and last angle)
1148  -Recover the distance between the gamma camera detector surfaces and the center of rotation
1149  (directly read from the datafile, or extracted from the gamma camera configuratino file)
1150  \return 0 if success, positive value otherwise
1151 */
1153 {
1155 
1156  if (m_verbose>=VERBOSE_DETAIL) Cout("iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Specific to SPECT" << endl);
1157 
1158  // This function is intended to be called after the scanner initialization, so that any field present in the datafile header, similar to
1159  // one in the scanner configuration file, may overload the scanner value.
1160 
1161  // Get bed displacement (possibly overloading the one provided in the scanner configuration file)
1162  if (ReadDataASCIIFile(a_pathToDF, "multiple bed displacement", &m_multiBedDisplacementInMm, 1, KEYWORD_OPTIONAL) == 1)
1163  {
1164  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> An error occurred while trying to read the multiple bed displacement in the datafile header !" << endl);
1165  return 1;
1166  }
1167 
1168  if (ReadDataASCIIFile(a_pathToDF , "Number of bins", mp_nbOfBins, 2, KEYWORD_OPTIONAL) == 1)
1169  {
1170  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Error while reading number of bins in the header data file " << endl);
1171  return 1;
1172  }
1173 
1174  if (ReadDataASCIIFile(a_pathToDF , "Number of projections", &m_nbOfProjections, 1, KEYWORD_MANDATORY))
1175  {
1176  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Error while reading number of projections in the header data file " << endl);
1177  return 1;
1178  }
1179 
1180  string rotation_direction = "";
1181  if (ReadDataASCIIFile(a_pathToDF , "Head rotation direction", &rotation_direction, 1, KEYWORD_OPTIONAL) == 1)
1182  {
1183  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Error while reading head rotation orientation in the header data file " << endl);
1184  return 1;
1185  }
1186 
1187  if (SetRotDirection(rotation_direction) )
1188  {
1189  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Error occured while trying to initialize head rotation orientation " << endl);
1190  return 1;
1191  }
1192 
1193  // Allocate a one-dimensional vector to retrieve angles from the datafile, then copy it in the member variables
1194  FLTNB* angles = new FLTNB[m_nbOfProjections];
1195  FLTNB first_and_last_angles[2] = {-1.,-1.};
1196 
1197  // Two possible initializations for projection angles :
1198  // - All angles are provided with the keyword "Angles"
1199  // - The first and last angles are provided, and the intermediate angles are extrapolated from the number of projections
1200 
1201  // 'First/Last angles' tags : checking issue during data reading/conversion (==1)
1202  if (ReadDataASCIIFile(a_pathToDF, "First and last projection angles", first_and_last_angles, 2, KEYWORD_OPTIONAL) == 1)
1203  {
1204  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Error while reading Angle mandatory field in the header data file '" << endl);
1205  return 1;
1206  }
1207 
1208  // Check for 'Projection angles' tag
1209  int rvalue = ReadDataASCIIFile(a_pathToDF, "Projection angles", angles, m_nbOfProjections, KEYWORD_OPTIONAL);
1210 
1211  // Error while reading "Angles" tag (==1)
1212  if(rvalue==1)
1213  {
1214  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Error while reading Angles field in the header data file !'" << endl);
1215  return 1;
1216  }
1217 
1218  // Check if information on projection angles has been provided
1219  if ( rvalue>=2 && // "Angles" tag not found
1220  (first_and_last_angles[0] <0 || first_and_last_angles[1] <0) ) // Tags first/last angles not found)
1221  {
1222  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> No information on projection angles provided in the datafile !'" << endl);
1223  Cerr(" This information should be provided using either the 'Angles' tag, or both 'First angles', 'Last angles' tags !'" << endl);
1224  return 1;
1225  }
1226  else if (rvalue>=2) // "Angles" tag not found, but first angle and last angle provided
1227  {
1228  for(int a=0 ; a<m_nbOfProjections ; a++)
1229  angles[a] = first_and_last_angles[0] + a*(first_and_last_angles[1]-first_and_last_angles[0]) / (m_nbOfProjections-1);
1230  }
1231  // else : Angles have been recovered using ReadDataASCIIFile() above
1232 
1233  // Instanciate here the projection angles variable
1235 
1236  for(int a=0 ; a<m_nbOfProjections ; a++)
1237  mp_projectionAngles[a] = angles[a];
1238 
1239  // Recover distance between the detectors and scanner center of rotation
1240  // Allocate with the number of projections by default
1242 
1243  // Initialize by default with the scanner radius
1244  for(int hId=0 ; hId<m_nbHeads ; hId++)
1245  for(int a=0 ; a<m_nbOfProjections/m_nbHeads ; a++)
1246  mp_CORtoDetectorDistance[a+hId*m_nbOfProjections/m_nbHeads] = mp_radius[hId];
1247 
1248  // Read datafile value if any. First case : we have a radius specific to each projections
1249  int read_flag = 0;
1250  read_flag = ReadDataASCIIFile(a_pathToDF, "Distance camera surface to COR", mp_CORtoDetectorDistance, m_nbOfProjections, KEYWORD_OPTIONAL);
1251 
1252  if (read_flag==1)
1253  {
1254  // Error during reading
1255  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Error while reading the distance between the camera detectors to the center of rotation in the header data file " << endl);
1256  return 1;
1257  }
1258  // Check the second case : we have a global radius for each projection
1259  else if (read_flag==2)
1260  {
1261  read_flag = ReadDataASCIIFile(a_pathToDF, "Global distance camera surface to COR", mp_CORtoDetectorDistance, 1, KEYWORD_OPTIONAL);
1262 
1263  if(read_flag==0)
1264  {
1265  // Field was found : Initialize the distance for each projection angle with the global value
1266  for(int a=1 ; a<m_nbOfProjections ; a++)
1268  }
1269  else if(read_flag==1)
1270  {
1271  // Error during reading
1272  Cerr("***** iScannerSPECTConv::GetGeometricInfoFromDatafile() -> Error while reading the global distance between the camera detectors to the center of rotation in the header data file " << endl);
1273  return 1;
1274  }
1275  else if (read_flag==2)
1276  {
1277  // Initialization with default values from the scanner file
1278  for(int hId=0 ; hId<m_nbHeads ; hId++)
1279  for(int a=0 ; a<m_nbOfProjections/m_nbHeads ; a++)
1280  mp_CORtoDetectorDistance[a+hId*m_nbOfProjections/m_nbHeads] = mp_radius[hId];
1281  }
1282  }
1283 
1284  delete[] angles;
1285 
1286  return 0;
1287 }
1288 
1289 // =====================================================================
1290 // ---------------------------------------------------------------------
1291 // ---------------------------------------------------------------------
1292 // =====================================================================
1293 /*
1294  \fn GetSPECTSpecificParameters
1295  \param ap_nbOfProjections : total number of views
1296  \param ap_nbHeads : number of heads in the system
1297  \param ap_nbOfBins : 2 elements array containing transaxial/axial number of pixels
1298  \param ap_pixSizeXY : 2 elements array containing transaxial/axial pixel sizes
1299  \param ap_angles : Array containing angles of each projection view
1300  \param ap_CORtoDetectorDistance : Radius (distance between FOV center and detector)
1301  \param ap_headRotDirection : head rotation direction
1302  \brief Set pointers passed in argument with the related SPECT specific variables
1303  This function is used to recover these values in the datafile object
1304  \return 0 if success, positive value otherwise
1305 */
1306 int iScannerSPECTConv::GetSPECTSpecificParameters(uint16_t* ap_nbOfProjections,
1307  uint16_t* ap_nbHeads,
1308  uint16_t* ap_nbOfBins,
1309  FLTNB* ap_pixSizeXY,
1310  FLTNB*& ap_angles,
1311  FLTNB*& ap_CORtoDetectorDistance,
1312  int* ap_headRotDirection)
1313 {
1315  // Verbose
1317  // Verify that all parameters have been correctly checked
1319  {
1320  Cerr("***** iScannerSPECTConv::GetSPECTSpecificParameters() -> Parameters have not been checked !" << endl);
1321  return 1;
1322  }
1323  // Get them
1324  *ap_nbOfProjections = m_nbOfProjections;
1325  *ap_nbHeads = m_nbHeads;
1326  ap_nbOfBins[0] = mp_nbOfBins[0];
1327  ap_nbOfBins[1] = mp_nbOfBins[1];
1328  ap_pixSizeXY[0] = m_vPixelsSizeTrans;
1329  ap_pixSizeXY[1] = m_vPixelsSizeAxial;
1330  ap_angles = mp_projectionAngles;
1331  ap_CORtoDetectorDistance = mp_CORtoDetectorDistance;
1332  *ap_headRotDirection = m_rotDirection;
1333  // End
1334  return 0;
1335 }
1336 
1337 // =====================================================================
1338 // ---------------------------------------------------------------------
1339 // ---------------------------------------------------------------------
1340 // =====================================================================
1341 /*
1342  \fn PROJ_SetSPECTAngles
1343  \param ap_projectionAngles : an array containing the projection angles
1344  \brief Set the projection angles with the array provided in parameter
1345  \return 0 if success, positive value otherwise
1346 */
1348 {
1350 
1351  if(m_verbose>=VERBOSE_NORMAL) Cout("iScannerSPECTConv::PROJ_SetSPECTAngles ..." << endl);
1352 
1353  // Check initialization of the number of projections
1354  if(m_nbOfProjections <= 0)
1355  {
1356  Cerr("***** iScannerSPECTConv::PROJ_SetSPECTAngles -> Error number of projection should be >0 ! '" << endl);
1357  return 1;
1358  }
1359  else
1360  {
1362 
1363  for(int a=0 ; a<m_nbOfProjections ; a++)
1364  mp_projectionAngles[a] = ap_projectionAngles[a];
1365  }
1366 
1367  return 0;
1368 }
1369 
1370 // =====================================================================
1371 // ---------------------------------------------------------------------
1372 // ---------------------------------------------------------------------
1373 // =====================================================================
1374 /*
1375  \fn PROJ_SetSPECTCORtoDetectorDistance
1376  \param a_distance
1377  \brief Set distance between the center of rotation and SPECT detectors if arg value>0,
1378  Set with the geometric information in the scanner configuration file otherwise
1379  \return 0 if success, positive value otherwise
1380 */
1382 {
1384 
1385  if (m_verbose>=VERBOSE_NORMAL) Cout("iScannerSPECTConv::PROJ_SetSPECTCORtoDetectorDistance ..." << endl);
1386 
1387  // Check initialization of the number of projections
1388  if(m_nbOfProjections <= 0)
1389  {
1390  Cerr("***** iScannerSPECTConv::PROJ_SetSPECTCORtoDetectorDistance -> Error number of projection should be >0 ! '" << endl);
1391  return 1;
1392  }
1393  else
1394  {
1396 
1397  if(a_distance>0)
1398  {
1399  // Set all radius to the provided distance
1400  for(int a=0 ; a<m_nbOfProjections ; a++)
1401  mp_CORtoDetectorDistance[a] = a_distance;
1402  }
1403  else
1404  {
1405  // Set all distance according to scanner geometric informations (default)
1406  for(int hId=0 ; hId<m_nbHeads ; hId++)
1407  for(int a=0 ; a<m_nbOfProjections/m_nbHeads ; a++)
1408  mp_CORtoDetectorDistance[a+hId*m_nbOfProjections/m_nbHeads] = mp_radius[hId];
1409  }
1410  }
1411  return 0;
1412 }
1413 
1414 // =====================================================================
1415 // ---------------------------------------------------------------------
1416 // ---------------------------------------------------------------------
1417 // =====================================================================
1418 /*
1419  \fn PROJ_GetSPECTNbBins
1420  \brief Get the number of SPECT heads in the pointer provided in parameter
1421  \return 0 by default (no error)
1422 */
1423 int iScannerSPECTConv::PROJ_GetSPECTNbBins(uint16_t* ap_nbOfBins)
1424 {
1426  ap_nbOfBins = mp_nbOfBins;
1427  return 0;
1428 }
1429 
1430 // =====================================================================
1431 // ---------------------------------------------------------------------
1432 // ---------------------------------------------------------------------
1433 // =====================================================================
1434 /*
1435  \fn PROJ_SetSPECTNbBins
1436  \param ap_nbOfBins
1437  \brief Set number of bins
1438  \return 0 by default (no error)
1439 */
1440 int iScannerSPECTConv::PROJ_SetSPECTNbBins(uint16_t* ap_nbOfBins)
1441 {
1443  for(int i=0 ; i<2 ; i++)
1444  mp_nbOfBins[i]=ap_nbOfBins[i];
1445  return 0;
1446 }
1447 
1448 // =====================================================================
1449 // ---------------------------------------------------------------------
1450 // ---------------------------------------------------------------------
1451 // =====================================================================
1452 /*
1453  \fn PROJ_SetSPECTNbProjections
1454  \param a_nbOfProjections
1455  \brief Set number of projections
1456  \return 0 by default (no error)
1457 */
1458 int iScannerSPECTConv::PROJ_SetSPECTNbProjections(uint32_t a_nbOfProjections)
1459 {
1461  m_nbOfProjections = a_nbOfProjections;
1462  return 0;
1463 }
1464 
1465 
1466 // =====================================================================
1467 // ---------------------------------------------------------------------
1468 // ---------------------------------------------------------------------
1469 // =====================================================================
1470 /*
1471  \fn ShowHelp
1472  \brief Display help
1473  \todo Provide informations about SPECT system initialization ?
1474 */
1476 {
1478  cout << "This scanner class is dedicated to the description of parallel, convergent and multi-convergent SPECT systems." << endl;
1479 }
This class is used to represent any SPECT camera with parallel/convergent collimator.
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
#define FLTNB
Definition: gVariables.hh:55
double GenerateRdmNber()
Generate a random number for the thread whose index is recovered from the ompenMP function...
FLTNB * mp_crystalCentralPositionX
oMatrix * mp_positionMatrix_out
Definition: vScanner.hh:368
#define VERBOSE_DETAIL
#define GEO_ROT_CCW
Definition: vScanner.hh:30
FLTNB ** m2p_axialFocalParameters
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...
FLTNB ** m2p_transFocalParameters
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)
This is a pure virtual method that must be implemented by children. Get the central positions and o...
string GetPathToScannerFile()
int SetMatriceElt(uint16_t l, uint16_t c, FLTNBLUT a_val)
Set the matrix element corresponding to the argument indices with the provided value.
Definition: oMatrix.cc:127
void ShowHelp()
Display help.
int PROJ_SetSPECTCORtoDetectorDistance(FLTNB a_distance)
Set distance between the center of rotation and SPECT detectors if arg value>0, Set with the geomet...
FLTNB * mp_crystalCentralPositionY
FLTNB m_multiBedDisplacementInMm
Definition: vScanner.hh:370
oMatrix * mp_rotationMatrix
Definition: vScanner.hh:366
iScannerSPECTConv()
iScannerSPECTConv constructor. Initialize the member variables to their default values.
#define Cerr(MESSAGE)
int PROJ_SetSPECTAngles(FLTNB *ap_projectionAngles)
Set the projection angles with the array provided in parameter.
#define SCANNER_SPECT_CONVERGENT
int ComputeLUT()
Computes the LUT of the scanner from a generic (.geom) file.
#define VERBOSE_DEBUG_LIGHT
virtual int SetRotDirection(string a_rotDirection)
Set rotation direction of the system.
Definition: vScanner.cc:232
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:111
Singleton class that Instantiate and initialize the scanner object.
Declaration of class sScannerManager.
int Multiplication(oMatrix *a_Mtx, oMatrix *a_MtxResult)
Multiply the member matrix with the matrix provided in 1st parameter Return the result in the matric ...
Definition: oMatrix.cc:173
int m_verbose
Definition: vScanner.hh:364
Declaration of class iScannerSPECTConv.
#define VERBOSE_NORMAL
int BuildLUT(bool a_scannerFileIsLUT)
Call the functions to generate the LUT or read the user-made LUT depending on the user choice...
bool m_allParametersChecked
Definition: vScanner.hh:365
#define KEYWORD_MANDATORY
Definition: gOptions.hh:25
#define KEYWORD_OPTIONAL
Definition: gOptions.hh:27
Singleton class that generate a thread-safe random generator number for openMP As singleton...
int GetSPECTSpecificParameters(uint16_t *ap_nbOfProjections, uint16_t *ap_nbHeads, uint16_t *ap_nbOfBins, FLTNB *ap_pixSizeXY, FLTNB *&ap_angles, FLTNB *&ap_CORtoDetectorDistance, int *ap_headRotDirection)
Set pointers passed in argument with the related SPECT specific variables This function is used to ...
int ComputeFocalPositions(FLTNB a_posX, FLTNB a_posY, FLTNB a_posZ, int a_headID, int a_cryID)
Compute focal positions for a specific crystal ID.
FLTNBLUT GetMatriceElt(uint16_t l, uint16_t c)
Definition: oMatrix.cc:154
int m_scannerType
Definition: vScanner.hh:363
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 the focal point and random positions on the crystal surface and its orientations from the event i...
Declaration of class sOutputManager.
oMatrix * mp_positionMatrix_ref
Definition: vScanner.hh:367
int Initialize()
Check general initialization and set several parameters to their default value.
Structure designed for basic matrices operations.
Definition: oMatrix.hh:20
int PROJ_SetSPECTNbBins(uint16_t *ap_nbOfBins)
Set number of bins.
int CheckParameters()
Check that all parameters have been correctly initialized.
int PROJ_SetSPECTNbProjections(uint32_t a_nbOfProjections)
Set number of projections.
FLTNB * mp_crystalCentralPositionZ
#define DEBUG_VERBOSE(IGNORED1, IGNORED2)
int GetGeometricInfoFromDatafile(string a_pathToDF)
Recover geometric informations specific to the scanner class from the datafile header.
int PROJ_GetSPECTNbBins(uint16_t *ap_nbOfBins)
Get the number of SPECT heads in the pointer provided in parameter.
#define Cout(MESSAGE)
int Instantiate(bool a_scannerFileIsLUT)
Get mandatory informations from the scanner file and allocate memory for the member variables...
~iScannerSPECTConv()
iScannerSPECTConv destructor.
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.
int m_rotDirection
Definition: vScanner.hh:369
#define GEO_ROT_CW
Definition: vScanner.hh:28
Generic class for scanner objects.
Definition: vScanner.hh:48
int LoadLUT()
Load a precomputed scanner LUT.
#define VERBOSE_DEBUG_NORMAL