Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-31 09:21:58

0001 //
0002 // ********************************************************************
0003 // * License and Disclaimer                                           *
0004 // *                                                                  *
0005 // * The  Geant4 software  is  copyright of the Copyright Holders  of *
0006 // * the Geant4 Collaboration.  It is provided  under  the terms  and *
0007 // * conditions of the Geant4 Software License,  included in the file *
0008 // * LICENSE and available at  http://cern.ch/geant4/license .  These *
0009 // * include a list of copyright holders.                             *
0010 // *                                                                  *
0011 // * Neither the authors of this software system, nor their employing *
0012 // * institutes,nor the agencies providing financial support for this *
0013 // * work  make  any representation or  warranty, express or implied, *
0014 // * regarding  this  software system or assume any liability for its *
0015 // * use.  Please see the license in the file  LICENSE  and URL above *
0016 // * for the full disclaimer and the limitation of liability.         *
0017 // *                                                                  *
0018 // * This  code  implementation is the result of  the  scientific and *
0019 // * technical work of the GEANT4 collaboration.                      *
0020 // * By using,  copying,  modifying or  distributing the software (or *
0021 // * any work based  on the software)  you  agree  to acknowledge its *
0022 // * use  in  resulting  scientific  publications,  and indicate your *
0023 // * acceptance of all terms of the Geant4 Software license.          *
0024 // ********************************************************************
0025 
0026 //GEANT4 - Depth-of-Interaction enabled Positron emission tomography (PET) advanced example 
0027 
0028 //Authors and contributors
0029 
0030 // Author list to be updated, with names of co-authors and contributors from National Institute of Radiological Sciences (NIRS)
0031 
0032 // Abdella M. Ahmed (1, 2), Andrew Chacon (1, 2), Harley Rutherford (1, 2),
0033 // Hideaki Tashima (3), Go Akamatsu (3), Akram Mohammadi (3), Eiji Yoshida (3), Taiga Yamaya (3)
0034 // Susanna Guatelli (2), and Mitra Safavi-Naeini (1, 2)
0035 
0036 // (1) Australian Nuclear Science and Technology Organisation, Australia
0037 // (2) University of Wollongong, Australia
0038 // (3) National Institute of Radiological Sciences, Japan
0039 
0040 //Implemetation of the doiPETAnalysis.cc class
0041 //This implementation mimics readout (or digitizer) of the PET scanner. To mimic realistic PET detector, the signals are blurred. Blurring 
0042 //parameters are given in inputParameters.txt file. Crystal based energy resolution and quantum efficiency has been applied. Deadtime (on 
0043 //each detector block and axially multiplexed detector) is also applied before the event is rejected by the energy window. The units for 
0044 //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
0045 //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
0046 //(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)
0047 //distance from the PMTs. Light sharing method (reflector based) DOI identification method has been used. If the crystal ID is out of bound, 
0048 //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 
0049 //processed into coinsidence list-mode data. As, an option, binary output method is also given.  
0050 //Explanation is given for the methods provided. 
0051 
0052 
0053 #include "doiPETAnalysis.hh"
0054 #include "G4SystemOfUnits.hh"
0055 #include "G4PhysicalConstants.hh"
0056 #include <iomanip>
0057 #include "Randomize.hh"
0058 #include "G4SPSRandomGenerator.hh"
0059 #include "doiPETAnalysisMessenger.hh"
0060 #include "G4UnitsTable.hh"
0061 #include "globals.hh"
0062 
0063 doiPETAnalysis* doiPETAnalysis::instance=0;
0064 
0065 /////////// Constructor /////////////////////////////////////////////
0066 doiPETAnalysis::doiPETAnalysis()
0067 {
0068     factoryOn = false; //G4ROOT
0069     // Initialization ntuple
0070     for (G4int k=0; k<MaxNtCol; k++) fNtColId[k] = 0;
0071 
0072     fAnalysisMessenger = new doiPETAnalysisMessenger(this);
0073 
0074     //Set energy window
0075     lowerThreshold = 400*keV;
0076     upperThreshold = 600*keV;
0077     triggerEnergy = 50*eV;
0078     
0079     //give default initial activity. Activity strength is changed in the .mac file
0080     InitialActivity = 1000000*becquerel;
0081 
0082     //In NEMA NU2, all test is done with F-18
0083     halfLife = 109.771*60 * s;//The halfLife of a given isotope can be changed via the run.mac file
0084 
0085     //
0086     totalTime = 0 * s;
0087     prev_totalTime = 0 * s;
0088     prev_eventID = 0;
0089 
0090     //
0091     //Initialize crystal ID
0092     crystalIDNew_tan = -1;
0093     crystalIDNew_axial = -1;
0094     crystalIDNew_DOI = -1;
0095 
0096     //
0097     scatterIndex = 0;
0098 
0099     //
0100     numberOfPixel_tan = 2*numberOfCrystal_tangential; //32;
0101     numberOfPixel_axial = 2*numberOfCrystal_axial; //32;
0102 
0103     //Default value for deadtime.
0104     block_DeadTime = 256*ns;
0105     module_DeadTime = 0*ns;
0106     //
0107 
0108     //Crystal blurring parameters. One detector has 1024 crystals. All the crystals have different energy resolution. 
0109     //So, a range of energy resolution is applied between minumun and maximum values. 
0110     //The energy resolution can be set in the inputParameter.txt file
0111     crystalResolutionMin = 0.13;//13%
0112     crystalResolutionMax = 0.17;//17%
0113 
0114     crystalEnergyRef = 511 * keV;//Energy of reference in which the energy resolution of the crystal is computed
0115 
0116     //The quantum efficiency models the probability for the event to be detected by the photo-detector.
0117     //The quantum efficiency can be set in the inputParameter.txt file
0118     crystalQuantumEfficiency = 1;//100% 
0119     //
0120 
0121     //intialize deadtime for blocks and modules
0122     numberOfBlocks_total = numberOfRings * numberOfDetector_perRing; 
0123     blockTime = new double[numberOfBlocks_total];//for each individual block.
0124     moduleTime = new double[numberOfBlocks_total];//for axially multiplexed detectors.
0125 
0126     //Initialize the deadtime for each detector and axially multiplexed detector (also called modules)
0127     for(G4int i = 0; i<numberOfBlocks_total; i++){
0128         blockTime [i] = 0.0;
0129         moduleTime [i] = 0.0;
0130     }
0131 
0132     //Initialize type of output. The default output is single events
0133     getSinglesData  = false; //default value 
0134     getCoincidenceData = false;
0135 
0136     ApplyAngerLogic = true;
0137     isDOIlookUpTablePrepared = false;
0138 
0139     numberOfHit = 0;
0140 
0141     //This value is based on the assumption that the shift of the response due to the reflector is half distance from the interaction position to the air gap.
0142     shiftCoeff = 0.5;
0143 
0144     //
0145     PMTblurring_tan = 0.0;
0146     PMTblurring_axial = 0.0;
0147 }
0148 ////////// Destructor ///////////////////////////////////////////////
0149 doiPETAnalysis::~doiPETAnalysis()
0150 {
0151     delete fAnalysisMessenger;
0152     delete [] blockTime;
0153     delete [] moduleTime;
0154 }
0155 
0156 ////////// GetInstance /////////////////////////////////////////////
0157 doiPETAnalysis* doiPETAnalysis::GetInstance()
0158 {
0159     if(instance==0) instance = new doiPETAnalysis();
0160     return instance;
0161 }
0162 void doiPETAnalysis::Delete()
0163 {
0164     delete instance;
0165 }
0166 
0167 //If there is energy deposition in the phantom by the photon, the scatter index is 1, otherwise it is 0
0168 //Use this for checking
0169 void doiPETAnalysis::SetScatterIndexInPhantom(G4int sci){
0170     scatterIndex = sci;
0171 }
0172 
0173 //Get the source position if the process is annihilation.
0174 //Use this for checking
0175 void doiPETAnalysis::SetSourcePosition(G4ThreeVector spos){
0176     spositionX = spos.x();
0177     spositionY = spos.y();
0178     spositionZ = spos.z();
0179 }
0180 
0181 
0182 //Set the event ID
0183 //Use this for checking. eventID should not be used to sort coincidence events if realistic simulation is done
0184 void doiPETAnalysis::SetEventID(G4int evID){
0185     eventID = evID;
0186 }
0187 
0188 //
0189 
0190 void doiPETAnalysis::GetSizeOfDetector(G4double detSizeDoi, G4double detSizeTan, G4double detSizeAxial){
0191     sizeOfDetector_DOI = detSizeDoi;
0192     sizeOfDetector_axial = detSizeTan;
0193     sizeOfDetector_tangential = detSizeAxial;
0194 }
0195 
0196 //
0197 void doiPETAnalysis::SetActivity(G4double newActivity){
0198     InitialActivity = newActivity;
0199     G4cout<<"Initial activity: "<<InitialActivity/becquerel<<" Bq."<<G4endl;
0200 }
0201 void doiPETAnalysis::SetIsotopeHalfLife(G4double newHalfLife){
0202     halfLife = newHalfLife;
0203     G4cout<<"Half life of the isotope "<<halfLife/s<<" sec."<<G4endl;
0204 }
0205 
0206 //The time is based on random time intervals between events. See https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3267383/
0207 //This time mimics acquisition time of a PET scanner for a given number of particles. 
0208 void doiPETAnalysis::CalulateAcquisitionTime(){
0209     //Calculate the strength of activity at t=totaltime using decay equation 
0210     activityNow = InitialActivity * std::exp(-((0.693147/halfLife)*totalTime)); //ln(2) = 0.693147181
0211 
0212     //Activity based time interval. 
0213     timeInterval = -std::log(G4UniformRand())*(1./activityNow);
0214     totalTime = timeInterval+prev_totalTime;
0215     prev_totalTime = totalTime; 
0216 }
0217 
0218 //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
0219 G4double doiPETAnalysis::QuantumEffifciency(G4double edep, G4int blkID, G4int cysID)
0220 {
0221     if(fixedResolution){
0222         crystalResolution = energyResolution_fixed;
0223     }
0224     else{
0225         crystalResolution = energyResolution_cryDependent[blkID][cysID];
0226     }
0227     crystalCoeff = crystalResolution * std::sqrt(crystalEnergyRef);
0228 
0229     G4double QE = G4UniformRand();
0230 
0231     //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
0232     if(QE <= crystalQuantumEfficiency)
0233     {
0234         edep_AfterCrystalBlurring = G4RandGauss::shoot(edep,crystalCoeff*std::sqrt(edep)/2.35);
0235     }
0236     else {
0237         //not detected by the photodetector, eventhough there was an interaction
0238         edep_AfterCrystalBlurring = 0 *keV;
0239     }
0240     return edep_AfterCrystalBlurring;
0241 }
0242 
0243 ///////// ReadOut ///////////////////////////////////
0244 
0245 void doiPETAnalysis::ReadOut(G4int blkID, G4int cryID, G4double interTime, G4double timeAnnih, G4ThreeVector interPos, G4double edep)
0246 {
0247     blockID = blkID;
0248     crystalID = cryID;
0249     interactionTime = interTime;
0250     time_annihil = timeAnnih;
0251     interactionPos = interPos;
0252     totalEdep = edep;
0253 
0254     //Get the time of flight. This is the duration from the annihilation process to the detection of the photons by the scintillator. 
0255     time_tof = interactionTime - time_annihil;
0256 
0257     //time of the event when detected (timerTag)
0258     timeStamp = totalTime + time_tof;
0259 
0260     //triggerEnergy is the energy deposited in the detector below which the detector is insensitive to any interaction.
0261     if(totalEdep<triggerEnergy)return;
0262 
0263     //************************************** Apply dead-time ********************************************//
0264     //Apply paralizable dead-time in the block beofore events are rejected by the energy window
0265     if(std::fabs(timeStamp - blockTime[blockID]) >=  block_DeadTime){ //If true, the event is accepted
0266         blockTime[blockID] = timeStamp;
0267     }
0268     else {
0269         //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.
0270         blockTime[blockID] = timeStamp;
0271 
0272         //the event is then rejected
0273         //continue;
0274         return;
0275     }
0276 
0277     //Apply Non-paralyzable dead-time on axially multiplexed detectors (4 detectors are arranged axailly)
0278     //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
0279     if(std::fabs(timeStamp - moduleTime[blockID]) > module_DeadTime){
0280 
0281         //The following finds the block id's of four blocks which are arranged axially
0282         for (G4int r_ring = 0; r_ring < numberOfRings; r_ring++){
0283             if (blockID >= r_ring*numberOfDetector_perRing && blockID <(r_ring + 1)*numberOfDetector_perRing){
0284                 for (G4int m_module = 0; m_module < numberOfRings; m_module++){
0285 
0286                     //Set the time of the module (four blocks) the same
0287                     moduleTime[blockID + (m_module - r_ring)*numberOfDetector_perRing] = timeStamp;
0288                 }
0289             }
0290         }
0291     }
0292     else return;
0293 
0294 
0295     /////////////////////////   Write qualified single events based the energy deposition in the detector   ///////////
0296 
0297     if(totalEdep>lowerThreshold && totalEdep<upperThreshold ){
0298 
0299         //identifiy the layer
0300         DOI_ID =  G4int(crystalID/(numberOfCrystal_tangential * numberOfCrystal_axial));
0301 
0302         //identify the crystal id for each Layer. Now, crystalID_2D can take  0,1, ... numberOfCrystal_tangential x numberOfCrystal_axial 
0303         crystalID_2D = crystalID - (DOI_ID*numberOfCrystal_tangential * numberOfCrystal_axial);
0304 
0305         //identify the crystal ID in the tangential and axial direction
0306         crystalID_axial = crystalID_2D/numberOfCrystal_axial;
0307         crystalID_tangential = crystalID_2D%numberOfCrystal_tangential;
0308 
0309         intPosX = interactionPos.x();
0310         intPosY = interactionPos.y();
0311         intPosZ = interactionPos.z();
0312 
0313 
0314         if(ApplyAngerLogic){
0315             //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. 
0316             AngerLogic(intPosX, intPosY, intPosZ, totalEdep, shiftCoeff, isDOIlookUpTablePrepared);//
0317         }
0318 
0319         //Single event output. Coincidence events can then be made using the single events.
0320         if(getSinglesData) WriteOutput();
0321 
0322         //Coincidence output
0323         if(getCoincidenceData){
0324             eventID_coin.push_back(eventID);
0325             blockID_coin.push_back(blockID);
0326             cryID_axial_coin.push_back(crystalID_axial);
0327             cryID_tan_coin.push_back(crystalID_tangential);
0328             edep_coin.push_back(totalEdep);
0329             cryDOI_coin.push_back(DOI_ID);
0330             time_coin.push_back(timeStamp);
0331 
0332             numberOfHit++;
0333 
0334             if(numberOfHit == 2){ //two events within the energy window are qualified.
0335                 WriteOutput();
0336                 ResetNumberOfHits();
0337             }
0338         }
0339     }
0340 }
0341 
0342 
0343 
0344 ////////// Clear ///////////////////////////////////////////////////
0345 void doiPETAnalysis::ResetNumberOfHits()
0346 {
0347     numberOfHit = 0;
0348     eventID_coin.clear();
0349     blockID_coin.clear();
0350     cryID_axial_coin.clear();
0351     cryID_tan_coin.clear();
0352     edep_coin.clear();
0353     cryDOI_coin.clear();
0354     time_coin.clear();
0355 
0356 }
0357 
0358 //
0359 void doiPETAnalysis::Open(G4String fileName)
0360 {
0361     if(getSinglesData){
0362         asciiFileName = fileName + "Singles.data";
0363         rootFileName = fileName+"Singles.root";
0364     }
0365     if(getCoincidenceData){
0366         asciiFileName = fileName + "Coincidence.data";
0367         rootFileName = fileName+"Coincidence.root";
0368     }
0369 
0370     ofs.open(asciiFileName.c_str());
0371     if(!ofs.is_open()){
0372         G4cerr<<"=== \n File opening Error to write the output ===="<<G4endl;
0373         exit(0);
0374     }
0375 
0376 }
0377 
0378 void doiPETAnalysis::WriteOutput(){
0379     if(getSinglesData){
0380         ofs<<eventID<<" "<<blockID<<" "<<std::setprecision(17)<<timeStamp/s<<" "<<std::setprecision(7)<<totalEdep/keV<<" "<<intPosX<<" "<<intPosY<<" "<<intPosZ<<" "<<spositionX<<" "<<spositionY<<" "<<spositionZ<<G4endl;
0381         //ofs<<eventID<<" "<<blockID<<" "<<crystalID_axial<<" "<<crystalID_tangential<<" "<<DOI_ID<<" "<<std::setprecision(17)<<timeStamp/s<<" "<<std::setprecision(7)<<totalEdep/keV<<G4endl;
0382 
0383     }
0384     if(getCoincidenceData){
0385         //2 singles will qualify to be in coincidence within the energy window.
0386         for(G4int i=0; i<2; i++){
0387 
0388             //First Single
0389             if(i==0){
0390                 eventID0                = eventID_coin[0];
0391                 blockID0                = blockID_coin[0];
0392                 crystalID_axial0        = cryID_axial_coin[0];
0393                 crystalID_tangential0   = cryID_tan_coin[0];
0394                 DOI_ID0                 = cryDOI_coin[0];
0395                 timeStamp0              = time_coin[0];
0396                 totalEdep0              = edep_coin[0];
0397             }
0398             if(i==1){
0399                 //Second Single
0400                 eventID1                = eventID_coin[1];
0401                 blockID1                = blockID_coin[1];
0402                 crystalID_axial1        = cryID_axial_coin[1];
0403                 crystalID_tangential1   = cryID_tan_coin[1];
0404                 DOI_ID1                 = cryDOI_coin[1];
0405                 timeStamp1              = time_coin[1];
0406                 totalEdep1              = edep_coin[1];
0407             }
0408         }
0409 
0410         ofs<<eventID0<<" "<<blockID0<<" "<<crystalID_axial0<<" "<<crystalID_tangential0<<" "<<DOI_ID0<<" "<<std::setprecision(17)<<timeStamp0/s<<" "<<std::setprecision(7)<<totalEdep0/keV<<" "
0411             <<eventID1<<" "<<blockID1<<" "<<crystalID_axial1<<" "<<crystalID_tangential1<<" "<<DOI_ID1<<" "<<std::setprecision(17)<<timeStamp1/s<<" "<<std::setprecision(7)<<totalEdep1/keV<<" "
0412             <<spositionX<<" "<<spositionY<<" "<<spositionZ<<G4endl;
0413 
0414     }
0415 #ifdef ANALYSIS_USE
0416     FillListModeEvent();
0417 #endif
0418 
0419 }
0420 
0421 //
0422 ///////// Close /////////////////////////////////////////////////////
0423 void doiPETAnalysis::Close()
0424 {
0425     //close ascii file
0426     ofs.close();
0427 
0428 }
0429 
0430 //Place the photomultiplier tube (PMT) at each corner of the detector. The positions of the PMT is with respect to the axis of the detector block
0431 //All the PMTs are placed at the same doi (x) position (at +sizeOfDetector_DOI/2 which is at the top of the detector). 
0432 
0433 //The PMT is placed at each corner of the crystal block and is assumed to be an ideal PMT.
0434 //The signal (energy deposition) of each PMT depends on  the distance of the respective PMT from the interaction point
0435 void doiPETAnalysis::PMTPosition(){
0436 
0437     sizeOfDetector_DOI = (numberOfCrystal_DOI * sizeOfCrystal_DOI) + (numberOfCrystal_DOI - 1)*crystalGap_DOI;
0438     sizeOfDetector_axial = (numberOfCrystal_axial * sizeOfCrystal_axial) + (numberOfCrystal_axial - 1)*crystalGap_axial;
0439     sizeOfDetector_tangential = (numberOfCrystal_tangential * sizeOfCrystal_tangential) + (numberOfCrystal_tangential - 1)*crystalGap_tangential;
0440 
0441     //Position of PMT1. 
0442     posPMT1x = sizeOfDetector_DOI/2;//mm
0443     posPMT1y = -sizeOfDetector_tangential/2;
0444     posPMT1z = -sizeOfDetector_axial/2;
0445 
0446     //Position of PMT2
0447     posPMT2x = sizeOfDetector_DOI/2;
0448     posPMT2y = sizeOfDetector_tangential/2;
0449     posPMT2z = -sizeOfDetector_axial/2;
0450 
0451     //Position of PMT3 
0452     posPMT3x = sizeOfDetector_DOI/2;
0453     posPMT3y = -sizeOfDetector_tangential/2;
0454     posPMT3z = sizeOfDetector_axial/2;
0455 
0456     //Position of PMT4 
0457     posPMT4x = sizeOfDetector_DOI/2;
0458     posPMT4y = sizeOfDetector_tangential/2;
0459     posPMT4z = sizeOfDetector_axial/2;
0460 
0461     G4cout<<"PMT positions: "<<G4endl;
0462     G4cout<<"PMT1 (mm) ("<<posPMT1x<<", "<<posPMT1y<<", "<<posPMT1z<<")"<<G4endl;
0463     G4cout<<"PMT2 (mm) ("<<posPMT2x<<", "<<posPMT2y<<", "<<posPMT2z<<")"<<G4endl;
0464     G4cout<<"PMT3 (mm) ("<<posPMT3x<<", "<<posPMT3y<<", "<<posPMT3z<<")"<<G4endl;
0465     G4cout<<"PMT4 (mm) ("<<posPMT4x<<", "<<posPMT4y<<", "<<posPMT4z<<")"<<G4endl;
0466 
0467 }
0468 
0469 //The blurring parameters are given and can be changed in the inputParameter.txt file
0470 void doiPETAnalysis::BlurringParameters(){
0471     char inputChar[256];
0472     std::string inputLine;
0473     G4String value[7];
0474     std::string  filename = "inputParameter.txt";
0475     ifs.open(filename.c_str());
0476     if(!ifs.good()){
0477         G4cerr<<"File opening Error: Could not open "<<filename<<G4endl;
0478         exit(0);
0479     }
0480     while(!ifs.eof()){
0481         ifs.getline(inputChar,256);
0482         inputLine = inputChar;
0483         if(inputChar[0]!='#' && inputLine.length()!=0 ){
0484             if( (std::string::size_type)inputLine.find("block_DeadTime:")!=std::string::npos){
0485                 std::istringstream tmpStream(inputLine);
0486                 tmpStream >> value[0] >> value[1] >> value[2];
0487                 block_DeadTime = atof(value[1].c_str());
0488                 if(value[2] != "ns"){
0489                     G4cerr<<" Dead time unit is not in nano seconds (ns), Make it in 'ns' "<<G4endl; 
0490                     exit(0);
0491                 }
0492                 block_DeadTime = block_DeadTime*ns;
0493                 G4cout<<"Dead time of the detector: "<<block_DeadTime <<" ns."<<G4endl;
0494             }
0495             if( (std::string::size_type)inputLine.find("module_DeadTime:")!=std::string::npos){
0496                 std::istringstream tmpStream(inputLine);
0497                 tmpStream >> value[0] >> value[1] >> value[2];
0498                 module_DeadTime = atof(value[1].c_str());
0499                 if(value[2] != "ns"){
0500                     G4cerr<<" Dead time unit is not in nano seconds (ns), Make it in 'ns' "<<G4endl; 
0501                     exit(0);
0502                 }
0503                 module_DeadTime = module_DeadTime*ns;
0504                 G4cout<<"Dead time of the module (axially multiplexed detectors): "<<module_DeadTime <<" ns."<<G4endl;
0505             }
0506             //
0507             if( (std::string::size_type)inputLine.find("crystalResolutionMin:")!=std::string::npos){
0508                 std::istringstream tmpStream(inputLine);
0509                 tmpStream >> value[0] >> value[1];
0510                 crystalResolutionMin = atof(value[1].c_str());
0511                 G4cout<<"crystal Resolution (Min.): "<<crystalResolutionMin*100<< " %." <<G4endl;
0512             }
0513             if( (std::string::size_type)inputLine.find("crystalResolutionMax:")!=std::string::npos){
0514                 std::istringstream tmpStream(inputLine);
0515                 tmpStream >> value[0] >> value[1];
0516                 crystalResolutionMax = atof(value[1].c_str());
0517                 G4cout<<"crystal Resolution (Max.): "<<crystalResolutionMax*100<<" %"<<G4endl;
0518             }
0519 
0520             //
0521             if( (std::string::size_type)inputLine.find("fixedResolution:")!=std::string::npos){
0522                 std::istringstream tmpStream(inputLine);
0523                 tmpStream >> value[0] >> value[1];
0524                 if(value[1]=="true"){
0525                     fixedResolution = true;
0526                     energyResolution_fixed = (crystalResolutionMin + crystalResolutionMax)*0.5;
0527                     G4cout<<"Fixed crystal resolution is used. "<<G4endl;
0528                 }
0529                 else {
0530                     fixedResolution = false;
0531                     //Store into a file if needed.
0532                     std::string fname = "crystalDependentResolution.txt";
0533                     std::ofstream outFname(fname.c_str());
0534 
0535                     G4cout<<" \n Crystal dependent resolution is used. preparing look-up table .... "<<G4endl;
0536                     energyResolution_cryDependent.resize(numberOfBlocks_total,std::vector<G4double>(numberOfCrystal_tangential*numberOfCrystal_axial*numberOfCrystal_DOI,0));   
0537                     for(G4int i_blk = 0; i_blk < numberOfBlocks_total; i_blk++){
0538                         for(G4int i_cry = 0; i_cry < numberOfCrystal_tangential*numberOfCrystal_axial*numberOfCrystal_DOI; i_cry++){
0539                             energyResolution_cryDependent[i_blk][i_cry] = crystalResolutionMin + (crystalResolutionMax - crystalResolutionMin)*G4UniformRand();
0540                             //store into a file
0541                             outFname<<i_blk<<" "<<i_cry<<" "<<energyResolution_cryDependent[i_blk][i_cry]<<G4endl;
0542                         }
0543                     }
0544                     G4cout<<"Done. \n"<<G4endl;
0545                     outFname.close();
0546                 }
0547 
0548             }
0549             //
0550 
0551             if( (std::string::size_type)inputLine.find("crystalEnergyRef:")!=std::string::npos){
0552                 std::istringstream tmpStream(inputLine);
0553                 tmpStream >> value[0] >> value[1] >> value[2];
0554                 crystalEnergyRef = atof(value[1].c_str());
0555                 if(value[2] != "keV"){
0556                     G4cerr<<" The unit of reference energy is not in keV, Make it in 'keV' "<<G4endl; 
0557                     exit(0);
0558                 }
0559                 crystalEnergyRef = crystalEnergyRef*keV;
0560                 G4cout<<"Energy of refernce: "<<crystalEnergyRef/keV<<" keV."<<G4endl;
0561             }
0562             if( (std::string::size_type)inputLine.find("crystalQuantumEfficiency:")!=std::string::npos){
0563                 std::istringstream tmpStream(inputLine);
0564                 tmpStream >> value[0] >> value[1];
0565                 crystalQuantumEfficiency = atof(value[1].c_str());
0566                 G4cout<<"Quantum Efficiency "<<crystalQuantumEfficiency*100<< " % "<<G4endl;
0567             }
0568             if( (std::string::size_type)inputLine.find("lowerThreshold:")!=std::string::npos){
0569                 std::istringstream tmpStream(inputLine);
0570                 tmpStream >> value[0] >> value[1] >> value[2];
0571                 lowerThreshold = atof(value[1].c_str());
0572                 if(value[2] != "keV"){
0573                     G4cerr<<" The unit of Lower energy threshold is not in keV, Make it in 'keV' "<<G4endl; 
0574                     exit(0);
0575                 }
0576                 lowerThreshold = lowerThreshold*keV;
0577                 G4cout<<"Lower energy threshold: "<<lowerThreshold/keV<<" keV."<<G4endl;
0578 
0579             }
0580             if( (std::string::size_type)inputLine.find("upperThreshold:")!=std::string::npos){
0581                 std::istringstream tmpStream(inputLine);
0582                 tmpStream >> value[0] >> value[1] >> value[2];
0583                 upperThreshold = atof(value[1].c_str());
0584                 if(value[2] != "keV"){
0585                     G4cerr<<" The unit of Upper energy threshold is not in keV, Make it in 'keV' "<<G4endl; 
0586                     exit(0);
0587                 }
0588                 upperThreshold = upperThreshold*keV;
0589                 G4cout<<"Upper energy threshold: "<<upperThreshold/keV<<" keV."<<G4endl;
0590 
0591             }
0592 
0593             //
0594             if( (std::string::size_type)inputLine.find("triggerEnergy:")!=std::string::npos){
0595                 std::istringstream tmpStream(inputLine);
0596                 tmpStream >> value[0] >> value[1] >> value[2];
0597                 triggerEnergy = atof(value[1].c_str());
0598                 if(value[2] != "keV"){
0599                     G4cerr<<" The unit of Trigger energy threshold is not in keV, Make it in 'keV' "<<G4endl; 
0600                     exit(0);
0601                 }
0602                 triggerEnergy = triggerEnergy*keV;
0603                 G4cout<<"Trigger energy threshold: "<<triggerEnergy/keV<<" keV."<<G4endl;
0604 
0605             }
0606 
0607             //Option to apply AngerLogic
0608             if( (std::string::size_type)inputLine.find("ApplyAngerLogic:")!=std::string::npos){
0609                 std::istringstream tmpStream(inputLine);
0610                 tmpStream >> value[0] >> value[1];
0611                 if(value[1]=="true"){
0612                     ApplyAngerLogic = true;
0613                     G4cout<<"Angler Logic calculation is applied. "<<G4endl;
0614                 }
0615                 else if(value[1]=="false") {
0616                     ApplyAngerLogic = false;
0617                     G4cout<<"Angler Logic calculation is NOT applied. "<<G4endl;
0618                 }
0619                 else {
0620                     ApplyAngerLogic = true;
0621                     G4cout<<"Angler Logic calculation is applied (by defualt). "<<G4endl;
0622                 }
0623             }
0624 
0625             //PMT position calculation blurring at FWHM
0626             if( (std::string::size_type)inputLine.find("PMTblurring:")!=std::string::npos){
0627                 std::istringstream tmpStream(inputLine);
0628                 tmpStream >> value[0] >> value[1]>>value[2];
0629                 PMTblurring_tan = atof(value[1].c_str());
0630                 PMTblurring_axial = atof(value[2].c_str());
0631                 G4cout<<"PMTblurring position response blurring at FWHM (tan x axial) "<<PMTblurring_tan<<" x " <<PMTblurring_axial<<" mm2"<<G4endl;
0632             }
0633 
0634             //
0635             if( (std::string::size_type)inputLine.find("TypeOfOutput:")!=std::string::npos){
0636                 std::istringstream tmpStream(inputLine);
0637                 tmpStream >> value[0] >> value[1];
0638                 if(value[1]=="singlesOutput"){
0639                     getSinglesData = true;
0640                     G4cout<<"Single mode output enabled. "<<G4endl;
0641                 }
0642                 else if(value[1]=="coincidenceOutput") {
0643                     getCoincidenceData = true;
0644                     G4cout<<"Coicidence mode output enabled. "<<G4endl;
0645                 }
0646 
0647             }
0648 
0649         }
0650     }
0651     ifs.close();
0652 }
0653 
0654 //The following function reads the reflector pattern for each layer. Each layer has different patterns along the tangetial and axial positions.
0655 //For defualt reflector pattern, see https://link.springer.com/article/10.1007/s12194-013-0231-4
0656 //The patter of the reflectors can be changed in the inputParameter.txt file
0657 //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.
0658 
0659 void doiPETAnalysis::ReadReflectorPattern(){
0660     G4cout<<" Reflector pattern is being read "<<G4endl;
0661     //
0662     std::vector<std::string> stringReflectorValue;
0663     //
0664     char inputChar[256];
0665     std::string inputLine;
0666 
0667     //open inputParameter.txt to read reflector pattern. 
0668     std::string  filename = "inputParameter.txt";
0669 
0670     G4String refValue;
0671 
0672     ifs.open(filename.c_str());
0673     if(!ifs.good()){
0674         G4cerr<<"File opening Error: Could not open "<<filename<<G4endl;
0675         exit(0);
0676     }
0677     while(!ifs.eof()){
0678         ifs.getline(inputChar,256);
0679         inputLine = inputChar;
0680 
0681         //The reflector patter in read from the inputparamter.txt file          
0682         if(inputChar[0]!='#' && inputLine.length()!=0 ){
0683 
0684             //Reflector patter for Layer1 in the tangential direction
0685             if( (std::string::size_type)inputLine.find("reflectorLayer1_Tangential:")!=std::string::npos){
0686                 std::istringstream tmpStream(inputLine);
0687                 while(tmpStream >> refValue){
0688                     stringReflectorValue.push_back(refValue);
0689                     if(stringReflectorValue.size()>1){
0690                         G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
0691                         ireflectorLayer1_Tangential.push_back(tmp_value);
0692                     }
0693                 }
0694                 stringReflectorValue.clear();
0695             }
0696             
0697 
0698             //Reflector patter for Layer1 in the axial direction
0699             if( (std::string::size_type)inputLine.find("reflectorLayer1_Axial:")!=std::string::npos){
0700                 std::istringstream tmpStream(inputLine);
0701                 while(tmpStream >> refValue){
0702                     stringReflectorValue.push_back(refValue);
0703                     if(stringReflectorValue.size()>1){
0704                         G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
0705                         ireflectorLayer1_Axial.push_back(tmp_value);
0706                     }
0707                 }
0708                 stringReflectorValue.clear();
0709             }
0710 
0711             //Reflector patter for Layer2 in the tangential direction
0712             if( (std::string::size_type)inputLine.find("reflectorLayer2_Tangential:")!=std::string::npos){
0713                 std::istringstream tmpStream(inputLine);
0714                 while(tmpStream >> refValue){
0715                     stringReflectorValue.push_back(refValue);
0716                     if(stringReflectorValue.size()>1){
0717                         G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
0718                         ireflectorLayer2_Tangential.push_back(tmp_value);
0719                     }
0720                 }
0721                 stringReflectorValue.clear();
0722             }
0723 
0724             //Reflector patter for Layer2 in the axial direction
0725             if( (std::string::size_type)inputLine.find("reflectorLayer2_Axial:")!=std::string::npos){
0726                 std::istringstream tmpStream(inputLine);
0727                 while(tmpStream >> refValue){
0728                     stringReflectorValue.push_back(refValue);
0729                     if(stringReflectorValue.size()>1){
0730                         G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
0731                         ireflectorLayer2_Axial.push_back(tmp_value);
0732                     }
0733                 }
0734                 stringReflectorValue.clear();
0735             }
0736 
0737             //Reflector patter for Layer3 in the tangential direction
0738             if( (std::string::size_type)inputLine.find("reflectorLayer3_Tangential:")!=std::string::npos){
0739                 std::istringstream tmpStream(inputLine);
0740                 while(tmpStream >> refValue){
0741                     stringReflectorValue.push_back(refValue);
0742                     if(stringReflectorValue.size()>1){
0743                         G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
0744                         ireflectorLayer3_Tangential.push_back(tmp_value);
0745                     }
0746                 }
0747                 stringReflectorValue.clear();
0748             }
0749 
0750             //Reflector patter for Layer3 in the axial direction
0751             if( (std::string::size_type)inputLine.find("reflectorLayer3_Axial:")!=std::string::npos){
0752                 std::istringstream tmpStream(inputLine);
0753                 while(tmpStream >> refValue){
0754                     stringReflectorValue.push_back(refValue);
0755                     if(stringReflectorValue.size()>1){
0756                         G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
0757                         ireflectorLayer3_Axial.push_back(tmp_value);
0758                     }
0759                 }
0760                 stringReflectorValue.clear();
0761             }
0762 
0763             //Reflector patter for Layer4 in the tangential direction
0764             if( (std::string::size_type)inputLine.find("reflectorLayer4_Tangential:")!=std::string::npos){
0765                 std::istringstream tmpStream(inputLine);
0766                 while(tmpStream >> refValue){
0767                     stringReflectorValue.push_back(refValue);
0768                     if(stringReflectorValue.size()>1){
0769                         G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
0770                         ireflectorLayer4_Tangential.push_back(tmp_value);
0771                     }
0772                 }
0773                 stringReflectorValue.clear();
0774             }
0775 
0776             //Reflector patter for Layer4 in the axial direction
0777             if( (std::string::size_type)inputLine.find("reflectorLayer4_Axial:")!=std::string::npos){
0778                 std::istringstream tmpStream(inputLine);
0779                 while(tmpStream >> refValue){
0780                     stringReflectorValue.push_back(refValue);
0781                     if(stringReflectorValue.size()>1){
0782                         G4int tmp_value = atoi(stringReflectorValue[stringReflectorValue.size()-1].c_str());
0783                         ireflectorLayer4_Axial.push_back(tmp_value);
0784                     }
0785                 }
0786                 stringReflectorValue.clear();
0787             }
0788         }//#
0789     }//while(eof)
0790 
0791     //
0792     //for debug
0793     G4cout<<"\n========= Reflector Pattern ==========="<<G4endl;
0794     G4cout<<"Layer 1"<<G4endl;
0795     for(unsigned int i = 0; i<ireflectorLayer1_Tangential.size();i++){
0796         G4cout<<ireflectorLayer1_Tangential[i]<<" ";
0797     }G4cout<<G4endl;
0798     for(unsigned int i = 0; i<ireflectorLayer1_Axial.size();i++){
0799         G4cout<<ireflectorLayer1_Axial[i]<<" ";
0800     }G4cout<<G4endl;
0801     G4cout<<"Layer 2"<<G4endl;
0802     for(unsigned int i = 0; i<ireflectorLayer2_Tangential.size();i++){
0803         G4cout<<ireflectorLayer2_Tangential[i]<<" ";
0804     }G4cout<<G4endl;
0805     for(unsigned int i = 0; i<ireflectorLayer2_Axial.size();i++){
0806         G4cout<<ireflectorLayer2_Axial[i]<<" ";
0807     }G4cout<<G4endl;
0808     G4cout<<"Layer 3"<<G4endl;
0809     for(unsigned int i = 0; i<ireflectorLayer3_Tangential.size();i++){
0810         G4cout<<ireflectorLayer3_Tangential[i]<<" ";
0811     }G4cout<<G4endl;
0812     for(unsigned int i = 0; i<ireflectorLayer3_Axial.size();i++){
0813         G4cout<<ireflectorLayer3_Axial[i]<<" ";
0814     }G4cout<<G4endl;
0815     G4cout<<"Layer 4"<<G4endl;
0816     for(unsigned int i = 0; i<ireflectorLayer4_Tangential.size();i++){
0817         G4cout<<ireflectorLayer4_Tangential[i]<<" ";
0818     }G4cout<<G4endl;
0819     for(unsigned int i = 0; i<ireflectorLayer4_Axial.size();i++){
0820         G4cout<<ireflectorLayer4_Axial[i]<<" ";
0821     }G4cout<<G4endl;
0822     G4cout<<"========= Reflector Pattern Ended ===========\n"<<G4endl;
0823     
0824     //Read DOI look-up-table. This look-up-table is prepared based on the assumption that the interaction is occured at the center of the crystal.
0825     G4int index_doi = 0, doiID;
0826     doi_table.resize(numberOfCrystal_tangential*numberOfCrystal_axial*numberOfCrystal_DOI,0);
0827 
0828     std::string LUT_FileName = "look_up_table_DOI.txt";
0829     std::ifstream ifs_doiLUT;
0830     std::ofstream ofs_doiLUT;
0831     ifs_doiLUT.open(LUT_FileName.c_str());
0832     if(ifs_doiLUT.is_open()){
0833         G4cout<<" DOI Look-up table found and used: File name: "<<LUT_FileName<<G4endl;
0834         //Read from file
0835         while(ifs_doiLUT>>index_doi>>doiID && index_doi < int(doi_table.size())){
0836             doi_table[index_doi] = doiID;
0837         }
0838         if(index_doi==int(doi_table.size())){
0839             G4cout<<"!!!Warning: The DOI table index is greater than the total number of crystals."<<G4endl;
0840             PrepareDOILookUpTable(LUT_FileName);
0841         }
0842         isDOIlookUpTablePrepared = true; //
0843     }
0844     else
0845     {
0846         PrepareDOILookUpTable(LUT_FileName);
0847     }
0848     //Write into a file.
0849     ofs_doiLUT.open(LUT_FileName.c_str());
0850     if(!ofs_doiLUT.is_open()){
0851         G4cerr<<"Unable to open file to write doi_LUT"<<G4endl;
0852         exit(0);
0853     }
0854     for(G4int i=0;i<int(doi_table.size()); i++){
0855         ofs_doiLUT<<i<<"\t"<<doi_table[i]<<G4endl;
0856     }
0857     ifs_doiLUT.close();
0858 
0859 
0860     ifs.close();
0861 }
0862 void doiPETAnalysis::PrepareDOILookUpTable(G4String){
0863     isDOIlookUpTablePrepared = false; 
0864         G4cout<<"Preparing DOI look-up table... "<<G4endl;
0865         std::string outputFileName = "_check_2Dposition.txt";// excuted only once
0866         std::ofstream outFile(outputFileName.c_str());
0867 
0868         G4double crystalPositionX;
0869         G4double crystalPositionY;
0870         G4double crystalPositionZ;
0871         //doi_table.resize(numberOfCrystal_tangential*numberOfCrystal_axial*numberOfCrystal_DOI,0);
0872 
0873         for(G4int i_DOI = 0; i_DOI<numberOfCrystal_DOI; i_DOI++){
0874             crystalPositionX=(i_DOI-((float)numberOfCrystal_DOI)/2 + 0.5)*(sizeOfCrystal_DOI + crystalGap_DOI); //Becuase only lateral distances are used
0875             for(G4int i_axial=0; i_axial< numberOfCrystal_axial;i_axial++){
0876                 crystalPositionZ = (i_axial-((float)numberOfCrystal_axial)/2 + 0.5)*(sizeOfCrystal_axial + crystalGap_axial);
0877                 for(G4int i_tan=0; i_tan<numberOfCrystal_tangential;i_tan++){
0878                     crystalPositionY=(i_tan-((float)numberOfCrystal_tangential)/2 + 0.5)*(sizeOfCrystal_tangential + crystalGap_tangential);
0879                     AngerLogic(crystalPositionX, crystalPositionY, crystalPositionZ, 1, 0.5, isDOIlookUpTablePrepared);
0880                     outFile<<PositionAngerZ<<" "<<PositionAngerY<<G4endl;
0881                     doi_table[crystalID_in2D_posHist]=i_DOI;
0882 
0883                 }
0884             }
0885         }
0886         G4cout<<"done."<<G4endl;
0887         isDOIlookUpTablePrepared = true;
0888 }
0889 
0890 
0891 //Based on ideal photomultiplier tube (PMT) placement, the interaction position of the photon with the detector is calculated using Anger Logic method. 
0892 //The reflectors shifts the response by some distance so that the response can be projected into 2D position histogram. 
0893 //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.
0894 //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.
0895 void doiPETAnalysis::AngerLogic(G4double posCrystalX, G4double posCrystalY, G4double posCrystalZ, G4double Edep, G4double shiftDis, G4bool isDOI_LUT)
0896 { 
0897     G4double crystalPitch_DOI = sizeOfCrystal_DOI + crystalGap_DOI;
0898     G4double crystalPitch_tan = sizeOfCrystal_tangential + crystalGap_tangential;
0899     G4double crystalPitch_axial = sizeOfCrystal_axial + crystalGap_axial;
0900 
0901     //The crystal ID are calculated based on the center of mass 
0902     G4int i_doi = posCrystalX/crystalPitch_DOI + (float)numberOfCrystal_DOI*0.5;
0903     G4int i_tan = posCrystalY/crystalPitch_tan + (float)numberOfCrystal_tangential*0.5;
0904     G4int i_axial = posCrystalZ/crystalPitch_axial + (float)numberOfCrystal_axial*0.5;
0905 
0906     //position of interaction is shifted  the centre of the crystal. This is to use DOI-look up tables as the real scanner
0907     posCrystalX = (i_doi-((float)numberOfCrystal_DOI)/2 + 0.5)*crystalPitch_DOI;
0908     posCrystalY = (i_tan-((float)numberOfCrystal_tangential)/2 + 0.5)*crystalPitch_tan;
0909     posCrystalZ = (i_axial-((float)numberOfCrystal_axial)/2 + 0.5)*crystalPitch_axial;
0910 
0911     //1z and 2z are at the same z distance; 3z and 4z are at the same z distance
0912     //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
0913 
0914     //The following is based on symetrical placment of the PMTs 
0915 
0916     //Calculate the axial (z) distance from the position of interaction to each PMT
0917     dist1z = std::fabs(posPMT1z - posCrystalZ);
0918     dist2z = std::fabs(posPMT2z - posCrystalZ);
0919     dist3z = std::fabs(posPMT3z - posCrystalZ);
0920     dist4z = std::fabs(posPMT4z - posCrystalZ);
0921 
0922     //Calculate the resultant distance
0923     //dist1z = dist2z, and dist3z = dist4z, so only take two of them or take the average
0924     //distz = ((dist1z + dist2z) + (dist3z + dist4z))/2; 
0925     distz = dist1z + dist3z; 
0926 
0927     //1y and 3y are at the same y distance; and 2y and 4y are at the same y distance
0928     //Calculate the tangential (y)  distance from the position of interaction to each PMT
0929     dist1y = std::fabs(posPMT1y - posCrystalY);
0930     dist2y = std::fabs(posPMT2y - posCrystalY);
0931     dist3y = std::fabs(posPMT3y - posCrystalY);
0932     dist4y = std::fabs(posPMT4y - posCrystalY);
0933 
0934     //Calculate the resultant distance
0935     //dist1y = dist3y, and dist2y = dist4y, so only take two of them or take the average
0936     //disty = ((dist1y + dist3y) + (dist2y+dist4y))/2;
0937     disty = dist1y + dist2y;
0938 
0939     //signalPMT1z = signalPMT2z, and signalPMT3z = signalPMT4z
0940     signalPMT1z = Edep * dist3z/(dist1z + dist3z);
0941     signalPMT3z = Edep * dist1z/(dist1z + dist3z);
0942 
0943     signalPMT2z = Edep * dist4z/(dist2z + dist4z);
0944     signalPMT4z = Edep * dist2z/(dist2z + dist4z);
0945 
0946 
0947     //signalPMT1y = signalPMT3y, and signalPMT2y = signalPMT4y
0948     signalPMT1y = Edep * dist2y/(dist1y + dist2y);
0949     signalPMT2y = Edep * dist1y/(dist1y + dist2y);
0950 
0951     signalPMT3y = Edep * dist4y/(dist3y + dist4y);
0952     signalPMT4y = Edep * dist3y/(dist3y + dist4y);
0953 
0954     //Calculate the signal on each PMT from the 'component' signal
0955     signalPMT1 = (signalPMT1z +  signalPMT1y)*0.5; 
0956     signalPMT2 = (signalPMT2z +  signalPMT2y)*0.5;
0957     signalPMT3 = (signalPMT3z +  signalPMT3y)*0.5;
0958     signalPMT4 = (signalPMT4z +  signalPMT4y)*0.5;
0959 
0960 
0961     signalZplus = (signalPMT3 + signalPMT4);
0962     signalZminus = (signalPMT1 + signalPMT2);
0963     signalYplus = (signalPMT2 + signalPMT4);
0964     signalYminus = (signalPMT1 + signalPMT3);
0965 
0966 
0967     //Position of interaction is calculated based on Anger logic method. 
0968     //To get the position by Anger calculation, the result should be multiplied by the dimenion of the total distance.
0969     PositionAngerZ = (signalZplus - signalZminus)/(signalZplus + signalZminus)*distz; 
0970     PositionAngerY = (signalYplus - signalYminus)/(signalYplus + signalYminus)*disty;
0971 
0972 
0973     //For detectors with reflector insertion (light sharing), the response is shifted depending on the reflector patter.
0974     //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)
0975 
0976     //If reflector is only in the left side of the crystal, then response shift is to the right side (away from the reflector).
0977     //If reflector is only in the right side of the crystal, then response shift is to the left side (away from the reflector).
0978 
0979     //Response shift for 1st Layer
0980     if(i_doi == 0){
0981         //If reflector is only in one (left) side of the crystal, then response shifts to the right side (away from the reflector)
0982         if(ireflectorLayer1_Tangential[i_tan] == 1 && ireflectorLayer1_Tangential[i_tan + 1] == 0) PositionAngerY += (crystalPitch_tan/2)*shiftDis;
0983 
0984         //If reflector is only in one (right) side of the crystal, then response shifts to the left side (away from the reflector)
0985         if(ireflectorLayer1_Tangential[i_tan] == 0 && ireflectorLayer1_Tangential[i_tan + 1] == 1) PositionAngerY -= (crystalPitch_tan/2)*shiftDis;
0986 
0987         if(ireflectorLayer1_Axial[i_axial] == 1 && ireflectorLayer1_Axial [i_axial + 1] == 0) PositionAngerZ += (crystalPitch_axial/2)*shiftDis;
0988         if(ireflectorLayer1_Axial[i_axial] == 0 && ireflectorLayer1_Axial [i_axial + 1] == 1) PositionAngerZ -= (crystalPitch_axial/2)*shiftDis;
0989     }
0990     if(i_doi == 1){ //Response shift for 2nd Layer
0991         if(ireflectorLayer2_Tangential[i_tan] == 1 && ireflectorLayer2_Tangential[i_tan + 1] == 0) PositionAngerY += (crystalPitch_tan/2)*shiftDis;
0992         if(ireflectorLayer2_Tangential[i_tan] == 0 && ireflectorLayer2_Tangential[i_tan + 1] == 1) PositionAngerY -= (crystalPitch_tan/2)*shiftDis;
0993 
0994         if(ireflectorLayer2_Axial[i_axial] == 1 && ireflectorLayer2_Axial [i_axial + 1] == 0) PositionAngerZ += (crystalPitch_axial/2)*shiftDis;
0995         if(ireflectorLayer2_Axial[i_axial] == 0 && ireflectorLayer2_Axial [i_axial + 1] == 1) PositionAngerZ -= (crystalPitch_axial/2)*shiftDis;
0996     }
0997     if(i_doi == 2){ //Response shift for 3rd Layer
0998         if(ireflectorLayer3_Tangential[i_tan] == 1 && ireflectorLayer3_Tangential[i_tan + 1] == 0) PositionAngerY += (crystalPitch_tan/2)*shiftDis;
0999         if(ireflectorLayer3_Tangential[i_tan] == 0 && ireflectorLayer3_Tangential[i_tan + 1] == 1) PositionAngerY -= (crystalPitch_tan/2)*shiftDis;
1000 
1001         if(ireflectorLayer3_Axial[i_axial] == 1 && ireflectorLayer3_Axial [i_axial + 1] == 0) PositionAngerZ += (crystalPitch_axial/2)*shiftDis;
1002         if(ireflectorLayer3_Axial[i_axial] == 0 && ireflectorLayer3_Axial [i_axial + 1] == 1) PositionAngerZ -= (crystalPitch_axial/2)*shiftDis;
1003     }
1004     if(i_doi == 3){ //Response shift for 4th Layer
1005         if(ireflectorLayer4_Tangential[i_tan] == 1 && ireflectorLayer4_Tangential[i_tan + 1] == 0) PositionAngerY += (crystalPitch_tan/2)*shiftDis;
1006         if(ireflectorLayer4_Tangential[i_tan] == 0 && ireflectorLayer4_Tangential[i_tan + 1] == 1) PositionAngerY -= (crystalPitch_tan/2)*shiftDis;
1007 
1008         if(ireflectorLayer4_Axial[i_axial] == 1 && ireflectorLayer4_Axial [i_axial + 1] == 0) PositionAngerZ += (crystalPitch_axial/2)*shiftDis;
1009         if(ireflectorLayer4_Axial[i_axial] == 0 && ireflectorLayer4_Axial [i_axial + 1] == 1) PositionAngerZ -= (crystalPitch_axial/2)*shiftDis;
1010     }
1011    
1012    //Blur the 2D position (obtained by ANger Logic method) to include uncertainity of the PMT position response.
1013     if(isDOI_LUT){
1014         PositionAngerZ = G4RandGauss::shoot(PositionAngerZ,PMTblurring_axial/2.35);
1015         PositionAngerY = G4RandGauss::shoot(PositionAngerY,PMTblurring_tan/2.35);
1016     }
1017     //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 
1018     //by comparing with a look-up-table which is prepared based on the reflector insertion. 
1019 
1020     //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
1021     crystalID_in2D_posHist_axial = (G4int)(PositionAngerZ/(crystalPitch_axial*0.5) + (G4double)numberOfPixel_axial*0.5);//Note! crystalPitch_axial*0.5 is the pitch for the 32x32 2D pixel space, and 0.5 is added for round off
1022 
1023     //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
1024     crystalID_in2D_posHist_tan =   (G4int)(PositionAngerY/(crystalPitch_tan*0.5) + (G4double)numberOfPixel_tan * 0.5);
1025     
1026     //continuous crystal ID in the 2D position histogram. It will be from 0 to 1023 (in the case of 16x16x4 crystal array). 
1027     crystalID_in2D_posHist = crystalID_in2D_posHist_axial + crystalID_in2D_posHist_tan * numberOfPixel_tan;//32;
1028 
1029 
1030     //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.
1031 
1032     //Crystal ID along the tangential diraction after Anger Logic calculation
1033     crystalIDNew_tan = (G4int)(crystalID_in2D_posHist_tan/2);
1034 
1035     //Crystal ID along the axial diraction after Anger Logic calculation
1036     crystalIDNew_axial = (G4int)(crystalID_in2D_posHist_axial/2);
1037 
1038     ////Crystal ID along the DOI diraction after Anger Logic calculation
1039     if(crystalID_in2D_posHist>numberOfCrystal_tangential*numberOfCrystal_axial*numberOfCrystal_DOI) return;
1040     crystalIDNew_DOI = doi_table[crystalID_in2D_posHist];
1041 
1042     //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
1043     if(crystalIDNew_tan < 0 || crystalIDNew_axial < 0 || crystalIDNew_DOI < 0 ||
1044         crystalIDNew_tan >= numberOfCrystal_tangential || crystalIDNew_axial >= numberOfCrystal_axial || crystalIDNew_DOI >= numberOfCrystal_DOI){
1045             return;
1046     }
1047 
1048     CrystalIDAfterAngerLogic(crystalIDNew_tan,crystalIDNew_axial,crystalIDNew_DOI); 
1049 }
1050 
1051 /////
1052 void doiPETAnalysis::CrystalIDAfterAngerLogic(G4int i_tan, G4int i_axial, G4int i_doi){
1053     crystalID_tangential = i_tan;
1054     crystalID_axial = i_axial;
1055     DOI_ID = i_doi;
1056 }
1057 
1058 void doiPETAnalysis::book() 
1059 { 
1060   auto manager = G4AnalysisManager::Instance();
1061   
1062   //manager->SetVerboseLevel(2);
1063  
1064   G4bool fileOpen = manager->OpenFile(rootFileName);
1065   if (!fileOpen) {
1066     G4cout << "\n---> HistoManager::book(): cannot open " 
1067            << rootFileName << G4endl;
1068     return;
1069   }
1070   // Create directories  
1071   //manager->SetNtupleDirectoryName("ListModeData");
1072 
1073   manager->SetFirstNtupleId(1);
1074 
1075   if(getSinglesData){
1076     manager -> CreateNtuple("Singles", "Singles");
1077     fNtColId[0] = manager -> CreateNtupleIColumn("eventID");
1078     fNtColId[1] = manager -> CreateNtupleIColumn("blockID");
1079     //fNtColId[2] = manager -> CreateNtupleDColumn("crystalID_axial");
1080     //fNtColId[3] = manager -> CreateNtupleDColumn("crystalID_tangential");
1081     //fNtColId[4] = manager -> CreateNtupleDColumn("DOI_ID");
1082     fNtColId[2] = manager -> CreateNtupleDColumn("timeStamp");
1083     fNtColId[3] = manager -> CreateNtupleDColumn("totalEdep");
1084 
1085     //Interaction position of the photon with the detector
1086     fNtColId[4] = manager -> CreateNtupleDColumn("intPosX");
1087     fNtColId[5] = manager -> CreateNtupleDColumn("intPosY");
1088     fNtColId[6] = manager -> CreateNtupleDColumn("intPosZ");
1089 
1090     ////source position (annihilation position)
1091     fNtColId[7] = manager -> CreateNtupleDColumn("spositionX");
1092     fNtColId[8] = manager -> CreateNtupleDColumn("spositionY");
1093     fNtColId[9] = manager -> CreateNtupleDColumn("spositionZ");
1094 
1095     manager -> FinishNtuple();    
1096   }
1097   if(getCoincidenceData){
1098       manager -> CreateNtuple("Coincidence", "Coincidence");
1099     fNtColId[0] = manager -> CreateNtupleIColumn("eventID0");
1100     fNtColId[1] = manager -> CreateNtupleIColumn("blockID0");
1101     fNtColId[2] = manager -> CreateNtupleIColumn("crystalID_axial0");
1102     fNtColId[3] = manager -> CreateNtupleIColumn("crystalID_tangential0");
1103     fNtColId[4] = manager -> CreateNtupleIColumn("DOI_ID0");
1104     fNtColId[5] = manager -> CreateNtupleDColumn("timeStamp0");
1105     fNtColId[6] = manager -> CreateNtupleDColumn("totalEdep0");
1106 
1107     fNtColId[7] = manager -> CreateNtupleIColumn("eventID1");
1108     fNtColId[8] = manager -> CreateNtupleIColumn("blockID1");
1109     fNtColId[9] = manager -> CreateNtupleIColumn("crystalID_axial1");
1110     fNtColId[10] = manager -> CreateNtupleIColumn("crystalID_tangential1");
1111     fNtColId[11] = manager -> CreateNtupleIColumn("DOI_ID1");
1112     fNtColId[12] = manager -> CreateNtupleDColumn("timeStamp1");
1113     fNtColId[13] = manager -> CreateNtupleDColumn("totalEdep1");
1114 
1115     //source position
1116     fNtColId[14] = manager -> CreateNtupleDColumn("spositionX");
1117     fNtColId[15] = manager -> CreateNtupleDColumn("spositionY");
1118     fNtColId[16] = manager -> CreateNtupleDColumn("spositionZ");
1119 
1120     manager -> FinishNtuple();
1121 
1122   }
1123   
1124   
1125   factoryOn = true;    
1126 }
1127 void doiPETAnalysis::FillListModeEvent()
1128 {
1129 
1130   auto manager = G4AnalysisManager::Instance();
1131   if(getSinglesData){
1132         manager -> FillNtupleIColumn(1, fNtColId[0], G4int(eventID));
1133         manager -> FillNtupleIColumn(1, fNtColId[1], G4int(blockID));
1134         //manager -> FillNtupleDColumn(1, fNtColId[2], crystalID_axial);
1135         //manager -> FillNtupleDColumn(1, fNtColId[3], crystalID_tangential);
1136         //manager -> FillNtupleDColumn(1, fNtColId[4], DOI_ID);
1137         manager -> FillNtupleDColumn(1, fNtColId[2], timeStamp/s);// in second
1138         manager -> FillNtupleDColumn(1, fNtColId[3], totalEdep/keV); //in keV
1139         
1140         //Interaction position of the photon in the detector
1141         manager -> FillNtupleDColumn(1, fNtColId[4], intPosX); //mm
1142         manager -> FillNtupleDColumn(1, fNtColId[5], intPosY); //mm
1143         manager -> FillNtupleDColumn(1, fNtColId[6], intPosZ); //mm
1144 
1145         //
1146         //Add source position
1147         manager -> FillNtupleDColumn(1, fNtColId[7], spositionX);
1148         manager -> FillNtupleDColumn(1, fNtColId[8], spositionY);
1149         manager -> FillNtupleDColumn(1, fNtColId[9], spositionZ);
1150 
1151         manager -> AddNtupleRow(1);
1152   }
1153 
1154   if(getCoincidenceData){
1155       //First Single
1156         manager -> FillNtupleIColumn(1, fNtColId[0], eventID0);
1157         manager -> FillNtupleIColumn(1, fNtColId[1], blockID0);
1158         manager -> FillNtupleIColumn(1, fNtColId[2], crystalID_axial0);
1159         manager -> FillNtupleIColumn(1, fNtColId[3], crystalID_tangential0);
1160         manager -> FillNtupleIColumn(1, fNtColId[4], DOI_ID0);
1161         manager -> FillNtupleDColumn(1, fNtColId[5], timeStamp0/s);
1162         manager -> FillNtupleDColumn(1, fNtColId[6], totalEdep0/keV);
1163     
1164     //Second Single
1165         manager -> FillNtupleIColumn(1, fNtColId[7], eventID1);
1166         manager -> FillNtupleIColumn(1, fNtColId[8], blockID1);
1167         manager -> FillNtupleIColumn(1, fNtColId[9], crystalID_axial1);
1168         manager -> FillNtupleIColumn(1, fNtColId[10], crystalID_tangential1);
1169         manager -> FillNtupleIColumn(1, fNtColId[11], DOI_ID1);
1170         manager -> FillNtupleDColumn(1, fNtColId[12], timeStamp1/s);
1171         manager -> FillNtupleDColumn(1, fNtColId[13], totalEdep1/keV);
1172     
1173     //Add source position
1174         manager -> FillNtupleDColumn(1, fNtColId[14], spositionX);
1175         manager -> FillNtupleDColumn(1, fNtColId[15], spositionY);
1176         manager -> FillNtupleDColumn(1, fNtColId[16], spositionZ);
1177 
1178         manager -> AddNtupleRow(1);
1179   }
1180     
1181 }
1182 void doiPETAnalysis::finish() 
1183 {   
1184  if (factoryOn) 
1185    {
1186     auto manager = G4AnalysisManager::Instance();    
1187     manager -> Write();
1188     manager -> CloseFile();  
1189       
1190    // delete G4AnalysisManager::Instance();
1191     factoryOn = false;
1192    }
1193 }