ECCE @ EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
doiPETAnalysis.cc
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file doiPETAnalysis.cc
1 //
2 // ********************************************************************
3 // * License and Disclaimer *
4 // * *
5 // * The Geant4 software is copyright of the Copyright Holders of *
6 // * the Geant4 Collaboration. It is provided under the terms and *
7 // * conditions of the Geant4 Software License, included in the file *
8 // * LICENSE and available at http://cern.ch/geant4/license . These *
9 // * include a list of copyright holders. *
10 // * *
11 // * Neither the authors of this software system, nor their employing *
12 // * institutes,nor the agencies providing financial support for this *
13 // * work make any representation or warranty, express or implied, *
14 // * regarding this software system or assume any liability for its *
15 // * use. Please see the license in the file LICENSE and URL above *
16 // * for the full disclaimer and the limitation of liability. *
17 // * *
18 // * This code implementation is the result of the scientific and *
19 // * technical work of the GEANT4 collaboration. *
20 // * By using, copying, modifying or distributing the software (or *
21 // * any work based on the software) you agree to acknowledge its *
22 // * use in resulting scientific publications, and indicate your *
23 // * acceptance of all terms of the Geant4 Software license. *
24 // ********************************************************************
25 
26 //GEANT4 - Depth-of-Interaction enabled Positron emission tomography (PET) advanced example
27 
28 //Authors and contributors
29 
30 // Author list to be updated, with names of co-authors and contributors from National Institute of Radiological Sciences (NIRS)
31 
32 // Abdella M. Ahmed (1, 2), Andrew Chacon (1, 2), Harley Rutherford (1, 2),
33 // Hideaki Tashima (3), Go Akamatsu (3), Akram Mohammadi (3), Eiji Yoshida (3), Taiga Yamaya (3)
34 // Susanna Guatelli (2), and Mitra Safavi-Naeini (1, 2)
35 
36 // (1) Australian Nuclear Science and Technology Organisation, Australia
37 // (2) University of Wollongong, Australia
38 // (3) National Institute of Radiological Sciences, Japan
39 
40 //Implemetation of the doiPETAnalysis.cc class
41 //This implementation mimics readout (or digitizer) of the PET scanner. To mimic realistic PET detector, the signals are blurred. Blurring
42 //parameters are given in inputParameters.txt file. Crystal based energy resolution and quantum efficiency has been applied. Deadtime (on
43 //each detector block and axially multiplexed detector) is also applied before the event is rejected by the energy window. The units for
44 //blurring parameters are in keV (for energy) and ns (nano sec) for dead time. If the units are different, exception will be thrown and the
45 //program quits. In this class, an ideal PMT is assumed to be placed at the corners of the crystal block. First, the ideal interaction position
46 //(obtained by G4) is used to determine the distance of the PMT from the interaction point. The signal on each PMT depends on the lateral (2D)
47 //distance from the PMTs. Light sharing method (reflector based) DOI identification method has been used. If the crystal ID is out of bound,
48 //error message will be displayed and the event will be rejected. The output file is single based list-mode ASCII file and can be then be
49 //processed into coinsidence list-mode data. As, an option, binary output method is also given.
50 //Explanation is given for the methods provided.
51 
52 
53 #include "doiPETAnalysis.hh"
54 #include "G4SystemOfUnits.hh"
55 #include "G4PhysicalConstants.hh"
56 #include <iomanip>
57 #include "Randomize.hh"
58 #include "G4SPSRandomGenerator.hh"
60 
61 
63 
66 {
68 
69  //Set energy window
70  lowerThreshold = 400*keV;
71  upperThreshold = 600*keV;
72 
73 
74  //give default initial activity. Activity strength is changed in the .mac file
75  InitialActivity = 1000000*becquerel;
76 
77  //In NEMA NU2, all test is done with F-18
78  halfLife = 109.771*60 * s;//The halfLife of a given isotope can be changed via the run.mac file
79 
80  //
81  totalTime = 0 * s;
82  prev_totalTime = 0 * s;
83  prev_eventID = 0;
84 
85  //
86  //Initialize crystal ID
87  crystalIDNew_tan = -1;
88  crystalIDNew_axial = -1;
89  crystalIDNew_DOI = -1;
90 
91  //
92  scatterIndex = 0;
93 
94  //
95  numberOfPixel_tan = 32;
97 
98  //Default value for deadtime.
99  block_DeadTime = 256*ns;
100  module_DeadTime = 0*ns;
101  //
102 
103  //Crystal blurring parameters. One detector has 1024 crystals. All the crystals have different energy resolution.
104  //So, a range of energy resolution is applied between minumun and maximum values.
105  //The energy resolution can be set in the inputParameter.txt file
106  crystalResolutionMin = 0.13;//13%
107  crystalResolutionMax = 0.17;//17%
108 
109  crystalEnergyRef = 511 * keV;//Energy of reference in which the energy resolution of the crystal is computed
110 
111  //The quantum efficiency models the probability for the event to be detected by the photo-detector.
112  //The quantum efficiency can be set inputParameter.txt file
113  crystalQuantumEfficiency = 1;//100%
114  //
115 
116  //intialize deadtime for blocks and modules
118  blockTime = new double[numberOfBlocks_total];//for each individual block.
119  moduleTime = new double[numberOfBlocks_total];//for axially multiplexed detectors.
120 
121  //Initialize the deadtime for each detector and axially multiplexed detector (also called modules)
122  for(G4int i = 0; i<numberOfBlocks_total; i++){
123  blockTime [i] = 0.0;
124  moduleTime [i] = 0.0;
125  }
126 
127  //Initialize type of output. The default output is single events
128  getSinglesData = false; //default value
129  getCoincidenceData = false;
130  numberOfHit = 0;
131 
132  //This value is based on the assumption that the shift due to the reflector is half distance from the interaction position to the air gap.
133  shiftCoeff = 0.5;
134 }
137 {
138  delete fAnalysisMessenger;
139  delete [] blockTime;
140  delete [] moduleTime;
141 }
142 
145 {
146  if(instance==0) instance = new doiPETAnalysis();
147  return instance;
148 }
150 {
151  delete instance;
152 }
153 
154 //If there is energy deposition in the phantom by the photon, the scatter index is 1, otherwise it is 0
155 //Use this for checking
157  if(edepInPhantom>0)scatterIndex = 1;
158  else scatterIndex = 0;
159 }
160 
161 //Get the source position if the process is annihilation.
162 //Use this for checking
164  spositionX = spos.x();
165  spositionY = spos.y();
166  spositionZ = spos.z();
167 }
168 
169 
170 //Set the event ID
171 //Use this for checking. eventID should not be used to sort coincidence events if realistic simulation is done
173  eventID = evID;
174 }
175 
176 //
177 void doiPETAnalysis::GetSizeOfDetector(G4double detSizeDoi, G4double detSizeTan, G4double detSizeAxial){
178  sizeOfDetector_DOI = detSizeDoi;
179  sizeOfDetector_axial = detSizeTan;
180  sizeOfDetector_tangential = detSizeAxial;
181 }
182 
183 //
185  InitialActivity = newActivity;
186  G4cout<<"Initial activity: "<<InitialActivity/becquerel<<" Bq."<<G4endl;
187 }
189  halfLife = newHalfLife;
190  G4cout<<"Half life of the isotope "<<halfLife/s<<" sec."<<G4endl;
191 }
192 
193 //The time is based on random time intervals between events. See https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3267383/
194 //This time mimics acquisition time of a PET scanner for a given number of particles.
196  //Calculate the strength of activity at t=totaltime using decay equation
197  activityNow = InitialActivity * std::exp(-((0.693147/halfLife)*totalTime)); //ln(2) = 0.693147181
198 
199  //Activity based time interval.
200  timeInterval = -std::log(G4UniformRand())*(1./activityNow);
201  totalTime = timeInterval+prev_totalTime;
202  prev_totalTime = totalTime;
203 }
204 
205 //Apply energy blurring on the crystals. The value of the energy blurring with respect to a reference energy is given in the inputParameter.txt file
207 {
208  if(fixedResolution){
210  }
211  else{
213  }
215 
216  G4double QE = G4UniformRand();
217 
218  //The quantum efficiency models the probability for the event to be detected by the photo-detector. It can be changed in the inputParameter.txt file
219  if(QE <= crystalQuantumEfficiency)
220  {
221  edep_AfterCrystalBlurring = G4RandGauss::shoot(edep,crystalCoeff*std::sqrt(edep)/2.35);
222  }
223  else {
224  //not detected by the photodetector, eventhough there was an interaction
226  }
228 }
229 
231 
232 void doiPETAnalysis::ReadOut(G4int blkID, G4int cryID, G4double interTime, G4double timeAnnih, G4ThreeVector interPos, G4double edep)
233 {
234  blockID = blkID;
235  crystalID = cryID;
236  interactionTime = interTime;
237  time_annihil = timeAnnih;
238  interactionPos = interPos;
239  totalEdep = edep;
240 
241  //Get the time of flight. This is the duration from the annihilation process to the detection of the photons by the scintillator.
243 
244  //time of the event when detected (timerTag)
246 
247 
248  //************************************** Apply dead-time ********************************************//
249  //Apply paralizable dead-time in the block beofore events are rejected by the energy window
250  if(std::fabs(timeStamp - blockTime[blockID]) >= block_DeadTime){ //If true, the event is accepted
252  }
253  else {
254  //If the time difference is less than the processing time of the detector (dead time), then the dead time (blockTime) of the block is extended.
256 
257  //the event is then rejected
258  //continue;
259  return;
260  }
261 
262  //Apply Non-paralyzable dead-time on axially multiplexed detectors (4 detectors are arranged axailly)
263  //If the time difference is less than the processing time of the module, the event is rejected without extending the dead time of the module
264  if(std::fabs(timeStamp - moduleTime[blockID]) > module_DeadTime){
265 
266  //The following finds the block id's of four blocks which are arranged axially
267  for (G4int r_ring = 0; r_ring < numberOfRings; r_ring++){
268  if (blockID >= r_ring*numberOfDetector_perRing && blockID <(r_ring + 1)*numberOfDetector_perRing){
269  for (G4int m_module = 0; m_module < numberOfRings; m_module++){
270 
271  //Set the time of the module (four blocks) the same
272  moduleTime[blockID + (m_module - r_ring)*numberOfDetector_perRing] = timeStamp;
273  }
274  }
275  }
276  }
277  else return;
278 
279 
281 
283 
284  //identifiy the layer
286 
287  //identify the crystal id for each Layer. Now, crystalID_2D can take 0,1, ... numberOfCrystal_tangential x numberOfCrystal_axial
289 
290  //identify the crystal ID in the tangential and axial direction
293 
294  //Calculate local position of the crystal with respect to the detector. Only the lateral distances (tangential (y) and axial (z) are needed.)
295  //G4double posCrystalX = (DOI_ID-((G4double)numberOfCrystal_DOI)/2 + 0.5)*(sizeOfCrystal_DOI + crystalGap_DOI) + interactionPos.x();
297  G4double posCrystalZ = (crystalID_axial-((G4double)numberOfCrystal_axial)/2 + 0.5)*(sizeOfCrystal_axial + crystalGap_axial) + interactionPos.z();
298 
299 
300  //shiftCoeff = 0.5 is used. This value is based on the assumption that the shift due to the reflector is half distance from the interaction position to the air gap.
301  AngerLogic(DOI_ID, crystalID_tangential, crystalID_axial, posCrystalY, posCrystalZ, totalEdep, shiftCoeff);//
302 
303 
304  //Single event output. Coincidence events can then be made using the single events.
306 
307  //Coincidence output
308  if(getCoincidenceData){
309  eventID_coin.push_back(eventID);
310  blockID_coin.push_back(blockID);
311  cryID_axial_coin.push_back(crystalID_axial);
313  edep_coin.push_back(totalEdep);
314  cryDOI_coin.push_back(DOI_ID);
315  time_coin.push_back(timeStamp);
316 
317  numberOfHit++;
318 
319  if(numberOfHit == 2){ //two events within the energy window are qualified.
320  WriteOutput();
322  }
323  }
324  }
325 }
326 
327 
328 
331 {
332  numberOfHit = 0;
333  eventID_coin.clear();
334  blockID_coin.clear();
335  cryID_axial_coin.clear();
336  cryID_tan_coin.clear();
337  edep_coin.clear();
338  cryDOI_coin.clear();
339  time_coin.clear();
340 
341 }
342 
343 //
345 {
346  if(getSinglesData){
347  asciiFileName = fileName + "Singles.data";
348  }
349  if(getCoincidenceData){
350  asciiFileName = fileName + "Coincidence.data";
351  }
352 
353  ofs.open(asciiFileName.c_str());
354  if(!ofs.is_open()){
355  G4cerr<<"=== \n File opening Error to write the output ===="<<G4endl;
356  exit(0);
357  }
358  //
359 #ifdef USEROOT
360  if(getSinglesData){
361  rootFileName = fileName+"Singles.root";
362  tSingles = new TTree("tSingles","SinglesTree");
363  tSingles->Branch("eventID",&eventID,"eventID/I");
364  tSingles->Branch("blockID",&blockID,"blockID/I");
365  tSingles->Branch("crystalID_axial",&crystalID_axial,"crystalID_axial/I");
366  tSingles->Branch("crystalID_tangential",&crystalID_axial,"crystalID_tangential/I");
367  tSingles->Branch("DOI_ID",&DOI_ID0,"DOI_ID/I");
368  tSingles->Branch("timeStamp",&timeStamp,"timeStamp/D");
369  tSingles->Branch("totalEdep",&totalEdep,"totalEdep/D");
370  }
371 
372  if(getCoincidenceData){
373  rootFileName = fileName+"Coincidence.root";
374  tCoincidence = new TTree("tCoincidence","CoincidenceTree");
375  //First Single
376  tCoincidence->Branch("eventID0",&eventID0,"eventID0/I");
377  tCoincidence->Branch("blockID0",&blockID0,"blockID0/I");
378  tCoincidence->Branch("crystalID_axial0",&crystalID_axial0,"crystalID_axial0/I");
379  tCoincidence->Branch("crystalID_tangential0",&crystalID_axial0,"crystalID_tangential0/I");
380  tCoincidence->Branch("DOI_ID0",&DOI_ID0,"DOI_ID0/I");
381  tCoincidence->Branch("timeStamp0",&timeStamp0,"timeStamp0/D");
382  tCoincidence->Branch("totalEdep0",&totalEdep0,"totalEdep0/D");
383 
384  //Second Single
385  tCoincidence->Branch("eventID1",&eventID1,"eventID1/I");
386  tCoincidence->Branch("blockID1",&blockID1,"blockID1/I");
387  tCoincidence->Branch("crystalID_axial1",&crystalID_axial1,"crystalID_axial1/I");
388  tCoincidence->Branch("crystalID_tangential1",&crystalID_axial1,"crystalID_tangential1/I");
389  tCoincidence->Branch("DOI_ID1",&DOI_ID1,"DOI_ID1/I");
390  tCoincidence->Branch("timeStamp1",&timeStamp1,"timeStamp1/D");
391  tCoincidence->Branch("totalEdep1",&totalEdep1,"totalEdep1/D");
392  }
393 #endif
394  //
395 }
396 
398  if(getSinglesData){
399  ofs<<eventID<<" "<<blockID<<" "<<crystalID_axial<<" "<<crystalID_tangential<<" "<<DOI_ID<<" "<<std::setprecision(17)<<timeStamp/s<<" "<<std::setprecision(7)<<totalEdep/keV<<G4endl;
400 #ifdef USEROOT
401  tSingles->Fill();
402 #endif
403  }
404  if(getCoincidenceData){
405  //2 singles will qualify to be in coincidence within the energy window.
406  for(G4int i=0; i<2; i++){
407 
408  //First Single
409  if(i==0){
410  eventID0 = eventID_coin[0];
411  blockID0 = blockID_coin[0];
414  DOI_ID0 = cryDOI_coin[0];
415  timeStamp0 = time_coin[0];
416  totalEdep0 = edep_coin[0];
417  }
418  if(i==1){
419  //Second Single
420  eventID1 = eventID_coin[1];
421  blockID1 = blockID_coin[1];
424  DOI_ID1 = cryDOI_coin[1];
425  timeStamp1 = time_coin[1];
426  totalEdep1 = edep_coin[1];
427  }
428  }
429 
430  ofs<<eventID0<<" "<<blockID0<<" "<<crystalID_axial0<<" "<<crystalID_tangential0<<" "<<DOI_ID0<<" "<<std::setprecision(17)<<timeStamp0/s<<" "<<std::setprecision(7)<<totalEdep0/keV<<" "
431  <<eventID1<<" "<<blockID1<<" "<<crystalID_axial1<<" "<<crystalID_tangential1<<" "<<DOI_ID1<<" "<<std::setprecision(17)<<timeStamp1/s<<" "<<std::setprecision(7)<<totalEdep1/keV<<G4endl;
432 
433 #ifdef USEROOT
434  tCoincidence->Fill();
435 #endif
436  }
437 
438 }
439 
440 //
443 {
444  //close ascii file
445  ofs.close();
446 
447  //
448 #ifdef USEROOT
449  TFile f(rootFileName.c_str(),"RECREATE");
450  if(getSinglesData){
451  tSingles->Write();
452  delete tSingles;
453  }
454  if(getCoincidenceData){
455  tCoincidence->Write();
456  delete tCoincidence;
457  }
458 
459  f.Close();
460 
461 
462 #endif
463 
464 }
465 
466 //Place the photomultiplier tube (PMT) at each corner of the detector.
467 //The positions of the PMT is with respect to the axis of the detector block
468 //All the PMTs are placed at the same doi (x) position
469 //(at +sizeOfDetector_DOI/2 which is at the top of the detector).
470 
471 //The PMT is placed at each corner of the crystal block and is assumed to be an ideal PMT.
472 //The signal (energy deposition) of each PMT depends on the distance of the respective
473 // PMT from the interaction point
475 
479 
480  //Position of PMT1.
484 
485  //Position of PMT2
489 
490  //Position of PMT3
494 
495  //Position of PMT4
499 
500  G4cout<<"PMT positions: "<<G4endl;
501  G4cout<<"PMT1 (mm) ("<<posPMT1x<<", "<<posPMT1y<<", "<<posPMT1z<<")"<<G4endl;
502  G4cout<<"PMT2 (mm) ("<<posPMT2x<<", "<<posPMT2y<<", "<<posPMT2z<<")"<<G4endl;
503  G4cout<<"PMT3 (mm) ("<<posPMT3x<<", "<<posPMT3y<<", "<<posPMT3z<<")"<<G4endl;
504  G4cout<<"PMT4 (mm) ("<<posPMT4x<<", "<<posPMT4y<<", "<<posPMT4z<<")"<<G4endl;
505 
506 }
507 
508 //The blurring parameters are given and can be changed in the inputParameter.txt file
510  char inputChar[256];
511  std::string inputLine;
512  G4String value[7];
513  std::string filename = "inputParameter.txt";
514  ifs.open(filename.c_str());
515  if(!ifs.good()){
516  G4cerr<<"File opening Error: Could not open "<<filename<<G4endl;
517  exit(0);
518  }
519  while(!ifs.eof()){
520  ifs.getline(inputChar,256);
521  inputLine = inputChar;
522  if(inputChar[0]!='#' && inputLine.length()!=0 ){
523  if( (std::string::size_type)inputLine.find("block_DeadTime:")!=std::string::npos){
524  std::istringstream tmpStream(inputLine);
525  tmpStream >> value[0] >> value[1] >> value[2];
526  block_DeadTime = atof(value[1].c_str());
527  if(value[2] != "ns"){
528  G4cerr<<" Dead time unit is not in nano seconds (ns), Make it in 'ns' "<<G4endl;
529  exit(0);
530  }
532  G4cout<<"Dead time of the detector: "<<block_DeadTime <<" ns."<<G4endl;
533  }
534  if( (std::string::size_type)inputLine.find("module_DeadTime:")!=std::string::npos){
535  std::istringstream tmpStream(inputLine);
536  tmpStream >> value[0] >> value[1] >> value[2];
537  module_DeadTime = atof(value[1].c_str());
538  if(value[2] != "ns"){
539  G4cerr<<" Dead time unit is not in nano seconds (ns), Make it in 'ns' "<<G4endl;
540  exit(0);
541  }
543  G4cout<<"Dead time of the module (axially multiplexed detectors): "<<module_DeadTime <<" ns."<<G4endl;
544  }
545  //
546  if( (std::string::size_type)inputLine.find("crystalResolutionMin:")!=std::string::npos){
547  std::istringstream tmpStream(inputLine);
548  tmpStream >> value[0] >> value[1];
549  crystalResolutionMin = atof(value[1].c_str());
550  G4cout<<"crystal Resolution (Min.): "<<crystalResolutionMin*100<< " %." <<G4endl;
551  }
552  if( (std::string::size_type)inputLine.find("crystalResolutionMax:")!=std::string::npos){
553  std::istringstream tmpStream(inputLine);
554  tmpStream >> value[0] >> value[1];
555  crystalResolutionMax = atof(value[1].c_str());
556  G4cout<<"crystal Resolution (Max.): "<<crystalResolutionMax*100<<" %"<<G4endl;
557  }
558 
559  //
560  if( (std::string::size_type)inputLine.find("fixedResolution:")!=std::string::npos){
561  std::istringstream tmpStream(inputLine);
562  tmpStream >> value[0] >> value[1];
563  if(value[1]=="true"){
564  fixedResolution = true;
566  G4cout<<"Fixed crystal resolution is used. "<<G4endl;
567  }
568  else {
569  fixedResolution = false;
570  //Store into a file if needed.
571  //std::string fname = "crystalDependentResolution.txt";
572  //std::ofstream outFname(fname.c_str());
573 
574  G4cout<<" \n Crystal dependent resolution is used. preparing look-up table .... "<<G4endl;
576  for(G4int i_blk = 0; i_blk < numberOfBlocks_total; i_blk++){
579  //store into a file
580  //outFname<<i_blk<<" "<<i_cry<<" "<<energyResolution_cryDependent[i_blk][i_cry]<<G4endl;
581  }
582  }
583  G4cout<<"Done. \n"<<G4endl;
584  //outFname.close();
585  }
586 
587  }
588  //
589 
590  if( (std::string::size_type)inputLine.find("crystalEnergyRef:")!=std::string::npos){
591  std::istringstream tmpStream(inputLine);
592  tmpStream >> value[0] >> value[1] >> value[2];
593  crystalEnergyRef = atof(value[1].c_str());
594  if(value[2] != "keV"){
595  G4cerr<<" The unit of reference energy is not in keV, Make it in 'keV' "<<G4endl;
596  exit(0);
597  }
599  G4cout<<"Energy of refernce: "<<crystalEnergyRef/keV<<" keV."<<G4endl;
600  }
601  if( (std::string::size_type)inputLine.find("crystalQuantumEfficiency:")!=std::string::npos){
602  std::istringstream tmpStream(inputLine);
603  tmpStream >> value[0] >> value[1];
604  crystalQuantumEfficiency = atof(value[1].c_str());
605  G4cout<<"Quantum Efficiency "<<crystalQuantumEfficiency*100<< " % "<<G4endl;
606  }
607  if( (std::string::size_type)inputLine.find("lowerThreshold:")!=std::string::npos){
608  std::istringstream tmpStream(inputLine);
609  tmpStream >> value[0] >> value[1] >> value[2];
610  lowerThreshold = atof(value[1].c_str());
611  if(value[2] != "keV"){
612  G4cerr<<" The unit of Lower energy threshold is not in keV, Make it in 'keV' "<<G4endl;
613  exit(0);
614  }
616  G4cout<<"Lower energy threshold: "<<lowerThreshold/keV<<" keV."<<G4endl;
617 
618  }
619  if( (std::string::size_type)inputLine.find("upperThreshold:")!=std::string::npos){
620  std::istringstream tmpStream(inputLine);
621  tmpStream >> value[0] >> value[1] >> value[2];
622  upperThreshold = atof(value[1].c_str());
623  if(value[2] != "keV"){
624  G4cerr<<" The unit of Upper energy threshold is not in keV, Make it in 'keV' "<<G4endl;
625  exit(0);
626  }
628  G4cout<<"Upper energy threshold: "<<upperThreshold/keV<<" keV."<<G4endl;
629 
630  }
631 
632  if( (std::string::size_type)inputLine.find("numberOfPixel_2D_Pixel:")!=std::string::npos){
633  std::istringstream tmpStream(inputLine);
634  tmpStream >> value[0] >> value[1] >> value[2];
635  numberOfPixel_axial = atof(value[1].c_str());
636  numberOfPixel_tan = atof(value[2].c_str());
637  G4cout<<"Number of pixels for a 2D position histogram of the response: "<<numberOfPixel_tan<<" x "<< numberOfPixel_axial <<G4endl;
638  }
639 
640  //
641  if( (std::string::size_type)inputLine.find("TypeOfOutput:")!=std::string::npos){
642  std::istringstream tmpStream(inputLine);
643  tmpStream >> value[0] >> value[1];
644  if(value[1]=="singlesOutput"){
645  getSinglesData = true;
646  G4cout<<"Single mode output enabled. "<<G4endl;
647  }
648  else if(value[1]=="coincidenceOutput") {
649  getCoincidenceData = true;
650  G4cout<<"Coicidence mode output enabled. "<<G4endl;
651  }
652 
653  }
654 
655  }
656  }
657  ifs.close();
658 }
659 
660 //The following function reads the reflector pattern for each layer.
661 //Each layer has different patterns along the tangetial and axial positions.
662 //For defualt reflector pattern, see https://link.springer.com/article/10.1007/s12194-013-0231-4
663 //The patter of the reflectors can be changed in the inputParameter.txt file
664 //The pattern is given as 0 and 1. If there is reflector the value is 1 and if there is no reflector, the value is 0.
665 
667  G4cout<<" Reflector pattern is being read "<<G4endl;
668  //
669  std::vector<std::string> stringReflectorValue;
670  //
671  char inputChar[256];
672  std::string inputLine;
673 
674  //open inputParameter.txt to read reflector pattern.
675  std::string filename = "inputParameter.txt";
676 
677  G4String refValue;
678 
679  ifs.open(filename.c_str());
680  if(!ifs.good()){
681  G4cerr<<"File opening Error: Could not open "<<filename<<G4endl;
682  exit(0);
683  }
684  while(!ifs.eof()){
685  ifs.getline(inputChar,256);
686  inputLine = inputChar;
687 
688  //The reflector patter in read from the inputparamter.txt file
689  if(inputChar[0]!='#' && inputLine.length()!=0 ){
690 
691  //Reflector patter for Layer1 in the tangential direction
692  if( (std::string::size_type)inputLine.find("reflectorLayer1_Tangential:")!=std::string::npos){
693  std::istringstream tmpStream(inputLine);
694  while(tmpStream >> refValue){
695  stringReflectorValue.push_back(refValue);
696  if(stringReflectorValue.size()>1){
697  G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
698  ireflectorLayer1_Tangential.push_back(tmp_value);
699  }
700  }
701  }
702  stringReflectorValue.clear();
703 
704  //Reflector patter for Layer1 in the axial direction
705  if( (std::string::size_type)inputLine.find("reflectorLayer1_Axial:")!=std::string::npos){
706  std::istringstream tmpStream(inputLine);
707  while(tmpStream >> refValue){
708  stringReflectorValue.push_back(refValue);
709  if(stringReflectorValue.size()>1){
710  G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
711  ireflectorLayer1_Axial.push_back(tmp_value);
712  }
713  }
714  }
715  stringReflectorValue.clear();
716 
717  //Reflector patter for Layer2 in the tangential direction
718  if( (std::string::size_type)inputLine.find("reflectorLayer2_Tangential:")!=std::string::npos){
719  std::istringstream tmpStream(inputLine);
720  while(tmpStream >> refValue){
721  stringReflectorValue.push_back(refValue);
722  if(stringReflectorValue.size()>1){
723  G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
724  ireflectorLayer2_Tangential.push_back(tmp_value);
725  }
726  }
727  }
728  stringReflectorValue.clear();
729 
730  //Reflector patter for Layer2 in the axial direction
731  if( (std::string::size_type)inputLine.find("reflectorLayer2_Axial:")!=std::string::npos){
732  std::istringstream tmpStream(inputLine);
733  while(tmpStream >> refValue){
734  stringReflectorValue.push_back(refValue);
735  if(stringReflectorValue.size()>1){
736  G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
737  ireflectorLayer2_Axial.push_back(tmp_value);
738  }
739  }
740  }
741  stringReflectorValue.clear();
742 
743  //Reflector patter for Layer3 in the tangential direction
744  if( (std::string::size_type)inputLine.find("reflectorLayer3_Tangential:")!=std::string::npos){
745  std::istringstream tmpStream(inputLine);
746  while(tmpStream >> refValue){
747  stringReflectorValue.push_back(refValue);
748  if(stringReflectorValue.size()>1){
749  G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
750  ireflectorLayer3_Tangential.push_back(tmp_value);
751  }
752  }
753  }
754  stringReflectorValue.clear();
755 
756  //Reflector patter for Layer3 in the axial direction
757  if( (std::string::size_type)inputLine.find("reflectorLayer3_Axial:")!=std::string::npos){
758  std::istringstream tmpStream(inputLine);
759  while(tmpStream >> refValue){
760  stringReflectorValue.push_back(refValue);
761  if(stringReflectorValue.size()>1){
762  G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
763  ireflectorLayer3_Axial.push_back(tmp_value);
764  }
765  }
766  }
767  stringReflectorValue.clear();
768 
769  //Reflector patter for Layer4 in the tangential direction
770  if( (std::string::size_type)inputLine.find("reflectorLayer4_Tangential:")!=std::string::npos){
771  std::istringstream tmpStream(inputLine);
772  while(tmpStream >> refValue){
773  stringReflectorValue.push_back(refValue);
774  if(stringReflectorValue.size()>1){
775  G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
776  ireflectorLayer4_Tangential.push_back(tmp_value);
777  }
778  }
779  }
780  stringReflectorValue.clear();
781 
782  //Reflector patter for Layer4 in the axial direction
783  if( (std::string::size_type)inputLine.find("reflectorLayer4_Axial:")!=std::string::npos){
784  std::istringstream tmpStream(inputLine);
785  while(tmpStream >> refValue){
786  stringReflectorValue.push_back(refValue);
787  if(stringReflectorValue.size()>1){
788  G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
789  ireflectorLayer4_Axial.push_back(tmp_value);
790  }
791  }
792  }
793  stringReflectorValue.clear();
794  }//#
795  }//while(eof)
796 
797 
798  //prepare Look up table for crystal identification.
799  G4cout<<"DOI look-up table is being prepared. "<<G4endl;
800  std::string outputFileName = "check_2Dposition.txt";// excuted only once
801  std::ofstream outFile(outputFileName.c_str());
802 
803  G4double crystalPositionY;
804  G4double crystalPositionZ;
806 
807  for(G4int i_DOI = 0; i_DOI<numberOfCrystal_DOI; i_DOI++){
808  //crystalPositionX=(i_DOI-((float)numberOfCrystal_DOI)/2 + 0.5)*(sizeOfCrystal_DOI + crystalGap_DOI); //Becuase only lateral distances are used
809  for(G4int i_axial=0; i_axial< numberOfCrystal_axial;i_axial++){
810  crystalPositionZ = (i_axial-((float)numberOfCrystal_axial)/2 + 0.5)*(sizeOfCrystal_axial + crystalGap_axial);
811  for(G4int i_tan=0; i_tan<numberOfCrystal_tangential;i_tan++){
812  crystalPositionY=(i_tan-((float)numberOfCrystal_tangential)/2 + 0.5)*(sizeOfCrystal_tangential + crystalGap_tangential);
813  AngerLogic(i_DOI, i_tan, i_axial, crystalPositionY, crystalPositionZ, 1, 0.5);
814  outFile<<i_DOI<<" "<<i_axial<<" "<<i_tan<<" "<<crystalID_in2D_posHist<<" "<<PositionAngerZ<<" "<<PositionAngerY<<G4endl;
816  }
817  }
818  }
819  ifs.close();
820 }
821 
822 
823 //Based on ideal photomultiplier tube (PMT) placement, the interaction position of the photon with the detector is calculated using Anger Logic method.
824 //The reflectors shifts the response by some distance so that the response can be projected into 2D position histogram.
825 //From this 2D position histogram, the new crystal ID (in 3D along the tangential (y), axial (z) and DOI (x)) (after Anger Logic method is applied) can be obtained.
826 //If the crystal ID after Anger method apllied is out of the give number of crystals (in 3D), then an error message is displayed and the event will be rejected.
827 void doiPETAnalysis::AngerLogic(G4int i_doi, G4int i_tan, G4int i_axial, G4double posCrystalY, G4double posCrystalZ, G4double Edep, G4double shiftDis)
828 {
829  //1z and 2z are at the same z distance; 3z and 4z are at the same z distance
830  //The signal (the energy deposition) is devided into the four PMTs depending on their lateral distances (in the axial and tangential directions) from the interaction position
831 
832  //The following is based on symetrical placment of the PMTs
833 
834  //Calculate the axial (z) distance from the position of interaction to each PMT
835  dist1z = std::fabs(posPMT1z - posCrystalZ);
836  dist2z = std::fabs(posPMT2z - posCrystalZ);
837  dist3z = std::fabs(posPMT3z - posCrystalZ);
838  dist4z = std::fabs(posPMT4z - posCrystalZ);
839 
840  //Calculate the resultant distance
841  //dist1z = dist2z, and dist3z = dist4z, so only take two of them or take the average
842  //distz = ((dist1z + dist2z) + (dist3z + dist4z))/2;
843  distz = dist1z + dist3z;
844 
845  //1y and 3y are at the same y distance; and 2y and 4y are at the same y distance
846  //Calculate the tangential (y) distance from the position of interaction to each PMT
847  dist1y = std::fabs(posPMT1y - posCrystalY);
848  dist2y = std::fabs(posPMT2y - posCrystalY);
849  dist3y = std::fabs(posPMT3y - posCrystalY);
850  dist4y = std::fabs(posPMT4y - posCrystalY);
851 
852  //Calculate the resultant distance
853  //dist1y = dist3y, and dist2y = dist4y, so only take two of them or take the average
854  //disty = ((dist1y + dist3y) + (dist2y+dist4y))/2;
855  disty = dist1y + dist2y;
856 
857  //signalPMT1z = signalPMT2z, and signalPMT3z = signalPMT4z
858  signalPMT1z = Edep * dist3z/(dist1z + dist3z);
859  signalPMT3z = Edep * dist1z/(dist1z + dist3z);
860 
861  signalPMT2z = Edep * dist4z/(dist2z + dist4z);
862  signalPMT4z = Edep * dist2z/(dist2z + dist4z);
863 
864 
865  //signalPMT1y = signalPMT3y, and signalPMT2y = signalPMT4y
866  signalPMT1y = Edep * dist2y/(dist1y + dist2y);
867  signalPMT2y = Edep * dist1y/(dist1y + dist2y);
868 
869  signalPMT3y = Edep * dist4y/(dist3y + dist4y);
870  signalPMT4y = Edep * dist3y/(dist3y + dist4y);
871 
872  //Calculate the signal on each PMT from the 'component' signal
877 
878 
883 
884 
885  //Position of interaction is calculated based on Anger logic method.
886  //To get the position by Anger calculation, the result should be multiplied by the dimenion of the total distance.
889 
890  //Find local position in the interacting cystal to estimate the shift due to reflector
891  //double localPosX = posCrystalX - (i_doi-((G4double)numberOfCrystal_DOI)/2 + 0.5)*(sizeOfCrystal_DOI + crystalGap_DOI);
893  G4double localPosZ = posCrystalZ - (G4double)(i_axial-((G4double)numberOfCrystal_axial)/2 + 0.5)*(sizeOfCrystal_axial + crystalGap_axial);
894 
895 
897  G4double crystalPitch_axial = sizeOfCrystal_axial + crystalGap_axial;
898 
899  //For detectors with reflector insertion (light sharing), the response is shifted depending on the reflector patter.
900  //Here, it is assumed that the shift of the response is equal to half of the distance from the interaction position to the airgap in the lateral (transversal direction)
901 
902  //If reflector is only in the left side of the crystal, then response shift is to the right side (away from the reflector).
903  //If reflector is only in the right side of the crystal, then response shift is to the left side (away from the reflector).
904 
905  //Response shift for 1st Layer
906  if(i_doi == 0){
907  //If reflector is only in one (left) side of the crystal, then response shifts to the right side (away from the reflector)
908  if(ireflectorLayer1_Tangential[i_tan] == 1 && ireflectorLayer1_Tangential[i_tan + 1] == 0) PositionAngerY += (crystalPitch_tan/2 - localPosY)*shiftDis;
909 
910  //If reflector is only in one (right) side of the crystal, then response shifts to the left side (away from the reflector)
911  if(ireflectorLayer1_Tangential[i_tan] == 0 && ireflectorLayer1_Tangential[i_tan + 1] == 1) PositionAngerY -= (crystalPitch_tan/2 - localPosY)*shiftDis;
912 
913  if(ireflectorLayer1_Axial[i_axial] == 1 && ireflectorLayer1_Axial [i_axial + 1] == 0) PositionAngerZ += (crystalPitch_axial/2 - localPosZ)*shiftDis;
914  if(ireflectorLayer1_Axial[i_axial] == 0 && ireflectorLayer1_Axial [i_axial + 1] == 1) PositionAngerZ -= (crystalPitch_axial/2 - localPosZ)*shiftDis;
915  }
916  if(i_doi == 1){ //Response shift for 2nd Layer
917  if(ireflectorLayer2_Tangential[i_tan] == 1 && ireflectorLayer2_Tangential[i_tan + 1] == 0) PositionAngerY += (crystalPitch_tan/2 - localPosY)*shiftDis;
918  if(ireflectorLayer2_Tangential[i_tan] == 0 && ireflectorLayer2_Tangential[i_tan + 1] == 1) PositionAngerY -= (crystalPitch_tan/2 - localPosY)*shiftDis;
919 
920  if(ireflectorLayer2_Axial[i_axial] == 1 && ireflectorLayer2_Axial [i_axial + 1] == 0) PositionAngerZ += (crystalPitch_axial/2 - localPosZ)*shiftDis;
921  if(ireflectorLayer2_Axial[i_axial] == 0 && ireflectorLayer2_Axial [i_axial + 1] == 1) PositionAngerZ -= (crystalPitch_axial/2 - localPosZ)*shiftDis;
922  }
923  if(i_doi == 2){ //Response shift for 3rd Layer
924  if(ireflectorLayer3_Tangential[i_tan] == 1 && ireflectorLayer3_Tangential[i_tan + 1] == 0) PositionAngerY += (crystalPitch_tan/2 - localPosY)*shiftDis;
925  if(ireflectorLayer3_Tangential[i_tan] == 0 && ireflectorLayer3_Tangential[i_tan + 1] == 1) PositionAngerY -= (crystalPitch_tan/2 - localPosY)*shiftDis;
926 
927  if(ireflectorLayer3_Axial[i_axial] == 1 && ireflectorLayer3_Axial [i_axial + 1] == 0) PositionAngerZ += (crystalPitch_axial/2 - localPosZ)*shiftDis;
928  if(ireflectorLayer3_Axial[i_axial] == 0 && ireflectorLayer3_Axial [i_axial + 1] == 1) PositionAngerZ -= (crystalPitch_axial/2 - localPosZ)*shiftDis;
929  }
930  if(i_doi == 3){ //Response shift for 4th Layer
931  if(ireflectorLayer4_Tangential[i_tan] == 1 && ireflectorLayer4_Tangential[i_tan + 1] == 0) PositionAngerY += (crystalPitch_tan/2 - localPosY)*shiftDis;
932  if(ireflectorLayer4_Tangential[i_tan] == 0 && ireflectorLayer4_Tangential[i_tan + 1] == 1) PositionAngerY -= (crystalPitch_tan/2 - localPosY)*shiftDis;
933 
934  if(ireflectorLayer4_Axial[i_axial] == 1 && ireflectorLayer4_Axial [i_axial + 1] == 0) PositionAngerZ += (crystalPitch_axial/2 - localPosZ)*shiftDis;
935  if(ireflectorLayer4_Axial[i_axial] == 0 && ireflectorLayer4_Axial [i_axial + 1] == 1) PositionAngerZ -= (crystalPitch_axial/2 - localPosZ)*shiftDis;
936  }
937 
938  //The main purpose of shifting the response is to be able to project the response of all the crytal elements into a 2D position histogram so that we can identify the DOI layer
939  //by comparing with a look-up-table which is prepared based on the reflector insertion.
940 
941  //The crystal ID in 2D position histogram along the axial (z) direction. It can have values of: 0, 1, .. , 31, in 32x32 pixel position histogram
942  crystalID_in2D_posHist_axial = (G4int)((G4double)PositionAngerZ/(G4double)(crystalPitch_axial*0.5) + (G4double)(numberOfPixel_axial - 1.0)*0.5 + 0.5);//0.5 is added for round off
943 
944  //The crystal ID in 2D position histogram along the tangential (y) direction. It can have values of: 0, 1, .. , 31, in 32x32 pixel position histogram
945  crystalID_in2D_posHist_tan = (G4int)((G4double)PositionAngerY/(G4double)(crystalPitch_tan*0.5) + (G4double)(numberOfPixel_tan - 1.0)*0.5 + 0.5);//y_ID
946 
947  //continuous crystal ID in the 2D position histogram. It will be from 0 to 1023 (in the case of 16x16x4 crystal array).
949 
950 
951  //Now, lets find the crystal ID in 3D after applying Anger Logic calculation. NOTE that its value can be the same as the original crystal ID or not.
952 
953  //Crystal ID along the tangential diraction after Anger Logic calculation
955 
956  //Crystal ID along the axial diraction after Anger Logic calculation
958 
962 
963  //If the crsytal ID is beyond the given the number of crystal in the detector, the following is excecuted and the event will be rejected
966  return;
967  }
968 
970 }
971 
973  crystalID_tangential = i_tan;
974  crystalID_axial = i_axial;
975  DOI_ID = i_doi;
976 }