diff --git a/core/opengate_core/opengate_core.cpp b/core/opengate_core/opengate_core.cpp index bd0294893..d79518704 100644 --- a/core/opengate_core/opengate_core.cpp +++ b/core/opengate_core/opengate_core.cpp @@ -310,6 +310,10 @@ void init_GateKineticEnergyFilter(py::module &); // Gate actors void init_GateDoseActor(py::module &m); +void init_GateVoxelizedPromptGammaTLEActor(py::module &m); + +void init_GateVoxelizedPromptGammaAnalogActor(py::module &m); + void init_GateFluenceActor(py::module &m); void init_GateLETActor(py::module &m); @@ -403,6 +407,8 @@ void init_GatePencilBeamSource(py::module &m); void init_GateVoxelSource(py::module &); +void init_GateVoxelizedPromptGammaTLESource(py::module &); + void init_GateGANSource(py::module &); void init_GatePhaseSpaceSource(py::module &); @@ -593,6 +599,7 @@ PYBIND11_MODULE(opengate_core, m) { init_GateTemplateSource(m); init_GatePencilBeamSource(m); init_GateVoxelSource(m); + init_GateVoxelizedPromptGammaTLESource(m); init_GateGANSource(m); init_GatePhaseSpaceSource(m); init_GateGANPairSource(m); @@ -604,6 +611,8 @@ PYBIND11_MODULE(opengate_core, m) { init_GateDoseActor(m); init_GateTLEDoseActor(m); + init_GateVoxelizedPromptGammaTLEActor(m); + init_GateVoxelizedPromptGammaAnalogActor(m); init_GateFluenceActor(m); init_GateLETActor(m); init_GateProductionAndStoppingActor(m); diff --git a/core/opengate_core/opengate_lib/GateHelpersImage.h b/core/opengate_core/opengate_lib/GateHelpersImage.h index 974ed0590..ae45ee571 100644 --- a/core/opengate_core/opengate_lib/GateHelpersImage.h +++ b/core/opengate_core/opengate_lib/GateHelpersImage.h @@ -21,8 +21,14 @@ void ImageAddValue(typename ImageType::Pointer image, template void AttachImageToVolume(typename ImageType::Pointer image, std::string volumeName, - G4ThreeVector initial_translation = G4ThreeVector(), - G4RotationMatrix img_rotation = G4RotationMatrix()); + G4ThreeVector image_offset = G4ThreeVector(), + G4RotationMatrix volume_rotation = G4RotationMatrix()); + +template +void GetStepVoxelPosition(G4Step *step, std::string hitType, + typename ImageType::Pointer cpp_image, + G4ThreeVector &position, bool &isInside, + typename ImageType::IndexType &index); #include "GateHelpersImage.txx" diff --git a/core/opengate_core/opengate_lib/GateHelpersImage.txx b/core/opengate_core/opengate_lib/GateHelpersImage.txx index 152667bab..742aac220 100644 --- a/core/opengate_core/opengate_lib/GateHelpersImage.txx +++ b/core/opengate_core/opengate_lib/GateHelpersImage.txx @@ -7,6 +7,7 @@ #include "G4LogicalVolume.hh" #include "GateHelpersGeometry.h" +#include "G4RandomTools.hh" template void ImageAddValue(typename ImageType::Pointer image, @@ -53,3 +54,41 @@ void AttachImageToVolume(typename ImageType::Pointer image, image->SetOrigin(o); image->SetDirection(dir); } + +template +void GetStepVoxelPosition(G4Step *step, + std::string hitType, + typename ImageType::Pointer cpp_image, + G4ThreeVector &position, + bool &isInside, + typename ImageType::IndexType &index) +{ + auto preGlobal = step->GetPreStepPoint()->GetPosition(); + auto postGlobal = step->GetPostStepPoint()->GetPosition(); + auto touchable = step->GetPreStepPoint()->GetTouchable(); + + // consider random position between pre and post + if (hitType == "pre") { + position = preGlobal; + } + if (hitType == "random") { + auto x = G4UniformRand(); + auto direction = postGlobal - preGlobal; + position = preGlobal + x * direction; + } + if (hitType == "middle") { + auto direction = postGlobal - preGlobal; + position = preGlobal + 0.5 * direction; + } + + auto localPosition = + touchable->GetHistory()->GetTransform(0).TransformPoint(position); + + // convert G4ThreeVector to itk PointType + typename ImageType::PointType point; + point[0] = localPosition[0]; + point[1] = localPosition[1]; + point[2] = localPosition[2]; + + isInside = cpp_image->TransformPhysicalPointToIndex(point, index); +} diff --git a/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaAnalogActor.cpp b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaAnalogActor.cpp new file mode 100644 index 000000000..9e327e3d3 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaAnalogActor.cpp @@ -0,0 +1,295 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "G4EmCalculator.hh" +#include "G4Gamma.hh" +#include "G4ParticleDefinition.hh" +#include "G4RandomTools.hh" +#include "G4RunManager.hh" +#include "G4Threading.hh" +#include "G4Track.hh" + +#include "G4HadronInelasticProcess.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" +#include "GateMaterialMuHandler.h" +#include "GateVoxelizedPromptGammaAnalogActor.h" + +#include "CLHEP/Random/Randomize.h" +#include +#include +#include +#include +#include + +#include +#include +// #include +#include +#include + +GateVoxelizedPromptGammaAnalogActor::GateVoxelizedPromptGammaAnalogActor( + py::dict &user_info) + : GateVActor(user_info, true) { + fMultiThreadReady = + true; // But used as a single thread python side : nb pf runs = 1 +} + +GateVoxelizedPromptGammaAnalogActor::~GateVoxelizedPromptGammaAnalogActor() { + // not needed + cpp_tof_proton_image = nullptr; + cpp_E_proton_image = nullptr; + cpp_E_neutron_image = nullptr; + cpp_tof_neutron_image = nullptr; + + // Release the 3D volume + volume = nullptr; + std::cout << "GateVoxelizedPromptGammaAnalogActor destructor called. " + "Resources released." + << std::endl; +} + +void GateVoxelizedPromptGammaAnalogActor::InitializeUserInfo( + py::dict &user_info) { + GateVActor::InitializeUserInfo(user_info); + + // retrieve the python param here + timebins = py::int_(user_info["timebins"]); + timerange = py::float_(user_info["timerange"]); + energybins = py::int_(user_info["energybins"]); + energyrange = py::float_(user_info["energyrange"]); + + fTranslation = DictGetG4ThreeVector(user_info, "translation"); + fsize = DictGetG4ThreeVector(user_info, "size"); + fspacing = DictGetG4ThreeVector(user_info, "spacing"); +} + +void GateVoxelizedPromptGammaAnalogActor::InitializeCpp() { + GateVActor::InitializeCpp(); + // Create the image pointers + // (the size and allocation will be performed on the py side) + if (fProtonTimeFlag) { + cpp_tof_proton_image = ImageType::New(); + } + if (fProtonEnergyFlag) { + cpp_E_proton_image = ImageType::New(); + } + if (fNeutronEnergyFlag) { + cpp_E_neutron_image = ImageType::New(); + } + if (fNeutronTimeFlag) { + cpp_tof_neutron_image = ImageType::New(); + } + + // Construction of the 3D image with the same shape/mat that the voxel of the + // actor but is accepted by the method of volume_attach and "isInside" + volume = Image3DType::New(); + + Image3DType::RegionType region; + Image3DType::SizeType size; + Image3DType::SpacingType spacing; + + size[0] = fsize[0]; + size[1] = fsize[1]; + size[2] = fsize[2]; + region.SetSize(size); + + spacing[0] = fspacing[0]; + spacing[1] = fspacing[1]; + spacing[2] = fspacing[2]; + + volume->SetRegions(region); + volume->SetSpacing(spacing); + volume->Allocate(); + volume->FillBuffer(0); + + incidentParticles = + 0; // initiate the conuter of incidente protons - scaling factor +} + +void GateVoxelizedPromptGammaAnalogActor::BeginOfRunActionMasterThread( + int run_id) { + // Attach the 3D volume used to + + // Fill the 4D volume of interest with 0 to ensure that it is well initiated + if (fProtonTimeFlag) { + cpp_tof_proton_image->FillBuffer(0); + } + if (fProtonEnergyFlag) { + cpp_E_proton_image->FillBuffer(0); + } + if (fNeutronEnergyFlag) { + cpp_E_neutron_image->FillBuffer(0); + } + if (fNeutronTimeFlag) { + cpp_tof_neutron_image->FillBuffer(0); + } + AttachImageToVolume(volume, fPhysicalVolumeName, fTranslation); +} + +void GateVoxelizedPromptGammaAnalogActor::BeginOfRunAction(const G4Run *run) {} + +void GateVoxelizedPromptGammaAnalogActor::BeginOfEventAction( + const G4Event *event) { + T0 = event->GetPrimaryVertex()->GetT0(); + incidentParticles++; +} + +void GateVoxelizedPromptGammaAnalogActor::SteppingAction(G4Step *step) { + if (step->GetTrack()->GetParticleDefinition() != G4Neutron::Neutron() && + (step->GetTrack()->GetParticleDefinition() != G4Proton::Proton())) { + return; + } + static G4HadronicProcessStore *store = G4HadronicProcessStore::Instance(); + static G4VProcess *protonInelastic = + store->FindProcess(G4Proton::Proton(), fHadronInelastic); + static G4VProcess *neutronInelastic = + store->FindProcess(G4Neutron::Neutron(), fHadronInelastic); + if (step->GetPostStepPoint()->GetProcessDefinedStep() != protonInelastic && + step->GetPostStepPoint()->GetProcessDefinedStep() != neutronInelastic) { + return; + } + + auto position = step->GetPostStepPoint()->GetPosition(); + auto touchable = step->GetPreStepPoint()->GetTouchable(); + // Get the voxel index + auto localPosition = + touchable->GetHistory()->GetTransform(0).TransformPoint(position); + + // convert G4ThreeVector to itk PointType + Image3DType::PointType point; + point[0] = localPosition[0]; + point[1] = localPosition[1]; + point[2] = localPosition[2]; + + Image3DType::IndexType index; + G4bool isInside = volume->TransformPhysicalPointToIndex(point, index); + if (!isInside) { // verification + return; // Skip if not inside the volume + } + + // Get the spatial index from the index obtained with the 3D volume and th + // emethod GetStepVoxelPosition() + ImageType::IndexType ind; + ind[0] = index[0]; + ind[1] = index[1]; + ind[2] = index[2]; + + auto secondaries = step->GetSecondary(); + for (size_t i = 0; i < secondaries->size(); i++) { + auto secondary = secondaries->at(i); + auto secondary_def = secondary->GetParticleDefinition(); + if (secondary_def != G4Gamma::Gamma()) { + continue; + } + G4double gammaEnergy = secondary->GetKineticEnergy(); // in MeV + // thershold with a minimum energy of 40 keV + if (gammaEnergy < 0.04 * CLHEP::MeV) { + continue; + } + + if (fProtonTimeFlag || + fNeutronTimeFlag) { // If the quantity of interest is the time of flight + // Get the time of flight + // G4double randomtime = G4UniformRand(); + // G4double pretime = step->GetPreStepPoint()->GetGlobalTime()- T0; //ns + // G4double posttime = step->GetPostStepPoint()->GetGlobalTime()- T0;//ns + G4double time = secondary->GetGlobalTime() - T0; // ns + + // Get the voxel index (fourth dim) corresponding to the time of flight + G4int bin = static_cast( + time / (timerange / timebins)); // Always the left bin + + if (bin >= timebins) { + bin = timebins; + } + ind[3] = bin; + // Store the value in the volume for neutrons OR protons -> LEFT BINNING + if (fProtonTimeFlag && + step->GetTrack()->GetParticleDefinition()->GetParticleName() == + "proton") { + ImageAddValue(cpp_tof_proton_image, ind, 1); + } + if (fNeutronTimeFlag && + step->GetTrack()->GetParticleDefinition()->GetParticleName() == + "neutron") { + ImageAddValue(cpp_tof_neutron_image, ind, 1); + } + } + if (fProtonEnergyFlag || + fNeutronEnergyFlag) { // when the quantity of interest is the energy + // Get the voxel index (fourth dim) corresponding to the energy of the + // projectile + G4int bin = static_cast( + gammaEnergy / (energyrange / energybins)); // Always the left bin + if (bin >= energybins) { + bin = energybins; + } + if (bin < 0) { + bin = 0; // underflow + } + ind[3] = bin; + // Store the value in the volume for neutrons OR protons -> LEFT BINNING + if (fProtonEnergyFlag && + step->GetTrack()->GetParticleDefinition()->GetParticleName() == + "proton") { + ImageAddValue(cpp_E_proton_image, ind, 1); + } + if (fNeutronEnergyFlag && + step->GetTrack()->GetParticleDefinition()->GetParticleName() == + "neutron") { + ImageAddValue(cpp_E_neutron_image, ind, 1); + } + } + } +} + +void GateVoxelizedPromptGammaAnalogActor::EndOfRunAction(const G4Run *run) { + std::cout << "incident particles : " << incidentParticles << std::endl; + if (incidentParticles == 0) { + std::cerr << "Error: incidentParticles is zero. Skipping scaling." + << std::endl; + return; + } + // scaling all the 4D voxels with th enumber of incident protons (= number of + // event) + if (fProtonTimeFlag) { + itk::ImageRegionIterator it( + cpp_tof_proton_image, cpp_tof_proton_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fProtonEnergyFlag) { + itk::ImageRegionIterator it( + cpp_E_proton_image, cpp_E_proton_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fNeutronEnergyFlag) { + itk::ImageRegionIterator it( + cpp_E_neutron_image, cpp_E_neutron_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fNeutronTimeFlag) { + itk::ImageRegionIterator it( + cpp_tof_neutron_image, + cpp_tof_neutron_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } +} + +int GateVoxelizedPromptGammaAnalogActor::EndOfRunActionMasterThread( + int run_id) { + return 0; +} diff --git a/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaAnalogActor.h b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaAnalogActor.h new file mode 100644 index 000000000..f207a5846 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaAnalogActor.h @@ -0,0 +1,98 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateVoxelizedPromptGammaAnalogActor_h +#define GateVoxelizedPromptGammaAnalogActor_h + +#include "G4Cache.hh" +#include "G4EmCalculator.hh" +#include "G4NistManager.hh" +#include "G4VPrimitiveScorer.hh" +#include "GateDoseActor.h" +#include "GateMaterialMuHandler.h" + +#include + +namespace py = pybind11; + +class GateVoxelizedPromptGammaAnalogActor : public GateVActor { + +public: + // destructor + ~GateVoxelizedPromptGammaAnalogActor() override; + + explicit GateVoxelizedPromptGammaAnalogActor(py::dict &user_info); + + void InitializeUserInfo(py::dict &user_info) override; + + void InitializeCpp() override; + + void BeginOfRunActionMasterThread(int run_id); + + int EndOfRunActionMasterThread(int run_id) override; + + void EndOfRunAction(const G4Run *run); + + void BeginOfRunAction(const G4Run *run); + + void BeginOfEventAction(const G4Event *event) override; + + void SteppingAction(G4Step *) override; + + inline bool GetProtonTimeFlag() const { return fProtonTimeFlag; } + + inline void SetProtonTimeFlag(const bool b) { fProtonTimeFlag = b; } + + inline bool GetNeutronTimeFlag() const { return fNeutronTimeFlag; } + + inline void SetNeutronTimeFlag(const bool b) { fNeutronTimeFlag = b; } + + inline bool GetProtonEnergyFlag() const { return fProtonEnergyFlag; } + + inline void SetProtonEnergyFlag(const bool b) { fProtonEnergyFlag = b; } + + inline bool GetNeutronEnergyFlag() const { return fNeutronEnergyFlag; } + + inline void SetNeutronEnergyFlag(const bool b) { fNeutronEnergyFlag = b; } + + inline std::string GetPhysicalVolumeName() const { + return fPhysicalVolumeName; + } + + inline void SetPhysicalVolumeName(std::string s) { fPhysicalVolumeName = s; } + + std::string fPhysicalVolumeName; + + // Image type + typedef itk::Image ImageType; + ImageType::Pointer cpp_tof_neutron_image; + ImageType::Pointer cpp_tof_proton_image; + ImageType::Pointer cpp_E_proton_image; + ImageType::Pointer cpp_E_neutron_image; + + typedef itk::Image Image3DType; + Image3DType::Pointer volume; + + G4double T0; + G4int incidentParticles; + + G4int timebins; + G4double timerange; + G4int energybins; + G4double energyrange; + + G4bool fProtonTimeFlag{}; + G4bool fProtonEnergyFlag{}; + G4bool fNeutronTimeFlag{}; + G4bool fNeutronEnergyFlag{}; + + G4ThreeVector fsize; + G4ThreeVector fspacing; + G4ThreeVector fTranslation; +}; + +#endif // GateVoxelizedPromptGammaAnalogActor_h diff --git a/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLEActor.cpp b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLEActor.cpp new file mode 100644 index 000000000..97c0dad42 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLEActor.cpp @@ -0,0 +1,282 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "G4EmCalculator.hh" +#include "G4Gamma.hh" +#include "G4ParticleDefinition.hh" +#include "G4RandomTools.hh" +#include "G4RunManager.hh" +#include "G4Threading.hh" +#include "G4Track.hh" + +#include "G4HadronInelasticProcess.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" +#include "GateMaterialMuHandler.h" +#include "GateVoxelizedPromptGammaTLEActor.h" + +#include "CLHEP/Random/Randomize.h" +#include +#include +#include +#include +#include + +GateVoxelizedPromptGammaTLEActor::GateVoxelizedPromptGammaTLEActor( + py::dict &user_info) + : GateVActor(user_info, true) { + fMultiThreadReady = + true; // But used as a single thread python side : nb pf runs = 1 +} + +GateVoxelizedPromptGammaTLEActor::~GateVoxelizedPromptGammaTLEActor() { + // not needed +} + +void GateVoxelizedPromptGammaTLEActor::InitializeUserInfo(py::dict &user_info) { + GateVActor::InitializeUserInfo(user_info); + + // retrieve the python param here + timebins = py::int_(user_info["timebins"]); + timerange = py::float_(user_info["timerange"]); + energybins = py::int_(user_info["energybins"]); + energyrange = py::float_(user_info["energyrange"]); + weight = py::bool_(user_info["weight"]); + fTranslation = DictGetG4ThreeVector(user_info, "translation"); + fsize = DictGetG4ThreeVector(user_info, "size"); + fspacing = DictGetG4ThreeVector(user_info, "spacing"); +} + +void GateVoxelizedPromptGammaTLEActor::InitializeCpp() { + GateVActor::InitializeCpp(); + // Create the image pointers + // (the size and allocation will be performed on the py side) + if (fProtonTimeFlag) { + cpp_tof_proton_image = ImageType::New(); + } + if (fProtonEnergyFlag) { + cpp_E_proton_image = ImageType::New(); + } + if (fNeutronEnergyFlag) { + cpp_E_neutron_image = ImageType::New(); + } + if (fNeutronTimeFlag) { + cpp_tof_neutron_image = ImageType::New(); + } + + // Construction of the 3D image with the same shape/mat that the voxel of the + // actor but is accepted by the method of volume_attach and "isInside" + volume = Image3DType::New(); + + Image3DType::RegionType region; + Image3DType::SizeType size; + Image3DType::SpacingType spacing; + + size[0] = fsize[0]; + size[1] = fsize[1]; + size[2] = fsize[2]; + region.SetSize(size); + + spacing[0] = fspacing[0]; + spacing[1] = fspacing[1]; + spacing[2] = fspacing[2]; + + volume->SetRegions(region); + volume->SetSpacing(spacing); + volume->Allocate(); + volume->FillBuffer(0); + + incidentParticles = + 0; // initiate the conuter of incidente protons - scaling factor +} + +void GateVoxelizedPromptGammaTLEActor::BeginOfRunActionMasterThread( + int run_id) { + // Attach the 3D volume used to + + // Fill the 4D volume of interest with 0 to ensure that it is well initiated + if (fProtonTimeFlag) { + cpp_tof_proton_image->FillBuffer(0); + } + if (fProtonEnergyFlag) { + cpp_E_proton_image->FillBuffer(0); + } + if (fNeutronEnergyFlag) { + cpp_E_neutron_image->FillBuffer(0); + } + if (fNeutronTimeFlag) { + cpp_tof_neutron_image->FillBuffer(0); + } + AttachImageToVolume(volume, fPhysicalVolumeName, fTranslation); +} + +void GateVoxelizedPromptGammaTLEActor::BeginOfRunAction(const G4Run *run) {} + +void GateVoxelizedPromptGammaTLEActor::BeginOfEventAction( + + const G4Event *event) { + T0 = event->GetPrimaryVertex()->GetT0(); + incidentParticles++; +} + +void GateVoxelizedPromptGammaTLEActor::SteppingAction(G4Step *step) { + const G4ParticleDefinition *particle = + step->GetTrack()->GetParticleDefinition(); + if ((particle != G4Neutron::Neutron()) && (particle != G4Proton::Proton())) { + return; + } + auto position = step->GetPostStepPoint()->GetPosition(); + auto touchable = step->GetPreStepPoint()->GetTouchable(); + // Get the voxel index + + auto localPosition = + touchable->GetHistory()->GetTransform(0).TransformPoint(position); + + // convert G4ThreeVector to itk PointType + Image3DType::PointType point; + point[0] = localPosition[0]; + point[1] = localPosition[1]; + point[2] = localPosition[2]; + + Image3DType::IndexType index; + G4bool isInside = volume->TransformPhysicalPointToIndex(point, index); + if (!isInside) { // verification + return; // Skip if not inside the volume + } + + // Convert the index to a 4D index + ImageType::IndexType ind; + ind[0] = index[0]; + ind[1] = index[1]; + ind[2] = index[2]; + + // Get the step lenght + const G4double &l = step->GetStepLength(); + G4Material *mat = step->GetPreStepPoint()->GetMaterial(); + G4double rho = mat->GetDensity() / (CLHEP::g / CLHEP::cm3); + auto w = step->GetTrack() + ->GetWeight(); // Get the weight of the track (particle history) + // for potential russian roulette or splitting + + // Get the energy of the projectile + const G4double &preE = step->GetPreStepPoint()->GetKineticEnergy(); + const G4double &postE = step->GetPostStepPoint()->GetKineticEnergy(); + G4double projectileEnergy = preE; + if (postE != 0) { + const G4double &randomenergy = G4UniformRand(); + projectileEnergy = postE + randomenergy * (preE - postE); // MeV + } + // Get the energy bin index + G4int binE = static_cast( + projectileEnergy / (energyrange / energybins)); // Always the left bin + if (binE >= energybins) { + binE = energybins; // last bin = overflow + } + + if (binE < 0) { + binE = 0; // underflow + } + + if ((fProtonTimeFlag) || + (fNeutronTimeFlag)) { // If the quantity of interest is the time of flight + // Get the time of flight + const G4double &randomtime = G4UniformRand(); + const G4double &pretime = + step->GetPreStepPoint()->GetGlobalTime() - T0; // ns + const G4double &posttime = + step->GetPostStepPoint()->GetGlobalTime() - T0; // ns + G4double time = (posttime + randomtime * (pretime - posttime)); // ns + // Get the voxel index (fourth dim) corresponding to the time of flight + G4int bin = static_cast(time / (timerange / timebins)); + if (bin >= timebins) { + bin = timebins; // overflow + } + if (bin < 0) { + bin = 0; // underflow + } + ind[3] = bin; + G4double pg_sum = 1; + // Store the value in the volume for neutrons OR protons -> LEFT BINNING + if (fProtonTimeFlag && particle == G4Proton::Proton()) { + if (weight) { + pg_sum = fProtonVector[binE]; + } + ImageAddValue(cpp_tof_proton_image, ind, pg_sum * l * rho * w); + } + if (fNeutronTimeFlag && particle == G4Neutron::Neutron()) { + if (weight) { + pg_sum = fProtonVector[binE]; + } + ImageAddValue(cpp_tof_neutron_image, ind, + pg_sum * l * rho * w); + } + } + if (fProtonEnergyFlag || + fNeutronEnergyFlag) { // when the quantity of interest is the energy + ind[3] = binE; + // Store the value in the volume for neutrons OR protons -> LEFT BINNING + if (fProtonEnergyFlag && particle == G4Proton::Proton()) { + ImageAddValue(cpp_E_proton_image, ind, l * rho * w); + } + if (fNeutronEnergyFlag && particle == G4Neutron::Neutron()) { + ImageAddValue(cpp_E_neutron_image, ind, l * rho * w); + } + } +} + +void GateVoxelizedPromptGammaTLEActor::EndOfRunAction(const G4Run *run) { + std::cout << "incident particles : " << incidentParticles << std::endl; + if (incidentParticles == 0) { + std::cerr << "Error: incidentParticles is zero. Skipping scaling." + << std::endl; + return; + } + // scaling all the 4D voxels with th enumber of incident protons (= number of + // event) + if (fProtonTimeFlag) { + itk::ImageRegionIterator it( + cpp_tof_proton_image, cpp_tof_proton_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fProtonEnergyFlag) { + itk::ImageRegionIterator it( + cpp_E_proton_image, cpp_E_proton_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fNeutronEnergyFlag) { + itk::ImageRegionIterator it( + cpp_E_neutron_image, cpp_E_neutron_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fNeutronTimeFlag) { + itk::ImageRegionIterator it( + cpp_tof_neutron_image, + cpp_tof_neutron_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } +} + +int GateVoxelizedPromptGammaTLEActor::EndOfRunActionMasterThread(int run_id) { + return 0; +} + +void GateVoxelizedPromptGammaTLEActor::SetVector(py::array_t vect_p, + py::array_t vect_n) { + fProtonVector = + std::vector(vect_p.data(), vect_p.data() + vect_p.size()); + fNeutronVector = + std::vector(vect_n.data(), vect_n.data() + vect_n.size()); +} \ No newline at end of file diff --git a/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLEActor.h b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLEActor.h new file mode 100644 index 000000000..202e5b3c5 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLEActor.h @@ -0,0 +1,107 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateVoxelizedPromptGammaTLEActor_h +#define GateVoxelizedPromptGammaTLEActor_h + +#include "G4Cache.hh" +#include "G4EmCalculator.hh" +#include "G4NistManager.hh" +#include "G4VPrimitiveScorer.hh" +#include "GateDoseActor.h" +#include "GateMaterialMuHandler.h" +#include +#include +#include + +namespace py = pybind11; + +class GateVoxelizedPromptGammaTLEActor : public GateVActor { + +public: + // destructor + ~GateVoxelizedPromptGammaTLEActor() override; + + explicit GateVoxelizedPromptGammaTLEActor(py::dict &user_info); + + void InitializeUserInfo(py::dict &user_info) override; + + void InitializeCpp() override; + + void BeginOfRunActionMasterThread(int run_id); + + int EndOfRunActionMasterThread(int run_id) override; + + void EndOfRunAction(const G4Run *run); + + void BeginOfRunAction(const G4Run *run); + + void BeginOfEventAction(const G4Event *event) override; + + void SteppingAction(G4Step *) override; + + inline bool GetProtonTimeFlag() const { return fProtonTimeFlag; } + + inline void SetProtonTimeFlag(const bool b) { fProtonTimeFlag = b; } + + inline bool GetNeutronTimeFlag() const { return fNeutronTimeFlag; } + + inline void SetNeutronTimeFlag(const bool b) { fNeutronTimeFlag = b; } + + inline bool GetProtonEnergyFlag() const { return fProtonEnergyFlag; } + + inline void SetProtonEnergyFlag(const bool b) { fProtonEnergyFlag = b; } + + inline bool GetNeutronEnergyFlag() const { return fNeutronEnergyFlag; } + + inline void SetNeutronEnergyFlag(const bool b) { fNeutronEnergyFlag = b; } + + void SetVector(py::array_t vect_p, py::array_t vect_n); + + inline std::string GetPhysicalVolumeName() const { + return fPhysicalVolumeName; + } + + // void SetVector(pybind11::array_t vect_p, pybind11::array_t + // vect_n ); + + inline void SetPhysicalVolumeName(std::string s) { fPhysicalVolumeName = s; } + + std::string fPhysicalVolumeName; + + // Image type + typedef itk::Image ImageType; + ImageType::Pointer cpp_tof_neutron_image; + ImageType::Pointer cpp_tof_proton_image; + ImageType::Pointer cpp_E_proton_image; + ImageType::Pointer cpp_E_neutron_image; + + typedef itk::Image Image3DType; + Image3DType::Pointer volume; + + G4double T0; + G4int incidentParticles; + + G4int timebins; + G4double timerange; + G4int energybins; + G4double energyrange; + + G4bool fProtonTimeFlag{}; + G4bool fProtonEnergyFlag{}; + G4bool fNeutronTimeFlag{}; + G4bool fNeutronEnergyFlag{}; + G4bool weight; + + G4ThreeVector fsize; + G4ThreeVector fspacing; + G4ThreeVector fTranslation; + std::vector fProtonVector; + std::vector fNeutronVector; +}; + +#endif // GateVoxelizedPromptGammaTLEActor_h diff --git a/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLESource.cpp b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLESource.cpp new file mode 100644 index 000000000..2b3a5daba --- /dev/null +++ b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLESource.cpp @@ -0,0 +1,52 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include "GateVoxelizedPromptGammaTLESource.h" +#include "G4ParticleTable.hh" +#include "GateHelpersDict.h" +#include "GateHelpersGeometry.h" + +GateVoxelizedPromptGammaTLESource::GateVoxelizedPromptGammaTLESource() + : GateGenericSource() { + fVoxelPositionGenerator = new GateSPSVoxelsPosDistribution(); + std::cout << "construct GateVoxelizedPromptGammaTLESource" << std::endl; +} + +GateVoxelizedPromptGammaTLESource::~GateVoxelizedPromptGammaTLESource() = + default; + +void GateVoxelizedPromptGammaTLESource::PrepareNextRun() { + std::cout << "GateVoxelizedPromptGammaTLESource::PrepareNextRun" << std::endl; + // GateGenericSource::PrepareNextRun(); + // rotation and translation to apply, according to mother volume + GateVSource::PrepareNextRun(); + // This global transformation is given to the SPS that will + // generate particles in the correct coordinate system + auto &l = GetThreadLocalData(); + auto &ll = GetThreadLocalDataGenericSource(); + auto *pos = ll.fSPS->GetPosDist(); + pos->SetCentreCoords(l.fGlobalTranslation); + + // orientation according to mother volume + auto rotation = l.fGlobalRotation; + G4ThreeVector r1(rotation(0, 0), rotation(1, 0), rotation(2, 0)); + G4ThreeVector r2(rotation(0, 1), rotation(1, 1), rotation(2, 1)); + pos->SetPosRot1(r1); + pos->SetPosRot2(r2); + + // auto &l = fThreadLocalData.Get(); + fVoxelPositionGenerator->fGlobalRotation = l.fGlobalRotation; + fVoxelPositionGenerator->fGlobalTranslation = l.fGlobalTranslation; + // the direction is 'isotropic' so we don't care about rotating the direction. +} + +void GateVoxelizedPromptGammaTLESource::InitializePosition(py::dict) { + auto &ll = GetThreadLocalDataGenericSource(); + ll.fSPS->SetPosGenerator(fVoxelPositionGenerator); + // we set a fake value (not used) + fVoxelPositionGenerator->SetPosDisType("Point"); +} diff --git a/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLESource.h b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLESource.h new file mode 100644 index 000000000..84b3afcb4 --- /dev/null +++ b/core/opengate_core/opengate_lib/GateVoxelizedPromptGammaTLESource.h @@ -0,0 +1,37 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateVoxelizedPromptGammaTLESource_h +#define GateVoxelizedPromptGammaTLESource_h + +#include "GateGenericSource.h" +#include "GateSPSVoxelsPosDistribution.h" +#include "GateSingleParticleSource.h" +#include + +namespace py = pybind11; + +class GateVoxelizedPromptGammaTLESource : public GateGenericSource { + +public: + GateVoxelizedPromptGammaTLESource(); + + ~GateVoxelizedPromptGammaTLESource() override; + + void PrepareNextRun() override; + + GateSPSVoxelsPosDistribution *GetSPSVoxelPosDistribution() { + return fVoxelPositionGenerator; + } + +protected: + void InitializePosition(py::dict user_info) override; + + GateSPSVoxelsPosDistribution *fVoxelPositionGenerator; +}; + +#endif // GateVoxelizedPromptGammaTLESource_h diff --git a/core/opengate_core/opengate_lib/helpers_itk_image_py.h b/core/opengate_core/opengate_lib/helpers_itk_image_py.h index 801b78025..eef349f79 100644 --- a/core/opengate_core/opengate_lib/helpers_itk_image_py.h +++ b/core/opengate_core/opengate_lib/helpers_itk_image_py.h @@ -52,20 +52,18 @@ inline void set_region(TImagePointer &img, size) { using RegionType = typename TImagePointer::ObjectType::RegionType; typename RegionType::IndexType itk_index; - const auto *data_index = - static_cast(np_array_ptr_after_check_dim_and_shape(index)); - itk_index[0] = data_index[0]; - itk_index[1] = data_index[1]; - itk_index[2] = data_index[2]; + const auto *data_index = static_cast( + np_array_ptr_after_check_dim_and_shape(index, img->ImageDimension)); + for (int i = 0; i < img->ImageDimension; i++) + itk_index[i] = data_index[i]; typename RegionType::SizeType itk_size; - const auto *data_size = - static_cast(np_array_ptr_after_check_dim_and_shape(size)); + const auto *data_size = static_cast( + np_array_ptr_after_check_dim_and_shape(size, img->ImageDimension)); if (data_size[0] < 0 || data_size[1] < 0 || data_size[2] < 0) { throw std::runtime_error("In set_regions, input size cannot be negative."); } - itk_size[0] = data_size[0]; - itk_size[1] = data_size[1]; - itk_size[2] = data_size[2]; + for (int i = 0; i < img->ImageDimension; i++) + itk_size[i] = data_size[i]; RegionType itk_region(itk_index, itk_size); img->SetRegions(itk_region); img->Allocate(); @@ -95,9 +93,10 @@ void declare_itk_image_ptr(pybind11::module &m, const std::string &typestr) { [](TImagePointer &img, py::array_t size) { py::array_t - zero_index(3); + zero_index(img->ImageDimension); int *raw = static_cast(zero_index.request().ptr); - raw[0] = raw[1] = raw[2] = 0; + for (int i = 0; i < img->ImageDimension; i++) + raw[i] = 0; return set_region(img, zero_index, size); }) .def( @@ -118,7 +117,8 @@ void declare_itk_image_ptr(pybind11::module &m, const std::string &typestr) { py::array_t spacing) { const auto *data = static_cast( - np_array_ptr_after_check_dim_and_shape(spacing)); + np_array_ptr_after_check_dim_and_shape( + spacing, img->ImageDimension)); img->SetSpacing(data); }) .def("origin", @@ -131,7 +131,8 @@ void declare_itk_image_ptr(pybind11::module &m, const std::string &typestr) { py::array_t origin) { const auto *data = static_cast( - np_array_ptr_after_check_dim_and_shape(origin)); + np_array_ptr_after_check_dim_and_shape( + origin, img->ImageDimension)); img->SetOrigin(data); }) .def("direction", @@ -173,13 +174,26 @@ void declare_itk_image_ptr(pybind11::module &m, const std::string &typestr) { "to_pyarray", [](const TImagePointer &img, const std::string &contiguous) { const auto size = img->GetLargestPossibleRegion().GetSize(); - const auto shape = - (contiguous == "F") - ? std::vector{size[2], size[1], size[0]} - : std::vector{size[0], size[1], size[2]}; - return py::array( - py::dtype::of(), - shape, img->GetBufferPointer()); + if (img->ImageDimension == 3) { + const auto shape = + (contiguous == "F") + ? std::vector{size[2], size[1], size[0]} + : std::vector{size[0], size[1], size[2]}; + return py::array( + py::dtype::of< + typename TImagePointer::ObjectType::PixelType>(), + shape, img->GetBufferPointer()); + } + if (img->ImageDimension == 4) { + const auto shape = + (contiguous == "F") + ? std::vector{size[3], size[2], size[1], size[0]} + : std::vector{size[0], size[1], size[2], size[3]}; + return py::array( + py::dtype::of< + typename TImagePointer::ObjectType::PixelType>(), + shape, img->GetBufferPointer()); + } }, py::arg("contig") = "F") // TODO: Create a view (non-copy) of the data diff --git a/core/opengate_core/opengate_lib/pyGateItkImage.cpp b/core/opengate_core/opengate_lib/pyGateItkImage.cpp index 0a97f53d9..f4c8b5af3 100644 --- a/core/opengate_core/opengate_lib/pyGateItkImage.cpp +++ b/core/opengate_core/opengate_lib/pyGateItkImage.cpp @@ -19,10 +19,12 @@ using IUC3P = itk::Image::Pointer; using IUS3P = itk::Image::Pointer; using IF3P = itk::Image::Pointer; using ID3P = itk::Image::Pointer; +using ID4P = itk::Image::Pointer; void init_itk_image(py::module &m) { declare_itk_image_ptr(m, "IUC3P"); declare_itk_image_ptr(m, "IUS3P"); declare_itk_image_ptr(m, "IF3P"); declare_itk_image_ptr(m, "ID3P"); + declare_itk_image_ptr(m, "ID4P"); } diff --git a/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaAnalogActor.cpp b/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaAnalogActor.cpp new file mode 100644 index 000000000..bf197db05 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaAnalogActor.cpp @@ -0,0 +1,81 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include +#include + +namespace py = pybind11; + +#include "GateVoxelizedPromptGammaAnalogActor.h" + +class PyGateVoxelizedPromptGammaAnalogActor + : public GateVoxelizedPromptGammaAnalogActor { +public: + // Inherit the constructors + using GateVoxelizedPromptGammaAnalogActor:: + GateVoxelizedPromptGammaAnalogActor; + + void BeginOfRunActionMasterThread(int run_id) override { + PYBIND11_OVERLOAD(void, GateVoxelizedPromptGammaAnalogActor, + BeginOfRunActionMasterThread, run_id); + } + + int EndOfRunActionMasterThread(int run_id) override { + PYBIND11_OVERLOAD(int, GateVoxelizedPromptGammaAnalogActor, + EndOfRunActionMasterThread, run_id); + } +}; + +void init_GateVoxelizedPromptGammaAnalogActor(py::module &m) { + py::class_, + GateVActor>(m, "GateVoxelizedPromptGammaAnalogActor") + .def(py::init()) + .def("InitializeUserInfo", + &GateVoxelizedPromptGammaAnalogActor::InitializeUserInfo) + .def("BeginOfRunActionMasterThread", + &GateVoxelizedPromptGammaAnalogActor::BeginOfRunActionMasterThread) + .def("EndOfRunActionMasterThread", + &GateVoxelizedPromptGammaAnalogActor::EndOfRunActionMasterThread) + .def("SetPhysicalVolumeName", + &GateVoxelizedPromptGammaAnalogActor::SetPhysicalVolumeName) + .def("GetPhysicalVolumeName", + &GateVoxelizedPromptGammaAnalogActor::GetPhysicalVolumeName) + + .def("SetProtonTimeFlag", + &GateVoxelizedPromptGammaAnalogActor::SetProtonTimeFlag) + .def("GetProtonTimeFlag", + &GateVoxelizedPromptGammaAnalogActor::GetProtonTimeFlag) + + .def("SetProtonEnergyFlag", + &GateVoxelizedPromptGammaAnalogActor::SetProtonEnergyFlag) + .def("GetProtonEnergyFlag", + &GateVoxelizedPromptGammaAnalogActor::GetProtonEnergyFlag) + + .def("SetNeutronEnergyFlag", + &GateVoxelizedPromptGammaAnalogActor::SetNeutronEnergyFlag) + .def("GetNeutronEnergyFlag", + &GateVoxelizedPromptGammaAnalogActor::GetNeutronEnergyFlag) + + .def("SetNeutronTimeFlag", + &GateVoxelizedPromptGammaAnalogActor::SetNeutronTimeFlag) + .def("GetNeutronTimeFlag", + &GateVoxelizedPromptGammaAnalogActor::GetNeutronTimeFlag) + + .def_readwrite("fPhysicalVolumeName", + &GateVoxelizedPromptGammaAnalogActor::fPhysicalVolumeName) + .def_readwrite( + "cpp_tof_neutron_image", + &GateVoxelizedPromptGammaAnalogActor::cpp_tof_neutron_image) + .def_readwrite("cpp_tof_proton_image", + &GateVoxelizedPromptGammaAnalogActor::cpp_tof_proton_image) + .def_readwrite("cpp_E_neutron_image", + &GateVoxelizedPromptGammaAnalogActor::cpp_E_neutron_image) + .def_readwrite("cpp_E_proton_image", + &GateVoxelizedPromptGammaAnalogActor::cpp_E_proton_image); +} diff --git a/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaTLEActor.cpp b/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaTLEActor.cpp new file mode 100644 index 000000000..64458959f --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaTLEActor.cpp @@ -0,0 +1,79 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include +#include + +namespace py = pybind11; + +#include "GateVoxelizedPromptGammaTLEActor.h" + +class PyGateVoxelizedPromptGammaTLEActor + : public GateVoxelizedPromptGammaTLEActor { +public: + // Inherit the constructors + using GateVoxelizedPromptGammaTLEActor::GateVoxelizedPromptGammaTLEActor; + + void BeginOfRunActionMasterThread(int run_id) override { + PYBIND11_OVERLOAD(void, GateVoxelizedPromptGammaTLEActor, + BeginOfRunActionMasterThread, run_id); + } + + int EndOfRunActionMasterThread(int run_id) override { + PYBIND11_OVERLOAD(int, GateVoxelizedPromptGammaTLEActor, + EndOfRunActionMasterThread, run_id); + } +}; + +void init_GateVoxelizedPromptGammaTLEActor(py::module &m) { + py::class_, + GateVActor>(m, "GateVoxelizedPromptGammaTLEActor") + .def(py::init()) + .def("InitializeUserInfo", + &GateVoxelizedPromptGammaTLEActor::InitializeUserInfo) + .def("BeginOfRunActionMasterThread", + &GateVoxelizedPromptGammaTLEActor::BeginOfRunActionMasterThread) + .def("EndOfRunActionMasterThread", + &GateVoxelizedPromptGammaTLEActor::EndOfRunActionMasterThread) + .def("SetPhysicalVolumeName", + &GateVoxelizedPromptGammaTLEActor::SetPhysicalVolumeName) + .def("GetPhysicalVolumeName", + &GateVoxelizedPromptGammaTLEActor::GetPhysicalVolumeName) + .def("SetVector", &GateVoxelizedPromptGammaTLEActor::SetVector) + .def("SetProtonTimeFlag", + &GateVoxelizedPromptGammaTLEActor::SetProtonTimeFlag) + .def("GetProtonTimeFlag", + &GateVoxelizedPromptGammaTLEActor::GetProtonTimeFlag) + + .def("SetProtonEnergyFlag", + &GateVoxelizedPromptGammaTLEActor::SetProtonEnergyFlag) + .def("GetProtonEnergyFlag", + &GateVoxelizedPromptGammaTLEActor::GetProtonEnergyFlag) + + .def("SetNeutronEnergyFlag", + &GateVoxelizedPromptGammaTLEActor::SetNeutronEnergyFlag) + .def("GetNeutronEnergyFlag", + &GateVoxelizedPromptGammaTLEActor::GetNeutronEnergyFlag) + + .def("SetNeutronTimeFlag", + &GateVoxelizedPromptGammaTLEActor::SetNeutronTimeFlag) + .def("GetNeutronTimeFlag", + &GateVoxelizedPromptGammaTLEActor::GetNeutronTimeFlag) + + .def_readwrite("fPhysicalVolumeName", + &GateVoxelizedPromptGammaTLEActor::fPhysicalVolumeName) + .def_readwrite("cpp_tof_neutron_image", + &GateVoxelizedPromptGammaTLEActor::cpp_tof_neutron_image) + .def_readwrite("cpp_tof_proton_image", + &GateVoxelizedPromptGammaTLEActor::cpp_tof_proton_image) + .def_readwrite("cpp_E_neutron_image", + &GateVoxelizedPromptGammaTLEActor::cpp_E_neutron_image) + .def_readwrite("cpp_E_proton_image", + &GateVoxelizedPromptGammaTLEActor::cpp_E_proton_image); +} diff --git a/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaTLESource.cpp b/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaTLESource.cpp new file mode 100644 index 000000000..69b4b74e2 --- /dev/null +++ b/core/opengate_core/opengate_lib/pyGateVoxelizedPromptGammaTLESource.cpp @@ -0,0 +1,24 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include + +namespace py = pybind11; + +#include "GateVoxelizedPromptGammaTLESource.h" + +void init_GateVoxelizedPromptGammaTLESource(py::module &m) { + + py::class_( + m, "GateVoxelizedPromptGammaTLESource") + .def(py::init()) + .def("GetSPSVoxelPosDistribution", + &GateVoxelizedPromptGammaTLESource::GetSPSVoxelPosDistribution, + py::return_value_policy::reference_internal) + .def("InitializeUserInfo", + &GateVoxelizedPromptGammaTLESource::InitializeUserInfo); +} diff --git a/opengate/actors/actoroutput.py b/opengate/actors/actoroutput.py index 7b03e79e9..5037f4aba 100644 --- a/opengate/actors/actoroutput.py +++ b/opengate/actors/actoroutput.py @@ -6,6 +6,7 @@ import opengate_core as g4 from ..base import GateObject, process_cls +from ..image import create_3d_image_of_histogram from ..utility import insert_suffix_before_extension, ensure_filename_is_str from ..exception import warning, fatal, GateImplementationError from .dataitems import ( @@ -845,11 +846,25 @@ def create_empty_image(self, run_index, size, spacing, origin=None, **kwargs): ) +class ActorOutputImageOfHistogram(ActorOutputImage): + def create_image_of_histograms( + self, run_index, size, spacing, bins, origin=None, **kwargs + ): + if run_index not in self.data_per_run: + self.data_per_run[run_index] = self.data_container_class(belongs_to=self) + img = create_3d_image_of_histogram(size, spacing, bins, origin, **kwargs) + self.data_per_run[run_index].set_data(img) + + # concrete classes usable in Actors: class ActorOutputSingleImage(ActorOutputImage): data_container_class = SingleItkImage +class ActorOutputSingleImageOfHistogram(ActorOutputImageOfHistogram): + data_container_class = SingleItkImage + + class ActorOutputSingleMeanImage(ActorOutputImage): data_container_class = SingleMeanItkImage @@ -960,6 +975,7 @@ def initialize_cpp_parameters(self): process_cls(ActorOutputUsingDataItemContainer) process_cls(ActorOutputImage) process_cls(ActorOutputSingleImage) +process_cls(ActorOutputSingleImageOfHistogram) process_cls(ActorOutputSingleMeanImage) process_cls(ActorOutputSingleImageWithVariance) process_cls(ActorOutputQuotientImage) diff --git a/opengate/actors/dataitems.py b/opengate/actors/dataitems.py index 9514dff4e..2906eb8ae 100644 --- a/opengate/actors/dataitems.py +++ b/opengate/actors/dataitems.py @@ -298,7 +298,17 @@ def set_image_properties(self, **properties): if "origin" in properties and properties["origin"] is not None: self.data.SetOrigin(properties["origin"]) if "rotation" in properties and properties["rotation"] is not None: - self.data.SetDirection(properties["rotation"]) + r = properties["rotation"] + if self.data.GetImageDimension() == 4: + # for 4D image, the rotation is enhanced + r = np.pad( + r, + pad_width=((0, 1), (0, 1)), + mode="constant", + constant_values=0, + ) + r[3][3] = 1.0 + self.data.SetDirection(r) def get_image_properties(self): return get_info_from_image(self.data) diff --git a/opengate/actors/doseactors.py b/opengate/actors/doseactors.py index 61a2b209b..6d8c78c35 100644 --- a/opengate/actors/doseactors.py +++ b/opengate/actors/doseactors.py @@ -178,7 +178,10 @@ def _update_output_coordinate_system(self, which_output, run_index): origin = -size * spacing / 2.0 + spacing / 2.0 translation = np.array(self.translation) - origin_local = Rotation.from_matrix(self.rotation).apply(origin) + translation + # the slicing "[:3]" is required for 4D images (3D image of histograms) + origin_local = ( + Rotation.from_matrix(self.rotation).apply(origin[:3]) + translation[:3] + ) # image centered at (0,0,0), no rotation if self.output_coordinate_system is None: @@ -190,6 +193,9 @@ def _update_output_coordinate_system(self, which_output, run_index): # image centered at self.translation and rotated by self.rotation, # i.e. in the reference frame of the volume to which the actor is attached. elif self.output_coordinate_system in ("local",): + if size.shape != origin_local.shape: + # special case for 4D images + origin_local = np.append(origin_local, [0]) self.user_output[which_output].set_image_properties( run_index, origin=origin_local.tolist(), rotation=self.rotation ) diff --git a/opengate/actors/tleactors.py b/opengate/actors/tleactors.py new file mode 100644 index 000000000..ba4423cc1 --- /dev/null +++ b/opengate/actors/tleactors.py @@ -0,0 +1,453 @@ +import opengate_core as g4 +from ..exception import fatal +from ..utility import g4_units +from ..base import process_cls +from .actoroutput import ( + ActorOutputSingleImageOfHistogram, + UserInterfaceToActorOutputImage, +) + +from .doseactors import DoseActor, VoxelDepositActor + + +class TLEDoseActor(DoseActor, g4.GateTLEDoseActor): + """ + TLE = Track Length Estimator + + The Track Length Estimator (TLE) is a variance reduction technique used in Monte Carlo simulations to accelerate dose computation, particularly for photon transport. It calculates the dose contribution by integrating the track length of particles within a voxel, reducing the number of particles required for accurate results. TLE is efficient for photon simulations as it avoids the need to explicitly simulate individual interactions within each voxel. + + The main limitation of TLE is its inability to account for dose delocalization, as it assumes energy deposition occurs along the particle's track within a voxel. TLE is typically restricted to photons with energies below 1-2 MeV for standard voxel sizes, as higher energies may result in larger dose spread beyond the voxel boundaries, reducing accuracy. + + Two databases can be used: EPDL (Evaluated Photon Data Library): Provides detailed photon interaction cross-sections, including photoelectric, Compton scattering, and pair production, for accurate modeling across a wide energy range. NIST (National Institute of Standards and Technology) XCOM database: Supplies photon attenuation and energy absorption coefficients, widely used for material property definitions and dose computations. + + """ + + energy_min: float + energy_max: float + database: str + + user_info_defaults = { + "energy_min": ( + 0.0, + {"doc": "Kill the gamma if below this energy"}, + ), + "energy_max": ( + 1.0 * g4_units.MeV, + { + "doc": "Above this energy, do not perform TLE (TLE is only relevant for low energy gamma)" + }, + ), + "database": ( + "EPDL", + { + "doc": "which database to use", + "allowed_values": ("EPDL", "NIST"), # "simulated" does not work + }, + ), + } + + def __initcpp__(self): + g4.GateTLEDoseActor.__init__(self, self.user_info) + self.AddActions( + { + "BeginOfRunActionMasterThread", + "EndOfRunActionMasterThread", + "BeginOfRunAction", + "EndOfRunAction", + "BeginOfEventAction", + "SteppingAction", + "PreUserTrackingAction", + } + ) + + def initialize(self, *args): + if self.score_in != "material": + fatal( + f"TLEDoseActor cannot score in {self.score_in}, only 'material' is allowed." + ) + super().initialize(args) + + +class VoxelizedPromptGammaTLEActor( + VoxelDepositActor, g4.GateVoxelizedPromptGammaTLEActor +): + """ + FIXME doc todo + """ + + user_info_defaults = { + "timebins": ( + 200, + { + "doc": "Number of bins in the histogram for time", + }, + ), + "timerange": ( + 5 * g4_units.ns, + { + "doc": "Range of the histogram in ns", + }, + ), + "energybins": ( + 250, + { + "doc": "Number of bins in the histogram for energy", + }, + ), + "energyrange": ( + 200 * g4_units.MeV, + { + "doc": "Range of the histogram in MeV", + }, + ), + "weight": ( + True, + { + "doc": "if the ToF spectra is weighted or not", + }, + ), + "vect_p": ( + None, + { + "doc": "Vector of weights for proton ToF deposition.", + }, + ), + "vect_n": ( + None, + { + "doc": "Vector of weights for neutron ToF deposition.", + }, + ), + } + + user_output_config = { + "p_E": { + "actor_output_class": ActorOutputSingleImageOfHistogram, + "interfaces": { + "prot_E": { + "interface_class": UserInterfaceToActorOutputImage, + "item": 0, + "active": True, + }, + }, + }, + "p_tof": { + "actor_output_class": ActorOutputSingleImageOfHistogram, + "interfaces": { + "prot_tof": { + "interface_class": UserInterfaceToActorOutputImage, + "item": 0, + "active": False, + }, + }, + }, + "n_E": { + "actor_output_class": ActorOutputSingleImageOfHistogram, + "interfaces": { + "neutr_E": { + "interface_class": UserInterfaceToActorOutputImage, + "item": 0, + "active": False, + }, + }, + }, + "n_tof": { + "actor_output_class": ActorOutputSingleImageOfHistogram, + "interfaces": { + "neutr_tof": { + "interface_class": UserInterfaceToActorOutputImage, + "item": 0, + "active": False, + }, + }, + }, + } + + def __init__(self, *args, **kwargs): + + VoxelDepositActor.__init__(self, *args, **kwargs) + self.__initcpp__() + + def __initcpp__(self): + + g4.GateVoxelizedPromptGammaTLEActor.__init__(self, self.user_info) + self.AddActions( + { + "BeginOfRunActionMasterThread", + "BeginOfEventAction", + "BeginOfRunAction", + "SteppingAction", + "EndOfRunAction", + "EndOfRunActionMasterThread", + } + ) + + def initialize(self, *args): + + self.check_user_input() + VoxelDepositActor.initialize(self) + + if not (self.user_output.p_E.get_active(item=0)): + self.user_output.p_E.set_write_to_disk(False, item=0) + if not (self.user_output.p_tof.get_active(item=0)): + self.user_output.p_tof.set_write_to_disk(False, item=0) + if not (self.user_output.n_E.get_active(item=0)): + self.user_output.n_E.set_write_to_disk(False, item=0) + if not (self.user_output.n_tof.get_active(item=0)): + self.user_output.n_tof.set_write_to_disk(False, item=0) + + self.InitializeUserInfo(self.user_info) + + self.SetProtonEnergyFlag(self.user_output.p_E.get_active(item=0)) + self.SetProtonTimeFlag(self.user_output.p_tof.get_active(item=0)) + self.SetNeutronEnergyFlag(self.user_output.n_E.get_active(item=0)) + self.SetNeutronTimeFlag(self.user_output.n_tof.get_active(item=0)) + + self.SetVector(self.user_info.get("vect_p"), self.user_info.get("vect_n")) + + self.SetPhysicalVolumeName(self.user_info.get("attached_to")) + self.InitializeCpp() + + def prepare_output_for_run(self, output_name, run_index, **kwargs): + # need to override because create image is different for img of histo + self._assert_output_exists(output_name) + if (output_name == "p_E") or (output_name == "n_E"): + self.user_output[output_name].create_image_of_histograms( + run_index, + self.size, + self.spacing, + self.energybins + 1, + origin=self.translation, + **kwargs, + ) + if (output_name == "p_tof") or (output_name == "n_tof"): + self.user_output[output_name].create_image_of_histograms( + run_index, + self.size, + self.spacing, + self.timebins + 1, + origin=self.translation, + **kwargs, + ) + + def BeginOfRunActionMasterThread(self, run_index): + if self.user_output.p_E.get_active(item=0): + self.prepare_output_for_run("p_E", run_index) + self.push_to_cpp_image("p_E", run_index, self.cpp_E_proton_image) + if self.user_output.p_tof.get_active(item=0): + self.prepare_output_for_run("p_tof", run_index) + self.push_to_cpp_image("p_tof", run_index, self.cpp_tof_proton_image) + if self.user_output.n_E.get_active(item=0): + self.prepare_output_for_run("n_E", run_index) + self.push_to_cpp_image("n_E", run_index, self.cpp_E_neutron_image) + if self.user_output.n_tof.get_active(item=0): + self.prepare_output_for_run("n_tof", run_index) + self.push_to_cpp_image("n_tof", run_index, self.cpp_tof_neutron_image) + g4.GateVoxelizedPromptGammaTLEActor.BeginOfRunActionMasterThread( + self, run_index + ) + + def EndOfRunActionMasterThread(self, run_index): + if self.user_output.p_E.get_active(item=0): + self.fetch_from_cpp_image("p_E", run_index, self.cpp_E_proton_image) + self._update_output_coordinate_system("p_E", run_index) + if self.user_output.p_tof.get_active(item=0): + self.fetch_from_cpp_image("p_tof", run_index, self.cpp_tof_proton_image) + self._update_output_coordinate_system("p_tof", run_index) + if self.user_output.n_E.get_active(item=0): + self.fetch_from_cpp_image("n_E", run_index, self.cpp_E_neutron_image) + self._update_output_coordinate_system("n_E", run_index) + if self.user_output.n_tof.get_active(item=0): + self.fetch_from_cpp_image("n_tof", run_index, self.cpp_tof_neutron_image) + self._update_output_coordinate_system("n_tof", run_index) + VoxelDepositActor.EndOfRunActionMasterThread(self, run_index) + return 0 + + def EndSimulationAction(self): + g4.GateVoxelizedPromptGammaTLEActor.EndSimulationAction(self) + VoxelDepositActor.EndSimulationAction(self) + + +class VoxelizedPromptGammaAnalogActor( + VoxelDepositActor, g4.GateVoxelizedPromptGammaAnalogActor +): + """ + FIXME doc todo + """ + + user_info_defaults = { + "timebins": ( + 200, + { + "doc": "Number of bins in the histogram for time", + }, + ), + "timerange": ( + 5 * g4_units.ns, + { + "doc": "Range of the histogram in ns", + }, + ), + "energybins": ( + 250, + { + "doc": "Number of bins in the histogram for energy", + }, + ), + "energyrange": ( + 200 * g4_units.MeV, + { + "doc": "Range of the histogram in MeV", + }, + ), + } + + user_output_config = { + "p_E": { + "actor_output_class": ActorOutputSingleImageOfHistogram, + "interfaces": { + "prot_E": { + "interface_class": UserInterfaceToActorOutputImage, + "item": 0, + "active": True, + }, + }, + }, + "p_tof": { + "actor_output_class": ActorOutputSingleImageOfHistogram, + "interfaces": { + "prot_tof": { + "interface_class": UserInterfaceToActorOutputImage, + "item": 0, + "active": False, + }, + }, + }, + "n_E": { + "actor_output_class": ActorOutputSingleImageOfHistogram, + "interfaces": { + "neutr_E": { + "interface_class": UserInterfaceToActorOutputImage, + "item": 0, + "active": False, + }, + }, + }, + "n_tof": { + "actor_output_class": ActorOutputSingleImageOfHistogram, + "interfaces": { + "neutr_tof": { + "interface_class": UserInterfaceToActorOutputImage, + "item": 0, + "active": False, + }, + }, + }, + } + + def __init__(self, *args, **kwargs): + + VoxelDepositActor.__init__(self, *args, **kwargs) + self.__initcpp__() + + def __initcpp__(self): + + g4.GateVoxelizedPromptGammaAnalogActor.__init__(self, self.user_info) + self.AddActions( + { + "BeginOfRunActionMasterThread", + "BeginOfEventAction", + "BeginOfRunAction", + "SteppingAction", + "EndOfRunAction", + "EndOfRunActionMasterThread", + } + ) + + def initialize(self, *args): + + self.check_user_input() + VoxelDepositActor.initialize(self) + if not (self.user_output.p_E.get_active(item=0)): + self.user_output.p_E.set_write_to_disk(False, item=0) + if not (self.user_output.p_tof.get_active(item=0)): + self.user_output.p_tof.set_write_to_disk(False, item=0) + if not (self.user_output.n_E.get_active(item=0)): + self.user_output.n_E.set_write_to_disk(False, item=0) + if not (self.user_output.n_tof.get_active(item=0)): + self.user_output.n_tof.set_write_to_disk(False, item=0) + + self.InitializeUserInfo(self.user_info) + + self.SetProtonEnergyFlag(self.user_output.p_E.get_active(item=0)) + self.SetProtonTimeFlag(self.user_output.p_tof.get_active(item=0)) + self.SetNeutronEnergyFlag(self.user_output.n_E.get_active(item=0)) + self.SetNeutronTimeFlag(self.user_output.n_tof.get_active(item=0)) + + self.SetPhysicalVolumeName(self.user_info.get("attached_to")) + self.InitializeCpp() + + def prepare_output_for_run(self, output_name, run_index, **kwargs): + # need to override because create image is different for img of histo + self._assert_output_exists(output_name) + if (output_name == "p_E") or (output_name == "n_E"): + self.user_output[output_name].create_image_of_histograms( + run_index, + self.size, + self.spacing, + self.energybins + 1, + origin=self.translation, + **kwargs, + ) + if (output_name == "p_tof") or (output_name == "n_tof"): + self.user_output[output_name].create_image_of_histograms( + run_index, + self.size, + self.spacing, + self.timebins + 1, + origin=self.translation, + **kwargs, + ) + + def BeginOfRunActionMasterThread(self, run_index): + + if self.user_output.p_E.get_active(item=0): + self.prepare_output_for_run("p_E", run_index) + self.push_to_cpp_image("p_E", run_index, self.cpp_E_proton_image) + if self.user_output.p_tof.get_active(item=0): + self.prepare_output_for_run("p_tof", run_index) + self.push_to_cpp_image("p_tof", run_index, self.cpp_tof_proton_image) + if self.user_output.n_E.get_active(item=0): + self.prepare_output_for_run("n_E", run_index) + self.push_to_cpp_image("n_E", run_index, self.cpp_E_neutron_image) + if self.user_output.n_tof.get_active(item=0): + self.prepare_output_for_run("n_tof", run_index) + self.push_to_cpp_image("n_tof", run_index, self.cpp_tof_neutron_image) + g4.GateVoxelizedPromptGammaAnalogActor.BeginOfRunActionMasterThread( + self, run_index + ) + + def EndOfRunActionMasterThread(self, run_index): + if self.user_output.p_E.get_active(item=0): + self.fetch_from_cpp_image("p_E", run_index, self.cpp_E_proton_image) + self._update_output_coordinate_system("p_E", run_index) + if self.user_output.p_tof.get_active(item=0): + self.fetch_from_cpp_image("p_tof", run_index, self.cpp_tof_proton_image) + self._update_output_coordinate_system("p_tof", run_index) + if self.user_output.n_E.get_active(item=0): + self.fetch_from_cpp_image("n_E", run_index, self.cpp_E_neutron_image) + self._update_output_coordinate_system("n_E", run_index) + if self.user_output.n_tof.get_active(item=0): + self.fetch_from_cpp_image("n_tof", run_index, self.cpp_tof_neutron_image) + self._update_output_coordinate_system("n_tof", run_index) + VoxelDepositActor.EndOfRunActionMasterThread(self, run_index) + return 0 + + def EndSimulationAction(self): + g4.GateVoxelizedPromptGammaAnalogActor.EndSimulationAction(self) + VoxelDepositActor.EndSimulationAction(self) + + +process_cls(TLEDoseActor) +process_cls(VoxelizedPromptGammaTLEActor) +process_cls(VoxelizedPromptGammaAnalogActor) diff --git a/opengate/contrib/vpgTLE/howto_vpg_TLE.pdf b/opengate/contrib/vpgTLE/howto_vpg_TLE.pdf new file mode 100644 index 000000000..33a582290 Binary files /dev/null and b/opengate/contrib/vpgTLE/howto_vpg_TLE.pdf differ diff --git a/opengate/contrib/vpgTLE/stage0/CMakeLists.txt b/opengate/contrib/vpgTLE/stage0/CMakeLists.txt new file mode 100644 index 000000000..f769e1fe5 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/CMakeLists.txt @@ -0,0 +1,62 @@ +#---------------------------------------------------------------------------- +# Setup the project +cmake_minimum_required(VERSION 3.16...3.27) +project(stage0) + +#---------------------------------------------------------------------------- +# Find Geant4 package, activating all available UI and Vis drivers by default +# You can set WITH_GEANT4_UIVIS to OFF via the command line or ccmake/cmake-gui +# to build a batch mode only executable +# +option(WITH_GEANT4_UIVIS "Build example with Geant4 UI and Vis drivers" ON) +if(WITH_GEANT4_UIVIS) + find_package(Geant4 REQUIRED ui_all vis_all) +else() + find_package(Geant4 REQUIRED) +endif() + +#---------------------------------------------------------------------------- +# Setup Geant4 include directories and compile definitions +# +find_package(ROOT REQUIRED COMPONENTS Tree RIO) +include(${ROOT_USE_FILE}) +include(${Geant4_USE_FILE}) + +#---------------------------------------------------------------------------- +# Locate sources and headers for this project +# +include_directories(${PROJECT_SOURCE_DIR}/include + ${Geant4_INCLUDE_DIR}) +file(GLOB sources ${PROJECT_SOURCE_DIR}/src/*.cc) +file(GLOB headers ${PROJECT_SOURCE_DIR}/include/*.hh) +# ROOT setup + + + + +#---------------------------------------------------------------------------- +# Add the executable, and link it to the Geant4 libraries +# +add_executable(stage0 stage0-vpgtle.cc ${sources} ${headers}) +target_link_libraries(stage0 ${Geant4_LIBRARIES} ${ROOT_LIBRARIES}) + + +#---------------------------------------------------------------------------- +# Copy all scripts to the build directory, i.e. the directory in which we +# build Hadr09. This is so that we can run the executable directly because it +# relies on these scripts being in the current working directory. +# +set(Hadr09_SCRIPTS) + +foreach(_script ${Hadr09_SCRIPTS}) + configure_file( + ${PROJECT_SOURCE_DIR}/${_script} + ${PROJECT_BINARY_DIR}/${_script} + COPYONLY + ) +endforeach() + +#---------------------------------------------------------------------------- +# Install the executable to 'bin' directory under CMAKE_INSTALL_PREFIX +# +install(TARGETS stage0 DESTINATION bin) diff --git a/opengate/contrib/vpgTLE/stage0/GNUmakefile b/opengate/contrib/vpgTLE/stage0/GNUmakefile new file mode 100644 index 000000000..6a86ce743 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/GNUmakefile @@ -0,0 +1,12 @@ +name := Hadr09 +G4TARGET := $(name) +G4EXLIB := true + +ifndef G4INSTALL + G4INSTALL = ../../.. +endif + +.PHONY: all +all: lib bin + +include $(G4INSTALL)/config/binmake.gmk diff --git a/opengate/contrib/vpgTLE/stage0/Hadr09.cc-ION_PROJECTILE b/opengate/contrib/vpgTLE/stage0/Hadr09.cc-ION_PROJECTILE new file mode 100644 index 000000000..8ea91dbec --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/Hadr09.cc-ION_PROJECTILE @@ -0,0 +1,365 @@ +// +// ******************************************************************** +// * License and Disclaimer * +// * * +// * The Geant4 software is copyright of the Copyright Holders of * +// * the Geant4 Collaboration. It is provided under the terms and * +// * conditions of the Geant4 Software License, included in the file * +// * LICENSE and available at http://cern.ch/geant4/license . These * +// * include a list of copyright holders. * +// * * +// * Neither the authors of this software system, nor their employing * +// * institutes,nor the agencies providing financial support for this * +// * work make any representation or warranty, express or implied, * +// * regarding this software system or assume any liability for its * +// * use. Please see the license in the file LICENSE and URL above * +// * for the full disclaimer and the limitation of liability. * +// * * +// * This code implementation is the result of the scientific and * +// * technical work of the GEANT4 collaboration. * +// * By using, copying, modifying or distributing the software (or * +// * any work based on the software) you agree to acknowledge its * +// * use in resulting scientific publications, and indicate your * +// * acceptance of all terms of the Geant4 Software license. * +// ******************************************************************** +// +// +/// \file Hadr09.cc +/// \brief Main program of the hadronic/Hadr09 example +// +//------------------------------------------------------------------------ +// This program shows how to use the class Hadronic Generator. +// The class HadronicGenerator is a kind of "hadronic generator", i.e. +// provides Geant4 final states (i.e. secondary particles) produced by +// hadron-nuclear inelastic collisions. +// Please see the class itself for more information. +// +// The use of the class Hadronic Generator is very simple: +// the constructor needs to be invoked only once - specifying the name +// of the Geant4 "physics case" to consider ("FTFP_BERT" will be +// considered as default is the name is not specified) - and then one +// method needs to be called at each collision, specifying the type of +// collision (hadron, energy, direction, material) to be simulated. +// The class HadronicGenerator is expected to work also in a +// multi-threaded environment with "external" threads (i.e. threads +// that are not necessarily managed by Geant4 run-manager): +// each thread should have its own instance of the class. +// +// See the string "***LOOKHERE***" below for the setting of parameters +// of this example: the "physics case", the set of possibilities from +// which to sample the projectile (i.e. whether the projectile is a +// hadron or an ion - in the case of hadron projectile, a list of hadrons +// is possible from which to sample at each collision; in the case of +// ion projectile, only one type of ion needs to be specified), +// the kinetic energy of the projectile (which can be sampled within +// an interval), whether the direction of the projectile is fixed or +// sampled at each collision, the target material (a list of materials +// is possible, from which the target material can be sampled at each +// collision, and then from this target material, the target nucleus +// will be chosen randomly by Geant4 itself), and whether to print out +// some information or not and how frequently. +// Once a well-defined type of hadron-nucleus or nucleus-nucleus +// inelastic collision has been chosen, the method +// HadronicGenerator::GenerateInteraction +// returns the secondaries produced by that interaction (in the form +// of a G4VParticleChange object). +// Some information about this final-state is printed out as an example. +// +// Usage: Hadr09 +//------------------------------------------------------------------------ + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +#include +#include "globals.hh" +#include "G4ios.hh" +#include "G4PhysicalConstants.hh" +#include "G4SystemOfUnits.hh" +#include "G4Material.hh" +#include "G4NistManager.hh" +#include "G4VParticleChange.hh" +#include "G4UnitsTable.hh" +#include "G4SystemOfUnits.hh" +#include "HadronicGenerator.hh" +#include "G4GenericIon.hh" +#include "G4ProcessManager.hh" +#include "G4ParticleTable.hh" +#include "G4IonTable.hh" +#include "CLHEP/Random/Randomize.h" +#include "CLHEP/Random/Ranlux64Engine.h" + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +int main( int , char** ) { + + G4cout << "=== Test of the HadronicGenerator ===" << G4endl; + + // See the HadronicGenerator class for the possibilities and meaning of the "physics cases". + // ( In short, it is the name of the Geant4 hadronic model used for the simulation of + // the collision, with the possibility of having a transition between two models in + // a given energy interval, as in physics lists. ) + //const G4String namePhysics = "FTFP_BERT"; //***LOOKHERE*** PHYSICS CASE + //const G4String namePhysics = "FTFP_BERT_ATL"; + //const G4String namePhysics = "QGSP_BERT"; + //const G4String namePhysics = "QGSP_BIC"; + //const G4String namePhysics = "FTFP_INCLXX"; + const G4String namePhysics = "FTFP"; + //const G4String namePhysics = "QGSP"; + //const G4String namePhysics = "BERT"; + //const G4String namePhysics = "BIC"; + //const G4String namePhysics = "IonBIC"; + //const G4String namePhysics = "INCL"; + + // The kinetic energy of the projectile will be sampled randomly, with flat probability + // in the interval [minEnergy, maxEnergy]. + G4double minEnergy = 1.0*CLHEP::GeV; //***LOOKHERE*** HADRON PROJECTILE MIN Ekin + G4double maxEnergy = 30.0*CLHEP::GeV; //***LOOKHERE*** HADRON PROJECTILE MAX Ekin + + const G4int numCollisions = 1000; //***LOOKHERE*** NUMBER OF COLLISIONS + + // Enable or disable the print out of this program: if enabled, the number of secondaries + // produced in each collisions is printed out; moreover, once every "printingGap" + // collisions, the list of secondaries is printed out. + const G4bool isPrintingEnabled = true; //***LOOKHERE*** PRINT OUT ON/OFF + const G4int printingGap = 100; //***LOOKHERE*** GAP IN PRINTING + + // Vector of Geant4 names of hadron projectiles: one of this will be sampled randomly + // (with uniform probability) for each collision, when the projectile is not an ion. + // Note: comment out the corresponding line in order to exclude a particle. + std::vector< G4String > vecProjectiles; //***LOOKHERE*** POSSIBLE HADRON PROJECTILES + vecProjectiles.push_back( "pi-" ); + //Note: vecProjectiles.push_back( "pi0" ); // Excluded because too short-lived + vecProjectiles.push_back( "pi+" ); + vecProjectiles.push_back( "kaon-" ); + vecProjectiles.push_back( "kaon+" ); + vecProjectiles.push_back( "kaon0L" ); + vecProjectiles.push_back( "kaon0S" ); + //Note: vecProjectiles.push_back( "eta" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "eta_prime" ); // Excluded because too short-lived + vecProjectiles.push_back( "proton" ); + vecProjectiles.push_back( "neutron" ); + vecProjectiles.push_back( "deuteron" ); + vecProjectiles.push_back( "triton" ); + vecProjectiles.push_back( "He3" ); + vecProjectiles.push_back( "alpha" ); + vecProjectiles.push_back( "lambda" ); + vecProjectiles.push_back( "sigma-" ); + //Note: vecProjectiles.push_back( "sigma0" ); // Excluded because too short-lived + vecProjectiles.push_back( "sigma+" ); + vecProjectiles.push_back( "xi-" ); + vecProjectiles.push_back( "xi0" ); + vecProjectiles.push_back( "omega-" ); + vecProjectiles.push_back( "anti_proton" ); + vecProjectiles.push_back( "anti_neutron" ); + vecProjectiles.push_back( "anti_lambda" ); + vecProjectiles.push_back( "anti_sigma-" ); + //Note: vecProjectiles.push_back( "anti_sigma0" ); // Excluded because too short-lived + vecProjectiles.push_back( "anti_sigma+" ); + vecProjectiles.push_back( "anti_xi-" ); + vecProjectiles.push_back( "anti_xi0" ); + vecProjectiles.push_back( "anti_omega-" ); + vecProjectiles.push_back( "anti_deuteron" ); + vecProjectiles.push_back( "anti_triton" ); + vecProjectiles.push_back( "anti_He3" ); + vecProjectiles.push_back( "anti_alpha" ); + // Charm and bottom hadrons + vecProjectiles.push_back( "D+" ); + vecProjectiles.push_back( "D-" ); + vecProjectiles.push_back( "D0" ); + vecProjectiles.push_back( "anti_D0" ); + vecProjectiles.push_back( "Ds+" ); + vecProjectiles.push_back( "Ds-" ); + //Note: vecProjectiles.push_back( "etac" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "J/psi" ); // Excluded because too short-lived + vecProjectiles.push_back( "B+" ); + vecProjectiles.push_back( "B-" ); + vecProjectiles.push_back( "B0" ); + vecProjectiles.push_back( "anti_B0" ); + vecProjectiles.push_back( "Bs0" ); + vecProjectiles.push_back( "anti_Bs0" ); + vecProjectiles.push_back( "Bc+" ); + vecProjectiles.push_back( "Bc-" ); + //Note: vecProjectiles.push_back( "Upsilon" ); // Excluded because too short-lived + vecProjectiles.push_back( "lambda_c+" ); + vecProjectiles.push_back( "anti_lambda_c+" ); + //Note: vecProjectiles.push_back( "sigma_c+" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "anti_sigma_c+" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "sigma_c0" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "anti_sigma_c0" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "sigma_c++" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "anti_sigma_c++" ); // Excluded because too short-lived + vecProjectiles.push_back( "xi_c+" ); + vecProjectiles.push_back( "anti_xi_c+" ); + vecProjectiles.push_back( "xi_c0" ); + vecProjectiles.push_back( "anti_xi_c0" ); + vecProjectiles.push_back( "omega_c0" ); + vecProjectiles.push_back( "anti_omega_c0" ); + vecProjectiles.push_back( "lambda_b" ); + vecProjectiles.push_back( "anti_lambda_b" ); + //Note: vecProjectiles.push_back( "sigma_b+" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "anti_sigma_b+" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "sigma_b0" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "sigma_b0" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "sigma_b-" ); // Excluded because too short-lived + //Note: vecProjectiles.push_back( "anti_sigma_b-" ); // Excluded because too short-lived + vecProjectiles.push_back( "xi_b0" ); + vecProjectiles.push_back( "anti_xi_b0" ); + vecProjectiles.push_back( "xi_b-" ); + vecProjectiles.push_back( "anti_xi_b-" ); + vecProjectiles.push_back( "omega_b-" ); + vecProjectiles.push_back( "anti_omega_b-" ); + + G4ParticleDefinition* projectileNucleus = nullptr; + G4GenericIon* gion = G4GenericIon::GenericIon(); + gion->SetProcessManager( new G4ProcessManager( gion ) ); + G4ParticleTable* partTable = G4ParticleTable::GetParticleTable(); + G4IonTable* ions = partTable->GetIonTable(); + partTable->SetReadiness(); + ions->CreateAllIon(); + ions->CreateAllIsomer(); + + const G4bool isProjectileIon = true; //***LOOKHERE*** HADRON (false) OR ION (true) PROJECTILE ? + if ( isProjectileIon ) { + minEnergy = 40.0*13.0*CLHEP::GeV; //***LOOKHERE*** ION PROJECTILE MIN Ekin + maxEnergy = 40.0*13.0*CLHEP::GeV; //***LOOKHERE*** ION PROJECTILE MAX Ekin + G4int ionZ = 18, ionA = 40; //***LOOKHERE*** ION PROJECTILE (Z, A) + projectileNucleus = partTable->GetIonTable()->GetIon( ionZ, ionA, 0.0 ); + } + + // Vector of Geant4 NIST names of materials: one of this will be sampled randomly + // (with uniform probability) for each collision and used as target material. + // Note: comment out the corresponding line in order to exclude a material; + // or, vice versa, add a new line to extend the list with another material. + std::vector< G4String > vecMaterials; //***LOOKHERE*** : NIST TARGET MATERIALS + //vecMaterials.push_back( "G4_H" ); + //vecMaterials.push_back( "G4_He" ); + //vecMaterials.push_back( "G4_Be" ); + //vecMaterials.push_back( "G4_C" ); + //vecMaterials.push_back( "G4_Al" ); + //vecMaterials.push_back( "G4_Si" ); + vecMaterials.push_back( "G4_Sc" ); + //vecMaterials.push_back( "G4_Ar" ); + //vecMaterials.push_back( "G4_Fe" ); + //vecMaterials.push_back( "G4_Cu" ); + //vecMaterials.push_back( "G4_W" ); + //vecMaterials.push_back( "G4_Pb" ); + + const G4int numProjectiles = vecProjectiles.size(); + const G4int numMaterials = vecMaterials.size(); + + G4cout << G4endl + << "================= Configuration ==================" << G4endl + << "Model: " << namePhysics << G4endl + << "Ekin: [ " << minEnergy/CLHEP::GeV << " , " << maxEnergy/CLHEP::GeV + << " ] GeV" << G4endl + << "Number of collisions: " << numCollisions << G4endl + << "Number of hadron projectiles: " << numProjectiles << G4endl + << "Number of materials: " << numMaterials << G4endl + << "IsIonProjectile: " << ( projectileNucleus != nullptr ? "true \t" : "false" ) + << ( projectileNucleus != nullptr ? projectileNucleus->GetParticleName() : "") << G4endl + << "===================================================" << G4endl + << G4endl; + + CLHEP::Ranlux64Engine defaultEngine( 1234567, 4 ); + CLHEP::HepRandom::setTheEngine( &defaultEngine ); + G4int seed = time( NULL ); + CLHEP::HepRandom::setTheSeed( seed ); + G4cout << G4endl << " Initial seed = " << seed << G4endl << G4endl; + + // Instanciate the HadronicGenerator providing the name of the "physics case" + HadronicGenerator* theHadronicGenerator = new HadronicGenerator( namePhysics ); + //**************************************************************************** + + if ( theHadronicGenerator == nullptr ) { + G4cerr << "ERROR: theHadronicGenerator is NULL !" << G4endl; + return 1; + } else if ( ! theHadronicGenerator->IsPhysicsCaseSupported() ) { + G4cerr << "ERROR: this physics case is NOT supported !" << G4endl; + return 2; + } + + // Loop over the collisions + G4double rnd1, rnd2, rnd3, rnd4, rnd5, rnd6, normalization, projectileEnergy; + G4VParticleChange* aChange = nullptr; + for ( G4int i = 0; i < numCollisions; ++i ) { + // Draw some random numbers to select the hadron-nucleus interaction: + // projectile hadron, projectile kinetic energy, projectile direction, and target material. + rnd1 = CLHEP::HepRandom::getTheEngine()->flat(); + rnd2 = CLHEP::HepRandom::getTheEngine()->flat(); + rnd3 = CLHEP::HepRandom::getTheEngine()->flat(); + rnd4 = CLHEP::HepRandom::getTheEngine()->flat(); + rnd5 = CLHEP::HepRandom::getTheEngine()->flat(); + rnd6 = CLHEP::HepRandom::getTheEngine()->flat(); + // Sample the projectile kinetic energy + projectileEnergy = minEnergy + rnd1*( maxEnergy - minEnergy ); + if ( projectileEnergy <= 0.0 ) projectileEnergy = minEnergy; + // Sample the projectile direction + normalization = 1.0 / std::sqrt( rnd2*rnd2 + rnd3*rnd3 + rnd4*rnd4 ); + const G4bool isOnSmearingDirection = false; //***LOOKHERE*** IF true THEN SMEAR DIRECTION + G4ThreeVector aDirection = G4ThreeVector( 0.0, 0.0, 1.0 ); //***LOOKHERE*** ELSE USE THIS FIXED DIRECTION + if ( isOnSmearingDirection ) { + aDirection = G4ThreeVector( normalization*rnd2, normalization*rnd3, normalization*rnd4 ); + } + // Sample the projectile hadron from the vector vecProjectiles + G4int index_projectile = std::trunc( rnd5*numProjectiles ); + G4String nameProjectile = vecProjectiles[ index_projectile ]; + G4ParticleDefinition* projectile = partTable->FindParticle( nameProjectile ); + if ( projectileNucleus ) { + nameProjectile = projectileNucleus->GetParticleName(); + projectile = projectileNucleus; + } + // Sample the target material from the vector vecMaterials + // (Note: the target nucleus will be sampled by Geant4) + G4int index_material = std::trunc( rnd6*numMaterials ); + G4String nameMaterial = vecMaterials[ index_material ]; + G4Material* material = G4NistManager::Instance()->FindOrBuildMaterial( nameMaterial ); + if ( material == nullptr ) { + G4cerr << "ERROR: Material " << nameMaterial << " is not found !" << G4endl; + return 3; + } + if ( isPrintingEnabled ) { + G4cout << "\t Collision " << i << " ; projectile=" << nameProjectile; + if ( projectileNucleus ) { + G4cout << " ; Ekin[MeV]/nucleon=" << projectileEnergy / + static_cast< G4double >( std::abs( projectileNucleus->GetBaryonNumber() ) ); + } else { + G4cout << " ; Ekin[MeV]=" << projectileEnergy; + } + G4cout << " ; direction=" << aDirection << " ; material=" << nameMaterial; + } + + // Call here the "hadronic generator" to get the secondaries produced by the hadronic collision + aChange = theHadronicGenerator->GenerateInteraction( projectile, projectileEnergy, + /* ********************************************** */ aDirection, material ); + + G4int nsec = aChange ? aChange->GetNumberOfSecondaries() : 0; + G4bool isPrintingOfSecondariesEnabled = false; + if ( isPrintingEnabled ) { + G4cout << G4endl << "\t --> #secondaries=" << nsec + << " ; impactParameter[fm]=" << theHadronicGenerator->GetImpactParameter() / fermi + << " ; #projectileSpectatorNucleons=" << theHadronicGenerator->GetNumberOfProjectileSpectatorNucleons() + << " ; #targetSpectatorNucleons=" << theHadronicGenerator->GetNumberOfTargetSpectatorNucleons() + << " ; #NNcollisions=" << theHadronicGenerator->GetNumberOfNNcollisions() << G4endl; + if ( i % printingGap == 0 ) { + isPrintingOfSecondariesEnabled = true; + G4cout << "\t \t List of produced secondaries: " << G4endl; + } + } + // Loop over produced secondaries and eventually print out some information. + for ( G4int j = 0; j < nsec; ++j ) { + const G4DynamicParticle* sec = aChange->GetSecondary(j)->GetDynamicParticle(); + if ( isPrintingOfSecondariesEnabled ) { + G4cout << "\t \t \t j=" << j << "\t" << sec->GetDefinition()->GetParticleName() + << "\t p=" << sec->Get4Momentum() << " MeV" << G4endl; + } + delete aChange->GetSecondary(j); + } + if ( aChange ) aChange->Clear(); + } + + G4cout << G4endl << " Final random number = " << CLHEP::HepRandom::getTheEngine()->flat() + << G4endl << "=== End of test ===" << G4endl; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/opengate/contrib/vpgTLE/stage0/HadronicGenerator.cc b/opengate/contrib/vpgTLE/stage0/HadronicGenerator.cc new file mode 100644 index 000000000..b6a4ff122 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/HadronicGenerator.cc @@ -0,0 +1,629 @@ +// +// ******************************************************************** +// * License and Disclaimer * +// * * +// * The Geant4 software is copyright of the Copyright Holders of * +// * the Geant4 Collaboration. It is provided under the terms and * +// * conditions of the Geant4 Software License, included in the file * +// * LICENSE and available at http://cern.ch/geant4/license . These * +// * include a list of copyright holders. * +// * * +// * Neither the authors of this software system, nor their employing * +// * institutes, nor the agencies providing financial support for this * +// * work make any representation or warranty, express or implied, * +// * regarding this software system or assume any liability for its * +// * use. Please see the license in the file LICENSE and URL above * +// * for the full disclaimer and the limitation of liability. * +// * * +// * This code implementation is the result of the scientific and * +// * technical work of the GEANT4 collaboration. * +// * By using, copying, modifying or distributing the software (or * +// * any work based on the software) you agree to acknowledge its * +// * use in resulting scientific publications, and indicate your * +// * acceptance of all terms of the Geant4 Software license. * +// ******************************************************************** +// +/// \file HadronicGenerator.cc +/// \brief Implementation of the HadronicGenerator class +// +//------------------------------------------------------------------------ +// Class: HadronicGenerator +// Author: Alberto Ribon (CERN EP/SFT) +// Date: May 2020 +//------------------------------------------------------------------------ + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +#include "HadronicGenerator-vpgtle.hh" + +#include +#include +#include + +#include "G4AblaInterface.hh" +#include "G4BGGNucleonInelasticXS.hh" +#include "G4BinaryCascade.hh" +#include "G4BinaryLightIonReaction.hh" +#include "G4Box.hh" +#include "G4CascadeInterface.hh" +#include "G4ComponentGGHadronNucleusXsc.hh" +#include "G4ComponentGGNuclNuclXsc.hh" +#include "G4CrossSectionDataStore.hh" +#include "G4CrossSectionInelastic.hh" +#include "G4DecayPhysics.hh" +#include "G4DynamicParticle.hh" +#include "G4ExcitationHandler.hh" +#include "G4ExcitedStringDecay.hh" +#include "G4FTFModel.hh" +#include "G4GeneratorPrecompoundInterface.hh" +#include "G4HadronInelasticProcess.hh" +#include "G4HadronicInteraction.hh" +#include "G4HadronicParameters.hh" +#include "G4HadronicProcessStore.hh" +#include "G4INCLXXInterface.hh" +#include "G4IonTable.hh" +#include "G4LundStringFragmentation.hh" +#include "G4Material.hh" +#include "G4NeutronInelasticXS.hh" +#include "G4PVPlacement.hh" +#include "G4ParticleTable.hh" +#include "G4PhysicalConstants.hh" +#include "G4PreCompoundModel.hh" +#include "G4ProcessManager.hh" +#include "G4QGSMFragmentation.hh" +#include "G4QGSModel.hh" +#include "G4QGSParticipants.hh" +#include "G4QuasiElasticChannel.hh" +#include "G4StateManager.hh" +#include "G4Step.hh" +#include "G4SystemOfUnits.hh" +#include "G4TheoFSGenerator.hh" +#include "G4TouchableHistory.hh" +#include "G4TransportationManager.hh" +#include "G4UnitsTable.hh" +#include "G4VCrossSectionDataSet.hh" +#include "G4VParticleChange.hh" +#include "G4ios.hh" +#include "globals.hh" + +// particle libraries +#include "G4Alpha.hh" +#include "G4Deuteron.hh" +#include "G4He3.hh" +#include "G4Neutron.hh" +#include "G4Proton.hh" +#include "G4Triton.hh" + +// HP libraries +#include "G4NeutronCaptureProcess.hh" +#include "G4NeutronHPCapture.hh" +#include "G4NeutronHPCaptureData.hh" +#include "G4NeutronHPElastic.hh" +#include "G4NeutronHPElasticData.hh" +#include "G4NeutronHPFission.hh" +#include "G4NeutronHPFissionData.hh" +#include "G4NeutronHPInelastic.hh" +#include "G4NeutronHPInelasticXS.hh" + +// root libraries +#include "TH1D.h" +#include "TH2D.h" + +#include "G4Element.hh" + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +HadronicGenerator::HadronicGenerator(const G4String physicsCase) + : fPhysicsCase(physicsCase), fLastHadronicProcess(nullptr), + fPartTable(nullptr) { + + // The constructor set-ups all the particles, models, cross sections and + // hadronic inelastic processes. + // This should be done only once for each application. + // In the case of a multi-threaded application using this class, + // the constructor should be invoked for each thread, + // i.e. one instance of the class should be kept per thread. + // The particles and processes that are created in this constructor + // will then be used by the method GenerateInteraction at each interaction. + // Notes: + // - Neither the hadronic models nor the cross sections are used directly + // by the method GenerateInteraction, but they are associated to the + // hadronic processes and used by Geant4 to simulate the collision; + // - Although the class generates only final states, but not free mean paths, + // inelastic hadron-nuclear cross sections are needed by Geant4 to sample + // the target nucleus from the target material. + + if (fPhysicsCase != "QGSP_BIC_HP") { + G4cerr + << "ERROR: Not supported final-state hadronic inelastic physics case !" + << fPhysicsCase << G4endl + << "\t Re-try by choosing one of the following:" << G4endl + << "\t - Hadronic models : QGSP" << G4endl + << "\t - \"Physics-list proxies\" : QGSP_BIC_HP" << G4endl; + } + + // Definition of particles + G4GenericIon *gion = G4GenericIon::Definition(); + gion->SetProcessManager(new G4ProcessManager(gion)); + G4DecayPhysics *decays = new G4DecayPhysics; + decays->ConstructParticle(); + fPartTable = G4ParticleTable::GetParticleTable(); + fPartTable->SetReadiness(); + G4IonTable *ions = fPartTable->GetIonTable(); + ions->CreateAllIon(); + ions->CreateAllIsomer(); + + std::vector myElements; + + myElements.push_back(new G4Element("Hydrogen", "H", 1., 1.01 * g / mole)); + myElements.push_back(new G4Element("Helium", "He", 2., 4.00 * g / mole)); + myElements.push_back(new G4Element("Lithium", "Li", 3., 6.94 * g / mole)); + myElements.push_back(new G4Element("Beryllium", "Be", 4., 9.01 * g / mole)); + myElements.push_back(new G4Element("Boron", "B", 5., 10.81 * g / mole)); + myElements.push_back(new G4Element("Carbon", "C", 6., 12.01 * g / mole)); + myElements.push_back(new G4Element("Nitrogen", "N", 7., 14.01 * g / mole)); + myElements.push_back(new G4Element("Oxygen", "O", 8., 16.00 * g / mole)); + myElements.push_back(new G4Element("Fluorine", "F", 9., 19.00 * g / mole)); + myElements.push_back(new G4Element("Neon", "Ne", 10., 20.18 * g / mole)); + myElements.push_back(new G4Element("Sodium", "Na", 11., 22.99 * g / mole)); + myElements.push_back(new G4Element("Magnesium", "Mg", 12., 24.31 * g / mole)); + myElements.push_back(new G4Element("Aluminium", "Al", 13., 26.98 * g / mole)); + myElements.push_back(new G4Element("Silicon", "Si", 14., 28.09 * g / mole)); + myElements.push_back(new G4Element("Phosphorus", "P", 15., 30.97 * g / mole)); + myElements.push_back(new G4Element("Sulfur", "S", 16., 32.07 * g / mole)); + myElements.push_back(new G4Element("Chlorine", "Cl", 17., 35.45 * g / mole)); + myElements.push_back(new G4Element("Argon", "Ar", 18., 39.95 * g / mole)); + myElements.push_back(new G4Element("Potassium", "K", 19., 39.10 * g / mole)); + myElements.push_back(new G4Element("Calcium", "Ca", 20., 40.08 * g / mole)); + + myElements.push_back(new G4Element("Titanium", "Ti", 22., 47.87 * g / mole)); + + myElements.push_back(new G4Element("Copper", "Cu", 29., 63.55 * g / mole)); + myElements.push_back(new G4Element("Zinc", "Zn", 30., 65.38 * g / mole)); + + myElements.push_back(new G4Element("Silver", "Ag", 47., 107.87 * g / mole)); + + myElements.push_back(new G4Element("Tin", "Sn", 50., 118.71 * g / mole)); + + // Build BIC model + G4BinaryCascade *theBICmodel = new G4BinaryCascade; + G4PreCompoundModel *thePreEquilib = + new G4PreCompoundModel(new G4ExcitationHandler); + theBICmodel->SetDeExcitation(thePreEquilib); + + // Build BinaryLightIon model + G4PreCompoundModel *thePreEquilibBis = + new G4PreCompoundModel(new G4ExcitationHandler); + G4BinaryLightIonReaction *theIonBICmodel = + new G4BinaryLightIonReaction(thePreEquilibBis); + + // HP model + G4NeutronHPInelastic *theNeutronHP = new G4NeutronHPInelastic; + theNeutronHP->BuildPhysicsTable(*G4Neutron::Neutron()); + // G4NeutronHPCapture* theNeutronHPCapture = new G4NeutronHPCapture; + // theNeutronHPCapture->BuildPhysicsTable(*G4Neutron::Neutron()); + + // Model instance with constraint to be above a kinetic energy threshold. + // (Used for ions in all physics lists) + + G4GeneratorPrecompoundInterface *theCascade = + new G4GeneratorPrecompoundInterface; + theCascade->SetDeExcitation(thePreEquilib); + G4LundStringFragmentation *theLundFragmentation = + new G4LundStringFragmentation; + G4ExcitedStringDecay *theStringDecay = + new G4ExcitedStringDecay(theLundFragmentation); + G4FTFModel *theStringModel = new G4FTFModel; + theStringModel->SetFragmentationModel(theStringDecay); + + G4TheoFSGenerator *theFTFPmodel_aboveThreshold = + new G4TheoFSGenerator("FTFP"); + theFTFPmodel_aboveThreshold->SetMaxEnergy( + G4HadronicParameters::Instance()->GetMaxEnergy()); + theFTFPmodel_aboveThreshold->SetTransport(theCascade); + theFTFPmodel_aboveThreshold->SetHighEnergyGenerator(theStringModel); + + // Model instance with constraint to be within two kinetic energy thresholds. + // (Used in the case of QGS-based physics lists for nucleons) + + G4TheoFSGenerator *theFTFPmodel_constrained = new G4TheoFSGenerator("FTFP"); + theFTFPmodel_constrained->SetMaxEnergy( + G4HadronicParameters::Instance()->GetMaxEnergy()); + theFTFPmodel_constrained->SetTransport(theCascade); + theFTFPmodel_constrained->SetHighEnergyGenerator(theStringModel); + + // Build the QGSP model + G4TheoFSGenerator *theQGSPmodel = new G4TheoFSGenerator("QGSP"); + theQGSPmodel->SetMaxEnergy(G4HadronicParameters::Instance()->GetMaxEnergy()); + theQGSPmodel->SetTransport(theCascade); + G4QGSMFragmentation *theQgsmFragmentation = new G4QGSMFragmentation; + G4ExcitedStringDecay *theQgsmStringDecay = + new G4ExcitedStringDecay(theQgsmFragmentation); + G4VPartonStringModel *theQgsmStringModel = new G4QGSModel; + theQgsmStringModel->SetFragmentationModel(theQgsmStringDecay); + theQGSPmodel->SetHighEnergyGenerator(theQgsmStringModel); + G4QuasiElasticChannel *theQuasiElastic = + new G4QuasiElasticChannel; // QGSP uses quasi-elastic + theQGSPmodel->SetQuasiElasticChannel(theQuasiElastic); + + // For the case of "physics-list proxies", select the energy range for each + // hadronic model. + + const G4double ftfpMinE = + G4HadronicParameters::Instance()->GetMinEnergyTransitionFTF_Cascade(); + const G4double BICMaxE = + G4HadronicParameters::Instance()->GetMaxEnergyTransitionFTF_Cascade(); + const G4double ftfpMaxE = + G4HadronicParameters::Instance()->GetMaxEnergyTransitionQGS_FTF(); + const G4double qgspMinE = + G4HadronicParameters::Instance()->GetMinEnergyTransitionQGS_FTF(); + + theBICmodel->SetMaxEnergy(BICMaxE); + theIonBICmodel->SetMaxEnergy(BICMaxE); + theFTFPmodel_aboveThreshold->SetMinEnergy(ftfpMinE); + theFTFPmodel_constrained->SetMinEnergy(ftfpMinE); + theFTFPmodel_constrained->SetMaxEnergy(ftfpMaxE); + theQGSPmodel->SetMinEnergy(qgspMinE); + + // Cross sections (needed by Geant4 to sample the target nucleus from the + // target material) + + G4VCrossSectionDataSet *theProtonXSdata = + new G4BGGNucleonInelasticXS(G4Proton::Proton()); + theProtonXSdata->BuildPhysicsTable(*(G4Proton::Definition())); + + G4VCrossSectionDataSet *theNeutronXSdata = new G4NeutronInelasticXS; + theNeutronXSdata->BuildPhysicsTable(*(G4Neutron::Definition())); + + G4VCrossSectionDataSet *theNeutronHPXSdatainel = new G4NeutronHPInelasticXS; + theNeutronHPXSdatainel->BuildPhysicsTable(*G4Neutron::Definition()); + + // G4VCrossSectionDataSet* theNeutronHPCapturedata = new + // G4NeutronHPCaptureData; + // theNeutronHPCapturedata->BuildPhysicsTable(*G4Neutron::Definition()); + + G4VCrossSectionDataSet *theHyperonsXSdata = + new G4CrossSectionInelastic(new G4ComponentGGHadronNucleusXsc); + + G4VCrossSectionDataSet *theNuclNuclXSdata = + new G4CrossSectionInelastic(new G4ComponentGGNuclNuclXsc); + + // Set up inelastic processes : store them in a map (with particle definition + // as key) + // for convenience + + typedef std::pair ProcessPair; + + G4HadronicProcess *theProtonInelasticProcess = + new G4HadronInelasticProcess("protonInelastic", G4Proton::Definition()); + fProcessMap.insert( + ProcessPair(G4Proton::Definition(), theProtonInelasticProcess)); + fProcessMapHP.insert( + ProcessPair(G4Proton::Definition(), theProtonInelasticProcess)); + + G4HadronicProcess *theNeutronHPInelasticProcess = + new G4HadronInelasticProcess("NeutronHPInelastic", + G4Neutron::Definition()); + fProcessMapHP.insert( + ProcessPair(G4Neutron::Definition(), theNeutronHPInelasticProcess)); + + G4HadronicProcess *theNeutronInelasticProcess = + new G4HadronInelasticProcess("neutronInelastic", G4Neutron::Definition()); + fProcessMap.insert( + ProcessPair(G4Neutron::Definition(), theNeutronInelasticProcess)); + + // For the HP model, we need to create a new process for the neutron : + // interaction that are considered as relevant are the capture and fission (+ + // inelastic HP process neutron -nucleus) + // Prompt-gamma timing with a track-length estimator in proton therapy, JM + // Létang et al, 2024 + + G4HadronicProcess *theDeuteronInelasticProcess = + new G4HadronInelasticProcess("dInelastic", G4Deuteron::Definition()); + fProcessMap.insert( + ProcessPair(G4Deuteron::Definition(), theDeuteronInelasticProcess)); + fProcessMapHP.insert( + ProcessPair(G4Deuteron::Definition(), theDeuteronInelasticProcess)); + + G4HadronicProcess *theTritonInelasticProcess = + new G4HadronInelasticProcess("tInelastic", G4Triton::Definition()); + fProcessMap.insert( + ProcessPair(G4Triton::Definition(), theTritonInelasticProcess)); + fProcessMapHP.insert( + ProcessPair(G4Triton::Definition(), theTritonInelasticProcess)); + + G4HadronicProcess *theHe3InelasticProcess = + new G4HadronInelasticProcess("he3Inelastic", G4He3::Definition()); + fProcessMap.insert(ProcessPair(G4He3::Definition(), theHe3InelasticProcess)); + fProcessMapHP.insert( + ProcessPair(G4He3::Definition(), theHe3InelasticProcess)); + + G4HadronicProcess *theAlphaInelasticProcess = + new G4HadronInelasticProcess("alphaInelastic", G4Alpha::Definition()); + fProcessMap.insert( + ProcessPair(G4Alpha::Definition(), theAlphaInelasticProcess)); + fProcessMapHP.insert( + ProcessPair(G4Alpha::Definition(), theAlphaInelasticProcess)); + + G4HadronicProcess *theIonInelasticProcess = + new G4HadronInelasticProcess("ionInelastic", G4GenericIon::Definition()); + fProcessMap.insert( + ProcessPair(G4GenericIon::Definition(), theIonInelasticProcess)); + fProcessMapHP.insert( + ProcessPair(G4GenericIon::Definition(), theIonInelasticProcess)); + + // Add the cross sections to the corresponding hadronic processes + + // cross-sections for nucleons + theProtonInelasticProcess->AddDataSet(theProtonXSdata); + theNeutronInelasticProcess->AddDataSet(theNeutronXSdata); + + // specific cross-sections for neutrons in HP model + theNeutronHPInelasticProcess->AddDataSet(theNeutronHPXSdatainel); + // theNeutronHPInelasticProcess->AddDataSet( theNeutronHPCapturedata ); + + // cross-sections for ions + theDeuteronInelasticProcess->AddDataSet(theNuclNuclXSdata); + theTritonInelasticProcess->AddDataSet(theNuclNuclXSdata); + theHe3InelasticProcess->AddDataSet(theNuclNuclXSdata); + theAlphaInelasticProcess->AddDataSet(theNuclNuclXSdata); + theIonInelasticProcess->AddDataSet(theNuclNuclXSdata); + + // Register the proper hadronic model(s) to the corresponding hadronic + // processes. in the physics list QGSP_BIC, BIC is used only for nucleons + + // processes for BIC => mean-energy on nucleons + theProtonInelasticProcess->RegisterMe(theBICmodel); + theNeutronInelasticProcess->RegisterMe(theBICmodel); + + // processes for QGSP => High-energy on nucleons + theProtonInelasticProcess->RegisterMe(theQGSPmodel); + theNeutronInelasticProcess->RegisterMe(theQGSPmodel); + + // processes for HP => low-energy on neutrons + theNeutronHPInelasticProcess->RegisterMe(theNeutronHP); + // theNeutronHPInelasticProcess->RegisterMe( theNeutronHPCapture ); + + // processes for ion (BIC for ions) + theDeuteronInelasticProcess->RegisterMe(theIonBICmodel); + theTritonInelasticProcess->RegisterMe(theIonBICmodel); + theHe3InelasticProcess->RegisterMe(theIonBICmodel); + theAlphaInelasticProcess->RegisterMe(theIonBICmodel); + theIonInelasticProcess->RegisterMe(theIonBICmodel); + + G4TheoFSGenerator *theFTFPmodelToBeUsed = theFTFPmodel_aboveThreshold; + + theFTFPmodelToBeUsed = theFTFPmodel_constrained; + theNeutronInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theProtonInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + + theFTFPmodelToBeUsed = theFTFPmodel_aboveThreshold; + theDeuteronInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theTritonInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theHe3InelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theAlphaInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theIonInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +HadronicGenerator::~HadronicGenerator() { fPartTable->DeleteAllParticles(); } + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +G4int HadronicGenerator::GenerateInteraction( + G4ParticleDefinition *projectileDefinition, G4Material *targetMaterial, + TH2D *TH2D_EpEpg, TH1D *TH1D_SigmaInelastic, TH2D *TH2D_GammaZ, + TH1D *TH1D_NrPG) { + // This is the most important method of the HadronicGenerator class: + // the method performs the specified hadronic interaction + // (by invoking the "PostStepDoIt" method of the corresponding hadronic + // process) and returns the final state, i.e. the secondaries produced by the + // collision. It is a relatively short method because the heavy load of + // setting up all possible hadronic processes - with their hadronic models, + // transition regions, and cross sections (the latter is needed for sampling + // the target nucleus from the target material) - was already done by the + // constructor of the class. + G4VParticleChange *aChange = nullptr; + G4int nbPG(0); + + if (projectileDefinition == nullptr) { + G4cerr << "ERROR: projectileDefinition is NULL !" << G4endl; + return 0; + } + + const G4Element *targetElement = targetMaterial->GetElement(0); // + G4double molarMass = targetElement->GetA() / (g / mole); + + // Geometry definition (not strictly needed) + const G4double dimX = 1.0 * mm; + const G4double dimY = 1.0 * mm; + const G4double dimZ = 1.0 * mm; + G4Box *sFrame = new G4Box("Box", dimX, dimY, dimZ); + G4LogicalVolume *lFrame = + new G4LogicalVolume(sFrame, targetMaterial, "Box", 0, 0, 0); + G4PVPlacement *pFrame = + new G4PVPlacement(0, G4ThreeVector(), "Box", lFrame, 0, false, 0); + G4TransportationManager::GetTransportationManager()->SetWorldForTracking( + pFrame); + + G4int numCollisions = 1e1; //***LOOKHERE*** NUMBER OF COLLISIONS + G4double rnd1(0); + G4double protonEnergy(0), GAMMAZPerRho(0), kappaPerRho(0), + kappaPerRhoMmCollisions(0); + G4int Ngamma(0); + G4double projectileEnergy; + G4ThreeVector projectileDirection = G4ThreeVector(0.0, 0.0, 1.0); + G4double CrossSection = 0; + + G4double energyStart = TH1D_SigmaInelastic->GetBinCenter(1) * CLHEP::MeV; + G4int nbBins = TH1D_SigmaInelastic->GetNbinsX(); + G4double energyEnd = TH1D_SigmaInelastic->GetBinCenter(nbBins) * CLHEP::MeV; + G4double energyIncrement = TH1D_SigmaInelastic->GetBinWidth(1) * CLHEP::MeV; + + // G4cout << energyStart << " " << energyEnd << " "<< nbBins << " " << + // energyIncrement << G4endl; + + // Loop on the energies + for (G4int f = 1; f <= nbBins; f++) { + + Ngamma = 0; + + // Projectile track & step + G4DynamicParticle *dParticle = new G4DynamicParticle( + projectileDefinition, projectileDirection, energyEnd); + const G4double aTime = 0.0; + const G4ThreeVector aPosition = G4ThreeVector(0.0, 0.0, 0.0); + gTrack = new G4Track(dParticle, aTime, aPosition); + + G4Step *step = new G4Step; + + step->SetTrack(gTrack); + gTrack->SetStep(step); + aPoint = new G4StepPoint; + + aPoint->SetPosition(aPosition); + aPoint->SetMaterial(targetMaterial); + step->SetPreStepPoint(aPoint); + gTrack->SetStep(step); + + for (G4int i = 0; i < numCollisions; ++i) { + + projectileEnergy = (TH1D_SigmaInelastic->GetBinCenter(f) + + (G4UniformRand() - 0.5) * energyIncrement) * + CLHEP::MeV; + // G4cout << "projectile energy = " << projectileEnergy << G4endl; + dParticle->SetKineticEnergy(projectileEnergy); + gTrack->SetKineticEnergy(projectileEnergy); + + // Loop on the collisions + + G4HadronicProcess *theProcess = nullptr; + G4ParticleDefinition *theProjectileDef = nullptr; + if (projectileDefinition->IsGeneralIon()) { + theProjectileDef = G4GenericIon::Definition(); + } else { + theProjectileDef = projectileDefinition; + } + if (projectileDefinition == G4Neutron::Neutron() && + projectileEnergy < 20 * MeV && projectileEnergy > 4.4 * MeV) { + // For the HP model, we use the neutron HP inelastic process + auto mapIndex = fProcessMapHP.find(theProjectileDef); + if (mapIndex != fProcessMapHP.end()) + theProcess = mapIndex->second; + if (theProcess == nullptr) { + G4cerr << "ERROR: theProcess is nullptr for neutron HP inelastic !" + << G4endl; + } + } else { + auto mapIndex = fProcessMap.find(theProjectileDef); + + if (mapIndex != fProcessMap.end()) + theProcess = mapIndex->second; + if (theProcess == nullptr) { + G4cerr << "ERROR: theProcess is nullptr for " + << theProjectileDef->GetParticleName() << " inelastic !" + << G4endl; + } + } + if (theProcess != nullptr) { + aChange = theProcess->PostStepDoIt(*gTrack, *step); + } else { + G4cerr << "ERROR: theProcess is nullptr !" << G4endl; + } + fLastHadronicProcess = theProcess; + CrossSection = GetCrossSection(dParticle, targetElement, targetMaterial); + kappaPerRho = (CrossSection * Avogadro) / molarMass; + // The assumption in the prompt-gamma TLE is to use the defaut distance + // unit, ie mm + // ($G4_DIR/src/source/externals/clhep/include/CLHEP/Units/SystemOfUnits.h) + kappaPerRhoMmCollisions = kappaPerRho / (numCollisions * cm); + + G4int nsec = aChange ? aChange->GetNumberOfSecondaries() : 0; + + // Loop over produced secondaries and eventually print out some + // information. + for (G4int j = 0; j < nsec; ++j) { + const G4DynamicParticle *sec = + aChange->GetSecondary(j)->GetDynamicParticle(); + const G4String g = sec->GetDefinition()->GetParticleName(); + Double_t E = 0; + + if (g == "gamma") { + Ngamma++; + nbPG++; + E = sec->GetKineticEnergy(); + if (E > 0.04 * MeV) { + TH2D_GammaZ->Fill(projectileEnergy, E, kappaPerRhoMmCollisions); + TH2D_EpEpg->Fill(projectileEnergy, E); + } + } + delete aChange->GetSecondary(j); + } + if (aChange) + aChange->Clear(); + // delete dynamicProjectile; + } + + dParticle->SetKineticEnergy(TH1D_SigmaInelastic->GetBinCenter(f) * + CLHEP::MeV); + CrossSection = GetCrossSection(dParticle, targetElement, targetMaterial); + kappaPerRho = (CrossSection * Avogadro) / molarMass; + TH1D_SigmaInelastic->SetBinContent(f, kappaPerRho); + TH1D_NrPG->SetBinContent(f, Ngamma); + + delete step; + delete dParticle; + } + + delete pFrame; + delete lFrame; + delete sFrame; + + return nbPG; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +G4double HadronicGenerator::GetCrossSection(const G4DynamicParticle *part, + const G4Element *elm, + const G4Material *targetMaterial) { + G4double CrossSection = -999; + G4HadronicProcess *hadProcess = GetHadronicProcess(); + const G4double squareCentimeter = cm * cm; + const G4ParticleDefinition *particle = part->GetParticleDefinition(); + + // Récupérer la section efficace + CrossSection = hadProcess->GetCrossSectionDataStore()->GetCrossSection( + part, elm, targetMaterial); + + return CrossSection / squareCentimeter; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +G4String HadronicGenerator::WeightCompute(TH2D *TH2D_GammaZ, TH1D *TH1D_weight, + G4double frac) { + // This method computes the weight of the interaction for ToF computing + // and returns a string with the result. + // The weight is computed as the ratio of the number of gammas per bin + // to the number of collisions per bin. + G4String result = "is counted, with a fraction of " + std::to_string(frac); + + G4int nbBins = TH2D_GammaZ->GetNbinsX(); + for (G4int i = 1; i <= nbBins; ++i) { + // Sum the column at index i + G4double columnSum = + TH2D_GammaZ->Integral(i, i, 1, TH2D_GammaZ->GetNbinsY()); + // Store the sum in the TH1D histogram + TH1D_weight->SetBinContent(i, TH1D_weight->GetBinContent(i) + + frac * columnSum); + std::cout << "Column " << i << " sum: " << TH1D_weight->GetBinContent(i) + << std::endl; + } + + return result; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... \ No newline at end of file diff --git a/opengate/contrib/vpgTLE/stage0/History b/opengate/contrib/vpgTLE/stage0/History new file mode 100644 index 000000000..0ed7de964 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/History @@ -0,0 +1,31 @@ +# Example Hadr09 History + +See `CONTRIBUTING.rst` for details of **required** info/format for each entry, +which **must** added in reverse chronological order (newest at the top). It must **not** +be used as a substitute for writing good git commit messages! + + +## 2023-10-26 Alberto Ribon (exhadr09-V11-01-00) +- README : documented macro hadr09.in + +## 2022-11-17 Alberto Ribon (exhadr09-V11-00-01) +- Hadr09, HadronicGenerator : extended to hypernuclei and anti-hypernuclei + +## 2021-12-10 Ben Morgan (exhadr09-V11-00-00) +- Change to new Markdown History format + +--- + +# History entries prior to 11.0 + +10-11-21 Alberto Ribon (exhadr09-V10-07-01) +- HadronicGenerator : extended interface and functionality; + used new methods available in the FTF model. + +04-03-21 Alberto Ribon (exhadr09-V10-07-00) +- HadronicGenerator : migrated per-particle inelastic processes + (that have been deleted) to G4HadronInelasticProcess. + +08-11-20 Alberto Ribon (exhadr09-V10-06-00) +- Created this example. + diff --git a/opengate/contrib/vpgTLE/stage0/Modified_G4HP_File/G4ParticleHPInelastic.cc b/opengate/contrib/vpgTLE/stage0/Modified_G4HP_File/G4ParticleHPInelastic.cc new file mode 100644 index 000000000..173173f82 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/Modified_G4HP_File/G4ParticleHPInelastic.cc @@ -0,0 +1,318 @@ +// +// ******************************************************************** +// * License and Disclaimer * +// * * +// * The Geant4 software is copyright of the Copyright Holders of * +// * the Geant4 Collaboration. It is provided under the terms and * +// * conditions of the Geant4 Software License, included in the file * +// * LICENSE and available at http://cern.ch/geant4/license . These * +// * include a list of copyright holders. * +// * * +// * Neither the authors of this software system, nor their employing * +// * institutes,nor the agencies providing financial support for this * +// * work make any representation or warranty, express or implied, * +// * regarding this software system or assume any liability for its * +// * use. Please see the license in the file LICENSE and URL above * +// * for the full disclaimer and the limitation of liability. * +// * * +// * This code implementation is the result of the scientific and * +// * technical work of the GEANT4 collaboration. * +// * By using, copying, modifying or distributing the software (or * +// * any work based on the software) you agree to acknowledge its * +// * use in resulting scientific publications, and indicate your * +// * acceptance of all terms of the Geant4 Software license. * +// ******************************************************************** +// +// this code implementation is the intellectual property of +// neutron_hp -- source file +// J.P. Wellisch, Nov-1996 +// A prototype of the low energy neutron transport model. +// +// By copying, distributing or modifying the Program (or any work +// based on the Program) you indicate your acceptance of this statement, +// and all its terms. +// +// +// 070523 bug fix for G4FPE_DEBUG on by A. Howard (and T. Koi) +// 081203 limit maximum trial for creating final states add protection for 1H +// isotope case by T. Koi +// +// P. Arce, June-2014 Conversion neutron_hp to particle_hp +// V. Ivanchenko, July-2023 Basic revision of particle HP classes +// +#include "G4ParticleHPInelastic.hh" + +#include "G4AutoLock.hh" +#include "G4HadronicParameters.hh" +#include "G4ParticleHP2AInelasticFS.hh" +#include "G4ParticleHP2N2AInelasticFS.hh" +#include "G4ParticleHP2NAInelasticFS.hh" +#include "G4ParticleHP2NDInelasticFS.hh" +#include "G4ParticleHP2NInelasticFS.hh" +#include "G4ParticleHP2NPInelasticFS.hh" +#include "G4ParticleHP2PInelasticFS.hh" +#include "G4ParticleHP3AInelasticFS.hh" +#include "G4ParticleHP3NAInelasticFS.hh" +#include "G4ParticleHP3NInelasticFS.hh" +#include "G4ParticleHP3NPInelasticFS.hh" +#include "G4ParticleHP4NInelasticFS.hh" +#include "G4ParticleHPAInelasticFS.hh" +#include "G4ParticleHPD2AInelasticFS.hh" +#include "G4ParticleHPDAInelasticFS.hh" +#include "G4ParticleHPDInelasticFS.hh" +#include "G4ParticleHPHe3InelasticFS.hh" +#include "G4ParticleHPManager.hh" +#include "G4ParticleHPN2AInelasticFS.hh" +#include "G4ParticleHPN2PInelasticFS.hh" +#include "G4ParticleHPN3AInelasticFS.hh" +#include "G4ParticleHPNAInelasticFS.hh" +#include "G4ParticleHPND2AInelasticFS.hh" +#include "G4ParticleHPNDInelasticFS.hh" +#include "G4ParticleHPNHe3InelasticFS.hh" +#include "G4ParticleHPNInelasticFS.hh" +#include "G4ParticleHPNPAInelasticFS.hh" +#include "G4ParticleHPNPInelasticFS.hh" +#include "G4ParticleHPNT2AInelasticFS.hh" +#include "G4ParticleHPNTInelasticFS.hh" +#include "G4ParticleHPNXInelasticFS.hh" +#include "G4ParticleHPPAInelasticFS.hh" +#include "G4ParticleHPPDInelasticFS.hh" +#include "G4ParticleHPPInelasticFS.hh" +#include "G4ParticleHPPTInelasticFS.hh" +#include "G4ParticleHPT2AInelasticFS.hh" +#include "G4ParticleHPTInelasticFS.hh" +#include "G4ParticleHPThermalBoost.hh" +#include "G4SystemOfUnits.hh" + +G4bool G4ParticleHPInelastic::fLock[] = {true, true, true, true, true, true}; +std::vector *G4ParticleHPInelastic::theInelastic[] = + {nullptr, nullptr, nullptr, nullptr, nullptr, nullptr}; + +namespace { +G4Mutex theHPInelastic = G4MUTEX_INITIALIZER; +} + +G4ParticleHPInelastic::G4ParticleHPInelastic(G4ParticleDefinition *p, + const char *name) + : G4HadronicInteraction(name), theProjectile(p) { + fManager = G4ParticleHPManager::GetInstance(); + dirName = fManager->GetParticleHPPath(theProjectile) + "/Inelastic"; + indexP = fManager->GetPHPIndex(theProjectile); +#ifdef G4VERBOSE + if (fManager->GetVerboseLevel() > 1) + G4cout << "@@@ G4ParticleHPInelastic instantiated for " + << p->GetParticleName() << " indexP=" << indexP + << "/n data directory " << dirName << G4endl; +#endif +} + +G4ParticleHPInelastic::~G4ParticleHPInelastic() { + // Vector is shared, only one delete + if (isFirst) { + ClearData(); + } +} + +void G4ParticleHPInelastic::ClearData() { + if (theInelastic[indexP] != nullptr) { + for (auto const &p : *(theInelastic[indexP])) { + delete p; + } + delete theInelastic[indexP]; + theInelastic[indexP] = nullptr; + } +} + +G4HadFinalState * +G4ParticleHPInelastic::ApplyYourself(const G4HadProjectile &aTrack, + G4Nucleus &aNucleus) { + G4ParticleHPManager::GetInstance()->OpenReactionWhiteBoard(); + const G4Material *theMaterial = aTrack.GetMaterial(); + auto n = (G4int)theMaterial->GetNumberOfElements(); + auto elm = theMaterial->GetElement(0); + std::size_t index = elm->GetIndex(); + G4int it = 0; + if (n != 1) { + auto xSec = new G4double[n]; + G4double sum = 0; + G4int i; + const G4double *NumAtomsPerVolume = theMaterial->GetVecNbOfAtomsPerVolume(); + G4double rWeight; + G4double xs; + G4ParticleHPThermalBoost aThermalE; + for (i = 0; i < n; ++i) { + elm = theMaterial->GetElement(i); + index = elm->GetIndex(); + + G4cout << "i=" << i << " index=" << index << " " << elm->GetName() + << " " << (*(theInelastic[indexP]))[index] << G4endl; + + rWeight = NumAtomsPerVolume[i]; + if (aTrack.GetDefinition() == G4Neutron::Neutron()) { + xs = (*(theInelastic[indexP]))[index]->GetXsec( + aThermalE.GetThermalEnergy(aTrack, elm, + theMaterial->GetTemperature())); + } else { + xs = (*(theInelastic[indexP]))[index]->GetXsec( + aTrack.GetKineticEnergy()); + } + xs *= rWeight; + sum += xs; + xSec[i] = sum; +#ifdef G4VERBOSE + if (fManager->GetDEBUG()) + G4cout << " G4ParticleHPInelastic XSEC ELEM " << i << " = " << xSec[i] + << G4endl; +#endif + } + sum *= G4UniformRand(); + for (it = 0; it < n; ++it) { + elm = theMaterial->GetElement(it); + index = elm->GetIndex(); + if (sum <= xSec[it]) + break; + } + delete[] xSec; + } +#ifdef G4VERBOSE + if (fManager->GetDEBUG()) + G4cout << " G4ParticleHPInelastic: Elem it=" << it << " " << elm->GetName() + << " index=" << index << " from material " << theMaterial->GetName() + << G4endl; +#endif + // a specific flag has been builded in order to deal with the case of the HP + // in stage 0. The problem rise with the method GetIndex() that returns the + // ElementTable size while the position of the element is needed. + if (GetHadrGenFlag()) { + G4double Z = elm->GetZ(); + if (Z <= 20) { + index = Z - 1; // Adjust index for elements with Z <= 22 + } + if (Z == 22) { + index = 20; + } + if (Z == 29) { + index = 21; + } + if (Z == 30) { + index = 22; + } + if (Z == 47) { + index = 23; + } + if (Z == 50) { + index = 24; + } + } + + G4HadFinalState *result = + (*(theInelastic[indexP]))[index]->ApplyYourself(elm, aTrack); + aNucleus.SetParameters(fManager->GetReactionWhiteBoard()->GetTargA(), + fManager->GetReactionWhiteBoard()->GetTargZ()); + const G4Element *target_element = (*G4Element::GetElementTable())[index]; + const G4Isotope *target_isotope = nullptr; + auto iele = (G4int)target_element->GetNumberOfIsotopes(); + for (G4int j = 0; j != iele; ++j) { + target_isotope = target_element->GetIsotope(j); + if (target_isotope->GetN() == fManager->GetReactionWhiteBoard()->GetTargA()) + break; + } + aNucleus.SetIsotope(target_isotope); + + G4ParticleHPManager::GetInstance()->CloseReactionWhiteBoard(); + + return result; +} + +const std::pair +G4ParticleHPInelastic::GetFatalEnergyCheckLevels() const { + // max energy non-conservation is mass of heavy nucleus + return std::pair(10.0 * perCent, 350.0 * CLHEP::GeV); +} + +void G4ParticleHPInelastic::BuildPhysicsTable( + const G4ParticleDefinition &projectile) { + if (fLock[indexP]) { + G4AutoLock l(&theHPInelastic); + if (fLock[indexP]) { + isFirst = true; + fLock[indexP] = false; + } + l.unlock(); + } + G4int nelm = (G4int)G4Element::GetNumberOfElements(); + G4int n0 = numEle; + numEle = nelm; + if (!isFirst || nelm == n0) { + return; + } + // extra elements should be initialized + G4AutoLock l(&theHPInelastic); + + if (nullptr == theInelastic[indexP]) { + theInelastic[indexP] = new std::vector; + } + + if (fManager->GetVerboseLevel() > 0 && isFirst) { + fManager->DumpSetting(); + G4cout << "@@@ G4ParticleHPInelastic instantiated for particle " + << theProjectile->GetParticleName() << "/n data directory is " + << dirName << G4endl; + } + + auto table = G4Element::GetElementTable(); + for (G4int i = n0; i < nelm; ++i) { + auto clist = new G4ParticleHPChannelList(36, theProjectile); + theInelastic[indexP]->push_back(clist); + clist->Init((*table)[i], dirName, theProjectile); + clist->Register(new G4ParticleHPNInelasticFS, "F01/"); // has + clist->Register(new G4ParticleHPNXInelasticFS, "F02/"); + clist->Register(new G4ParticleHP2NDInelasticFS, "F03/"); + clist->Register(new G4ParticleHP2NInelasticFS, "F04/"); // has, E Done + clist->Register(new G4ParticleHP3NInelasticFS, "F05/"); // has, E Done + clist->Register(new G4ParticleHPNAInelasticFS, "F06/"); + clist->Register(new G4ParticleHPN3AInelasticFS, "F07/"); + clist->Register(new G4ParticleHP2NAInelasticFS, "F08/"); + clist->Register(new G4ParticleHP3NAInelasticFS, "F09/"); + clist->Register(new G4ParticleHPNPInelasticFS, "F10/"); + clist->Register(new G4ParticleHPN2AInelasticFS, "F11/"); + clist->Register(new G4ParticleHP2N2AInelasticFS, "F12/"); + clist->Register(new G4ParticleHPNDInelasticFS, "F13/"); + clist->Register(new G4ParticleHPNTInelasticFS, "F14/"); + clist->Register(new G4ParticleHPNHe3InelasticFS, "F15/"); + clist->Register(new G4ParticleHPND2AInelasticFS, "F16/"); + clist->Register(new G4ParticleHPNT2AInelasticFS, "F17/"); + clist->Register(new G4ParticleHP4NInelasticFS, "F18/"); // has, E Done + clist->Register(new G4ParticleHP2NPInelasticFS, "F19/"); + clist->Register(new G4ParticleHP3NPInelasticFS, "F20/"); + clist->Register(new G4ParticleHPN2PInelasticFS, "F21/"); + clist->Register(new G4ParticleHPNPAInelasticFS, "F22/"); + clist->Register(new G4ParticleHPPInelasticFS, "F23/"); + clist->Register(new G4ParticleHPDInelasticFS, "F24/"); + clist->Register(new G4ParticleHPTInelasticFS, "F25/"); + clist->Register(new G4ParticleHPHe3InelasticFS, "F26/"); + clist->Register(new G4ParticleHPAInelasticFS, "F27/"); + clist->Register(new G4ParticleHP2AInelasticFS, "F28/"); + clist->Register(new G4ParticleHP3AInelasticFS, "F29/"); + clist->Register(new G4ParticleHP2PInelasticFS, "F30/"); + clist->Register(new G4ParticleHPPAInelasticFS, "F31/"); + clist->Register(new G4ParticleHPD2AInelasticFS, "F32/"); + clist->Register(new G4ParticleHPT2AInelasticFS, "F33/"); + clist->Register(new G4ParticleHPPDInelasticFS, "F34/"); + clist->Register(new G4ParticleHPPTInelasticFS, "F35/"); + clist->Register(new G4ParticleHPDAInelasticFS, "F36/"); +#ifdef G4VERBOSE + if (fManager->GetVerboseLevel() > 1) { + G4cout << "ParticleHP::Inelastic for " << theProjectile->GetParticleName() + << " off " << (*table)[i]->GetName() << G4endl; + } +#endif + } + fManager->RegisterInelasticFinalStates(&projectile, theInelastic[indexP]); + l.unlock(); +} + +void G4ParticleHPInelastic::ModelDescription(std::ostream &outFile) const { + outFile << "High Precision (HP) model for inelastic reaction of " + << theProjectile->GetParticleName() << " below 20MeV\n"; +} diff --git a/opengate/contrib/vpgTLE/stage0/Modified_G4HP_File/G4ParticleHPInelastic.hh b/opengate/contrib/vpgTLE/stage0/Modified_G4HP_File/G4ParticleHPInelastic.hh new file mode 100644 index 000000000..686479e68 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/Modified_G4HP_File/G4ParticleHPInelastic.hh @@ -0,0 +1,90 @@ +// +// ******************************************************************** +// * License and Disclaimer * +// * * +// * The Geant4 software is copyright of the Copyright Holders of * +// * the Geant4 Collaboration. It is provided under the terms and * +// * conditions of the Geant4 Software License, included in the file * +// * LICENSE and available at http://cern.ch/geant4/license . These * +// * include a list of copyright holders. * +// * * +// * Neither the authors of this software system, nor their employing * +// * institutes,nor the agencies providing financial support for this * +// * work make any representation or warranty, express or implied, * +// * regarding this software system or assume any liability for its * +// * use. Please see the license in the file LICENSE and URL above * +// * for the full disclaimer and the limitation of liability. * +// * * +// * This code implementation is the result of the scientific and * +// * technical work of the GEANT4 collaboration. * +// * By using, copying, modifying or distributing the software (or * +// * any work based on the software) you agree to acknowledge its * +// * use in resulting scientific publications, and indicate your * +// * acceptance of all terms of the Geant4 Software license. * +// ******************************************************************** +// +// +// Hadronic Process: High Precision low E neutron tracking +// original by H.P. Wellisch, TRIUMF, 14-Feb-97 +// Builds and has the Cross-section data for one material. +// +// P. Arce, June-2014 Conversion neutron_hp to particle_hp +// V. Ivanchenko, July-2023 Basic revision of particle HP classes +// + +#ifndef G4ParticleHPInelastic_h +#define G4ParticleHPInelastic_h 1 + +// Final state production model for a high precision (based on evaluated data +// libraries) description of neutron inelastic scattering below 20 MeV or +// light ion inelastic interaction below 100 MeV. +// 36 exclusive final states are consideded. + +#include "G4HadronicInteraction.hh" +#include "G4ParticleDefinition.hh" +#include "G4ParticleHPChannel.hh" +#include "G4ParticleHPChannelList.hh" +#include "globals.hh" + +class G4ParticleHPInelastic : public G4HadronicInteraction { +public: + G4ParticleHPInelastic(G4ParticleDefinition *p = G4Neutron::Neutron(), + const char *name = "NeutronHPInelastic"); + + ~G4ParticleHPInelastic() override; + + G4HadFinalState *ApplyYourself(const G4HadProjectile &aTrack, + G4Nucleus &aTargetNucleus) override; + + const std::pair + GetFatalEnergyCheckLevels() const override; + + inline bool GetHadrGenFlag() const { return HadrGen; } + + inline void SetHadrGenFlag(const bool b) { HadrGen = b; } + + void BuildPhysicsTable(const G4ParticleDefinition &) override; + void ModelDescription(std::ostream &outFile) const override; + + G4ParticleHPInelastic(G4ParticleHPInelastic &) = delete; + G4ParticleHPInelastic &operator=(const G4ParticleHPInelastic &right) = delete; + // flag to indicate if hadronic generator is used + G4bool HadrGen{false}; + +private: + void ClearData(); + + G4ParticleDefinition *theProjectile; + G4bool isFirst{false}; + static G4bool fLock[6]; + +protected: + // one List per element + static std::vector *theInelastic[6]; + G4ParticleHPManager *fManager; + G4String dirName; + G4int numEle{0}; + G4int indexP; +}; + +#endif diff --git a/opengate/contrib/vpgTLE/stage0/README b/opengate/contrib/vpgTLE/stage0/README new file mode 100644 index 000000000..5ac6ee631 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/README @@ -0,0 +1,43 @@ +This example shows how to use Geant4 as a generator for simulating +inelastic hadron-nuclear interactions. + +The class HadronicGenerator is the "generator". +The main hadronic models (FTFP, QGSP, BERT, BIC, IonBIC, INCL) +and some combinations of two of them - in a transition energy region, +similarly to what happens in physics lists - are available. +See include/HadronicGenerator.hh for more detailed information. + +The main, Hadr09.cc, shows an example of how to use it. +It samples randomly the projectile hadron, its energy, its direction +and the target material, and then it calls the generator. +Some information regarding the secondaries which are produced can be +printed out. +See the comments in Hadr09.cc for more information and how eventually +to change some of its configurations. +Notice that Hadr09.cc does nothing really useful: users should consider +to use eventually only the class HadronicGenerator. + +( + The file Hadr09.cc-ION_PROJECTILE shows an example of a ion-ion + collision, for fixed type of projectile ion, target ion, projectile + kinetic energy, and projectile direction. + This file is obtained from Hadr09.cc with minimal changes. +) + +Notice that the Geant4 run-manager is not used. + +To build this example: + mkdir Build; cd Build + cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DGeant4_DIR=/path-to-geant4-libraries ../. + make + +To run it: + ./Hadr09 + +which simulates 1000 hadron-nucleus collisions, randomnly selected, and +prints out some information about the secondaries produced in these +interactions. It takes only a few seconds to run. + +Note: this example has been included in Geant4 10.7, but it should work + also for early versions of Geant4, in particular 10.6, 10.5 and 10.4. diff --git a/opengate/contrib/vpgTLE/stage0/include/HadronicGenerator-vpgtle.hh b/opengate/contrib/vpgTLE/stage0/include/HadronicGenerator-vpgtle.hh new file mode 100644 index 000000000..9de4363a7 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/include/HadronicGenerator-vpgtle.hh @@ -0,0 +1,168 @@ +// +// ******************************************************************** +// * License and Disclaimer * +// * * +// * The Geant4 software is copyright of the Copyright Holders of * +// * the Geant4 Collaboration. It is provided under the terms and * +// * conditions of the Geant4 Software License, included in the file * +// * LICENSE and available at http://cern.ch/geant4/license . These * +// * include a list of copyright holders. * +// * * +// * Neither the authors of this software system, nor their employing * +// * institutes, nor the agencies providing financial support for this * +// * work make any representation or warranty, express or implied, * +// * regarding this software system or assume any liability for its * +// * use. Please see the license in the file LICENSE and URL above * +// * for the full disclaimer and the limitation of liability. * +// * * +// * This code implementation is the result of the scientific and * +// * technical work of the GEANT4 collaboration. * +// * By using, copying, modifying or distributing the software (or * +// * any work based on the software) you agree to acknowledge its * +// * use in resulting scientific publications, and indicate your * +// * acceptance of all terms of the Geant4 Software license. * +// ******************************************************************** +// +/// \file HadronicGenerator-vpgtle.hh +/// \brief Definition of the HadronicGenerator class +// +//------------------------------------------------------------------------ +// Class: HadronicGenerator +// Author: Alberto Ribon (CERN EP/SFT) +// Date: May 2020 +// +// This class shows how to use Geant4 as a generator for simulating +// inelastic hadron-nuclear interactions. +// Some of the most used hadronic models are currently supported in +// this class: +// - the hadronic string models Fritiof (FTF) and Quark-Gluon-String (QGS) +// coupled with Precompound/de-excitation +// - the intranuclear cascade models: Bertini (BERT), Binary Cascade (BIC), +// and Liege (INCL) +// Combinations of two models - in a transition energy interval, with a +// linear probability as a function of the energy - are also available to +// "mimic" the transition between hadronic models as in the most common +// Geant4 reference physics lists. +// +// The current version of this class does NOT support: +// - hadron elastic interactions +// - neutron capture and fission +// - precise low-energy inelastic interactions of neutrons and +// charged particles (i.e. ParticleHP) +// - gamma/lepton-nuclear inelastic interactions +// +// This class does NOT use the Geant4 run-manager, and therefore should +// be usable in a multi-threaded application, with one instance of this +// class in each thread. +// +// This class has been inspired by test30 (whose author is Vladimir +// Ivanchenko), with various simplifications and restricted to hadronic +// inelastic interactions. +//------------------------------------------------------------------------ + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +#ifndef HadronicGenerator_h +#define HadronicGenerator_h 1 + +#include "G4HadronicProcess.hh" +#include "G4ThreeVector.hh" +#include "G4ios.hh" +#include "globals.hh" +#include +#include +#include +#include + +class G4ParticleDefinition; +class G4VParticleChange; +class G4ParticleTable; +class G4Material; +class G4HadronicInteraction; + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +class HadronicGenerator { + // This class provides the functionality of a "hadronic generator" + // for Geant4 final-state inelastic hadronic collisions. + // Only a few of the available Geant4 final-state hadronic inelastic + // "physics cases" are currently available in this class - but it can + // be extended to other cases if needed. + // It is important to notice that this class does NOT use the Geant4 + // run-manager, so it should work fine in a multi-threaded environment, + // with a separate instance of this class in each thread. +public: + explicit HadronicGenerator(const G4String physicsCase = "QGSP_BIC_HP"); + // Currently supported final-state hadronic inelastic "physics cases": + // - Hadronic models : BERT, BIC, IonBIC, INCL, FTFP, QGSP + // - "Physics-list proxies" : FTFP_BERT_ATL (default), FTFP_BERT, + // QGSP_BERT, QGSP_BIC, FTFP_INCLXX + // (i.e. they are not real, complete physics lists - for instance + // they do not have: transportation, electromagnetic physics, + // hadron elastic scattering, neutron fission and capture, etc. - + // however, they cover all hadron types and all energies by + // combining different hadronic models, i.e. there are transitions + // between two hadronic models in well-defined energy intervals, + // e.g. "FTFP_BERT" has the transition between BERT and FTFP + // hadronic models; moreover, the transition intervals used in + // our "physics cases" might not be the same as in the corresponding + // physics lists). + + ~HadronicGenerator(); + + inline G4bool IsPhysicsCaseSupported() const; + // Returns "true" if the physicsCase is supported; "false" otherwise. + + G4int GenerateInteraction(G4ParticleDefinition *projectileDefinition, + G4Material *targetMaterial, TH2D *TH2D_EpEpg, + TH1D *TH1D_SigmaInelastic, TH2D *TH2D_GammaZ, + TH1D *NrPG, G4int numCollisions); + // This is the main method provided by the class: + // in input it receives the projectile (either by name or particle + // definition), its energy, its direction and the target material, and it + // returns one sampled final-state of the inelastic hadron-nuclear collision + // as modelled by the final-state hadronic inelastic "physics case" specified + // in the constructor. If the required hadronic collision is not possible, + // then the method returns immediately an empty "G4VParticleChange", i.e. + // without secondaries produced. + + inline G4HadronicProcess *GetHadronicProcess() const; + inline G4HadronicInteraction *GetHadronicInteraction() const; + // Returns the hadronic process and the hadronic interaction, respectively, + // that handled the last call of "GenerateInteraction". + + G4double GetCrossSection(const G4DynamicParticle *part, const G4Element *elm, + const G4Material *targetMaterial); + // In the case of hadronic interactions handled by the FTF model, returns, + // respectively, the impact parameter, the number of target/projectile + // spectator nucleons, and the number of nucleon-nucleon collisions, + // else, returns a negative value (-999). + + G4String WeightCompute(TH2D *TH2D_GammaZ, TH1D *TH1D_weight, G4double frac); + +private: + G4String fPhysicsCase; + G4bool fPhysicsCaseIsSupported; + G4HadronicProcess *fLastHadronicProcess; + G4ParticleTable *fPartTable; + std::map fProcessMap; + std::map fProcessMapHP; + G4Track *gTrack; + G4StepPoint *aPoint; +}; + +inline G4HadronicProcess *HadronicGenerator::GetHadronicProcess() const { + return fLastHadronicProcess; +} + +inline G4HadronicInteraction * +HadronicGenerator::GetHadronicInteraction() const { + return fLastHadronicProcess == nullptr + ? nullptr + : fLastHadronicProcess->GetHadronicInteraction(); +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +#endif diff --git a/opengate/contrib/vpgTLE/stage0/merge_root.py b/opengate/contrib/vpgTLE/stage0/merge_root.py new file mode 100644 index 000000000..f26badc1e --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/merge_root.py @@ -0,0 +1,98 @@ +import numpy as np +import glob +import os +import SimpleITK as sitk +import uproot +import hist + + +Path = "/sps/creatis/vguittet/geant-stage0/output" +folders = [d for d in glob.glob(os.path.join(Path, "*/"))] +Norm = len(folders) +case = "output_507312_" +print(Norm) +# recover all the mha files +listofFiles = glob.glob(Path + "/" + case + "*/neutr-data.root", recursive=True) +Norm = len(folders) + +X = np.linspace(0, 200, 501) +Y = np.linspace(0, 10, 251) + +elements = [ + "standard_Weight", + "Al", + "Ar", + "Be", + "B", + "Ca", + "C", + "Cl", + "Cu", + "F", + "He", + "H", + "Li", + "Mg", + "Ne", + "N", + "O", + "P", + "K", + "Si", + "Ag", + "Na", + "S", + "Sn", + "Ti", + "Zn", +] +quantity = ["GammaZ", "Kapa inelastique", "NrPG", "EnEpg"] + + +def accumulation(el, q): + if q == "GammaZ" or q == "EnEpg": + mega = np.zeros((500, 250)) + else: + mega = np.zeros(500) + for rootFile in listofFiles: + img = uproot.open(rootFile) + if q == "GammaZ" or q == "EnEpg": + arr, x, y = img[el][q].to_hist().to_numpy() + else: + arr, x = img[el][q].to_hist().to_numpy() + mega += arr / Norm + # vider l'arr + return mega + + +# concatenation of the data +with uproot.recreate("data_merge.root") as f: + for el in elements: + if el == "standard_Weight": + somme = accumulation(el, "Weight") + histo_data = hist.Hist( + hist.axis.Variable(X, name="Neutrons Energy (MeV)"), + storage=hist.storage.Double(), + ) + histo_data[...] = somme + f[f"{el}/Weight"] = histo_data + print(el) + continue + for q in quantity: + print(el, q) + somme = accumulation(el, q) + if q == "GammaZ" or q == "EnEpg": + histo_data = hist.Hist( + hist.axis.Variable(X, name="Neutrons Energy (MeV)"), + hist.axis.Variable(Y, name="PGs energy [MeV]"), + storage=hist.storage.Double(), + ) + histo_data[...] = somme + f[f"{el}/{q}"] = histo_data + else: + histo_data = hist.Hist( + hist.axis.Variable(X, name="Neutrons Energy (MeV)"), + storage=hist.storage.Double(), + ) + histo_data[...] = somme + f[f"{el}/{q}"] = histo_data diff --git a/opengate/contrib/vpgTLE/stage0/run_stage0.slurm b/opengate/contrib/vpgTLE/stage0/run_stage0.slurm new file mode 100644 index 000000000..d3b4ee8f8 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/run_stage0.slurm @@ -0,0 +1,24 @@ +#! /usr/bin/bash -lx +#SBATCH --output=Seq%j.out # nom du fichier de sortie +#SBATCH --time=1-00:00:00 +#SBATCH --ntasks=1 +#SBATCH --mem=2000 # Mémoire en MB par défaut +#SBATCH --error=Seq%A_%a.out # nom du fichier d'erreur (ici en commun avec la sortie) + +# sbatch --export=N=10000,P=proton,W=weight run_stage0.slurm + +module load /pbs/software/modulefiles/redhat-9-x86_64/Compilers/gcc/13.2.0 +module load /pbs/software/modulefiles/redhat-9-x86_64/Analysis/root/6.30.06 +source /path/to/theG4used/geant4.11.2.1/bin/geant4make.sh + +cd /path/to/your/outputfolder/geant-stage0/output +mkdir output_${SLURM_ARRAY_JOB_ID}_${SLURM_ARRAY_TASK_ID} +N=${1:-10000} +P=${2:-proton} +W=${3:-""} + +cd /path/to/your/compilation/folder/bin +./stage0-vpgtle ${SLURM_ARRAY_JOB_ID}_${SLURM_ARRAY_TASK_ID} -n ${N} -p ${P} ${W} + + + diff --git a/opengate/contrib/vpgTLE/stage0/src/HadronicGenerator-vpgtle.cc b/opengate/contrib/vpgTLE/stage0/src/HadronicGenerator-vpgtle.cc new file mode 100644 index 000000000..6330db8f6 --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/src/HadronicGenerator-vpgtle.cc @@ -0,0 +1,605 @@ +// +// ******************************************************************** +// * License and Disclaimer * +// * * +// * The Geant4 software is copyright of the Copyright Holders of * +// * the Geant4 Collaboration. It is provided under the terms and * +// * conditions of the Geant4 Software License, included in the file * +// * LICENSE and available at http://cern.ch/geant4/license . These * +// * include a list of copyright holders. * +// * * +// * Neither the authors of this software system, nor their employing * +// * institutes, nor the agencies providing financial support for this * +// * work make any representation or warranty, express or implied, * +// * regarding this software system or assume any liability for its * +// * use. Please see the license in the file LICENSE and URL above * +// * for the full disclaimer and the limitation of liability. * +// * * +// * This code implementation is the result of the scientific and * +// * technical work of the GEANT4 collaboration. * +// * By using, copying, modifying or distributing the software (or * +// * any work based on the software) you agree to acknowledge its * +// * use in resulting scientific publications, and indicate your * +// * acceptance of all terms of the Geant4 Software license. * +// ******************************************************************** +// +/// \file HadronicGenerator.cc +/// \brief Implementation of the HadronicGenerator class +// +//------------------------------------------------------------------------ +// Class: HadronicGenerator +// Author: Alberto Ribon (CERN EP/SFT) +// Date: May 2020 +//------------------------------------------------------------------------ + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +#include "HadronicGenerator-vpgtle.hh" + +#include +#include +#include + +#include "G4AblaInterface.hh" +#include "G4BGGNucleonInelasticXS.hh" +#include "G4BinaryCascade.hh" +#include "G4BinaryLightIonReaction.hh" +#include "G4Box.hh" +#include "G4CascadeInterface.hh" +#include "G4ComponentGGHadronNucleusXsc.hh" +#include "G4ComponentGGNuclNuclXsc.hh" +#include "G4CrossSectionDataStore.hh" +#include "G4CrossSectionInelastic.hh" +#include "G4DecayPhysics.hh" +#include "G4DynamicParticle.hh" +#include "G4ExcitationHandler.hh" +#include "G4ExcitedStringDecay.hh" +#include "G4FTFModel.hh" +#include "G4GeneratorPrecompoundInterface.hh" +#include "G4HadronInelasticProcess.hh" +#include "G4HadronicInteraction.hh" +#include "G4HadronicParameters.hh" +#include "G4HadronicProcessStore.hh" +#include "G4INCLXXInterface.hh" +#include "G4IonTable.hh" +#include "G4LundStringFragmentation.hh" +#include "G4Material.hh" +#include "G4NeutronInelasticXS.hh" +#include "G4PVPlacement.hh" +#include "G4ParticleTable.hh" +#include "G4PhysicalConstants.hh" +#include "G4PreCompoundModel.hh" +#include "G4ProcessManager.hh" +#include "G4QGSMFragmentation.hh" +#include "G4QGSModel.hh" +#include "G4QGSParticipants.hh" +#include "G4QuasiElasticChannel.hh" +#include "G4StateManager.hh" +#include "G4Step.hh" +#include "G4SystemOfUnits.hh" +#include "G4TheoFSGenerator.hh" +#include "G4TouchableHistory.hh" +#include "G4TransportationManager.hh" +#include "G4UnitsTable.hh" +#include "G4VCrossSectionDataSet.hh" +#include "G4VParticleChange.hh" +#include "G4ios.hh" +#include "globals.hh" + +// particle libraries +#include "G4Alpha.hh" +#include "G4Deuteron.hh" +#include "G4He3.hh" +#include "G4Neutron.hh" +#include "G4Proton.hh" +#include "G4Triton.hh" + +// HP libraries +#include "G4NeutronCaptureProcess.hh" +#include "G4NeutronHPCapture.hh" +#include "G4NeutronHPCaptureData.hh" +#include "G4NeutronHPElastic.hh" +#include "G4NeutronHPElasticData.hh" +#include "G4NeutronHPFission.hh" +#include "G4NeutronHPFissionData.hh" +#include "G4NeutronHPInelastic.hh" +#include "G4NeutronHPInelasticXS.hh" +#include "G4ParticleHPInelastic.hh" + +// root libraries +#include "TH1D.h" +#include "TH2D.h" + +#include "G4Element.hh" + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +HadronicGenerator::HadronicGenerator(const G4String physicsCase) + : fPhysicsCase(physicsCase), fLastHadronicProcess(nullptr), + fPartTable(nullptr) { + + // The constructor set-ups all the particles, models, cross sections and + // hadronic inelastic processes. + // This should be done only once for each application. + // In the case of a multi-threaded application using this class, + // the constructor should be invoked for each thread, + // i.e. one instance of the class should be kept per thread. + // The particles and processes that are created in this constructor + // will then be used by the method GenerateInteraction at each interaction. + // Notes: + // - Neither the hadronic models nor the cross sections are used directly + // by the method GenerateInteraction, but they are associated to the + // hadronic processes and used by Geant4 to simulate the collision; + // - Although the class generates only final states, but not free mean paths, + // inelastic hadron-nuclear cross sections are needed by Geant4 to sample + // the target nucleus from the target material. + + if (fPhysicsCase != "QGSP_BIC_HP") { + G4cerr + << "ERROR: Not supported final-state hadronic inelastic physics case !" + << fPhysicsCase << G4endl + << "\t Re-try by choosing one of the following:" << G4endl + << "\t - Hadronic models : QGSP" << G4endl + << "\t - \"Physics-list proxies\" : QGSP_BIC_HP" << G4endl; + } + + // Definition of particles + G4GenericIon *gion = G4GenericIon::Definition(); + gion->SetProcessManager(new G4ProcessManager(gion)); + G4DecayPhysics *decays = new G4DecayPhysics; + decays->ConstructParticle(); + fPartTable = G4ParticleTable::GetParticleTable(); + fPartTable->SetReadiness(); + G4IonTable *ions = fPartTable->GetIonTable(); + ions->CreateAllIon(); + ions->CreateAllIsomer(); + + // Building the ElementTable for the NeutronHP model + // TO BE MODIFIED :: there should be a method in Geant4 for this purpose + std::vector myElements; + myElements.push_back(new G4Element("Hydrogen", "H", 1., 1.01 * g / mole)); + myElements.push_back(new G4Element("Helium", "He", 2., 4.00 * g / mole)); + myElements.push_back(new G4Element("Lithium", "Li", 3., 6.94 * g / mole)); + myElements.push_back(new G4Element("Beryllium", "Be", 4., 9.01 * g / mole)); + myElements.push_back(new G4Element("Boron", "B", 5., 10.81 * g / mole)); + myElements.push_back(new G4Element("Carbon", "C", 6., 12.01 * g / mole)); + myElements.push_back(new G4Element("Nitrogen", "N", 7., 14.01 * g / mole)); + myElements.push_back(new G4Element("Oxygen", "O", 8., 16.00 * g / mole)); + myElements.push_back(new G4Element("Fluorine", "F", 9., 19.00 * g / mole)); + myElements.push_back(new G4Element("Neon", "Ne", 10., 20.18 * g / mole)); + myElements.push_back(new G4Element("Sodium", "Na", 11., 22.99 * g / mole)); + myElements.push_back(new G4Element("Magnesium", "Mg", 12., 24.31 * g / mole)); + myElements.push_back(new G4Element("Aluminium", "Al", 13., 26.98 * g / mole)); + myElements.push_back(new G4Element("Silicon", "Si", 14., 28.09 * g / mole)); + myElements.push_back(new G4Element("Phosphorus", "P", 15., 30.97 * g / mole)); + myElements.push_back(new G4Element("Sulfur", "S", 16., 32.07 * g / mole)); + myElements.push_back(new G4Element("Chlorine", "Cl", 17., 35.45 * g / mole)); + myElements.push_back(new G4Element("Argon", "Ar", 18., 39.95 * g / mole)); + myElements.push_back(new G4Element("Potassium", "K", 19., 39.10 * g / mole)); + myElements.push_back(new G4Element("Calcium", "Ca", 20., 40.08 * g / mole)); + myElements.push_back(new G4Element("Titanium", "Ti", 22., 47.87 * g / mole)); + myElements.push_back(new G4Element("Copper", "Cu", 29., 63.55 * g / mole)); + myElements.push_back(new G4Element("Zinc", "Zn", 30., 65.38 * g / mole)); + myElements.push_back(new G4Element("Silver", "Ag", 47., 107.87 * g / mole)); + myElements.push_back(new G4Element("Tin", "Sn", 50., 118.71 * g / mole)); + + // Build BIC model + G4BinaryCascade *theBICmodel = new G4BinaryCascade; + G4PreCompoundModel *thePreEquilib = + new G4PreCompoundModel(new G4ExcitationHandler); + theBICmodel->SetDeExcitation(thePreEquilib); + + // Build BinaryLightIon model + G4PreCompoundModel *thePreEquilibBis = + new G4PreCompoundModel(new G4ExcitationHandler); + G4BinaryLightIonReaction *theIonBICmodel = + new G4BinaryLightIonReaction(thePreEquilibBis); + + // HP model + G4NeutronHPInelastic *theNeutronHP = new G4NeutronHPInelastic; + theNeutronHP->BuildPhysicsTable(*G4Neutron::Neutron()); + theNeutronHP->SetHadrGenFlag( + true); // Enable hadronic generator flag :: due to the ElementTable + // building default :: TO BE MODIFIED + + // Model instance with constraint to be above a kinetic energy threshold. + // (Used for ions in all physics lists) + G4GeneratorPrecompoundInterface *theCascade = + new G4GeneratorPrecompoundInterface; + theCascade->SetDeExcitation(thePreEquilib); + G4LundStringFragmentation *theLundFragmentation = + new G4LundStringFragmentation; + G4ExcitedStringDecay *theStringDecay = + new G4ExcitedStringDecay(theLundFragmentation); + G4FTFModel *theStringModel = new G4FTFModel; + theStringModel->SetFragmentationModel(theStringDecay); + + G4TheoFSGenerator *theFTFPmodel_aboveThreshold = + new G4TheoFSGenerator("FTFP"); + theFTFPmodel_aboveThreshold->SetMaxEnergy( + G4HadronicParameters::Instance()->GetMaxEnergy()); + theFTFPmodel_aboveThreshold->SetTransport(theCascade); + theFTFPmodel_aboveThreshold->SetHighEnergyGenerator(theStringModel); + + // Model instance with constraint to be within two kinetic energy thresholds. + // (Used in the case of QGS-based physics lists for nucleons) + G4TheoFSGenerator *theFTFPmodel_constrained = new G4TheoFSGenerator("FTFP"); + theFTFPmodel_constrained->SetMaxEnergy( + G4HadronicParameters::Instance()->GetMaxEnergy()); + theFTFPmodel_constrained->SetTransport(theCascade); + theFTFPmodel_constrained->SetHighEnergyGenerator(theStringModel); + + // Build the QGSP model + G4TheoFSGenerator *theQGSPmodel = new G4TheoFSGenerator("QGSP"); + theQGSPmodel->SetMaxEnergy(G4HadronicParameters::Instance()->GetMaxEnergy()); + theQGSPmodel->SetTransport(theCascade); + G4QGSMFragmentation *theQgsmFragmentation = new G4QGSMFragmentation; + G4ExcitedStringDecay *theQgsmStringDecay = + new G4ExcitedStringDecay(theQgsmFragmentation); + G4VPartonStringModel *theQgsmStringModel = new G4QGSModel; + theQgsmStringModel->SetFragmentationModel(theQgsmStringDecay); + theQGSPmodel->SetHighEnergyGenerator(theQgsmStringModel); + G4QuasiElasticChannel *theQuasiElastic = + new G4QuasiElasticChannel; // QGSP uses quasi-elastic + theQGSPmodel->SetQuasiElasticChannel(theQuasiElastic); + + // For the case of "physics-list proxies", select the energy range for each + // hadronic model. + const G4double ftfpMinE = + G4HadronicParameters::Instance()->GetMinEnergyTransitionFTF_Cascade(); + const G4double BICMaxE = + G4HadronicParameters::Instance()->GetMaxEnergyTransitionFTF_Cascade(); + const G4double ftfpMaxE = + G4HadronicParameters::Instance()->GetMaxEnergyTransitionQGS_FTF(); + const G4double qgspMinE = + G4HadronicParameters::Instance()->GetMinEnergyTransitionQGS_FTF(); + + theBICmodel->SetMaxEnergy(BICMaxE); + theIonBICmodel->SetMaxEnergy(BICMaxE); + theFTFPmodel_aboveThreshold->SetMinEnergy(ftfpMinE); + theFTFPmodel_constrained->SetMinEnergy(ftfpMinE); + theFTFPmodel_constrained->SetMaxEnergy(ftfpMaxE); + theQGSPmodel->SetMinEnergy(qgspMinE); + + // Cross sections (needed by Geant4 to sample the target nucleus from the + // target material) + G4VCrossSectionDataSet *theProtonXSdata = + new G4BGGNucleonInelasticXS(G4Proton::Proton()); + theProtonXSdata->BuildPhysicsTable(*(G4Proton::Definition())); + + G4VCrossSectionDataSet *theNeutronXSdata = new G4NeutronInelasticXS; + theNeutronXSdata->BuildPhysicsTable(*(G4Neutron::Definition())); + + G4VCrossSectionDataSet *theNeutronHPXSdatainel = new G4NeutronHPInelasticXS; + theNeutronHPXSdatainel->BuildPhysicsTable(*G4Neutron::Definition()); + + G4VCrossSectionDataSet *theHyperonsXSdata = + new G4CrossSectionInelastic(new G4ComponentGGHadronNucleusXsc); + + G4VCrossSectionDataSet *theNuclNuclXSdata = + new G4CrossSectionInelastic(new G4ComponentGGNuclNuclXsc); + + // Set up inelastic processes : store them in a map (with particle definition + // as key) + // for convenience + + typedef std::pair ProcessPair; + + G4HadronicProcess *theProtonInelasticProcess = + new G4HadronInelasticProcess("protonInelastic", G4Proton::Definition()); + fProcessMap.insert( + ProcessPair(G4Proton::Definition(), theProtonInelasticProcess)); + + // a specific processmap is dedidcated to the HP physics for neutron + G4HadronicProcess *theNeutronHPInelasticProcess = + new G4HadronInelasticProcess("NeutronHPInelastic", + G4Neutron::Definition()); + fProcessMapHP.insert( + ProcessPair(G4Neutron::Definition(), theNeutronHPInelasticProcess)); + + G4HadronicProcess *theNeutronInelasticProcess = + new G4HadronInelasticProcess("neutronInelastic", G4Neutron::Definition()); + fProcessMap.insert( + ProcessPair(G4Neutron::Definition(), theNeutronInelasticProcess)); + + // For the HP model, we need to create a new process for the neutron + // Prompt-gamma timing with a track-length estimator in proton therapy, JM + // Létang et al, 2024 + + G4HadronicProcess *theDeuteronInelasticProcess = + new G4HadronInelasticProcess("dInelastic", G4Deuteron::Definition()); + fProcessMap.insert( + ProcessPair(G4Deuteron::Definition(), theDeuteronInelasticProcess)); + + G4HadronicProcess *theTritonInelasticProcess = + new G4HadronInelasticProcess("tInelastic", G4Triton::Definition()); + fProcessMap.insert( + ProcessPair(G4Triton::Definition(), theTritonInelasticProcess)); + + G4HadronicProcess *theHe3InelasticProcess = + new G4HadronInelasticProcess("he3Inelastic", G4He3::Definition()); + fProcessMap.insert(ProcessPair(G4He3::Definition(), theHe3InelasticProcess)); + + G4HadronicProcess *theAlphaInelasticProcess = + new G4HadronInelasticProcess("alphaInelastic", G4Alpha::Definition()); + fProcessMap.insert( + ProcessPair(G4Alpha::Definition(), theAlphaInelasticProcess)); + + G4HadronicProcess *theIonInelasticProcess = + new G4HadronInelasticProcess("ionInelastic", G4GenericIon::Definition()); + fProcessMap.insert( + ProcessPair(G4GenericIon::Definition(), theIonInelasticProcess)); + + // Add the cross sections to the corresponding hadronic processes + + // cross-sections for nucleons + theProtonInelasticProcess->AddDataSet(theProtonXSdata); + theNeutronInelasticProcess->AddDataSet(theNeutronXSdata); + + // specific cross-sections for neutrons in HP model + theNeutronHPInelasticProcess->AddDataSet(theNeutronHPXSdatainel); + + // cross-sections for ions + theDeuteronInelasticProcess->AddDataSet(theNuclNuclXSdata); + theTritonInelasticProcess->AddDataSet(theNuclNuclXSdata); + theHe3InelasticProcess->AddDataSet(theNuclNuclXSdata); + theAlphaInelasticProcess->AddDataSet(theNuclNuclXSdata); + theIonInelasticProcess->AddDataSet(theNuclNuclXSdata); + + // Register the proper hadronic model(s) to the corresponding hadronic + // processes. in the physics list QGSP_BIC, BIC is used only for nucleons + + // processes for BIC => mean-energy on nucleons + theProtonInelasticProcess->RegisterMe(theBICmodel); + theNeutronInelasticProcess->RegisterMe(theBICmodel); + + // processes for QGSP => High-energy on nucleons + theProtonInelasticProcess->RegisterMe(theQGSPmodel); + theNeutronInelasticProcess->RegisterMe(theQGSPmodel); + + // processes for HP => low-energy on neutrons + theNeutronHPInelasticProcess->RegisterMe(theNeutronHP); + + // processes for ion (BIC for ions) + theDeuteronInelasticProcess->RegisterMe(theIonBICmodel); + theTritonInelasticProcess->RegisterMe(theIonBICmodel); + theHe3InelasticProcess->RegisterMe(theIonBICmodel); + theAlphaInelasticProcess->RegisterMe(theIonBICmodel); + theIonInelasticProcess->RegisterMe(theIonBICmodel); + + G4TheoFSGenerator *theFTFPmodelToBeUsed = theFTFPmodel_aboveThreshold; + + theFTFPmodelToBeUsed = theFTFPmodel_constrained; + theNeutronInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theProtonInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + + theFTFPmodelToBeUsed = theFTFPmodel_aboveThreshold; + theDeuteronInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theTritonInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theHe3InelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theAlphaInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); + theIonInelasticProcess->RegisterMe(theFTFPmodelToBeUsed); +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +HadronicGenerator::~HadronicGenerator() { fPartTable->DeleteAllParticles(); } + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +G4int HadronicGenerator::GenerateInteraction( + G4ParticleDefinition *projectileDefinition, G4Material *targetMaterial, + TH2D *TH2D_EpEpg, TH1D *TH1D_SigmaInelastic, TH2D *TH2D_GammaZ, + TH1D *TH1D_NrPG, G4int nbCollisions) { + // This is the most important method of the HadronicGenerator class: + // the method performs the specified hadronic interaction + // (by invoking the "PostStepDoIt" method of the corresponding hadronic + // process) and returns the final state, i.e. the secondaries produced by the + // collision. It is a relatively short method because the heavy load of + // setting up all possible hadronic processes - with their hadronic models, + // transition regions, and cross sections (the latter is needed for sampling + // the target nucleus from the target material) - was already done by the + // constructor of the class. + G4VParticleChange *aChange = nullptr; + G4int nbPG(0); + + if (projectileDefinition == nullptr) { + G4cerr << "ERROR: projectileDefinition is NULL !" << G4endl; + return 0; + } + + const G4Element *targetElement = targetMaterial->GetElement(0); // + G4double molarMass = targetElement->GetA() / (g / mole); + + // Geometry definition (not strictly needed) + const G4double dimX = 1.0 * mm; + const G4double dimY = 1.0 * mm; + const G4double dimZ = 1.0 * mm; + G4Box *sFrame = new G4Box("Box", dimX, dimY, dimZ); + G4LogicalVolume *lFrame = + new G4LogicalVolume(sFrame, targetMaterial, "Box", 0, 0, 0); + G4PVPlacement *pFrame = + new G4PVPlacement(0, G4ThreeVector(), "Box", lFrame, 0, false, 0); + G4TransportationManager::GetTransportationManager()->SetWorldForTracking( + pFrame); + + G4int numCollisions = nbCollisions; //***LOOKHERE*** NUMBER OF COLLISIONS + G4double rnd1(0); + G4double protonEnergy(0), GAMMAZPerRho(0), kappaPerRho(0), + kappaPerRhoMmCollisions(0); + G4int Ngamma(0); + G4double projectileEnergy; + G4ThreeVector projectileDirection = G4ThreeVector(0.0, 0.0, 1.0); + G4double CrossSection = 0; + + G4double energyStart = TH1D_SigmaInelastic->GetBinCenter(1) * CLHEP::MeV; + G4int nbBins = TH1D_SigmaInelastic->GetNbinsX(); + G4double energyEnd = TH1D_SigmaInelastic->GetBinCenter(nbBins) * CLHEP::MeV; + G4double energyIncrement = TH1D_SigmaInelastic->GetBinWidth(1) * CLHEP::MeV; + + // G4cout << energyStart << " " << energyEnd << " "<< nbBins << " " << + // energyIncrement << G4endl; + + // Loop on the energies + for (G4int f = 1; f <= nbBins; f++) { + + Ngamma = 0; + + // Projectile track & step + G4DynamicParticle *dParticle = new G4DynamicParticle( + projectileDefinition, projectileDirection, energyEnd); + const G4double aTime = 0.0; + const G4ThreeVector aPosition = G4ThreeVector(0.0, 0.0, 0.0); + gTrack = new G4Track(dParticle, aTime, aPosition); + + G4Step *step = new G4Step; + + step->SetTrack(gTrack); + gTrack->SetStep(step); + aPoint = new G4StepPoint; + + aPoint->SetPosition(aPosition); + aPoint->SetMaterial(targetMaterial); + step->SetPreStepPoint(aPoint); + gTrack->SetStep(step); + + for (G4int i = 0; i < numCollisions; ++i) { + + projectileEnergy = (TH1D_SigmaInelastic->GetBinCenter(f) + + (G4UniformRand() - 0.5) * energyIncrement) * + CLHEP::MeV; + // G4cout << "projectile energy = " << projectileEnergy << G4endl; + dParticle->SetKineticEnergy(projectileEnergy); + gTrack->SetKineticEnergy(projectileEnergy); + + // Loop on the collisions + G4HadronicProcess *theProcess = nullptr; + G4ParticleDefinition *theProjectileDef = nullptr; + if (projectileDefinition->IsGeneralIon()) { + theProjectileDef = G4GenericIon::Definition(); + } else { + theProjectileDef = projectileDefinition; + } + // HP case ?? + if (projectileDefinition == G4Neutron::Neutron() && + projectileEnergy < 20 * MeV && projectileEnergy > 4.4 * MeV) { + // For the HP model, we use the neutron HP inelastic process => specific + // processmap is used + auto mapIndex = fProcessMapHP.find(theProjectileDef); + if (mapIndex != fProcessMapHP.end()) + theProcess = mapIndex->second; + if (theProcess == nullptr) { + G4cerr << "ERROR: theProcess is nullptr for neutron HP inelastic !" + << G4endl; + } + } else { + auto mapIndex = fProcessMap.find(theProjectileDef); + if (mapIndex != fProcessMap.end()) + theProcess = mapIndex->second; + if (theProcess == nullptr) { + G4cerr << "ERROR: theProcess is nullptr for " + << theProjectileDef->GetParticleName() << " inelastic !" + << G4endl; + } + } + if (theProcess != nullptr) { + aChange = theProcess->PostStepDoIt(*gTrack, *step); + } else { + G4cerr << "ERROR: theProcess is nullptr !" << G4endl; + } + fLastHadronicProcess = theProcess; + CrossSection = GetCrossSection(dParticle, targetElement, targetMaterial); + kappaPerRho = (CrossSection * Avogadro) / molarMass; + // The assumption in the prompt-gamma TLE is to use the defaut distance + // unit, ie mm + // ($G4_DIR/src/source/externals/clhep/include/CLHEP/Units/SystemOfUnits.h) + kappaPerRhoMmCollisions = kappaPerRho / (numCollisions * cm); + + G4int nsec = aChange ? aChange->GetNumberOfSecondaries() : 0; + + // Loop over produced secondaries and eventually print out some + // information. + for (G4int j = 0; j < nsec; ++j) { + const G4DynamicParticle *sec = + aChange->GetSecondary(j)->GetDynamicParticle(); + const G4String g = sec->GetDefinition()->GetParticleName(); + Double_t E = 0; + + if (g == "gamma") { + Ngamma++; + nbPG++; + E = sec->GetKineticEnergy(); + if (E > 0.04 * MeV) { + TH2D_GammaZ->Fill(projectileEnergy, E, kappaPerRhoMmCollisions); + TH2D_EpEpg->Fill(projectileEnergy, E); + } + } + delete aChange->GetSecondary(j); + } + if (aChange) + aChange->Clear(); + // delete dynamicProjectile; + } + + dParticle->SetKineticEnergy(TH1D_SigmaInelastic->GetBinCenter(f) * + CLHEP::MeV); + CrossSection = GetCrossSection(dParticle, targetElement, targetMaterial); + kappaPerRho = (CrossSection * Avogadro) / molarMass; + TH1D_SigmaInelastic->SetBinContent(f, kappaPerRho); + TH1D_NrPG->SetBinContent(f, Ngamma); + + delete step; + delete dParticle; + } + + delete pFrame; + delete lFrame; + delete sFrame; + + return nbPG; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +G4double HadronicGenerator::GetCrossSection(const G4DynamicParticle *part, + const G4Element *elm, + const G4Material *targetMaterial) { + G4double CrossSection = -999; + G4HadronicProcess *hadProcess = GetHadronicProcess(); + const G4double squareCentimeter = cm * cm; + const G4ParticleDefinition *particle = part->GetParticleDefinition(); + + // Récupérer la section efficace + CrossSection = hadProcess->GetCrossSectionDataStore()->GetCrossSection( + part, elm, targetMaterial); + + return CrossSection / squareCentimeter; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +G4String HadronicGenerator::WeightCompute(TH2D *TH2D_GammaZ, TH1D *TH1D_weight, + G4double frac) { + // This method computes the weight of the interaction for ToF computing + // and returns a string with the result. + // The weight is computed as the ratio of the number of gammas per bin + // to the number of collisions per bin. + G4String result = "is counted, with a fraction of " + std::to_string(frac); + + G4int nbBins = TH2D_GammaZ->GetNbinsX(); + for (G4int i = 1; i <= nbBins; ++i) { + // Sum the column at index i + G4double columnSum = + TH2D_GammaZ->Integral(i, i, 1, TH2D_GammaZ->GetNbinsY()); + // Store the sum in the TH1D histogram + TH1D_weight->SetBinContent(i, TH1D_weight->GetBinContent(i) + + frac * columnSum); + std::cout << "Column " << i << " sum: " << TH1D_weight->GetBinContent(i) + << std::endl; + } + + return result; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/opengate/contrib/vpgTLE/stage0/stage0-vpgtle.cc b/opengate/contrib/vpgTLE/stage0/stage0-vpgtle.cc new file mode 100644 index 000000000..e410987ce --- /dev/null +++ b/opengate/contrib/vpgTLE/stage0/stage0-vpgtle.cc @@ -0,0 +1,343 @@ +// +// ******************************************************************** +// * License and Disclaimer * +// * * +// * The Geant4 software is copyright of the Copyright Holders of * +// * the Geant4 Collaboration. It is provided under the terms and * +// * conditions of the Geant4 Software License, included in the file * +// * LICENSE and available at http://cern.ch/geant4/license . These * +// * include a list of copyright holders. * +// * * +// * Neither the authors of this software system, nor their employing * +// * institutes,nor the agencies providing financial support for this * +// * work make any representation or warranty, express or implied, * +// * regarding this software system or assume any liability for its * +// * use. Please see the license in the file LICENSE and URL above * +// * for the full disclaimer and the limitation of liability. * +// * * +// * This code implementation is the result of the scientific and * +// * technical work of the GEANT4 collaboration. * +// * By using, copying, modifying or distributing the software (or * +// * any work based on the software) you agree to acknowledge its * +// * use in resulting scientific publications, and indicate your * +// * acceptance of all terms of the Geant4 Software license. * +// ******************************************************************** +// +// +/// \file Hadr09.cc +/// \brief Main program of the hadronic/Hadr09 example +// +//------------------------------------------------------------------------ +// This program shows how to use the class Hadronic Generator. +// The class HadronicGenerator is a kind of "hadronic generator", i.e. +// provides Geant4 final states (i.e. secondary particles) produced by +// hadron-nuclear inelastic collisions. +// Please see the class itself for more information. +// +// The use of the class Hadronic Generator is very simple: +// the constructor needs to be invoked only once - specifying the name +// of the Geant4 "physics case" to consider ("FTFP_BERT" will be +// considered as default is the name is not specified) - and then one +// method needs to be called at each collision, specifying the type of +// collision (hadron, energy, direction, material) to be simulated. +// The class HadronicGenerator is expected to work also in a +// multi-threaded environment with "external" threads (i.e. threads +// that are not necessarily managed by Geant4 run-manager): +// each thread should have its own instance of the class. +// +// See the string "***LOOKHERE***" below for the setting of parameters +// of this example: the "physics case", the set of possibilities from +// which to sample the projectile (i.e. whether the projectile is a +// hadron or an ion - in the case of hadron projectile, a list of hadrons +// is possible from which to sample at each collision; in the case of +// ion projectile, only one type of ion needs to be specified), +// the kinetic energy of the projectile (which can be sampled within +// an interval), whether the direction of the projectile is fixed or +// sampled at each collision, the target material (a list of materials +// is possible, from which the target material can be sampled at each +// collision, and then from this target material, the target nucleus +// will be chosen randomly by Geant4 itself), and whether to print out +// some information or not and how frequently. +// Once a well-defined type of hadron-nucleus or nucleus-nucleus +// inelastic collision has been chosen, the method +// HadronicGenerator::GenerateInteraction +// returns the secondaries produced by that interaction (in the form +// of a G4VParticleChange object). +// Some information about this final-state is printed out as an example. +// +// Usage: Hadr09 +//------------------------------------------------------------------------ + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +#include "CLHEP/Random/Randomize.h" +#include "CLHEP/Random/Ranlux64Engine.h" +#include "G4GenericIon.hh" +#include "G4HadronicParameters.hh" +#include "G4IonTable.hh" +#include "G4Material.hh" +#include "G4NistManager.hh" +#include "G4ParticleTable.hh" +#include "G4PhysicalConstants.hh" +#include "G4ProcessManager.hh" +#include "G4SystemOfUnits.hh" +#include "G4UnitsTable.hh" +#include "G4VParticleChange.hh" +#include "G4ios.hh" +#include "HadronicGenerator-vpgtle.hh" +#include "TAxis.h" +#include "TFile.h" +#include "TGraph.h" +#include "TTree.h" +#include "globals.hh" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... + +int main(int argc, char *argv[]) { + + // G4cout << "=== Test of the HadronicGenerator ===" << G4endl; + + // Enable light hypernuclei and anti-hypernuclei + G4HadronicParameters::Instance()->SetEnableHyperNuclei(false); + + // stw, pro and num are bool that states if options for projectiles + // type/number and standard computation are activated + bool stw = false; + bool pro = false; + bool num = false; + + G4int numCollisions = 1e4; // Default number of collisions + G4String strprojectile = "proton"; // Default projectile type + + // to read the options + for (int i = 0; i < argc; ++i) { + std::string arg = argv[i]; + if (arg == "weight") { + stw = true; + } + if (arg == "-n") { + numCollisions = atoi(argv[i + 1]); + if (numCollisions <= 0) { + G4cout << "ERROR: Number of collisions must be a positive integer! 1e4 " + "will be used" + << G4endl; + numCollisions = 1e4; // Default value + } + num = true; + G4cout << "Number of collisions set to: " << numCollisions << G4endl; + } + if (arg == "-p") { + strprojectile = argv[i + 1]; + pro = true; + if (strprojectile != "proton" && strprojectile != "neutron") { + G4cout << "ERROR: Invalid projectile type! Proton will be used." + << G4endl; + strprojectile = "proton"; // Default value + } + G4cout << "Projectile type set to: " << strprojectile << G4endl; + } + } + + // See the HadronicGenerator class for the possibilities and meaning of the + // "physics cases". ( In short, it is the name of the Geant4 hadronic model + // used for the simulation of + // the collision, with the possibility of having a transition between two + // models in a given energy interval, as in physics lists. ) + const G4String namePhysics = "QGSP_BIC_HP"; + const TString physlist = namePhysics; + + // Create the ROOT file and histograms + TFile *file = new TFile("/path/to/your/stage0/output/test.root", "RECREATE"); + + // Set up the parameters for the standard computation + const std::vector Fraction = { + 0.65, 0.185, 0.103, 0.03, 0.015, 0.01, 0.002, 0.002, 0.001, 0.001, 0.001}; + const std::vector Body_approx = {"G4_O", "G4_C", "G4_H", "G4_N", + "G4_Ca", "G4_P", "G4_K", "G4_S", + "G4_Na", "G4_Cl", "G4_Mg"}; + + // List of elements in the root file. + const std::vector humanBodyElements = { + //"G4_Al", + //"G4_Ar", + //"G4_Be", + //"G4_B", + //"G4_Ca", + "G4_C", + }; + //"G4_Cl", + //"G4_Cu", + //"G4_F", + //"G4_He", + //"G4_H", + //"G4_Li", + //"G4_Mg", + //"G4_Ne", + //"G4_N", + //"G4_O",}; + //"G4_P", + //"G4_K", + //"G4_Si", + //"G4_Ag", + //"G4_Na", + //"G4_S", + //"G4_Sn", + //"G4_Ti", + //"G4_Zn"}; + + G4int humanbodyindex = + humanBodyElements.size(); //***LOOKHERE*** GAP IN PRINTING + + G4ParticleDefinition *projectileNucleus = nullptr; + G4GenericIon *gion = G4GenericIon::GenericIon(); + gion->SetProcessManager(new G4ProcessManager(gion)); + G4ParticleTable *partTable = G4ParticleTable::GetParticleTable(); + G4IonTable *ions = partTable->GetIonTable(); + partTable->SetReadiness(); + ions->CreateAllIon(); + ions->CreateAllIsomer(); + + // The kinetic energy of the projectile will be sampled randomly, with flat + // probability in the interval [minEnergy, maxEnergy]. + //***LOOKHERE*** HADRON PROJECTILE MAX Ekin + G4int protonNbBins = 500; + G4double protonMinEnergy = 0; // MeV + G4double protonMaxEnergy = 200.; // MeV + G4int gammaNbBins = 250; + G4double gammaMinEnergy = 0; // MeV + G4double gammaMaxEnergy = 10; // MeV + + // histogram for the standard weight + TH1D *TH1D_weight = + new TH1D("Weight", + "Weight of the interaction for ToF computing;Protons energy " + "[MeV];Weight [mm-1]", + protonNbBins, protonMinEnergy, protonMaxEnergy); + + HadronicGenerator *theHadronicGenerator = new HadronicGenerator(namePhysics); + + if (theHadronicGenerator == nullptr) { + G4cerr << "ERROR: theHadronicGenerator is NULL !" << G4endl; + return 1; + } + + // Loop on the element + for (G4int k = 0; k < humanbodyindex; k++) { + G4String done = "not counting in the approximation"; // pre-print for + // standard computation + + G4String nameMaterial = humanBodyElements[k]; + G4Material *targetMaterial = + G4NistManager::Instance()->FindOrBuildMaterial(nameMaterial); + if (targetMaterial == nullptr) { + G4cerr << "ERROR: Material " << nameMaterial << " is not found !" + << G4endl; + return 3; + } + const G4Element *targetElement = targetMaterial->GetElement(0); + + TDirectory *dir = file->mkdir( + targetElement + ->GetSymbol()); // Création du dossier pour chaque élément chimique + + TString PGbin = to_string(10. / 250); + TH2D *TH2D_EpEpg = + new TH2D("EpEpg", + "PGs energy as a function of protons;Protons energy [MeV];PGs " + "energy [MeV];Log(number of gammas)", + protonNbBins, protonMinEnergy, protonMaxEnergy, gammaNbBins, + gammaMinEnergy, gammaMaxEnergy); // Graph 2D + TH2D *TH2D_GammaZ = new TH2D( + "GammaZ", + "PG yield per [" + PGbin + + "MeV*cm];Protons energy [MeV];PGs energy [MeV];Log(GAMMAZ/1)", + protonNbBins, protonMinEnergy, protonMaxEnergy, gammaNbBins, + gammaMinEnergy, gammaMaxEnergy); // Graph 2D + TH1D *TH1D_SigmaInelastic = + new TH1D("Kapa inelastique", + "Linear attenuation coefficient [/cm];Protons energy " + "[MeV];kapa inelastique [cm-1]", + protonNbBins, protonMinEnergy, protonMaxEnergy); + TH1D *TH1D_NrPG = new TH1D( + "NrPG", + "Number of prompt gamma-rays;Protons energy [MeV];Number of PGs", + protonNbBins, protonMinEnergy, protonMaxEnergy); + + G4cout << "Loop on element: " << targetElement->GetSymbol() << G4endl; + + // G4DynamicParticle* dynamicProjectile = new G4DynamicParticle(projectile, + // aDirection, projectileEnergy); + + CLHEP::Ranlux64Engine defaultEngine(1234567, 4); + CLHEP::HepRandom::setTheEngine(&defaultEngine); + G4int seed = time(NULL); + CLHEP::HepRandom::setTheSeed(seed); + + G4int nbPG(0); + G4int nbCollisions = 1e4; // Default number of collisions + if (num) { + nbCollisions = numCollisions; + } + G4cout << "Number of collisions: " << nbCollisions << G4endl; + G4ParticleDefinition *projectile = nullptr; + if (pro) { + projectile = partTable->FindParticle(strprojectile); + } else { + projectile = partTable->FindParticle("proton"); + } + G4cout << "Projectile: " << projectile->GetParticleName() << G4endl; + nbPG = theHadronicGenerator->GenerateInteraction( + projectile, targetMaterial, TH2D_EpEpg, TH1D_SigmaInelastic, + TH2D_GammaZ, TH1D_NrPG, nbCollisions); + if (stw) { + auto ind = + std::find(Body_approx.begin(), Body_approx.end(), nameMaterial); + // Check if the element was found + if (ind != Body_approx.end()) { + // Calculate the index + size_t index = std::distance(Body_approx.begin(), ind); + done = theHadronicGenerator->WeightCompute(TH2D_GammaZ, TH1D_weight, + Fraction[index]); + } + } + + G4cout << "nr of PG: " << nbPG << G4endl; + G4cout << nameMaterial << " : " << done << G4endl; + dir->cd(); + TH2D_EpEpg->Write(); + TH1D_SigmaInelastic->Write(); + TH1D_NrPG->Write(); + TH2D_GammaZ->Write(); + + delete TH2D_EpEpg; + delete TH2D_GammaZ; + delete TH1D_SigmaInelastic; + delete TH1D_NrPG; + } + if (stw) { + TDirectory *dirw = file->mkdir("standard_Weight"); + dirw->cd(); + TH1D_weight->Write(); + } + delete TH1D_weight; + + file->Close(); + delete theHadronicGenerator; + + return 0; +} + +//....oooOO0OOooo........oooOO0OOooo........oooOO0OOooo........oooOO0OOooo...... diff --git a/opengate/contrib/vpgTLE/stageX/__init__.py b/opengate/contrib/vpgTLE/stageX/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaAnalogActor.cpp b/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaAnalogActor.cpp new file mode 100644 index 000000000..9e327e3d3 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaAnalogActor.cpp @@ -0,0 +1,295 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "G4EmCalculator.hh" +#include "G4Gamma.hh" +#include "G4ParticleDefinition.hh" +#include "G4RandomTools.hh" +#include "G4RunManager.hh" +#include "G4Threading.hh" +#include "G4Track.hh" + +#include "G4HadronInelasticProcess.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" +#include "GateMaterialMuHandler.h" +#include "GateVoxelizedPromptGammaAnalogActor.h" + +#include "CLHEP/Random/Randomize.h" +#include +#include +#include +#include +#include + +#include +#include +// #include +#include +#include + +GateVoxelizedPromptGammaAnalogActor::GateVoxelizedPromptGammaAnalogActor( + py::dict &user_info) + : GateVActor(user_info, true) { + fMultiThreadReady = + true; // But used as a single thread python side : nb pf runs = 1 +} + +GateVoxelizedPromptGammaAnalogActor::~GateVoxelizedPromptGammaAnalogActor() { + // not needed + cpp_tof_proton_image = nullptr; + cpp_E_proton_image = nullptr; + cpp_E_neutron_image = nullptr; + cpp_tof_neutron_image = nullptr; + + // Release the 3D volume + volume = nullptr; + std::cout << "GateVoxelizedPromptGammaAnalogActor destructor called. " + "Resources released." + << std::endl; +} + +void GateVoxelizedPromptGammaAnalogActor::InitializeUserInfo( + py::dict &user_info) { + GateVActor::InitializeUserInfo(user_info); + + // retrieve the python param here + timebins = py::int_(user_info["timebins"]); + timerange = py::float_(user_info["timerange"]); + energybins = py::int_(user_info["energybins"]); + energyrange = py::float_(user_info["energyrange"]); + + fTranslation = DictGetG4ThreeVector(user_info, "translation"); + fsize = DictGetG4ThreeVector(user_info, "size"); + fspacing = DictGetG4ThreeVector(user_info, "spacing"); +} + +void GateVoxelizedPromptGammaAnalogActor::InitializeCpp() { + GateVActor::InitializeCpp(); + // Create the image pointers + // (the size and allocation will be performed on the py side) + if (fProtonTimeFlag) { + cpp_tof_proton_image = ImageType::New(); + } + if (fProtonEnergyFlag) { + cpp_E_proton_image = ImageType::New(); + } + if (fNeutronEnergyFlag) { + cpp_E_neutron_image = ImageType::New(); + } + if (fNeutronTimeFlag) { + cpp_tof_neutron_image = ImageType::New(); + } + + // Construction of the 3D image with the same shape/mat that the voxel of the + // actor but is accepted by the method of volume_attach and "isInside" + volume = Image3DType::New(); + + Image3DType::RegionType region; + Image3DType::SizeType size; + Image3DType::SpacingType spacing; + + size[0] = fsize[0]; + size[1] = fsize[1]; + size[2] = fsize[2]; + region.SetSize(size); + + spacing[0] = fspacing[0]; + spacing[1] = fspacing[1]; + spacing[2] = fspacing[2]; + + volume->SetRegions(region); + volume->SetSpacing(spacing); + volume->Allocate(); + volume->FillBuffer(0); + + incidentParticles = + 0; // initiate the conuter of incidente protons - scaling factor +} + +void GateVoxelizedPromptGammaAnalogActor::BeginOfRunActionMasterThread( + int run_id) { + // Attach the 3D volume used to + + // Fill the 4D volume of interest with 0 to ensure that it is well initiated + if (fProtonTimeFlag) { + cpp_tof_proton_image->FillBuffer(0); + } + if (fProtonEnergyFlag) { + cpp_E_proton_image->FillBuffer(0); + } + if (fNeutronEnergyFlag) { + cpp_E_neutron_image->FillBuffer(0); + } + if (fNeutronTimeFlag) { + cpp_tof_neutron_image->FillBuffer(0); + } + AttachImageToVolume(volume, fPhysicalVolumeName, fTranslation); +} + +void GateVoxelizedPromptGammaAnalogActor::BeginOfRunAction(const G4Run *run) {} + +void GateVoxelizedPromptGammaAnalogActor::BeginOfEventAction( + const G4Event *event) { + T0 = event->GetPrimaryVertex()->GetT0(); + incidentParticles++; +} + +void GateVoxelizedPromptGammaAnalogActor::SteppingAction(G4Step *step) { + if (step->GetTrack()->GetParticleDefinition() != G4Neutron::Neutron() && + (step->GetTrack()->GetParticleDefinition() != G4Proton::Proton())) { + return; + } + static G4HadronicProcessStore *store = G4HadronicProcessStore::Instance(); + static G4VProcess *protonInelastic = + store->FindProcess(G4Proton::Proton(), fHadronInelastic); + static G4VProcess *neutronInelastic = + store->FindProcess(G4Neutron::Neutron(), fHadronInelastic); + if (step->GetPostStepPoint()->GetProcessDefinedStep() != protonInelastic && + step->GetPostStepPoint()->GetProcessDefinedStep() != neutronInelastic) { + return; + } + + auto position = step->GetPostStepPoint()->GetPosition(); + auto touchable = step->GetPreStepPoint()->GetTouchable(); + // Get the voxel index + auto localPosition = + touchable->GetHistory()->GetTransform(0).TransformPoint(position); + + // convert G4ThreeVector to itk PointType + Image3DType::PointType point; + point[0] = localPosition[0]; + point[1] = localPosition[1]; + point[2] = localPosition[2]; + + Image3DType::IndexType index; + G4bool isInside = volume->TransformPhysicalPointToIndex(point, index); + if (!isInside) { // verification + return; // Skip if not inside the volume + } + + // Get the spatial index from the index obtained with the 3D volume and th + // emethod GetStepVoxelPosition() + ImageType::IndexType ind; + ind[0] = index[0]; + ind[1] = index[1]; + ind[2] = index[2]; + + auto secondaries = step->GetSecondary(); + for (size_t i = 0; i < secondaries->size(); i++) { + auto secondary = secondaries->at(i); + auto secondary_def = secondary->GetParticleDefinition(); + if (secondary_def != G4Gamma::Gamma()) { + continue; + } + G4double gammaEnergy = secondary->GetKineticEnergy(); // in MeV + // thershold with a minimum energy of 40 keV + if (gammaEnergy < 0.04 * CLHEP::MeV) { + continue; + } + + if (fProtonTimeFlag || + fNeutronTimeFlag) { // If the quantity of interest is the time of flight + // Get the time of flight + // G4double randomtime = G4UniformRand(); + // G4double pretime = step->GetPreStepPoint()->GetGlobalTime()- T0; //ns + // G4double posttime = step->GetPostStepPoint()->GetGlobalTime()- T0;//ns + G4double time = secondary->GetGlobalTime() - T0; // ns + + // Get the voxel index (fourth dim) corresponding to the time of flight + G4int bin = static_cast( + time / (timerange / timebins)); // Always the left bin + + if (bin >= timebins) { + bin = timebins; + } + ind[3] = bin; + // Store the value in the volume for neutrons OR protons -> LEFT BINNING + if (fProtonTimeFlag && + step->GetTrack()->GetParticleDefinition()->GetParticleName() == + "proton") { + ImageAddValue(cpp_tof_proton_image, ind, 1); + } + if (fNeutronTimeFlag && + step->GetTrack()->GetParticleDefinition()->GetParticleName() == + "neutron") { + ImageAddValue(cpp_tof_neutron_image, ind, 1); + } + } + if (fProtonEnergyFlag || + fNeutronEnergyFlag) { // when the quantity of interest is the energy + // Get the voxel index (fourth dim) corresponding to the energy of the + // projectile + G4int bin = static_cast( + gammaEnergy / (energyrange / energybins)); // Always the left bin + if (bin >= energybins) { + bin = energybins; + } + if (bin < 0) { + bin = 0; // underflow + } + ind[3] = bin; + // Store the value in the volume for neutrons OR protons -> LEFT BINNING + if (fProtonEnergyFlag && + step->GetTrack()->GetParticleDefinition()->GetParticleName() == + "proton") { + ImageAddValue(cpp_E_proton_image, ind, 1); + } + if (fNeutronEnergyFlag && + step->GetTrack()->GetParticleDefinition()->GetParticleName() == + "neutron") { + ImageAddValue(cpp_E_neutron_image, ind, 1); + } + } + } +} + +void GateVoxelizedPromptGammaAnalogActor::EndOfRunAction(const G4Run *run) { + std::cout << "incident particles : " << incidentParticles << std::endl; + if (incidentParticles == 0) { + std::cerr << "Error: incidentParticles is zero. Skipping scaling." + << std::endl; + return; + } + // scaling all the 4D voxels with th enumber of incident protons (= number of + // event) + if (fProtonTimeFlag) { + itk::ImageRegionIterator it( + cpp_tof_proton_image, cpp_tof_proton_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fProtonEnergyFlag) { + itk::ImageRegionIterator it( + cpp_E_proton_image, cpp_E_proton_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fNeutronEnergyFlag) { + itk::ImageRegionIterator it( + cpp_E_neutron_image, cpp_E_neutron_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fNeutronTimeFlag) { + itk::ImageRegionIterator it( + cpp_tof_neutron_image, + cpp_tof_neutron_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } +} + +int GateVoxelizedPromptGammaAnalogActor::EndOfRunActionMasterThread( + int run_id) { + return 0; +} diff --git a/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaAnalogActor.h b/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaAnalogActor.h new file mode 100644 index 000000000..f207a5846 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaAnalogActor.h @@ -0,0 +1,98 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateVoxelizedPromptGammaAnalogActor_h +#define GateVoxelizedPromptGammaAnalogActor_h + +#include "G4Cache.hh" +#include "G4EmCalculator.hh" +#include "G4NistManager.hh" +#include "G4VPrimitiveScorer.hh" +#include "GateDoseActor.h" +#include "GateMaterialMuHandler.h" + +#include + +namespace py = pybind11; + +class GateVoxelizedPromptGammaAnalogActor : public GateVActor { + +public: + // destructor + ~GateVoxelizedPromptGammaAnalogActor() override; + + explicit GateVoxelizedPromptGammaAnalogActor(py::dict &user_info); + + void InitializeUserInfo(py::dict &user_info) override; + + void InitializeCpp() override; + + void BeginOfRunActionMasterThread(int run_id); + + int EndOfRunActionMasterThread(int run_id) override; + + void EndOfRunAction(const G4Run *run); + + void BeginOfRunAction(const G4Run *run); + + void BeginOfEventAction(const G4Event *event) override; + + void SteppingAction(G4Step *) override; + + inline bool GetProtonTimeFlag() const { return fProtonTimeFlag; } + + inline void SetProtonTimeFlag(const bool b) { fProtonTimeFlag = b; } + + inline bool GetNeutronTimeFlag() const { return fNeutronTimeFlag; } + + inline void SetNeutronTimeFlag(const bool b) { fNeutronTimeFlag = b; } + + inline bool GetProtonEnergyFlag() const { return fProtonEnergyFlag; } + + inline void SetProtonEnergyFlag(const bool b) { fProtonEnergyFlag = b; } + + inline bool GetNeutronEnergyFlag() const { return fNeutronEnergyFlag; } + + inline void SetNeutronEnergyFlag(const bool b) { fNeutronEnergyFlag = b; } + + inline std::string GetPhysicalVolumeName() const { + return fPhysicalVolumeName; + } + + inline void SetPhysicalVolumeName(std::string s) { fPhysicalVolumeName = s; } + + std::string fPhysicalVolumeName; + + // Image type + typedef itk::Image ImageType; + ImageType::Pointer cpp_tof_neutron_image; + ImageType::Pointer cpp_tof_proton_image; + ImageType::Pointer cpp_E_proton_image; + ImageType::Pointer cpp_E_neutron_image; + + typedef itk::Image Image3DType; + Image3DType::Pointer volume; + + G4double T0; + G4int incidentParticles; + + G4int timebins; + G4double timerange; + G4int energybins; + G4double energyrange; + + G4bool fProtonTimeFlag{}; + G4bool fProtonEnergyFlag{}; + G4bool fNeutronTimeFlag{}; + G4bool fNeutronEnergyFlag{}; + + G4ThreeVector fsize; + G4ThreeVector fspacing; + G4ThreeVector fTranslation; +}; + +#endif // GateVoxelizedPromptGammaAnalogActor_h diff --git a/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaTLEActor.cpp b/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaTLEActor.cpp new file mode 100644 index 000000000..b7d729652 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaTLEActor.cpp @@ -0,0 +1,282 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + ------------------------------------ -------------- */ + +#include "G4EmCalculator.hh" +#include "G4Gamma.hh" +#include "G4ParticleDefinition.hh" +#include "G4RandomTools.hh" +#include "G4RunManager.hh" +#include "G4Threading.hh" +#include "G4Track.hh" + +#include "G4HadronInelasticProcess.hh" +#include "GateHelpers.h" +#include "GateHelpersDict.h" +#include "GateHelpersImage.h" +#include "GateMaterialMuHandler.h" +#include "GateVoxelizedPromptGammaTLEActor.h" + +#include "CLHEP/Random/Randomize.h" +#include +#include +#include +#include +#include + +GateVoxelizedPromptGammaTLEActor::GateVoxelizedPromptGammaTLEActor( + py::dict &user_info) + : GateVActor(user_info, true) { + fMultiThreadReady = + true; // But used as a single thread python side : nb pf runs = 1 +} + +GateVoxelizedPromptGammaTLEActor::~GateVoxelizedPromptGammaTLEActor() { + // not needed +} + +void GateVoxelizedPromptGammaTLEActor::InitializeUserInfo(py::dict &user_info) { + GateVActor::InitializeUserInfo(user_info); + + // retrieve the python param here + timebins = py::int_(user_info["timebins"]); + timerange = py::float_(user_info["timerange"]); + energybins = py::int_(user_info["energybins"]); + energyrange = py::float_(user_info["energyrange"]); + weight = py::bool_(user_info["weight"]); + fTranslation = DictGetG4ThreeVector(user_info, "translation"); + fsize = DictGetG4ThreeVector(user_info, "size"); + fspacing = DictGetG4ThreeVector(user_info, "spacing"); +} + +void GateVoxelizedPromptGammaTLEActor::InitializeCpp() { + GateVActor::InitializeCpp(); + // Create the image pointers + // (the size and allocation will be performed on the py side) + if (fProtonTimeFlag) { + cpp_tof_proton_image = ImageType::New(); + } + if (fProtonEnergyFlag) { + cpp_E_proton_image = ImageType::New(); + } + if (fNeutronEnergyFlag) { + cpp_E_neutron_image = ImageType::New(); + } + if (fNeutronTimeFlag) { + cpp_tof_neutron_image = ImageType::New(); + } + + // Construction of the 3D image with the same shape/mat that the voxel of the + // actor but is accepted by the method of volume_attach and "isInside" + volume = Image3DType::New(); + + Image3DType::RegionType region; + Image3DType::SizeType size; + Image3DType::SpacingType spacing; + + size[0] = fsize[0]; + size[1] = fsize[1]; + size[2] = fsize[2]; + region.SetSize(size); + + spacing[0] = fspacing[0]; + spacing[1] = fspacing[1]; + spacing[2] = fspacing[2]; + + volume->SetRegions(region); + volume->SetSpacing(spacing); + volume->Allocate(); + volume->FillBuffer(0); + + incidentParticles = + 0; // initiate the conuter of incidente protons - scaling factor +} + +void GateVoxelizedPromptGammaTLEActor::BeginOfRunActionMasterThread( + int run_id) { + // Fill the 4D volume of interest with 0 to ensure that it is well initiated + if (fProtonTimeFlag) { + cpp_tof_proton_image->FillBuffer(0); + } + if (fProtonEnergyFlag) { + cpp_E_proton_image->FillBuffer(0); + } + if (fNeutronEnergyFlag) { + cpp_E_neutron_image->FillBuffer(0); + } + if (fNeutronTimeFlag) { + cpp_tof_neutron_image->FillBuffer(0); + } + // Attach the 3D volume to verify if the particle is inside the ct volume + AttachImageToVolume(volume, fPhysicalVolumeName, fTranslation); +} + +void GateVoxelizedPromptGammaTLEActor::BeginOfRunAction(const G4Run *run) {} + +void GateVoxelizedPromptGammaTLEActor::BeginOfEventAction( + + const G4Event *event) { + T0 = event->GetPrimaryVertex()->GetT0(); + incidentParticles++; +} + +void GateVoxelizedPromptGammaTLEActor::SteppingAction(G4Step *step) { + const G4ParticleDefinition *particle = + step->GetTrack()->GetParticleDefinition(); + if ((particle != G4Neutron::Neutron()) && (particle != G4Proton::Proton())) { + return; + } + auto position = step->GetPostStepPoint()->GetPosition(); + auto touchable = step->GetPreStepPoint()->GetTouchable(); + // Get the voxel index + + auto localPosition = + touchable->GetHistory()->GetTransform(0).TransformPoint(position); + + // convert G4ThreeVector to itk PointType + Image3DType::PointType point; + point[0] = localPosition[0]; + point[1] = localPosition[1]; + point[2] = localPosition[2]; + + Image3DType::IndexType index; + G4bool isInside = volume->TransformPhysicalPointToIndex(point, index); + if (!isInside) { // verification + return; // Skip if not inside the volume + } + + // Convert the index to a 4D index + ImageType::IndexType ind; + ind[0] = index[0]; + ind[1] = index[1]; + ind[2] = index[2]; + + // Get the step lenght + const G4double &l = step->GetStepLength(); + G4Material *mat = step->GetPreStepPoint()->GetMaterial(); + G4double rho = mat->GetDensity() / (CLHEP::g / CLHEP::cm3); + auto w = step->GetTrack() + ->GetWeight(); // Get the weight of the track (particle history) + // for potential russian roulette or splitting + + // Get the energy of the projectile + const G4double &preE = step->GetPreStepPoint()->GetKineticEnergy(); + const G4double &postE = step->GetPostStepPoint()->GetKineticEnergy(); + G4double projectileEnergy = preE; + if (postE != 0) { + const G4double &randomenergy = G4UniformRand(); + projectileEnergy = postE + randomenergy * (preE - postE); // MeV + } + // Get the energy bin index + G4int binE = static_cast( + projectileEnergy / (energyrange / energybins)); // Always the left bin + if (binE >= energybins) { + binE = energybins; // last bin = overflow + } + + if (binE < 0) { + binE = 0; // underflow + } + + if ((fProtonTimeFlag) || + (fNeutronTimeFlag)) { // If the quantity of interest is the time of flight + // Get the time of flight + const G4double &randomtime = G4UniformRand(); + const G4double &pretime = + step->GetPreStepPoint()->GetGlobalTime() - T0; // ns + const G4double &posttime = + step->GetPostStepPoint()->GetGlobalTime() - T0; // ns + G4double time = (posttime + randomtime * (pretime - posttime)); // ns + // Get the voxel index (fourth dim) corresponding to the time of flight + G4int bin = static_cast(time / (timerange / timebins)); + if (bin >= timebins) { + bin = timebins; // overflow + } + if (bin < 0) { + bin = 0; // underflow + } + ind[3] = bin; + G4double pg_sum = 1; + // Store the value in the volume for neutrons OR protons -> LEFT BINNING + if (fProtonTimeFlag && particle == G4Proton::Proton()) { + if (weight) { + pg_sum = fProtonVector[binE]; + } + ImageAddValue(cpp_tof_proton_image, ind, pg_sum * l * rho * w); + } + if (fNeutronTimeFlag && particle == G4Neutron::Neutron()) { + if (weight) { + pg_sum = fProtonVector[binE]; + } + ImageAddValue(cpp_tof_neutron_image, ind, + pg_sum * l * rho * w); + } + } + if (fProtonEnergyFlag || + fNeutronEnergyFlag) { // when the quantity of interest is the energy + ind[3] = binE; + // Store the value in the volume for neutrons OR protons -> LEFT BINNING + if (fProtonEnergyFlag && particle == G4Proton::Proton()) { + ImageAddValue(cpp_E_proton_image, ind, l * rho * w); + } + if (fNeutronEnergyFlag && particle == G4Neutron::Neutron()) { + ImageAddValue(cpp_E_neutron_image, ind, l * rho * w); + } + } +} + +void GateVoxelizedPromptGammaTLEActor::EndOfRunAction(const G4Run *run) { + std::cout << "incident particles : " << incidentParticles << std::endl; + if (incidentParticles == 0) { + std::cerr << "Error: incidentParticles is zero. Skipping scaling." + << std::endl; + return; + } + // scaling all the 4D voxels with the number of incident protons (= number of + // event) + if (fProtonTimeFlag) { + itk::ImageRegionIterator it( + cpp_tof_proton_image, cpp_tof_proton_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fProtonEnergyFlag) { + itk::ImageRegionIterator it( + cpp_E_proton_image, cpp_E_proton_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fNeutronEnergyFlag) { + itk::ImageRegionIterator it( + cpp_E_neutron_image, cpp_E_neutron_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } + if (fNeutronTimeFlag) { + itk::ImageRegionIterator it( + cpp_tof_neutron_image, + cpp_tof_neutron_image->GetLargestPossibleRegion()); + for (it.GoToBegin(); !it.IsAtEnd(); ++it) { + it.Set(it.Get() / incidentParticles); + } + } +} + +int GateVoxelizedPromptGammaTLEActor::EndOfRunActionMasterThread(int run_id) { + return 0; +} + +// Standarsized Vectors For ToF weights +void GateVoxelizedPromptGammaTLEActor::SetVector(py::array_t vect_p, + py::array_t vect_n) { + fProtonVector = + std::vector(vect_p.data(), vect_p.data() + vect_p.size()); + fNeutronVector = + std::vector(vect_n.data(), vect_n.data() + vect_n.size()); +} diff --git a/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaTLEActor.h b/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaTLEActor.h new file mode 100644 index 000000000..202e5b3c5 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/actor/GateVoxelizedPromptGammaTLEActor.h @@ -0,0 +1,107 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#ifndef GateVoxelizedPromptGammaTLEActor_h +#define GateVoxelizedPromptGammaTLEActor_h + +#include "G4Cache.hh" +#include "G4EmCalculator.hh" +#include "G4NistManager.hh" +#include "G4VPrimitiveScorer.hh" +#include "GateDoseActor.h" +#include "GateMaterialMuHandler.h" +#include +#include +#include + +namespace py = pybind11; + +class GateVoxelizedPromptGammaTLEActor : public GateVActor { + +public: + // destructor + ~GateVoxelizedPromptGammaTLEActor() override; + + explicit GateVoxelizedPromptGammaTLEActor(py::dict &user_info); + + void InitializeUserInfo(py::dict &user_info) override; + + void InitializeCpp() override; + + void BeginOfRunActionMasterThread(int run_id); + + int EndOfRunActionMasterThread(int run_id) override; + + void EndOfRunAction(const G4Run *run); + + void BeginOfRunAction(const G4Run *run); + + void BeginOfEventAction(const G4Event *event) override; + + void SteppingAction(G4Step *) override; + + inline bool GetProtonTimeFlag() const { return fProtonTimeFlag; } + + inline void SetProtonTimeFlag(const bool b) { fProtonTimeFlag = b; } + + inline bool GetNeutronTimeFlag() const { return fNeutronTimeFlag; } + + inline void SetNeutronTimeFlag(const bool b) { fNeutronTimeFlag = b; } + + inline bool GetProtonEnergyFlag() const { return fProtonEnergyFlag; } + + inline void SetProtonEnergyFlag(const bool b) { fProtonEnergyFlag = b; } + + inline bool GetNeutronEnergyFlag() const { return fNeutronEnergyFlag; } + + inline void SetNeutronEnergyFlag(const bool b) { fNeutronEnergyFlag = b; } + + void SetVector(py::array_t vect_p, py::array_t vect_n); + + inline std::string GetPhysicalVolumeName() const { + return fPhysicalVolumeName; + } + + // void SetVector(pybind11::array_t vect_p, pybind11::array_t + // vect_n ); + + inline void SetPhysicalVolumeName(std::string s) { fPhysicalVolumeName = s; } + + std::string fPhysicalVolumeName; + + // Image type + typedef itk::Image ImageType; + ImageType::Pointer cpp_tof_neutron_image; + ImageType::Pointer cpp_tof_proton_image; + ImageType::Pointer cpp_E_proton_image; + ImageType::Pointer cpp_E_neutron_image; + + typedef itk::Image Image3DType; + Image3DType::Pointer volume; + + G4double T0; + G4int incidentParticles; + + G4int timebins; + G4double timerange; + G4int energybins; + G4double energyrange; + + G4bool fProtonTimeFlag{}; + G4bool fProtonEnergyFlag{}; + G4bool fNeutronTimeFlag{}; + G4bool fNeutronEnergyFlag{}; + G4bool weight; + + G4ThreeVector fsize; + G4ThreeVector fspacing; + G4ThreeVector fTranslation; + std::vector fProtonVector; + std::vector fNeutronVector; +}; + +#endif // GateVoxelizedPromptGammaTLEActor_h diff --git a/opengate/contrib/vpgTLE/stageX/actor/pyGateVoxelizedPromptGammaAnalogActor.cpp b/opengate/contrib/vpgTLE/stageX/actor/pyGateVoxelizedPromptGammaAnalogActor.cpp new file mode 100644 index 000000000..bf197db05 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/actor/pyGateVoxelizedPromptGammaAnalogActor.cpp @@ -0,0 +1,81 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include +#include + +namespace py = pybind11; + +#include "GateVoxelizedPromptGammaAnalogActor.h" + +class PyGateVoxelizedPromptGammaAnalogActor + : public GateVoxelizedPromptGammaAnalogActor { +public: + // Inherit the constructors + using GateVoxelizedPromptGammaAnalogActor:: + GateVoxelizedPromptGammaAnalogActor; + + void BeginOfRunActionMasterThread(int run_id) override { + PYBIND11_OVERLOAD(void, GateVoxelizedPromptGammaAnalogActor, + BeginOfRunActionMasterThread, run_id); + } + + int EndOfRunActionMasterThread(int run_id) override { + PYBIND11_OVERLOAD(int, GateVoxelizedPromptGammaAnalogActor, + EndOfRunActionMasterThread, run_id); + } +}; + +void init_GateVoxelizedPromptGammaAnalogActor(py::module &m) { + py::class_, + GateVActor>(m, "GateVoxelizedPromptGammaAnalogActor") + .def(py::init()) + .def("InitializeUserInfo", + &GateVoxelizedPromptGammaAnalogActor::InitializeUserInfo) + .def("BeginOfRunActionMasterThread", + &GateVoxelizedPromptGammaAnalogActor::BeginOfRunActionMasterThread) + .def("EndOfRunActionMasterThread", + &GateVoxelizedPromptGammaAnalogActor::EndOfRunActionMasterThread) + .def("SetPhysicalVolumeName", + &GateVoxelizedPromptGammaAnalogActor::SetPhysicalVolumeName) + .def("GetPhysicalVolumeName", + &GateVoxelizedPromptGammaAnalogActor::GetPhysicalVolumeName) + + .def("SetProtonTimeFlag", + &GateVoxelizedPromptGammaAnalogActor::SetProtonTimeFlag) + .def("GetProtonTimeFlag", + &GateVoxelizedPromptGammaAnalogActor::GetProtonTimeFlag) + + .def("SetProtonEnergyFlag", + &GateVoxelizedPromptGammaAnalogActor::SetProtonEnergyFlag) + .def("GetProtonEnergyFlag", + &GateVoxelizedPromptGammaAnalogActor::GetProtonEnergyFlag) + + .def("SetNeutronEnergyFlag", + &GateVoxelizedPromptGammaAnalogActor::SetNeutronEnergyFlag) + .def("GetNeutronEnergyFlag", + &GateVoxelizedPromptGammaAnalogActor::GetNeutronEnergyFlag) + + .def("SetNeutronTimeFlag", + &GateVoxelizedPromptGammaAnalogActor::SetNeutronTimeFlag) + .def("GetNeutronTimeFlag", + &GateVoxelizedPromptGammaAnalogActor::GetNeutronTimeFlag) + + .def_readwrite("fPhysicalVolumeName", + &GateVoxelizedPromptGammaAnalogActor::fPhysicalVolumeName) + .def_readwrite( + "cpp_tof_neutron_image", + &GateVoxelizedPromptGammaAnalogActor::cpp_tof_neutron_image) + .def_readwrite("cpp_tof_proton_image", + &GateVoxelizedPromptGammaAnalogActor::cpp_tof_proton_image) + .def_readwrite("cpp_E_neutron_image", + &GateVoxelizedPromptGammaAnalogActor::cpp_E_neutron_image) + .def_readwrite("cpp_E_proton_image", + &GateVoxelizedPromptGammaAnalogActor::cpp_E_proton_image); +} diff --git a/opengate/contrib/vpgTLE/stageX/actor/pyGateVoxelizedPromptGammaTLEActor.cpp b/opengate/contrib/vpgTLE/stageX/actor/pyGateVoxelizedPromptGammaTLEActor.cpp new file mode 100644 index 000000000..64458959f --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/actor/pyGateVoxelizedPromptGammaTLEActor.cpp @@ -0,0 +1,79 @@ +/* -------------------------------------------------- + Copyright (C): OpenGATE Collaboration + This software is distributed under the terms + of the GNU Lesser General Public Licence (LGPL) + See LICENSE.md for further details + -------------------------------------------------- */ + +#include +#include + +namespace py = pybind11; + +#include "GateVoxelizedPromptGammaTLEActor.h" + +class PyGateVoxelizedPromptGammaTLEActor + : public GateVoxelizedPromptGammaTLEActor { +public: + // Inherit the constructors + using GateVoxelizedPromptGammaTLEActor::GateVoxelizedPromptGammaTLEActor; + + void BeginOfRunActionMasterThread(int run_id) override { + PYBIND11_OVERLOAD(void, GateVoxelizedPromptGammaTLEActor, + BeginOfRunActionMasterThread, run_id); + } + + int EndOfRunActionMasterThread(int run_id) override { + PYBIND11_OVERLOAD(int, GateVoxelizedPromptGammaTLEActor, + EndOfRunActionMasterThread, run_id); + } +}; + +void init_GateVoxelizedPromptGammaTLEActor(py::module &m) { + py::class_, + GateVActor>(m, "GateVoxelizedPromptGammaTLEActor") + .def(py::init()) + .def("InitializeUserInfo", + &GateVoxelizedPromptGammaTLEActor::InitializeUserInfo) + .def("BeginOfRunActionMasterThread", + &GateVoxelizedPromptGammaTLEActor::BeginOfRunActionMasterThread) + .def("EndOfRunActionMasterThread", + &GateVoxelizedPromptGammaTLEActor::EndOfRunActionMasterThread) + .def("SetPhysicalVolumeName", + &GateVoxelizedPromptGammaTLEActor::SetPhysicalVolumeName) + .def("GetPhysicalVolumeName", + &GateVoxelizedPromptGammaTLEActor::GetPhysicalVolumeName) + .def("SetVector", &GateVoxelizedPromptGammaTLEActor::SetVector) + .def("SetProtonTimeFlag", + &GateVoxelizedPromptGammaTLEActor::SetProtonTimeFlag) + .def("GetProtonTimeFlag", + &GateVoxelizedPromptGammaTLEActor::GetProtonTimeFlag) + + .def("SetProtonEnergyFlag", + &GateVoxelizedPromptGammaTLEActor::SetProtonEnergyFlag) + .def("GetProtonEnergyFlag", + &GateVoxelizedPromptGammaTLEActor::GetProtonEnergyFlag) + + .def("SetNeutronEnergyFlag", + &GateVoxelizedPromptGammaTLEActor::SetNeutronEnergyFlag) + .def("GetNeutronEnergyFlag", + &GateVoxelizedPromptGammaTLEActor::GetNeutronEnergyFlag) + + .def("SetNeutronTimeFlag", + &GateVoxelizedPromptGammaTLEActor::SetNeutronTimeFlag) + .def("GetNeutronTimeFlag", + &GateVoxelizedPromptGammaTLEActor::GetNeutronTimeFlag) + + .def_readwrite("fPhysicalVolumeName", + &GateVoxelizedPromptGammaTLEActor::fPhysicalVolumeName) + .def_readwrite("cpp_tof_neutron_image", + &GateVoxelizedPromptGammaTLEActor::cpp_tof_neutron_image) + .def_readwrite("cpp_tof_proton_image", + &GateVoxelizedPromptGammaTLEActor::cpp_tof_proton_image) + .def_readwrite("cpp_E_neutron_image", + &GateVoxelizedPromptGammaTLEActor::cpp_E_neutron_image) + .def_readwrite("cpp_E_proton_image", + &GateVoxelizedPromptGammaTLEActor::cpp_E_proton_image); +} diff --git a/opengate/contrib/vpgTLE/stageX/data/GateMaterials.db b/opengate/contrib/vpgTLE/stageX/data/GateMaterials.db new file mode 100644 index 000000000..d62192708 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/data/GateMaterials.db @@ -0,0 +1,478 @@ +[Elements] +Hydrogen: S= H ; Z= 1. ; A= 1.01 g/mole +Helium: S= He ; Z= 2. ; A= 4.003 g/mole +Lithium: S= Li ; Z= 3. ; A= 6.941 g/mole +Beryllium: S= Be ; Z= 4. ; A= 9.012 g/mole +Boron: S= B ; Z= 5. ; A= 10.811 g/mole +Carbon: S= C ; Z= 6. ; A= 12.01 g/mole +Nitrogen: S= N ; Z= 7. ; A= 14.01 g/mole +Oxygen: S= O ; Z= 8. ; A= 16.00 g/mole +Fluorine: S= F ; Z= 9. ; A= 18.998 g/mole +Neon: S= Ne ; Z= 10. ; A= 20.180 g/mole +Sodium: S= Na ; Z= 11. ; A= 22.99 g/mole +Magnesium: S= Mg ; Z= 12. ; A= 24.305 g/mole +Aluminium: S= Al ; Z= 13. ; A= 26.98 g/mole +Silicon: S= Si ; Z= 14. ; A= 28.09 g/mole +Phosphor: S= P ; Z= 15. ; A= 30.97 g/mole +Sulfur: S= S ; Z= 16. ; A= 32.066 g/mole +Chlorine: S= Cl ; Z= 17. ; A= 35.45 g/mole +Argon: S= Ar ; Z= 18. ; A= 39.95 g/mole +Potassium: S= K ; Z= 19. ; A= 39.098 g/mole +Calcium: S= Ca ; Z= 20. ; A= 40.08 g/mole +Scandium: S= Sc ; Z= 21. ; A= 44.956 g/mole +Titanium: S= Ti ; Z= 22. ; A= 47.867 g/mole +Vandium: S= V ; Z= 23. ; A= 50.942 g/mole +Chromium: S= Cr ; Z= 24. ; A= 51.996 g/mole +Manganese: S= Mn ; Z= 25. ; A= 54.938 g/mole +Iron: S= Fe ; Z= 26. ; A= 55.845 g/mole +Cobalt: S= Co ; Z= 27. ; A= 58.933 g/mole +Nickel: S= Ni ; Z= 28. ; A= 58.693 g/mole +Copper: S= Cu ; Z= 29. ; A= 63.39 g/mole +Zinc: S= Zn ; Z= 30. ; A= 65.39 g/mole +Gallium: S= Ga ; Z= 31. ; A= 69.723 g/mole +Germanium: S= Ge ; Z= 32. ; A= 72.61 g/mole +Yttrium: S= Y ; Z= 39. ; A= 88.91 g/mole +Cadmium: S= Cd ; Z= 48. ; A= 112.41 g/mole +Tellurium: S= Te ; Z= 52. ; A= 127.6 g/mole +Iodine: S= I ; Z= 53. ; A= 126.90 g/mole +Cesium: S= Cs ; Z= 55. ; A= 132.905 g/mole +Gadolinium: S= Gd ; Z= 64. ; A= 157.25 g/mole +Lutetium: S= Lu ; Z= 71. ; A= 174.97 g/mole +Tungsten: S= W ; Z= 74. ; A= 183.84 g/mole +Platin: S= Pt ; Z= 78. ; A= 195.078 g/mole +Thallium: S= Tl ; Z= 81. ; A= 204.37 g/mole +Lead: S= Pb ; Z= 82. ; A= 207.20 g/mole +Bismuth: S= Bi ; Z= 83. ; A= 208.98 g/mole +Uranium: S= U ; Z= 92. ; A= 238.03 g/mole + +[Materials] + +made_up_Ge: d=1000 g/cm3 ; n = 1 + +el: name=Germanium; f=1.000000 + +Water1ppmPt: d=1 g/cm3 ; n = 3 + +el: name=Hydrogen; f=0.111898 + +el: name=Oxygen; f=0.888101 + +el: name=Platin; f=0.000001 + +Water3ppmPt: d=1 g/cm3 ; n = 3 + +el: name=Hydrogen; f=0.111898 + +el: name=Oxygen; f=0.888099 + +el: name=Platin; f=0.000003 + +ppmPtry: d=1 g/cm3 ; n = 3 + +el: name=Hydrogen; f=0.100000 + +el: name=Oxygen; f=0.897000 + +el: name=Platin; f=0.003000 + +Water_3mgI: d=1 g/cm3 ; n = 3 + +el: name=Hydrogen; f=0.111562 + +el: name=Oxygen; f=0.885438 + +el: name=Iodine; f=0.003000 + +Water_50mgI: d=1 g/cm3 ; n = 3 + +el: name=Hydrogen; f=0.106303 + +el: name=Oxygen; f=0.843697 + +el: name=Iodine; f=0.050000 + +Water_300mgI: d=1 g/cm3 ; n = 3 + +el: name=Hydrogen; f=0.078329 + +el: name=Oxygen; f=0.621671 + +el: name=Iodine; f=0.300000 + +Water_3mgGd: d=1 g/cm3 ; n = 3 + +el: name=Hydrogen; f=0.111562 + +el: name=Oxygen; f=0.885438 + +el: name=Gadolinium; f=0.003000 + +H2O_I_Gd_mix: d=1 g/cm3 ; n = 4 + +el: name=Hydrogen; f=0.111562 + +el: name=Oxygen; f=0.885438 + +el: name=Iodine; f=0.001500 + +el: name=Gadolinium; f=0.001500 + +H2O_I_Gd_mix_2: d=1 g/cm3 ; n = 4 + +el: name=Hydrogen; f=0.111227 + +el: name=Oxygen; f=0.882773 + +el: name=Iodine; f=0.003000 + +el: name=Gadolinium; f=0.003000 + +H2O_I_Gd_mix_3: d=1 g/cm3 ; n = 4 + +el: name=Hydrogen; f=0.110779 + +el: name=Oxygen; f=0.879221 + +el: name=Iodine; f=0.005000 + +el: name=Gadolinium; f=0.005000 + +H2O_I_Gd_mix_4: d=1 g/cm3 ; n = 4 + +el: name=Hydrogen; f=0.110220 + +el: name=Oxygen; f=0.874780 + +el: name=Iodine; f=0.007500 + +el: name=Gadolinium; f=0.007500 + +H2O_I_Gd_mix_5: d=1 g/cm3 ; n = 4 + +el: name=Hydrogen; f=0.109660 + +el: name=Oxygen; f=0.870340 + +el: name=Iodine; f=0.010000 + +el: name=Gadolinium; f=0.010000 + +Vacuum: d=0.000001 mg/cm3 ; n=1 + +el: name=Hydrogen ; n=1 + +Aluminium: d=2.7 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Uranium: d=18.90 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Silicon: d=2.33 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Germanium: d=5.32 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Yttrium: d=4.47 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Gadolinium: d=7.9 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Lutetium: d=9.84 g/cm3 ; n=1 + +el: name=auto ; n=1 + +Tungsten: d=19.3 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Lead: d=11.4 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +Bismuth: d=9.75 g/cm3 ; n=1 ; state=solid + +el: name=auto ; n=1 + +NaI: d=3.67 g/cm3; n=2; state=solid + +el: name=Sodium ; n=1 + +el: name=Iodine ; n=1 + +NaITl: d=3.67 g/cm3; n=3; state=solid + +el: name=Sodium ; f=0.152 + +el: name=Iodine ; f=0.838 + +el: name=Thallium ; f=0.010 + +PWO: d=8.28 g/cm3; n=3 ; state=Solid + +el: name=Lead; n=1 + +el: name=Tungsten; n=1 + +el: name=Oxygen; n=4 + +BGO: d=7.13 g/cm3; n= 3 ; state=solid + +el: name=Bismuth; n=4 + +el: name=Germanium; n=3 + +el: name=Oxygen; n=12 + +LSO: d=7.4 g/cm3; n=3 ; state=Solid + +el: name=Lutetium ; n=2 + +el: name=Silicon; n=1 + +el: name=Oxygen; n=5 + +Plexiglass: d=1.19 g/cm3; n=3; state=solid + +el: name=Hydrogen; f=0.080538 + +el: name=Carbon; f=0.599848 + +el: name=Oxygen; f=0.319614 + + +GSO: d=6.7 g/cm3; n=3 ; state=Solid + +el: name=Gadolinium ; n=2 + +el: name=Silicon; n=1 + +el: name=Oxygen; n=5 + +LuAP: d=8.34 g/cm3; n=3 ; state=Solid + +el: name=Lutetium ; n=1 + +el: name=Aluminium; n=1 + +el: name=Oxygen; n=3 + +YAP: d=5.55 g/cm3; n=3 ; state=Solid + +el: name=Yttrium ; n=1 + +el: name=Aluminium; n=1 + +el: name=Oxygen; n=3 + +Water: d=1.00 g/cm3; n=2 ; state=liquid + +el: name=Hydrogen ; n=2 + +el: name=Oxygen; n=1 + +Quartz: d=2.2 g/cm3; n=2 ; state=Solid + +el: name=Silicon ; n=1 + +el: name=Oxygen ; n=2 + +Breast: d=1.020 g/cm3 ; n = 8 + +el: name=Oxygen; f=0.5270 + +el: name=Carbon; f=0.3320 + +el: name=Hydrogen ; f=0.1060 + +el: name=Nitrogen; f=0.0300 + +el: name=Sulfur ; f=0.0020 + +el: name=Sodium ; f=0.0010 + +el: name=Phosphor; f=0.0010 + +el: name=Chlorine ; f=0.0010 + +Air: d=1.29 mg/cm3 ; n=4 ; state=gas + +el: name=Nitrogen; f=0.755268 + +el: name=Oxygen; f=0.231781 + +el: name=Argon; f=0.012827 + +el: name=Carbon; f=0.000124 + +Glass: d=2.5 g/cm3; n=4; state=solid + +el: name=Sodium ; f=0.1020 + +el: name=Calcium; f=0.0510 + +el: name=Silicon; f=0.2480 + +el: name=Oxygen; f=0.5990 + +Scinti-C9H10: d=1.032 g/cm3 ; n=2 + +el: name=Carbon; n=9 + +el: name=Hydrogen; n=10 + +LuYAP-70: d=7.1 g/cm3 ; n=4 + +el: name=Lutetium ; n= 7 + +el: name=Yttrium ; n= 3 + +el: name=Aluminium; n=10 + +el: name=Oxygen; n=30 + +LuYAP-80: d=7.5 g/cm3 ; n=4 + +el: name=Lutetium ; n= 8 + +el: name=Yttrium ; n= 2 + +el: name=Aluminium; n=10 + +el: name=Oxygen; n=30 + +Plastic: d=1.18 g/cm3 ; n=3; state=solid + +el: name=Carbon ; n=5 + +el: name=Hydrogen ; n=8 + +el: name=Oxygen ; n=2 + +CZT: d=5.68 g/cm3 ; n=3; state=solid + +el: name=Cadmium ; n=9 + +el: name=Zinc ; n=1 + +el: name=Tellurium ; n=10 + +Lung: d=0.26 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.105 + +el: name=Nitrogen ; f=0.031 + +el: name=Oxygen ; f=0.749 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.002 + +Polyethylene: d=0.96 g/cm3 ; n=2 + +el: name=Hydrogen ; n=2 + +el: name=Carbon ; n=1 + +PVC: d=1.65 g/cm3 ; n=3 ; state=solid + +el: name=Hydrogen ; n=3 + +el: name=Carbon ; n=2 + +el: name=Chlorine ; n=1 + +SS304: d=7.92 g/cm3 ; n=4 ; state=solid + +el: name=Iron ; f=0.695 + +el: name=Chromium ; f=0.190 + +el: name=Nickel ; f=0.095 + +el: name=Manganese ; f=0.020 + +PTFE: d= 2.18 g/cm3 ; n=2 ; state=solid + +el: name=Carbon ; n=1 + +el: name=Fluorine ; n=2 + + +LYSO: d=5.37 g/cm3; n=4 ; state=Solid + +el: name=Lutetium ; f=0.31101534 + +el: name=Yttrium ; f=0.368765605 + +el: name=Silicon; f=0.083209699 + +el: name=Oxygen; f=0.237009356 + +Body: d=1.00 g/cm3 ; n=2 + +el: name=Hydrogen ; f=0.112 + +el: name=Oxygen ; f=0.888 + +Muscle: d=1.05 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.143 + +el: name=Nitrogen ; f=0.034 + +el: name=Oxygen ; f=0.71 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.001 + +el: name=Potassium ; f=0.004 + +LungMoby: d=0.30 g/cm3 ; n=6 + +el: name=Hydrogen ; f=0.099 + +el: name=Carbon ; f=0.100 + +el: name=Nitrogen ; f=0.028 + +el: name=Oxygen ; f=0.740 + +el: name=Phosphor ; f=0.001 + +el: name=Calcium ; f=0.032 + +SpineBone: d=1.42 g/cm3 ; n=11 + +el: name=Hydrogen ; f=0.063 + +el: name=Carbon ; f=0.261 + +el: name=Nitrogen ; f=0.039 + +el: name=Oxygen ; f=0.436 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.001 + +el: name=Phosphor ; f=0.061 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.001 + +el: name=Potassium ; f=0.001 + +el: name=Calcium ; f=0.133 + +RibBone: d=1.92 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.034 + +el: name=Carbon ; f=0.155 + +el: name=Nitrogen ; f=0.042 + +el: name=Oxygen ; f=0.435 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.002 + +el: name=Phosphor ; f=0.103 + +el: name=Sulfur ; f=0.003 + +el: name=Calcium ; f=0.225 + +Adipose: d=0.92 g/cm3 ; n=6 + +el: name=Hydrogen ; f=0.120 + +el: name=Carbon ; f=0.640 + +el: name=Nitrogen ; f=0.008 + +el: name=Oxygen ; f=0.229 + +el: name=Phosphor ; f=0.002 + +el: name=Calcium ; f=0.001 + +Blood: d=1.06 g/cm3 ; n=10 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.11 + +el: name=Nitrogen ; f=0.033 + +el: name=Oxygen ; f=0.745 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.001 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.002 + +el: name=Iron ; f=0.001 + +Heart: d=1.05 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.104 + +el: name=Carbon ; f=0.139 + +el: name=Nitrogen ; f=0.029 + +el: name=Oxygen ; f=0.718 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +Kidney: d=1.05 g/cm3 ; n=10 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.132 + +el: name=Nitrogen ; f=0.03 + +el: name=Oxygen ; f=0.724 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.002 + +el: name=Calcium ; f=0.001 + +Liver: d=1.06 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.102 + +el: name=Carbon ; f=0.139 + +el: name=Nitrogen ; f=0.03 + +el: name=Oxygen ; f=0.716 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.003 + +el: name=Sulfur ; f=0.003 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +Lymph: d=1.03 g/cm3 ; n=7 + +el: name=Hydrogen ; f=0.108 + +el: name=Carbon ; f=0.041 + +el: name=Nitrogen ; f=0.011 + +el: name=Oxygen ; f=0.832 + +el: name=Sodium ; f=0.003 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.004 + +Pancreas: d=1.04 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.106 + +el: name=Carbon ; f=0.169 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.694 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.002 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.002 + +Intestine: d=1.03 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.106 + +el: name=Carbon ; f=0.115 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.751 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.001 + +el: name=Sulfur ; f=0.001 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.001 + +Skull: d=1.61 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.05 + +el: name=Carbon ; f=0.212 + +el: name=Nitrogen ; f=0.04 + +el: name=Oxygen ; f=0.435 + +el: name=Sodium ; f=0.001 + +el: name=Magnesium ; f=0.002 + +el: name=Phosphor ; f=0.081 + +el: name=Sulfur ; f=0.003 + +el: name=Calcium ; f=0.176 + +Cartilage: d=1.10 g/cm3 ; n=8 + +el: name=Hydrogen ; f=0.096 + +el: name=Carbon ; f=0.099 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.744 + +el: name=Sodium ; f=0.005 + +el: name=Phosphor ; f=0.022 + +el: name=Sulfur ; f=0.009 + +el: name=Chlorine ; f=0.003 + +Brain: d=1.04 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.107 + +el: name=Carbon ; f=0.145 + +el: name=Nitrogen ; f=0.022 + +el: name=Oxygen ; f=0.712 + +el: name=Sodium ; f=0.002 + +el: name=Phosphor ; f=0.004 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.003 + +el: name=Potassium ; f=0.003 + +Spleen: d=1.06 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.103 + +el: name=Carbon ; f=0.113 + +el: name=Nitrogen ; f=0.032 + +el: name=Oxygen ; f=0.741 + +el: name=Sodium ; f=0.001 + +el: name=Phosphor ; f=0.003 + +el: name=Sulfur ; f=0.002 + +el: name=Chlorine ; f=0.002 + +el: name=Potassium ; f=0.003 + +Testis: d=1.04 g/cm3 ; n=9 + +el: name=Hydrogen ; f=0.106000 + +el: name=Carbon ; f=0.099000 + +el: name=Nitrogen ; f=0.020000 + +el: name=Oxygen ; f=0.766000 + +el: name=Sodium ; f=0.002000 + +el: name=Phosphor ; f=0.001000 + +el: name=Sulfur ; f=0.002000 + +el: name=Chlorine ; f=0.002000 + +el: name=Potassium ; f=0.002000 + +PMMA: d=1.195 g/cm3; n=3 ; state=Solid + +el: name=Hydrogen ; f=0.080541 + +el: name=Carbon ; f=0.599846 + +el: name=Sulfur; f=0.319613 diff --git a/opengate/contrib/vpgTLE/stageX/data/GateMaterialsElements.db b/opengate/contrib/vpgTLE/stageX/data/GateMaterialsElements.db new file mode 100644 index 000000000..3bfc007bb --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/data/GateMaterialsElements.db @@ -0,0 +1,52 @@ +[Materials] + +Hydrogen: d=1.0 g/cm3; n=1; state=Solid + +el: name=Hydrogen; n=1 +Helium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Helium; n=1 +Lithium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Lithium; n=1 +Beryllium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Beryllium; n=1 +Boron: d=1.0 g/cm3; n=1; state=Solid + +el: name=Boron; n=1 +Carbon: d=1.0 g/cm3; n=1; state=Solid + +el: name=Carbon; n=1 +Nitrogen: d=1.0 g/cm3; n=1; state=Solid + +el: name=Nitrogen; n=1 +Oxygen: d=1.0 g/cm3; n=1; state=Solid + +el: name=Oxygen; n=1 +Fluorine: d=1.0 g/cm3; n=1; state=Solid + +el: name=Fluorine; n=1 +Neon: d=1.0 g/cm3; n=1; state=Solid + +el: name=Neon; n=1 +Sodium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Sodium; n=1 +Magnesium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Magnesium; n=1 +Aluminium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Aluminium; n=1 +Silicon: d=1.0 g/cm3; n=1; state=Solid + +el: name=Silicon; n=1 +Phosphor: d=1.0 g/cm3; n=1; state=Solid + +el: name=Phosphor; n=1 +Sulfur: d=1.0 g/cm3; n=1; state=Solid + +el: name=Sulfur; n=1 +Chlorine: d=1.0 g/cm3; n=1; state=Solid + +el: name=Chlorine; n=1 +Argon: d=1.0 g/cm3; n=1; state=Solid + +el: name=Argon; n=1 +Potassium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Potassium; n=1 +Calcium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Calcium; n=1 +Titanium: d=1.0 g/cm3; n=1; state=Solid + +el: name=Titanium; n=1 +Copper: d=1.0 g/cm3; n=1; state=Solid + +el: name=Copper; n=1 +Zinc: d=1.0 g/cm3; n=1; state=Solid + +el: name=Zinc; n=1 +Silver: d=1.0 g/cm3; n=1; state=Solid + +el: name=Silver; n=1 +Tin: d=1.0 g/cm3; n=1; state=Solid + +el: name=Tin; n=1 diff --git a/opengate/contrib/vpgTLE/stageX/data/Materials.xml b/opengate/contrib/vpgTLE/stageX/data/Materials.xml new file mode 100644 index 000000000..403917efd --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/data/Materials.xml @@ -0,0 +1,849 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/opengate/contrib/vpgTLE/stageX/data/Schneider2000DensitiesTable.txt b/opengate/contrib/vpgTLE/stageX/data/Schneider2000DensitiesTable.txt new file mode 100644 index 000000000..1e2ad7436 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/data/Schneider2000DensitiesTable.txt @@ -0,0 +1,14 @@ +# =================== +# HU density g/cm3 +# =================== +-1000 1.21e-3 +-98 0.93 +-97 0.930486 +0 1.0 +14 1.03 +23 1.031 +100 1.119900 +101 1.076200 +1600 1.964200 +3000 2.8 + diff --git a/opengate/contrib/vpgTLE/stageX/data/Schneider2000MaterialsTable.txt b/opengate/contrib/vpgTLE/stageX/data/Schneider2000MaterialsTable.txt new file mode 100644 index 000000000..fd77c5ea5 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/data/Schneider2000MaterialsTable.txt @@ -0,0 +1,36 @@ +[Elements] +Hydrogen Carbon Nitrogen Oxygen Sodium Magnesium Phosphor Sulfur +Chlorine Argon Potassium Calcium +Titanium Copper Zinc Silver Tin Lithium Neon Aluminium Beryllium Boron Fluorine Helium Silicon +[/Elements] +# =================================================================================================================== +# HU H C N O Na Mg P S Cl Ar K Ca Ti Cu Zn Ag Sn Li Ne Al Be B F He Si +# =================================================================================================================== + -1050 0 0 75.5 23.2 0 0 0 0 0 1.3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Air + -950 10.3 10.5 3.1 74.9 0.2 0 0.2 0.3 0.3 0 0.2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Lung + -120 11.6 68.1 0.2 19.8 0.1 0 0 0.1 0.1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 AT_AG_SI1 + -82 11.3 56.7 0.9 30.8 0.1 0 0 0.1 0.1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 AT_AG_SI2 + -52 11.0 45.8 1.5 41.1 0.1 0 0.1 0.2 0.2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 AT_AG_SI3 + -22 10.8 35.6 2.2 50.9 0 0 0.1 0.2 0.2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 AT_AG_SI4 + 0 66.6 0 0 33.4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Water + 8 10.6 28.4 2.6 57.8 0 0 0.1 0.2 0.2 0 0.1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 AT_AG_SI5 + 19 10.3 13.4 3.0 72.3 0.2 0 0.2 0.2 0.2 0 0.2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 SoftTissus + 80 9.4 20.7 6.2 62.2 0.6 0 0 0.6 0.3 0 0.0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ConnectiveTissue + 120 9.5 45.5 2.5 35.5 0.1 0 2.1 0.1 0.1 0 0.1 4.5 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone01 + 200 8.9 42.3 2.7 36.3 0.1 0 3.0 0.1 0.1 0 0.1 6.4 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone02 + 300 8.2 39.1 2.9 37.2 0.1 0 3.9 0.1 0.1 0 0.1 8.3 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone03 + 400 7.6 36.1 3.0 38.0 0.1 0.1 4.7 0.2 0.1 0 0 10.1 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone04 + 500 7.1 33.5 3.2 38.7 0.1 0.1 5.4 0.2 0 0 0 11.7 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone05 + 600 6.6 31.0 3.3 39.4 0.1 0.1 6.1 0.2 0 0 0 13.2 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone06 + 700 6.1 28.7 3.5 40.0 0.1 0.1 6.7 0.2 0 0 0 14.6 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone07 + 800 5.6 26.5 3.6 40.5 0.1 0.2 7.3 0.3 0 0 0 15.9 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone08 + 900 5.2 24.6 3.7 41.1 0.1 0.2 7.8 0.3 0 0 0 17.0 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone09 + 1000 4.9 22.7 3.8 41.6 0.1 0.2 8.3 0.3 0 0 0 18.1 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone10 + 1100 4.5 21.0 3.9 42.0 0.1 0.2 8.8 0.3 0 0 0 19.2 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone11 + 1200 4.2 19.4 4.0 42.5 0.1 0.2 9.2 0.3 0 0 0 20.1 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone12 + 1300 3.9 17.9 4.1 42.9 0.1 0.2 9.6 0.3 0 0 0 21.0 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone13 + 1400 3.6 16.5 4.2 43.2 0.1 0.2 10.0 0.3 0 0 0 21.9 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone14 + 1500 3.4 15.5 4.2 43.5 0.1 0.2 10.3 0.3 0 0 0 22.5 0 0 0 0 0 0 0 0 0 0 0 0 0 Marrow_Bone15 + 1640 0 0 0 0 0 0 0 0 0 0 0 0 0 4 2 65 29 0 0 0 0 0 0 0 0 AmalgamTooth + 2300 0 0 0 0 0 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 0 0 0 0 0 MetallImplants + 3000 0 0 0 0 0 0 0 0 0 0 0 0 100 0 0 0 0 0 0 0 0 0 0 0 0 MetallImplants \ No newline at end of file diff --git a/opengate/contrib/vpgTLE/stageX/data/database.db b/opengate/contrib/vpgTLE/stageX/data/database.db new file mode 100644 index 000000000..4b2fb20fd --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/data/database.db @@ -0,0 +1,690 @@ +[Materials] +Air_0: d=1.21 mg/cm3; n=3 ++el: name=N; f=0.755 ++el: name=O; f=0.23199999999999998 ++el: name=Ar; f=0.013000000000000001 + +Lung_1: d=77.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_2: d=127.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_3: d=177.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_4: d=227.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_5: d=277.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_6: d=327.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_7: d=377.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_8: d=427.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_9: d=477.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_10: d=527.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_11: d=577.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_12: d=627.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_13: d=677.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_14: d=727.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_15: d=777.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_16: d=827.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_17: d=877.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_18: d=905.021 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +AT_AG_SI1_19: d=926.911 mg/cm3; n=7 ++el: name=H; f=0.116 ++el: name=C; f=0.681 ++el: name=N; f=0.002 ++el: name=O; f=0.198 ++el: name=Na; f=0.001 ++el: name=S; f=0.001 ++el: name=Cl; f=0.001 + +AT_AG_SI2_20: d=951.985 mg/cm3; n=7 ++el: name=H; f=0.11300000000000002 ++el: name=C; f=0.5670000000000001 ++el: name=N; f=0.009000000000000001 ++el: name=O; f=0.30800000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=S; f=0.0010000000000000002 ++el: name=Cl; f=0.0010000000000000002 + +AT_AG_SI3_21: d=973.484 mg/cm3; n=8 ++el: name=H; f=0.11 ++el: name=C; f=0.45799999999999996 ++el: name=N; f=0.015 ++el: name=O; f=0.41100000000000003 ++el: name=Na; f=0.001 ++el: name=P; f=0.001 ++el: name=S; f=0.002 ++el: name=Cl; f=0.002 + +AT_AG_SI4_22: d=992.117 mg/cm3; n=7 ++el: name=H; f=0.10800000000000001 ++el: name=C; f=0.35600000000000004 ++el: name=N; f=0.022000000000000002 ++el: name=O; f=0.509 ++el: name=P; f=0.001 ++el: name=S; f=0.002 ++el: name=Cl; f=0.002 + +Water_23: d=1.00857 g/cm3 ; n=2 ++el: name=H; f=0.666 ++el: name=O; f=0.334 + +AT_AG_SI5_24: d=1.02893 g/cm3 ; n=8 ++el: name=H; f=0.106 ++el: name=C; f=0.284 ++el: name=N; f=0.026000000000000002 ++el: name=O; f=0.578 ++el: name=P; f=0.001 ++el: name=S; f=0.002 ++el: name=Cl; f=0.002 ++el: name=K; f=0.001 + +SoftTissus_25: d=1.05296 g/cm3 ; n=9 ++el: name=H; f=0.10300000000000002 ++el: name=C; f=0.134 ++el: name=N; f=0.030000000000000002 ++el: name=O; f=0.723 ++el: name=Na; f=0.0020000000000000005 ++el: name=P; f=0.0020000000000000005 ++el: name=S; f=0.0020000000000000005 ++el: name=Cl; f=0.0020000000000000005 ++el: name=K; f=0.0020000000000000005 + +SoftTissus_26: d=1.08817 g/cm3 ; n=9 ++el: name=H; f=0.10300000000000002 ++el: name=C; f=0.134 ++el: name=N; f=0.030000000000000002 ++el: name=O; f=0.723 ++el: name=Na; f=0.0020000000000000005 ++el: name=P; f=0.0020000000000000005 ++el: name=S; f=0.0020000000000000005 ++el: name=Cl; f=0.0020000000000000005 ++el: name=K; f=0.0020000000000000005 + +ConnectiveTissue_27: d=1.1199 g/cm3 ; n=7 ++el: name=H; f=0.09400000000000001 ++el: name=C; f=0.20700000000000002 ++el: name=N; f=0.06200000000000001 ++el: name=O; f=0.6220000000000001 ++el: name=Na; f=0.006000000000000001 ++el: name=S; f=0.006000000000000001 ++el: name=Cl; f=0.0030000000000000005 + +Marrow_Bone01_28: d=1.11115 g/cm3 ; n=10 ++el: name=H; f=0.09499999999999997 ++el: name=C; f=0.4549999999999999 ++el: name=N; f=0.024999999999999994 ++el: name=O; f=0.355 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.021 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.045 + +Marrow_Bone02_29: d=1.15985 g/cm3 ; n=10 ++el: name=H; f=0.089 ++el: name=C; f=0.4229999999999999 ++el: name=N; f=0.026999999999999996 ++el: name=O; f=0.363 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.029999999999999995 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.06399999999999999 + +Marrow_Bone02_30: d=1.18947 g/cm3 ; n=10 ++el: name=H; f=0.089 ++el: name=C; f=0.4229999999999999 ++el: name=N; f=0.026999999999999996 ++el: name=O; f=0.363 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.029999999999999995 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.06399999999999999 + +Marrow_Bone03_31: d=1.21909 g/cm3 ; n=10 ++el: name=H; f=0.08199999999999998 ++el: name=C; f=0.39099999999999985 ++el: name=N; f=0.02899999999999999 ++el: name=O; f=0.372 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.03899999999999999 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.08299999999999999 + +Marrow_Bone03_32: d=1.24871 g/cm3 ; n=10 ++el: name=H; f=0.08199999999999998 ++el: name=C; f=0.39099999999999985 ++el: name=N; f=0.02899999999999999 ++el: name=O; f=0.372 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.03899999999999999 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.08299999999999999 + +Marrow_Bone04_33: d=1.27833 g/cm3 ; n=10 ++el: name=H; f=0.076 ++el: name=C; f=0.361 ++el: name=N; f=0.03 ++el: name=O; f=0.37999999999999995 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.001 ++el: name=P; f=0.04699999999999999 ++el: name=S; f=0.002 ++el: name=Cl; f=0.001 ++el: name=Ca; f=0.10099999999999998 + +Marrow_Bone04_34: d=1.30795 g/cm3 ; n=10 ++el: name=H; f=0.076 ++el: name=C; f=0.361 ++el: name=N; f=0.03 ++el: name=O; f=0.37999999999999995 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.001 ++el: name=P; f=0.04699999999999999 ++el: name=S; f=0.002 ++el: name=Cl; f=0.001 ++el: name=Ca; f=0.10099999999999998 + +Marrow_Bone05_35: d=1.33757 g/cm3 ; n=9 ++el: name=H; f=0.071 ++el: name=C; f=0.335 ++el: name=N; f=0.032 ++el: name=O; f=0.387 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.001 ++el: name=P; f=0.054000000000000006 ++el: name=S; f=0.002 ++el: name=Ca; f=0.11699999999999999 + +Marrow_Bone05_36: d=1.36719 g/cm3 ; n=9 ++el: name=H; f=0.071 ++el: name=C; f=0.335 ++el: name=N; f=0.032 ++el: name=O; f=0.387 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.001 ++el: name=P; f=0.054000000000000006 ++el: name=S; f=0.002 ++el: name=Ca; f=0.11699999999999999 + +Marrow_Bone06_37: d=1.39681 g/cm3 ; n=9 ++el: name=H; f=0.066 ++el: name=C; f=0.31000000000000005 ++el: name=N; f=0.033 ++el: name=O; f=0.394 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0010000000000000002 ++el: name=P; f=0.061000000000000006 ++el: name=S; f=0.0020000000000000005 ++el: name=Ca; f=0.132 + +Marrow_Bone06_38: d=1.42642 g/cm3 ; n=9 ++el: name=H; f=0.066 ++el: name=C; f=0.31000000000000005 ++el: name=N; f=0.033 ++el: name=O; f=0.394 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0010000000000000002 ++el: name=P; f=0.061000000000000006 ++el: name=S; f=0.0020000000000000005 ++el: name=Ca; f=0.132 + +Marrow_Bone07_39: d=1.45604 g/cm3 ; n=9 ++el: name=H; f=0.061000000000000006 ++el: name=C; f=0.28700000000000003 ++el: name=N; f=0.035 ++el: name=O; f=0.4000000000000001 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0010000000000000002 ++el: name=P; f=0.06700000000000002 ++el: name=S; f=0.0020000000000000005 ++el: name=Ca; f=0.14600000000000002 + +Marrow_Bone07_40: d=1.48566 g/cm3 ; n=9 ++el: name=H; f=0.061000000000000006 ++el: name=C; f=0.28700000000000003 ++el: name=N; f=0.035 ++el: name=O; f=0.4000000000000001 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0010000000000000002 ++el: name=P; f=0.06700000000000002 ++el: name=S; f=0.0020000000000000005 ++el: name=Ca; f=0.14600000000000002 + +Marrow_Bone08_41: d=1.51528 g/cm3 ; n=9 ++el: name=H; f=0.055999999999999994 ++el: name=C; f=0.265 ++el: name=N; f=0.036000000000000004 ++el: name=O; f=0.405 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.073 ++el: name=S; f=0.003 ++el: name=Ca; f=0.159 + +Marrow_Bone08_42: d=1.5449 g/cm3 ; n=9 ++el: name=H; f=0.055999999999999994 ++el: name=C; f=0.265 ++el: name=N; f=0.036000000000000004 ++el: name=O; f=0.405 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.073 ++el: name=S; f=0.003 ++el: name=Ca; f=0.159 + +Marrow_Bone09_43: d=1.57452 g/cm3 ; n=9 ++el: name=H; f=0.052 ++el: name=C; f=0.246 ++el: name=N; f=0.037 ++el: name=O; f=0.411 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.078 ++el: name=S; f=0.0029999999999999996 ++el: name=Ca; f=0.16999999999999998 + +Marrow_Bone09_44: d=1.60414 g/cm3 ; n=9 ++el: name=H; f=0.052 ++el: name=C; f=0.246 ++el: name=N; f=0.037 ++el: name=O; f=0.411 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.078 ++el: name=S; f=0.0029999999999999996 ++el: name=Ca; f=0.16999999999999998 + +Marrow_Bone10_45: d=1.63376 g/cm3 ; n=9 ++el: name=H; f=0.049 ++el: name=C; f=0.22699999999999998 ++el: name=N; f=0.038 ++el: name=O; f=0.41600000000000004 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.083 ++el: name=S; f=0.003 ++el: name=Ca; f=0.18100000000000002 + +Marrow_Bone10_46: d=1.66338 g/cm3 ; n=9 ++el: name=H; f=0.049 ++el: name=C; f=0.22699999999999998 ++el: name=N; f=0.038 ++el: name=O; f=0.41600000000000004 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.083 ++el: name=S; f=0.003 ++el: name=Ca; f=0.18100000000000002 + +Marrow_Bone11_47: d=1.693 g/cm3 ; n=9 ++el: name=H; f=0.045 ++el: name=C; f=0.21 ++el: name=N; f=0.039 ++el: name=O; f=0.42 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.08800000000000001 ++el: name=S; f=0.003 ++el: name=Ca; f=0.192 + +Marrow_Bone11_48: d=1.72262 g/cm3 ; n=9 ++el: name=H; f=0.045 ++el: name=C; f=0.21 ++el: name=N; f=0.039 ++el: name=O; f=0.42 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.08800000000000001 ++el: name=S; f=0.003 ++el: name=Ca; f=0.192 + +Marrow_Bone12_49: d=1.75224 g/cm3 ; n=9 ++el: name=H; f=0.042 ++el: name=C; f=0.19399999999999998 ++el: name=N; f=0.04 ++el: name=O; f=0.425 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.092 ++el: name=S; f=0.003 ++el: name=Ca; f=0.201 + +Marrow_Bone12_50: d=1.78186 g/cm3 ; n=9 ++el: name=H; f=0.042 ++el: name=C; f=0.19399999999999998 ++el: name=N; f=0.04 ++el: name=O; f=0.425 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.092 ++el: name=S; f=0.003 ++el: name=Ca; f=0.201 + +Marrow_Bone13_51: d=1.81148 g/cm3 ; n=9 ++el: name=H; f=0.03900000000000001 ++el: name=C; f=0.17900000000000002 ++el: name=N; f=0.041 ++el: name=O; f=0.42900000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0020000000000000005 ++el: name=P; f=0.09600000000000002 ++el: name=S; f=0.0030000000000000005 ++el: name=Ca; f=0.21000000000000002 + +Marrow_Bone13_52: d=1.8411 g/cm3 ; n=9 ++el: name=H; f=0.03900000000000001 ++el: name=C; f=0.17900000000000002 ++el: name=N; f=0.041 ++el: name=O; f=0.42900000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0020000000000000005 ++el: name=P; f=0.09600000000000002 ++el: name=S; f=0.0030000000000000005 ++el: name=Ca; f=0.21000000000000002 + +Marrow_Bone14_53: d=1.87072 g/cm3 ; n=9 ++el: name=H; f=0.036000000000000004 ++el: name=C; f=0.165 ++el: name=N; f=0.042 ++el: name=O; f=0.43200000000000005 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.1 ++el: name=S; f=0.003 ++el: name=Ca; f=0.21899999999999997 + +Marrow_Bone14_54: d=1.90034 g/cm3 ; n=9 ++el: name=H; f=0.036000000000000004 ++el: name=C; f=0.165 ++el: name=N; f=0.042 ++el: name=O; f=0.43200000000000005 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.1 ++el: name=S; f=0.003 ++el: name=Ca; f=0.21899999999999997 + +Marrow_Bone15_55: d=1.92991 g/cm3 ; n=9 ++el: name=H; f=0.034 ++el: name=C; f=0.15500000000000003 ++el: name=N; f=0.04200000000000001 ++el: name=O; f=0.43500000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0020000000000000005 ++el: name=P; f=0.10300000000000002 ++el: name=S; f=0.0030000000000000005 ++el: name=Ca; f=0.22500000000000003 + +Marrow_Bone15_56: d=1.97143 g/cm3 ; n=9 ++el: name=H; f=0.034 ++el: name=C; f=0.15500000000000003 ++el: name=N; f=0.04200000000000001 ++el: name=O; f=0.43500000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0020000000000000005 ++el: name=P; f=0.10300000000000002 ++el: name=S; f=0.0030000000000000005 ++el: name=Ca; f=0.22500000000000003 + +AmalgamTooth_57: d=2.01308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_58: d=2.06308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_59: d=2.11308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_60: d=2.16308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_61: d=2.21308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_62: d=2.26308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_63: d=2.31308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_64: d=2.36009 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +MetallImplants_65: d=2.4071 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_66: d=2.4571 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_67: d=2.5071 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_68: d=2.5571 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_69: d=2.6071 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_70: d=2.6571 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_71: d=2.7071 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_72: d=2.7571 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_73: d=2.79105 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_74: d=2.8 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + diff --git a/opengate/contrib/vpgTLE/stageX/data/database.txt b/opengate/contrib/vpgTLE/stageX/data/database.txt new file mode 100644 index 000000000..d7d94a8bf --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/data/database.txt @@ -0,0 +1,686 @@ +[Materials] +Air_0: d=1.21 mg/cm3; n=3 ++el: name=N; f=0.755 ++el: name=O; f=0.23199999999999998 ++el: name=Ar; f=0.013000000000000001 + +Lung_1: d=77.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_2: d=127.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_3: d=177.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_4: d=227.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_5: d=277.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_6: d=327.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_7: d=377.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_8: d=427.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_9: d=477.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_10: d=527.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_11: d=577.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_12: d=627.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_13: d=677.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_14: d=727.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_15: d=777.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_16: d=827.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_17: d=877.695 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +Lung_18: d=905.021 mg/cm3; n=9 ++el: name=H; f=0.103 ++el: name=C; f=0.10499999999999998 ++el: name=N; f=0.030999999999999996 ++el: name=O; f=0.749 ++el: name=Na; f=0.002 ++el: name=P; f=0.002 ++el: name=S; f=0.0029999999999999996 ++el: name=Cl; f=0.0029999999999999996 ++el: name=K; f=0.002 + +AT_AG_SI1_19: d=926.911 mg/cm3; n=7 ++el: name=H; f=0.116 ++el: name=C; f=0.681 ++el: name=N; f=0.002 ++el: name=O; f=0.198 ++el: name=Na; f=0.001 ++el: name=S; f=0.001 ++el: name=Cl; f=0.001 + +AT_AG_SI2_20: d=957.382 mg/cm3; n=7 ++el: name=H; f=0.11300000000000002 ++el: name=C; f=0.5670000000000001 ++el: name=N; f=0.009000000000000001 ++el: name=O; f=0.30800000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=S; f=0.0010000000000000002 ++el: name=Cl; f=0.0010000000000000002 + +AT_AG_SI3_21: d=984.277 mg/cm3; n=8 ++el: name=H; f=0.11 ++el: name=C; f=0.45799999999999996 ++el: name=N; f=0.015 ++el: name=O; f=0.41100000000000003 ++el: name=Na; f=0.001 ++el: name=P; f=0.001 ++el: name=S; f=0.002 ++el: name=Cl; f=0.002 + +AT_AG_SI4_22: d=1.01117 g/cm3 ; n=7 ++el: name=H; f=0.10800000000000001 ++el: name=C; f=0.35600000000000004 ++el: name=N; f=0.022000000000000002 ++el: name=O; f=0.509 ++el: name=P; f=0.001 ++el: name=S; f=0.002 ++el: name=Cl; f=0.002 + +AT_AG_SI5_23: d=1.02955 g/cm3 ; n=8 ++el: name=H; f=0.106 ++el: name=C; f=0.284 ++el: name=N; f=0.026000000000000002 ++el: name=O; f=0.578 ++el: name=P; f=0.001 ++el: name=S; f=0.002 ++el: name=Cl; f=0.002 ++el: name=K; f=0.001 + +SoftTissus_24: d=1.05296 g/cm3 ; n=9 ++el: name=H; f=0.10300000000000002 ++el: name=C; f=0.134 ++el: name=N; f=0.030000000000000002 ++el: name=O; f=0.723 ++el: name=Na; f=0.0020000000000000005 ++el: name=P; f=0.0020000000000000005 ++el: name=S; f=0.0020000000000000005 ++el: name=Cl; f=0.0020000000000000005 ++el: name=K; f=0.0020000000000000005 + +SoftTissus_25: d=1.08817 g/cm3 ; n=9 ++el: name=H; f=0.10300000000000002 ++el: name=C; f=0.134 ++el: name=N; f=0.030000000000000002 ++el: name=O; f=0.723 ++el: name=Na; f=0.0020000000000000005 ++el: name=P; f=0.0020000000000000005 ++el: name=S; f=0.0020000000000000005 ++el: name=Cl; f=0.0020000000000000005 ++el: name=K; f=0.0020000000000000005 + +ConnectiveTissue_26: d=1.1199 g/cm3 ; n=7 ++el: name=H; f=0.09400000000000001 ++el: name=C; f=0.20700000000000002 ++el: name=N; f=0.06200000000000001 ++el: name=O; f=0.6220000000000001 ++el: name=Na; f=0.006000000000000001 ++el: name=S; f=0.006000000000000001 ++el: name=Cl; f=0.0030000000000000005 + +Marrow_Bone01_27: d=1.11115 g/cm3 ; n=10 ++el: name=H; f=0.09499999999999997 ++el: name=C; f=0.4549999999999999 ++el: name=N; f=0.024999999999999994 ++el: name=O; f=0.355 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.021 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.045 + +Marrow_Bone02_28: d=1.15985 g/cm3 ; n=10 ++el: name=H; f=0.089 ++el: name=C; f=0.4229999999999999 ++el: name=N; f=0.026999999999999996 ++el: name=O; f=0.363 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.029999999999999995 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.06399999999999999 + +Marrow_Bone02_29: d=1.18947 g/cm3 ; n=10 ++el: name=H; f=0.089 ++el: name=C; f=0.4229999999999999 ++el: name=N; f=0.026999999999999996 ++el: name=O; f=0.363 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.029999999999999995 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.06399999999999999 + +Marrow_Bone03_30: d=1.21909 g/cm3 ; n=10 ++el: name=H; f=0.08199999999999998 ++el: name=C; f=0.39099999999999985 ++el: name=N; f=0.02899999999999999 ++el: name=O; f=0.372 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.03899999999999999 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.08299999999999999 + +Marrow_Bone03_31: d=1.24871 g/cm3 ; n=10 ++el: name=H; f=0.08199999999999998 ++el: name=C; f=0.39099999999999985 ++el: name=N; f=0.02899999999999999 ++el: name=O; f=0.372 ++el: name=Na; f=0.0009999999999999998 ++el: name=P; f=0.03899999999999999 ++el: name=S; f=0.0009999999999999998 ++el: name=Cl; f=0.0009999999999999998 ++el: name=K; f=0.0009999999999999998 ++el: name=Ca; f=0.08299999999999999 + +Marrow_Bone04_32: d=1.27833 g/cm3 ; n=10 ++el: name=H; f=0.076 ++el: name=C; f=0.361 ++el: name=N; f=0.03 ++el: name=O; f=0.37999999999999995 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.001 ++el: name=P; f=0.04699999999999999 ++el: name=S; f=0.002 ++el: name=Cl; f=0.001 ++el: name=Ca; f=0.10099999999999998 + +Marrow_Bone04_33: d=1.30795 g/cm3 ; n=10 ++el: name=H; f=0.076 ++el: name=C; f=0.361 ++el: name=N; f=0.03 ++el: name=O; f=0.37999999999999995 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.001 ++el: name=P; f=0.04699999999999999 ++el: name=S; f=0.002 ++el: name=Cl; f=0.001 ++el: name=Ca; f=0.10099999999999998 + +Marrow_Bone05_34: d=1.33757 g/cm3 ; n=9 ++el: name=H; f=0.071 ++el: name=C; f=0.335 ++el: name=N; f=0.032 ++el: name=O; f=0.387 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.001 ++el: name=P; f=0.054000000000000006 ++el: name=S; f=0.002 ++el: name=Ca; f=0.11699999999999999 + +Marrow_Bone05_35: d=1.36719 g/cm3 ; n=9 ++el: name=H; f=0.071 ++el: name=C; f=0.335 ++el: name=N; f=0.032 ++el: name=O; f=0.387 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.001 ++el: name=P; f=0.054000000000000006 ++el: name=S; f=0.002 ++el: name=Ca; f=0.11699999999999999 + +Marrow_Bone06_36: d=1.39681 g/cm3 ; n=9 ++el: name=H; f=0.066 ++el: name=C; f=0.31000000000000005 ++el: name=N; f=0.033 ++el: name=O; f=0.394 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0010000000000000002 ++el: name=P; f=0.061000000000000006 ++el: name=S; f=0.0020000000000000005 ++el: name=Ca; f=0.132 + +Marrow_Bone06_37: d=1.42642 g/cm3 ; n=9 ++el: name=H; f=0.066 ++el: name=C; f=0.31000000000000005 ++el: name=N; f=0.033 ++el: name=O; f=0.394 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0010000000000000002 ++el: name=P; f=0.061000000000000006 ++el: name=S; f=0.0020000000000000005 ++el: name=Ca; f=0.132 + +Marrow_Bone07_38: d=1.45604 g/cm3 ; n=9 ++el: name=H; f=0.061000000000000006 ++el: name=C; f=0.28700000000000003 ++el: name=N; f=0.035 ++el: name=O; f=0.4000000000000001 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0010000000000000002 ++el: name=P; f=0.06700000000000002 ++el: name=S; f=0.0020000000000000005 ++el: name=Ca; f=0.14600000000000002 + +Marrow_Bone07_39: d=1.48566 g/cm3 ; n=9 ++el: name=H; f=0.061000000000000006 ++el: name=C; f=0.28700000000000003 ++el: name=N; f=0.035 ++el: name=O; f=0.4000000000000001 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0010000000000000002 ++el: name=P; f=0.06700000000000002 ++el: name=S; f=0.0020000000000000005 ++el: name=Ca; f=0.14600000000000002 + +Marrow_Bone08_40: d=1.51528 g/cm3 ; n=9 ++el: name=H; f=0.055999999999999994 ++el: name=C; f=0.265 ++el: name=N; f=0.036000000000000004 ++el: name=O; f=0.405 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.073 ++el: name=S; f=0.003 ++el: name=Ca; f=0.159 + +Marrow_Bone08_41: d=1.5449 g/cm3 ; n=9 ++el: name=H; f=0.055999999999999994 ++el: name=C; f=0.265 ++el: name=N; f=0.036000000000000004 ++el: name=O; f=0.405 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.073 ++el: name=S; f=0.003 ++el: name=Ca; f=0.159 + +Marrow_Bone09_42: d=1.57452 g/cm3 ; n=9 ++el: name=H; f=0.052 ++el: name=C; f=0.246 ++el: name=N; f=0.037 ++el: name=O; f=0.411 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.078 ++el: name=S; f=0.0029999999999999996 ++el: name=Ca; f=0.16999999999999998 + +Marrow_Bone09_43: d=1.60414 g/cm3 ; n=9 ++el: name=H; f=0.052 ++el: name=C; f=0.246 ++el: name=N; f=0.037 ++el: name=O; f=0.411 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.078 ++el: name=S; f=0.0029999999999999996 ++el: name=Ca; f=0.16999999999999998 + +Marrow_Bone10_44: d=1.63376 g/cm3 ; n=9 ++el: name=H; f=0.049 ++el: name=C; f=0.22699999999999998 ++el: name=N; f=0.038 ++el: name=O; f=0.41600000000000004 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.083 ++el: name=S; f=0.003 ++el: name=Ca; f=0.18100000000000002 + +Marrow_Bone10_45: d=1.66338 g/cm3 ; n=9 ++el: name=H; f=0.049 ++el: name=C; f=0.22699999999999998 ++el: name=N; f=0.038 ++el: name=O; f=0.41600000000000004 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.083 ++el: name=S; f=0.003 ++el: name=Ca; f=0.18100000000000002 + +Marrow_Bone11_46: d=1.693 g/cm3 ; n=9 ++el: name=H; f=0.045 ++el: name=C; f=0.21 ++el: name=N; f=0.039 ++el: name=O; f=0.42 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.08800000000000001 ++el: name=S; f=0.003 ++el: name=Ca; f=0.192 + +Marrow_Bone11_47: d=1.72262 g/cm3 ; n=9 ++el: name=H; f=0.045 ++el: name=C; f=0.21 ++el: name=N; f=0.039 ++el: name=O; f=0.42 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.08800000000000001 ++el: name=S; f=0.003 ++el: name=Ca; f=0.192 + +Marrow_Bone12_48: d=1.75224 g/cm3 ; n=9 ++el: name=H; f=0.042 ++el: name=C; f=0.19399999999999998 ++el: name=N; f=0.04 ++el: name=O; f=0.425 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.092 ++el: name=S; f=0.003 ++el: name=Ca; f=0.201 + +Marrow_Bone12_49: d=1.78186 g/cm3 ; n=9 ++el: name=H; f=0.042 ++el: name=C; f=0.19399999999999998 ++el: name=N; f=0.04 ++el: name=O; f=0.425 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.092 ++el: name=S; f=0.003 ++el: name=Ca; f=0.201 + +Marrow_Bone13_50: d=1.81148 g/cm3 ; n=9 ++el: name=H; f=0.03900000000000001 ++el: name=C; f=0.17900000000000002 ++el: name=N; f=0.041 ++el: name=O; f=0.42900000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0020000000000000005 ++el: name=P; f=0.09600000000000002 ++el: name=S; f=0.0030000000000000005 ++el: name=Ca; f=0.21000000000000002 + +Marrow_Bone13_51: d=1.8411 g/cm3 ; n=9 ++el: name=H; f=0.03900000000000001 ++el: name=C; f=0.17900000000000002 ++el: name=N; f=0.041 ++el: name=O; f=0.42900000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0020000000000000005 ++el: name=P; f=0.09600000000000002 ++el: name=S; f=0.0030000000000000005 ++el: name=Ca; f=0.21000000000000002 + +Marrow_Bone14_52: d=1.87072 g/cm3 ; n=9 ++el: name=H; f=0.036000000000000004 ++el: name=C; f=0.165 ++el: name=N; f=0.042 ++el: name=O; f=0.43200000000000005 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.1 ++el: name=S; f=0.003 ++el: name=Ca; f=0.21899999999999997 + +Marrow_Bone14_53: d=1.90034 g/cm3 ; n=9 ++el: name=H; f=0.036000000000000004 ++el: name=C; f=0.165 ++el: name=N; f=0.042 ++el: name=O; f=0.43200000000000005 ++el: name=Na; f=0.001 ++el: name=Mg; f=0.002 ++el: name=P; f=0.1 ++el: name=S; f=0.003 ++el: name=Ca; f=0.21899999999999997 + +Marrow_Bone15_54: d=1.92991 g/cm3 ; n=9 ++el: name=H; f=0.034 ++el: name=C; f=0.15500000000000003 ++el: name=N; f=0.04200000000000001 ++el: name=O; f=0.43500000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0020000000000000005 ++el: name=P; f=0.10300000000000002 ++el: name=S; f=0.0030000000000000005 ++el: name=Ca; f=0.22500000000000003 + +Marrow_Bone15_55: d=1.97143 g/cm3 ; n=9 ++el: name=H; f=0.034 ++el: name=C; f=0.15500000000000003 ++el: name=N; f=0.04200000000000001 ++el: name=O; f=0.43500000000000005 ++el: name=Na; f=0.0010000000000000002 ++el: name=Mg; f=0.0020000000000000005 ++el: name=P; f=0.10300000000000002 ++el: name=S; f=0.0030000000000000005 ++el: name=Ca; f=0.22500000000000003 + +AmalgamTooth_56: d=2.01308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_57: d=2.06308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_58: d=2.11308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_59: d=2.16308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_60: d=2.21308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_61: d=2.26308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_62: d=2.31308 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +AmalgamTooth_63: d=2.36009 g/cm3 ; n=4 ++el: name=Cu; f=0.04 ++el: name=Zn; f=0.02 ++el: name=Ag; f=0.65 ++el: name=Sn; f=0.29 + +MetallImplants_64: d=2.4071 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_65: d=2.4571 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_66: d=2.5071 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_67: d=2.5571 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_68: d=2.6071 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_69: d=2.6571 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_70: d=2.7071 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_71: d=2.7571 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_72: d=2.79105 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + +MetallImplants_73: d=2.8 g/cm3 ; n=1 ++el: name=Ti; f=1.0 + diff --git a/opengate/contrib/vpgTLE/stageX/energy_patient.py b/opengate/contrib/vpgTLE/stageX/energy_patient.py new file mode 100644 index 000000000..6acbfc95e --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/energy_patient.py @@ -0,0 +1,71 @@ +import itk +import numpy as np +import matplotlib.pyplot as plt +import uproot + +dossier = "patient" +part = "prot" +X = 62 +Y = 66 +Z = 94 +case = "try" +# Read input image +itk_image = itk.imread( + "output/" + dossier + "/optiVPG_tle_patient_0_" + part + "_e.nii.gz" +) +# Copy of itk.Image, pixel data is copied +array = itk.array_from_image(itk_image) + +# Read input image +itk_image = itk.imread("output/" + dossier + "/ana_patient_0_" + part + "_e.nii.gz") +# Copy of itk.Image, pixel data is copied +ana_array = itk.array_from_image(itk_image) +N = 1e7 + +histogram = np.zeros(array.shape[0]) +ana_histogram = np.zeros(ana_array.shape[0]) +ana_histogram = ana_array[:, Z, Y, X] +histogram = array[:, Z, Y, X] + +ana_histogram = ana_histogram[:-1] +inc = np.sqrt(ana_histogram / N) + +energy_axis = np.linspace(0, 10, len(ana_histogram)) +# Plot the histogram +plt.figure(figsize=(10, 6)) +plt.plot(energy_axis, histogram, label=f"vpgTLE MC", color="blue", linewidth=1) +plt.plot(energy_axis, ana_histogram, label=f"Analog MC", color="red", linewidth=1) +plt.fill_between( + energy_axis, + histogram + 3 * inc, + histogram - 3 * inc, + color="red", + alpha=0.1, + linewidth=1, +) + +# Add labels, title, and grid +plt.xlabel("PG energy (MeV)", fontsize=14) +plt.ylabel("PG yield / 40 keV / " + part + "ons", fontsize=14) + +# Enable minor ticks for a more precise grid +plt.minorticks_on() +plt.title(f"PG yield from " + part + "ons energy [" + str(4 * Y) + " mm]", fontsize=16) +# Customize the grid +plt.grid(which="major", linestyle="-", linewidth=0.8, alpha=0.8) # Major grid lines +plt.grid(which="minor", linestyle=":", linewidth=0.5, alpha=0.5) # Minor grid lines +plt.ticklabel_format(axis="y", style="sci", scilimits=(0, 0)) + + +# Add a legend +plt.legend(fontsize=12) +plt.savefig( + "output/" + dossier + "/Energy_" + part + "on_" + case + ".pdf", + dpi=300, + format="pdf", + bbox_inches="tight", +) + +# Show the plot + +plt.show() diff --git a/opengate/contrib/vpgTLE/stageX/lecture4Dcoupetheta_prot.py b/opengate/contrib/vpgTLE/stageX/lecture4Dcoupetheta_prot.py new file mode 100644 index 000000000..42dce726e --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/lecture4Dcoupetheta_prot.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import itk +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.ticker import ScalarFormatter +from matplotlib.colors import LogNorm + + +# Read input image +itk_image = itk.imread("output/box/map_analogu_0_prot_e.nii.gz") +spacing = itk.spacing(itk_image) +# Copy of itk.Image, pixel data is copied +array = itk.array_from_image(itk_image) + +# on stocke les dimensions de l'image +x_size = array.shape[3] +y_size = array.shape[2] +z_size = array.shape[1] + +# Create a 3D coordinate grid with proper axis handling +z, y, x = np.meshgrid( + np.linspace( + -z_size * spacing[1] / 20, z_size * spacing[1] / 20, z_size + ), # z-axis (first dimension in the array) + np.linspace( + 0, y_size * spacing[2] / 10, y_size + ), # y-axis (second dimension in the array) + np.linspace( + -x_size * spacing[3] / 20, x_size * spacing[3] / 20, x_size + ), # x-axis (third dimension in the array) + indexing="ij", # Use 'ij' indexing to match the array's shape +) + +x = x.ravel() +y = y.ravel() +z = z.ravel() + +# La couleur prise par les voxels dans la vue globale se fera par rapport à la moyenne du nombre d'occurence dans les histogrammes + +array_intensity = np.sum(array, axis=0) + +# Define the radius and height bins +Max = np.sqrt(x[x_size - 1] ** 2 + z[z_size - 1] ** 2) +R = np.linspace(0, Max, z_size) +H = np.linspace(0, y_size * spacing[2] / 10, y_size) +theta = np.linspace(0, np.pi / 2, 100) # Angle for cylindrical coordinates +# Initialize the graphs +graph = np.zeros((len(R), len(H)), dtype=float) + +# Calculate the bin width +width = spacing[1] / 10 +center = int(x_size / 2) # Center index for the x-axis +dh = spacing[2] / 10 +dr = width +for h in range(len(H)): + Y = h + for r in range(len(R)): + for t in range(len(theta)): + X = int(R[r] * np.cos(theta[t]) / width) + Z = int(R[r] * np.sin(theta[t]) / width) + # si hors du cylindre d'intérêt (dans les coins du carré de 14 cm de côté) + if X >= 100 or Z >= 100: + continue + D = R[r] + dr / 2 + quotient = 2 * np.pi * D * dr * dh + # premier quart + graph[r, h] += array_intensity[center + Z, Y, center + X] / quotient + # deuxième quart + graph[r, h] += array_intensity[center + Z, Y, center - X] / quotient + # troisième quart + graph[r, h] += array_intensity[center - Z, Y, center - X] / quotient + # quatrième quart + graph[r, h] += array_intensity[center - Z, Y, center + X] / quotient + + +s, h = np.meshgrid( + np.linspace(0, 15, x_size), # r-axis (first dimension in the array) + H, # h-axis (second dimension in the array) + indexing="ij", # Use 'ij' indexing to match the array's shape +) + +s = s.ravel() +h = h.ravel() +values = graph.ravel() +mask = values != 0 # Create a boolean mask for non-zero values +s = s[mask] +h = h[mask] +vmax = 1e-2 # Find the maximum value for normalization +values = values[mask] + +fig, ax = plt.subplots(figsize=(8, 6)) + +# Create a scatter plot for the proton data +p_scatter = ax.scatter( + s, + h, # r and h coordinates + c=values, # Color represents the intensity + marker="o", # Marker style + cmap="Reds", + s=8, + norm=LogNorm(vmin=1e-7, vmax=vmax), +) # Normalize color scale + +formatter = ScalarFormatter(useMathText=True) # Create a ScalarFormatter +formatter.set_scientific(True) +formatter.set_powerlimits((-3, 3)) # Set the range for scientific notation + +# Add a colorbar +cbar_p = plt.colorbar(p_scatter, ax=ax, shrink=1, pad=0.1) +cbar_p.set_label("cts/protons/mm²") # Label for the colorbar +# Explicitly set ticks for the colorbar +ticks_p = [1e-7, 1e-6, 1e-5, 1e-4, 1e-3, 1e-2] +cbar_p.set_ticks(ticks_p) +cbar_p.set_ticklabels( + [f"{tick:.0e}" for tick in ticks_p] +) # Format ticks as scientific notation + +# labels and limits +ax.set_xlabel("radius (cm)") +ax.set_ylabel("depth (cm)") +ax.set_xlim(0, 15) # R-axis: cm +ax.set_ylim(0, 20) # H-axis: cm +ax.set_title("2D map of PGs emission by protons", fontsize=16) +plt.grid(True) +plt.savefig("coupe-protons-analog.pdf", bbox_inches="tight") + +plt.show() diff --git a/opengate/contrib/vpgTLE/stageX/profile12.py b/opengate/contrib/vpgTLE/stageX/profile12.py new file mode 100644 index 000000000..a3f0d8016 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/profile12.py @@ -0,0 +1,148 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import itk +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.ticker import ScalarFormatter + +# Read input image +itk_image = itk.imread("output/box/map_analogu_0_prot_e.nii.gz") +spacing = itk.spacing(itk_image) +# Copy of itk.Image, pixel data is copied +array = itk.array_from_image(itk_image) + +# Read input image +Nitk_image = itk.imread("output/box/map_analogu_0_neutr_e.nii.gz") +Nspacing = itk.spacing(Nitk_image) +# Copy of itk.Image, pixel data is copied +Narray = itk.array_from_image(Nitk_image) + +# on stocke les dimensions de l'image +x_size = array.shape[3] +y_size = array.shape[2] +z_size = array.shape[1] +H = np.linspace(0, y_size * spacing[2] / 10, y_size) + +# Create a 3D coordinate grid with proper axis handling +z, y, x = np.meshgrid( + np.linspace( + -z_size * spacing[1] / 20, z_size * spacing[1] / 20, z_size + ), # z-axis (first dimension in the array) + np.linspace( + 0, y_size * spacing[2] / 10, y_size + ), # y-axis (second dimension in the array) + np.linspace( + -x_size * spacing[3] / 20, x_size * spacing[3] / 20, x_size + ), # x-axis (third dimension in the array) + indexing="ij", # Use 'ij' indexing to match the array's shape +) +x = x.ravel() +y = y.ravel() +z = z.ravel() + +# La couleur prise par les voxels dans la vue globale se fera par rapport à la moyenne du nombre d'occurence dans les histogrammes +Narray_intensity = np.sum(Narray, axis=0) +array_intensity = np.sum(array, axis=0) + +N = 1e7 +Ninc = np.sqrt(Narray_intensity / N) +inc = np.sqrt(array_intensity / N) +# Define the radius and height bins +Max = np.sqrt(x[x_size - 1] ** 2 + z[z_size - 1] ** 2) +R = np.linspace(0, Max, z_size) +H = np.linspace(0, y_size * spacing[2] / 10, y_size) +theta = np.linspace(0, np.pi / 2, 100) # Angle for cylindrical coordinates +# Initialize the graphs +graph = np.zeros((len(R)), dtype=float) +Ngraph = np.zeros((len(R)), dtype=float) + +sig = np.zeros(graph.shape) +Nsig = np.zeros(Ngraph.shape) + +tot = np.zeros((len(R)), dtype=float) +# Calculate the bin width +width = spacing[1] / 10 +center = int(x_size / 2) # Center index for the x-axis +dh = spacing[2] / 10 +dr = width + +for r in range(len(R)): + for t in range(len(theta)): + for h in range(y_size): + X = int(R[r] * np.cos(theta[t]) / width) + Z = int(R[r] * np.sin(theta[t]) / width) + # si hors du cylindre d'intérêt (dans les coins du carré de 14 cm de côté) + if X >= 100 or Z >= 100: + continue + D = R[r] + dr / 2 + quotient = 2 * np.pi * D * dr * dh + # premier quart + graph[r] += array_intensity[center + Z, h, center + X] / quotient + Ngraph[r] += Narray_intensity[center + Z, h, center + X] / quotient + sig[r] += (inc[center + Z, h, center + X] / quotient) ** 2 + Nsig[r] += (Ninc[center + Z, h, center + X] / quotient) ** 2 + # deuxième quart + graph[r] += array_intensity[center + Z, h, center - X] / quotient + Ngraph[r] += Narray_intensity[center + Z, h, center - X] / quotient + sig[r] += (inc[center + Z, h, center - X] / quotient) ** 2 + Nsig[r] += (Ninc[center + Z, h, center - X] / quotient) ** 2 + # troisième quart + graph[r] += array_intensity[center - Z, h, center - X] / quotient + Ngraph[r] += Narray_intensity[center - Z, h, center - X] / quotient + sig[r] += (inc[center - Z, h, center - X] / quotient) ** 2 + Nsig[r] += (Ninc[center - Z, h, center - X] / quotient) ** 2 + # quatrième quart + graph[r] += array_intensity[center - Z, h, center + X] / quotient + Ngraph[r] += Narray_intensity[center - Z, h, center + X] / quotient + sig[r] += (inc[center - Z, h, center + X] / quotient) ** 2 + Nsig[r] += (Ninc[center - Z, h, center + X] / quotient) ** 2 + tot[r] = graph[r] + Ngraph[r] +R = R.ravel() +sig = np.sqrt(sig) +Nsig = np.sqrt(Nsig) +Ngraph = Ngraph.ravel() +graph = graph.ravel() +sig = sig.ravel() +Nsig = Nsig.ravel() +tot = tot.ravel() +print("min inc prot", np.min(sig)) +print("max inc prot", np.max(sig)) +print("min inc neut", np.min(Nsig)) +print("max Ninc neutr", np.max(Nsig)) +# Create a figure and axis +fig = plt.figure(figsize=(10, 6)) +# Plot graph and Ngraph against R +plt.plot(R, graph, label="Protons", color="red", linewidth=1) +plt.fill_between(R, graph - 3 * sig, graph + 3 * sig, color="red", alpha=0.08) +plt.plot(R, Ngraph, label="Neutrons", color="blue", linewidth=1) +plt.fill_between(R, Ngraph - 3 * Nsig, Ngraph + 3 * Nsig, color="blue", alpha=0.08) +plt.plot(R, tot, label="total", color="black", linewidth=1, linestyle="--") +# Add labels, legend, and title +plt.xlabel("Radius (cm)", fontsize=14) +plt.ylabel("cts / protons / (cm3)", fontsize=14) +plt.title("PGs emission against the radius", fontsize=20) +plt.legend(fontsize=12) + +ax = plt.gca() +# Set the y-axis to scientific notation +formatter = ScalarFormatter(useMathText=True) +formatter.set_scientific(True) +formatter.set_powerlimits((-2, 3)) # Set limits for scientific notation +ax.yaxis.set_major_formatter(formatter) + +# Enable minor ticks for a more precise grid +plt.minorticks_on() + +# Customize the grid +plt.grid(which="major", linestyle="-", linewidth=0.8, alpha=0.8) # Major grid lines +plt.grid(which="minor", linestyle=":", linewidth=0.5, alpha=0.5) # Minor grid lines +# Add more ticks to the y-axis +# plt.gca().yaxis.set_major_locator(MultipleLocator(1)) # Major ticks every 1 unit +# plt.gca().yaxis.set_minor_locator(MultipleLocator(0.2)) # Minor ticks every 0.2 units + +plt.yscale("log") +plt.xlim(0, 14) +# Show the plot +plt.savefig("analogprofileradial.pdf", format="pdf", bbox_inches="tight", dpi=300) +plt.show() diff --git a/opengate/contrib/vpgTLE/stageX/profilelongitude.py b/opengate/contrib/vpgTLE/stageX/profilelongitude.py new file mode 100644 index 000000000..8f9cc6425 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/profilelongitude.py @@ -0,0 +1,134 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import itk +import numpy as np +import matplotlib.pyplot as plt +from matplotlib.ticker import ScalarFormatter + + +# Read input image +itk_image = itk.imread("output/box/map_analogu_0_prot_e.nii.gz") +spacing = itk.spacing(itk_image) +# Copy of itk.Image, pixel data is copied +array = itk.array_from_image(itk_image) + +# Read input image +Nitk_image = itk.imread("output/box/map_analogu_0_neutr_e.nii.gz") +Nspacing = itk.spacing(Nitk_image) +# Copy of itk.Image, pixel data is copied +Narray = itk.array_from_image(Nitk_image) + +# on stocke les dimensions de l'image +x_size = array.shape[3] +y_size = array.shape[2] +z_size = array.shape[1] +H = np.linspace(0, y_size * spacing[2] / 10, y_size) +# Create a 3D coordinate grid with proper axis handling +z, y, x = np.meshgrid( + np.linspace( + -z_size * spacing[1] / 20, z_size * spacing[1] / 20, z_size + ), # z-axis (first dimension in the array) + np.linspace( + 0, y_size * spacing[2] / 10, y_size + ), # y-axis (second dimension in the array) + np.linspace( + -x_size * spacing[3] / 20, x_size * spacing[3] / 20, x_size + ), # x-axis (third dimension in the array) + indexing="ij", # Use 'ij' indexing to match the array's shape +) +x = x.ravel() +y = y.ravel() +z = z.ravel() + +# La couleur prise par les voxels dans la vue globale se fera par rapport à la moyenne du nombre d'occurence dans les histogrammes +Narray_intensity = np.sum(Narray, axis=0) +array_intensity = np.sum(array, axis=0) + +N = 1e7 +Ninc = np.sqrt(Narray_intensity / N) +inc = np.sqrt(array_intensity / N) + +H = np.linspace(0, y_size * spacing[2] / 10, y_size) +# Initialize the graphs +graph = np.zeros((len(H)), dtype=float) +Ngraph = np.zeros((len(H)), dtype=float) +tot = np.zeros((len(H)), dtype=float) + +sig = np.zeros(graph.shape) +Nsig = np.zeros(Ngraph.shape) + +for h in range(len(H)): + N = 0 + T = 0 + Ni = 0 + Ti = 0 + for i in range(x_size): + for j in range(z_size): + N = N + Narray_intensity[j, h, i] + T = T + array_intensity[j, h, i] + Ni = Ni + Ninc[j, h, i] ** 2 + Ti = Ti + inc[j, h, i] ** 2 + graph[h] = T + Ngraph[h] = N + sig[h] = Ti + Nsig[h] = Ni + tot[h] = T + N + +graph = graph[1:] +Ngraph = Ngraph[1:] +tot = tot[1:] +sig = sig[1:] +Nsig = Nsig[1:] + +sig = np.sqrt(sig) +Nsig = np.sqrt(Nsig) + +H = H[1:] +H = H.ravel() +Ngraph = Ngraph.ravel() +graph = graph.ravel() + +sig = sig.ravel() +Nsig = Nsig.ravel() + +tot = tot.ravel() +print("min inc prot", np.min(sig)) +print("max inc prot", np.max(sig)) +print("min inc neut", np.min(Nsig)) +print("max Ninc neutr", np.max(Nsig)) +# Create a figure and axis +fig = plt.figure(figsize=(10, 6)) +# Plot graph and Ngraph against R +plt.plot(H, graph, label="Protons", color="red", linewidth=1) +plt.fill_between(H, graph - 3 * sig, graph + 3 * sig, color="red", alpha=0.08) +plt.plot(H, Ngraph, label="Neutrons", color="blue", linewidth=1) +plt.fill_between(H, Ngraph - 3 * Nsig, Ngraph + 3 * Nsig, color="blue", alpha=0.08) +plt.plot(H, tot, label="total", color="black", linewidth=1, linestyle="--") +# Add labels, legend, and title +plt.xlabel("depth (cm)", fontsize=14) +plt.ylabel("cts/ protons / (cm3)", fontsize=14) +plt.title("PGs emission against the depth", fontsize=20) +plt.legend(fontsize=12) + +ax = plt.gca() +# Set the y-axis to scientific notation +formatter = ScalarFormatter(useMathText=True) +formatter.set_scientific(True) +formatter.set_powerlimits((-2, 3)) # Set limits for scientific notation +ax.yaxis.set_major_formatter(formatter) + +plt.minorticks_on() + +# Customize the grid +plt.grid(which="major", linestyle="-", linewidth=0.8, alpha=0.8) # Major grid lines +plt.grid(which="minor", linestyle=":", linewidth=0.5, alpha=0.5) # Minor grid lines +# Add more ticks to the y-axis +# plt.gca().yaxis.set_major_locator(MultipleLocator(1)) # Major ticks every 1 unit +# plt.gca().yaxis.set_minor_locator(MultipleLocator(0.2)) # Minor ticks every 0.2 units + +# plt.yscale('log') +plt.xlim(-1, 20) +# Show the plot +plt.savefig("analog-profilelongitude.pdf", format="pdf", bbox_inches="tight", dpi=300) +plt.show() diff --git a/opengate/contrib/vpgTLE/stageX/readme.md b/opengate/contrib/vpgTLE/stageX/readme.md new file mode 100644 index 000000000..a62d1a703 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/readme.md @@ -0,0 +1,8 @@ + +TESTS + +Run all tests with ```opengate_tests``` + +Every tests can be run with, e.g., ```./src/test008_dose_actor.py``` + +(documentation in progress : https://opengate-python.readthedocs.io) diff --git a/opengate/contrib/vpgTLE/stageX/remerge_data.py b/opengate/contrib/vpgTLE/stageX/remerge_data.py new file mode 100644 index 000000000..29f2ea03c --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/remerge_data.py @@ -0,0 +1,38 @@ +import uproot + +case = "neutron" + +# Open the source ROOT file (data_merge_neutron.root) and extract the histogram +input_A = uproot.open("data/data_" + case + "_A.root") +input_B = uproot.open("data/data_" + case + "_B.root") + +# Create a new ROOT file with the updated histogram +with uproot.recreate("data/data_merge_" + case + ".root") as new_file: + for key, obj in input_A.items(): + # Remove the first ";1" from the key name + clean_key = key.split(";")[0] # Remove the cycle suffix + if isinstance(obj, uproot.models.TH.Model_TH2D_v4): + # Copy other TH2D histograms as they are + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + edges_y = obj.axis(1).edges() + new_file[key] = (bin_contents, (edges_x, edges_y)) + elif isinstance(obj, uproot.models.TH.Model_TH1D_v3): + # Handle TH1D histograms + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + new_file[key] = (bin_contents, edges_x) + for key, obj in input_B.items(): + # Remove the first ";1" from the key name + clean_key = key.split(";")[0] # Remove the cycle suffix + if isinstance(obj, uproot.models.TH.Model_TH2D_v4): + # Copy other TH2D histograms as they are + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + edges_y = obj.axis(1).edges() + new_file[key] = (bin_contents, (edges_x, edges_y)) + elif isinstance(obj, uproot.models.TH.Model_TH1D_v3): + # Handle TH1D histograms + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + new_file[key] = (bin_contents, edges_x) diff --git a/opengate/contrib/vpgTLE/stageX/split_data.py b/opengate/contrib/vpgTLE/stageX/split_data.py new file mode 100644 index 000000000..edc68311d --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/split_data.py @@ -0,0 +1,74 @@ +import uproot +import numpy as np + +case = "proton" +# Open the source ROOT file (data_merge_neutron.root) and extract the histogram +source_file = uproot.open("data/data_merge_" + case + ".root") + +elements_A = [ + "standard_Weight", + "Al", + "Ar", + "Be", + "B", + "Ca", + "C", + "Cl", + "Cu", + "F", + "He", + "H", + "Li", +] +elements_B = ["Mg", "Ne", "N", "O", "P", "K", "Si", "Ag", "Na", "S", "Sn", "Ti", "Zn"] + +if case == "neutron": + quantity = ["GammaZ", "Kapa inelastique", "NrPG", "EnEpg"] +else: + quantity = ["GammaZ", "Kapa inelastique", "NrPG", "EpEpg"] + +# Fichiers de sortie +output_A = uproot.recreate("data/data_" + case + "_A.root") +output_B = uproot.recreate("data/data_" + case + "_B.root") + +for el in elements_A: + if el == "standard_Weight": + q = "Weight" + key = f"{el}/{q}" + obj = source_file[key] + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + output_A[key] = (bin_contents, edges_x) + continue + for q in quantity: + # Check if the object is a histogram (TH1D or TH2D) + key = f"{el}/{q}" + obj = source_file[key] + if isinstance(obj, uproot.models.TH.Model_TH2D_v4): + # Copy other TH2D histograms as they are + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + edges_y = obj.axis(1).edges() + output_A[key] = (bin_contents, (edges_x, edges_y)) + elif isinstance(obj, uproot.models.TH.Model_TH1D_v3): + # Handle TH1D histograms + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + output_A[key] = (bin_contents, edges_x) + +for el in elements_B: + for q in quantity: + # Check if the object is a histogram (TH1D or TH2D) + key = f"{el}/{q}" + obj = source_file[key] + if isinstance(obj, uproot.models.TH.Model_TH2D_v4): + # Copy other TH2D histograms as they are + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + edges_y = obj.axis(1).edges() + output_B[key] = (bin_contents, (edges_x, edges_y)) + elif isinstance(obj, uproot.models.TH.Model_TH1D_v3): + # Handle TH1D histograms + bin_contents = obj.values() + edges_x = obj.axis(0).edges() + output_B[key] = (bin_contents, edges_x) diff --git a/opengate/contrib/vpgTLE/stageX/stage1/multijobs_stage1.py b/opengate/contrib/vpgTLE/stageX/stage1/multijobs_stage1.py new file mode 100644 index 000000000..df7b39d12 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/stage1/multijobs_stage1.py @@ -0,0 +1,304 @@ +#!/usr/bin/env python +import argparse +import os +import itk +from multiprocessing import Pool + +import opengate as gate +import glob +import numpy as np +from stage_1a import * + +import sys + +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +DEFAULT_OUTPUT = "debog" # file name where single root file are stocked +DEFAULT_NUMBER_OF_JOBS = 1 +DEFAULT_NUMBER_OF_PARTICLES = 1e6 +DEFAULT_POSITION = [0, 0, 0] +DEFAULT_STEP_SIZE = float(1) +DEFAULT_WORLD = "G4_Galactic" # default material of the world volume +DEFAULT_SAMPLE = "Water3ppmPt" # default material of the sample volume +# DEFAULT_FILE_NAME = "ana-vox" #default name of the output file. No "_" in the name !!!! +DEFAULT_FILE_NAME = "vox" +DEFAULT_BEAM_ENERGY = 130 +# DEFAULT_ACTOR = "VoxelizedPromptGammaAnalogActor" +DEFAULT_ACTOR = "VoxelizedPromptGammaTLEActor" +# DEFAULT_RANGE = 10 +DEFAULT_RANGE = 130 + +path = utility.get_default_test_paths(__file__, output_folder=DEFAULT_OUTPUT) + + +def opengate_run( + output=DEFAULT_OUTPUT, + job_id=0, + number_of_particles=DEFAULT_NUMBER_OF_PARTICLES, + visu=False, + verbose=False, + polarization=False, + energy_type=False, + world=DEFAULT_WORLD, + sample_material=DEFAULT_SAMPLE, + File_name=DEFAULT_FILE_NAME, + collimation=False, + energy=DEFAULT_BEAM_ENERGY, + position=DEFAULT_POSITION, + actor=DEFAULT_ACTOR, + Erange=DEFAULT_RANGE, +): + + if verbose: + print( + f"Running MC… " + str(locals()) + ) # locals = variable locales soit les paramètres de la fonction + + # Call the simulation function and retrieve sim and simulation_parameters + sim = simulation( + output, File_name, job_id, number_of_particles, visu, verbose, actor, Erange + ) + + sim.run() + + +def itk_merge( + output=DEFAULT_OUTPUT, + verbose=False, + File_name=DEFAULT_FILE_NAME, + number_of_jobs=DEFAULT_NUMBER_OF_JOBS, +): + def print_verbose(*args, **kwargs): + if verbose: + print(*args, **kwargs) + + if not os.path.exists(output): + os.makedirs(output) + if number_of_jobs == 1: + print_verbose("Skipping merge, only one job was run.") + return + try: + # Find all .nii.gz files matching the pattern + file_pattern = os.path.join( + f"/home/vguittet/Documents/G10/stage1.5/output/{output}/{File_name}_trace_*.nii.gz" + ) + file_list = sorted(glob.glob(file_pattern)) # Get all matching files + count = number_of_jobs + if not file_list: + raise FileNotFoundError( + f"No .nii.gz files found matching pattern: {file_pattern}" + ) + actor_list = [] + type_list = [] + for file_path in file_list: + base_name = os.path.basename(file_path) + actor_name = base_name.split("_")[1] + type_name = ( + base_name.split("_")[3] + "_" + base_name.split("_")[4] + ) # e.g., neutr_e + if actor_name not in actor_list: + actor_list.append(actor_name) + if type_name not in type_list: + type_list.append(type_name) + for type_name in type_list: + for actor_name in actor_list: + init = itk.imread( + path.output / f"{File_name}_{actor_name}_1_{type_name}" + ) + init_arr = itk.array_from_image(init) + merged_array = np.zeros_like(init_arr, dtype=np.float32) + merged_array.fill(0) # Initialize the merged array + for i in range(count): + file_path = ( + path.output / f"{File_name}_{actor_name}_{i + 1}_{type_name}" + ) + print(file_path) + # Check if the file exists + if not os.path.exists(file_path): + print(f"File not found: {file_path}") + continue + image = itk.imread(file_path) + array = itk.array_from_image(image) + merged_array = merged_array + array # Accumulate the pixel values + merged_array = merged_array / count + merged_image = itk.image_from_array(merged_array) + merged_array = np.array([]) + merged_image.CopyInformation( + init + ) # Copy metadata from the initial image + output_file = ( + path.output / f"{File_name}_{actor_name}_merged_{type_name}" + ) + print_verbose(f"Saving merged image to: {output_file}") + itk.imwrite(merged_image, output_file) + + except FileNotFoundError as e: + print(e) + + +def opengate_pool_run( + output, + number_of_jobs, # mettre autant que de positions sinon on ne simule pas toutes les particules + number_of_particles, + visu, + verbose, + polarization, + energy_type, + world, + sample_material, + File_name, + collimation, + energy, + # step, + # size, +): + + with Pool(maxtasksperchild=1) as pool: + + results = [] + + number_of_particles_per_job = int(number_of_particles / number_of_jobs) + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + position = [ + 0, + 0, + 20 * cm, + ] # position initiale à 30um -> trop proche de la source + # delta_x = 0.1*mm #pas du raster scan pour passer d'un pixel à l'autre + # delta_x = step*mm + # delta_y = -step*mm + job_id = 0 + + for i in range(number_of_jobs): + + job_id = i + 1 + # copied_position = position.copy() + print(f"launching job #{job_id}/{number_of_jobs}") + # print(f"launching job #{job_id}/{number_of_jobs} with position x={copied_position[0]/mm:.2f} mm") + result = pool.apply_async( + opengate_run, + kwds={ + "output": output, + "job_id": job_id, + "number_of_particles": number_of_particles_per_job, + "visu": visu, + "verbose": verbose, + "position": position, + "polarization": polarization, + "energy_type": energy_type, + "world": world, + "sample_material": sample_material, + "File_name": File_name, + "collimation": collimation, + "energy": energy, + #'position' : copied_position, + }, + ) + results.append(result) + # position[0] += delta_x + + pool.close() + pool.join() + + for result in results: + result.wait() + if not result.successful(): + print("Failure in MC simulation") + exit(1) + + itk_merge( + output=output, + verbose=verbose, + File_name=File_name, + number_of_jobs=number_of_jobs, + ) + + +def main(): + + parser = argparse.ArgumentParser( + formatter_class=argparse.ArgumentDefaultsHelpFormatter + ) + parser.add_argument("--output", help="Path of outputs", default=DEFAULT_OUTPUT) + parser.add_argument( + "-j", + "--number-of-jobs", + help="Number of jobs", + default=DEFAULT_NUMBER_OF_JOBS, + type=int, + ) + parser.add_argument( + "-n", + "--number-of-particles", + help="Number of generated particles (total)", + default=DEFAULT_NUMBER_OF_PARTICLES, + type=float, + ) + parser.add_argument( + "--visu", + help="Visualize Monte Carlo simulation", + default=False, + action="store_true", + ) + parser.add_argument( + "--verbose", "-v", help="Verbose execution", default=False, action="store_true" + ) + parser.add_argument( + "-p", + "--polarization", + help="Polarization of the beam", + default=False, + action="store_true", + ) + parser.add_argument( + "--energy_type", + help="Type of energy distribution", + default=False, + action="store_true", + ) # True active le mode polychromatique, rendre la commande plus clair après + parser.add_argument( + "-w", "--world", help="World's material", default=DEFAULT_WORLD, type=str + ) # pas besoin de mettre les guillemets au moment de la commande + parser.add_argument( + "-s", + "--sample_material", + help="sample's material", + default=DEFAULT_SAMPLE, + type=str, + ) + parser.add_argument( + "-f", + "--File_name", + help="name of the output file", + default=DEFAULT_FILE_NAME, + type=str, + ) + parser.add_argument( + "-c", + "--collimation", + help="collimation of the beam", + default=False, + action="store_true", + ) + parser.add_argument( + "-e", + "--energy", + help="Energy of the beam in keV", + default=DEFAULT_BEAM_ENERGY, + type=int, + ) + # parser.add_argument('-step', '--step', help="size of a step between two succesive positions en mm", default=DEFAULT_STEP_SIZE, type=float) + # parser.add_argument('-size', '--size', help="number of pixels in lines/columns", default=DEFAULT_STEP_SIZE, type=int) + # parser.add_argument('-pos', '--number-of-positions', help="Number of positions to simulate", default=DEFAULT_NUMBER_OF_POSITIONS, type=int) + # argument position initiale ? + # argument pour le nombre de position à couvrir (taille de l'image à balayer en gros) + args_info = parser.parse_args() + + opengate_pool_run(**vars(args_info)) + # opengate_run(output, 1, number_of_particles, visu, verbose) + + +if __name__ == "__main__": + main() diff --git a/opengate/contrib/vpgTLE/stageX/stage1/stage_1a.py b/opengate/contrib/vpgTLE/stageX/stage1/stage_1a.py new file mode 100644 index 000000000..bc4a0964d --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/stage1/stage_1a.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import opengate as gate +from opengate.tests import utility +import uproot + + +def simulation( + output, File_name, job_id, number_of_particles, visu, verbose, actor, Erange +): + + paths = utility.get_default_test_paths(__file__, output_folder=output) + + # create the simulation + sim = gate.Simulation() + # main options + sim.visu = visu + sim.g4_verbose = verbose + sim.random_seed = "auto" # FIXME to be replaced by a fixed number at the end + sim.random_engine = "MersenneTwister" + sim.output_dir = paths.output + sim.number_of_threads = 1 + sim.progress_bar = True + + # units + mm = gate.g4_units.mm + cm = gate.g4_units.cm + m = gate.g4_units.m + MeV = gate.g4_units.MeV + ns = gate.g4_units.ns + gcm3 = gate.g4_units.g_cm3 + + # change world size + world = sim.world + world.size = [3 * m, 3 * m, 3 * m] + world.material = "G4_Galactic" + + # patient + """ + ct = sim.add_volume("Image", "ct") + ct.image = paths.data / f"1mm-carbo-volume.mhd" + if visu : + ct.image = paths.data / f"visu-carbo-volume.mhd" + ct.mother = "world" + ct.material = "G4_C" + ct.voxel_materials = [ + # range format [) + [-2000, -700, "G4_C"], + ] + ct.dump_label_image = "labels.mhd" + """ + + ct = sim.add_volume("Image", vol_name) + ct.image = paths.data / f"ct_4mm.mhd" + if sim.visu: + ct.image = paths.data / f"ct_40mm.mhd" + ct.material = "G4_AIR" + f1 = str(paths.data / "Schneider2000MaterialsTable.txt") + f2 = str(paths.data / "Schneider2000DensitiesTable.txt") + tol = 0.05 * gcm3 + ( + ct.voxel_materials, + materials, + ) = gate.geometry.materials.HounsfieldUnit_to_material(sim, tol, f1, f2) + ct.dump_label_image = paths.output / "labels.mhd" + ct.mother = "world" + ct.load_input_image() + + # physics + sim.physics_manager.physics_list_name = "QGSP_BIC_HP_EMY" + + sim.physics_manager.apply_cuts = False # default + sim.physics_manager.global_production_cuts.gamma = 0.1 * mm + sim.physics_manager.global_production_cuts.electron = 0.1 * mm + sim.physics_manager.global_production_cuts.positron = 0.1 * mm + sim.physics_manager.global_production_cuts.proton = 0.1 * mm + + sim.physics_manager.set_max_step_size("ct", 0.1 * mm) + sim.physics_manager.set_user_limits_particles(["proton"]) + + # source of proton + # FIXME to replace by a more realistic proton beam, see tests 044 + source = sim.add_source("GenericSource", "DEFAULT") + source.energy.mono = Erange * MeV + source.particle = "proton" + source.position.type = "point" + source.position.radius = 1 * mm + source.position.translation = [0 * mm, -300 * mm, 0 * mm] + source.n = number_of_particles + source.direction.type = "momentum" + source.direction.momentum = [0, 1, 0] + + # LOOKHERE :: if database not well implanted, has to be modified + with uproot.open(paths.data / "data_merge_proton.root") as root_file: + histo = root_file["standard_Weight"]["Weight"].to_hist() + vect_p = histo.to_numpy()[0] + with uproot.open(paths.data / "data_merge_neutron.root") as root_file: + histo = root_file["standard_Weight"]["Weight"].to_hist() + vect_n = histo.to_numpy()[0] + + vpg_tle = sim.add_actor(actor, actor_name) + vpg_tle.attached_to = vol_name + vpg_tle.output_filename = paths.output / f"{File_name}.nii.gz" + vpg_tle.size = [125, 125, 189] # the same size than ct image is stronly adviced + vpg_tle.spacing = [ + 4, + 4, + 4, + ] # the same spacing is stronly adviced + vpg_tle.timebins = 250 + vpg_tle.timerange = 5 * ns + vpg_tle.energybins = 250 + vpg_tle.energyrange = Erange * MeV + vpg_tle.prot_E.active = True + vpg_tle.neutr_E.active = True + vpg_tle.prot_tof.active = True + vpg_tle.neutr_tof.active = True + vpg_tle.weight = True # True to obtain weighted time spectra + vpg_tle.vect_p = vect_p + vpg_tle.vect_n = vect_n + + # add stat actor + stats = sim.add_actor("SimulationStatisticsActor", "stats") + stats.track_types_flag = True + stats.output_filename = paths.output / f"stat_{job_id}_{File_name}.txt" + + return sim + + +# features of simulation that can be modify +output = "stage1a" +File_name = "vpg" +actor_name = "vpg_tle" +vol_name = "ct" +MJ = False +number_of_particles = 1e2 +# if analog is used, should be replace (Erange = 10 MeV and differenciate from the source energy) +actor = "VoxelizedPromptGammaTLEActor" +# source Energy andrange of the actor +Erange = 130 +if __name__ == "__main__": + sim = simulation( + output, File_name, 0, number_of_particles, False, False, actor, Erange + ) + # start simulation + sim.run() diff --git a/opengate/contrib/vpgTLE/stageX/stage1/stage_1b.py b/opengate/contrib/vpgTLE/stageX/stage1/stage_1b.py new file mode 100644 index 000000000..1836538a9 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/stage1/stage_1b.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import itk +import uproot +import hist +import opengate as gate + +# import xraylib +# import periodictable +from opengate.tests import utility +import os +import glob +import numpy as np + +# import matplotlib.pyplot as plt +# import matplotlib.ticker as mticker +from variables import tle_sim +from stage_1a import * + +# from multijobs_stage1 import output, File_name, number_of_particles, actor, Erange + +sim = simulation( + output=output, + File_name=File_name, + job_id=0, + number_of_particles=number_of_particles, + visu=False, + verbose=False, + actor=actor, + Erange=Erange, +) + + +tle_simu = tle_sim(sim) + +# FIRST STEP : retrieve the arrays and all the needed features +# CT volume : + +ct_object = tle_simu.ct +UH_array = itk.array_from_image(ct_object.load_input_image()) + +# ACTORS volume as a list of object and the output place: +path = tle_simu.path +actor = tle_simu.act +actor_list = tle_simu.actor_list + +# database + +data_protons = tle_simu.root_file +data_neutrons = tle_simu.root_file_neutron +mat = tle_simu.voxel_mat + +# SECOND STEP : compute the material Gamma for each materials present in the ct and store it in a list + +neutr = False # to skip step if neutron are not taling into account +if "neutr_e.nii.gz" in actor_list: + neutr = True + +gamma_neutron = {} +gamma_proton = {} + +# building a dico that links for each mterial name, the Gamma histograms +for data in mat: + name = data[2] + if neutr: + gamma_neutron[name] = tle_simu.gamma_mat(name, data_neutrons) + gamma_proton[name] = tle_simu.gamma_mat(name, data_protons) + +# THIRD STEP : convert the energy histogram stored the 4D output into emission spectra for each voxel of the ct volume +Ep = np.linspace(0, actor.energyrange, actor.energybins + 1) +E_db = np.linspace(0, 200, 500) +ind_db = np.abs(E_db[None, :] - Ep[:, None]).argmin( + axis=1 +) # LOOKHERE :: should find a better way to build the index list + +if MJ: # multijobs or not ?? + img_p = itk.imread(os.path.join(path.output, f"{File_name}_merged_prot_e.nii.gz")) +else: + img_p = itk.imread(os.path.join(path.output, f"{File_name}_0_prot_e.nii.gz")) + +array_p = itk.array_from_image(img_p) +treated_array_p = np.zeros( + (250, array_p.shape[1], array_p.shape[2], array_p.shape[3]) +) # Initialize the treated array for proton + +treated_array_n = np.zeros( + (250, array_p.shape[1], array_p.shape[2], array_p.shape[3]) +) # Initialize the treated array for neutron +gamma_array = np.zeros( + (250, array_p.shape[1], array_p.shape[2], array_p.shape[3]) +) # Initialize the treated array for neutron AND proton + +if neutr: + if MJ: + img_n = itk.imread( + os.path.join(path.output, f"{File_name}_merged_neutr_e.nii.gz") + ) + else: + img_n = itk.imread(os.path.join(path.output, f"{File_name}_0_neutr_e.nii.gz")) + array_n = itk.array_from_image(img_n) + +# treatment of the image voxel by voxel => to store gamma emission spectrum in each voxel +for x in range(array_p.shape[3]): + for y in range(array_p.shape[2]): + for z in range(array_p.shape[1]): + # Get the Hounsfield Unit (HU) value at the current voxel + if ct_object.size == actor.size: + # If the CT size matches the actor size, use the voxel coordinates directly + X, Y, Z = x, y, z + else: + X, Y, Z = tle_simu.redim((x, y, z), ct_object, actor, array_p) + if ( + UH_array[Z, Y, X] == -3024 + ): # LOOKHERE : conditions of the CT where there is no matter, to be removed if CT different from the exemple case + # Skip the voxel if HU value is -3024 + continue + name = tle_simu.voxel_to_mat_name(UH_array[Z, Y, X], tle_simu.voxel_mat) + histo_E_p = array_p[ + :, z, y, x + ] # Get the energy histogram for the current voxel + spectrum_p = np.zeros(250) # Initialize the spectrum for the current voxel + if histo_E_p.sum() != 0: # if nothing is detected, the voxel is not treated + Gamma_m_p = gamma_proton[name] + Gamma_p = Gamma_m_p[ind_db] + spectrum_p = np.dot( + histo_E_p, Gamma_p + ) # np.dot : method used for the spectrum computation BUT, can be optimised ??? + treated_array_p[:, z, y, x] = spectrum_p + if neutr: + histo_E_n = array_n[:, z, y, x] + spectrum_n = np.zeros(250) + if histo_E_n.sum() != 0: + Gamma_m_n = gamma_neutron[name] + Gamma_n = Gamma_m_n[ind_db] + spectrum_n = np.dot( + histo_E_n, Gamma_n + ) # np.dot : method used for the spectrum computation BUT, can be optimised ??? + treated_array_n[:, z, y, x] = spectrum_n + +# Create a new ITK image from the treated array +gamma_array += treated_array_p + treated_array_n +itk_output = itk.image_from_array(treated_array_p) +itk_output.CopyInformation(img_p) +if MJ: + itk.imwrite(itk_output, path.output / f"VPG_{File_name}_merged_prot_e.nii.gz") +else: + itk.imwrite(itk_output, path.output / f"VPG_{File_name}_0_prot_e.nii.gz") +if neutr: + itk_output = itk.image_from_array(treated_array_n) + itk_output.CopyInformation(img_n) + if MJ: + itk.imwrite(itk_output, path.output / f"VPG_{File_name}_merged_prot_e.nii.gz") + else: + itk.imwrite(itk_output, path.output / f"VPG_{File_name}_0_neutr_e.nii.gz") + +itk_output = itk.image_from_array(gamma_array) +if MJ: + itk.imwrite(itk_output, path.output / f"VPG_{File_name}_merged_gamma_e.nii.gz") +else: + itk.imwrite(itk_output, path.output / f"VPG_{File_name}_0_gamma_e.nii.gz") diff --git a/opengate/contrib/vpgTLE/stageX/stage1/variables.py b/opengate/contrib/vpgTLE/stageX/stage1/variables.py new file mode 100644 index 000000000..bb38277a2 --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/stage1/variables.py @@ -0,0 +1,160 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import uproot +import opengate as gate +from opengate.tests import utility +from opengate.definitions import elements_name_symbol +import os +import numpy as np +from stage_1a import * + + +gcm3 = gate.g4_units.g_cm3 + + +class tle_sim: + def __init__(self, sim): + paths = utility.get_default_test_paths(__file__, output_folder=output) + self.path = paths + self.sim = sim + self.ct = sim.volume_manager.volumes[vol_name] + self.act = sim.actor_manager.actors[actor_name] + actors_list = [] + if self.act.prot_E.active: + actors_list.append("prot_e.nii.gz") + if self.act.neutr_E.active: + actors_list.append("neutr_e.nii.gz") + self.actor_list = actors_list + + f1 = str(paths.data / "Schneider2000MaterialsTable.txt") + f2 = str(paths.data / "Schneider2000DensitiesTable.txt") + tol = 0.05 * gcm3 + ( + voxel_materials, + materials, + ) = gate.geometry.materials.HounsfieldUnit_to_material(sim, tol, f1, f2) + (mat_fraction, el) = gate.geometry.materials.HU_read_materials_table(f1) + mat_fraction.pop() + database_mat = gate.geometry.materials.write_material_database( + sim, materials, str(paths.data / "database.db") + ) + self.mat_fraction = mat_fraction + self.voxel_mat = voxel_materials + self.el = el + self.database_mat = database_mat + + p = os.path.abspath(str(paths.data / "database.db")) + self.data_way = p + + f3 = str(paths.data / "data_merge_proton.root") + f4 = str(paths.data / "data_merge_neutron.root") + self.root_file = uproot.open(f3) + self.root_file_neutron = uproot.open(f4) + + def redim( + self, ind, ct, actor_vol, array + ): # to convert the index from the actor volume to the ct volume + + # centres ct et actor + ct_trans = list(ct.translation) + vol_trans = list(actor_vol.translation) + + # distance entre le centre du ct et le centre du vol + decal = [ + vol_trans[2] - ct_trans[2], + vol_trans[1] - ct_trans[1], + vol_trans[0] - ct_trans[0], + ] + + # calculer la position du voxel dans le volume de l'actor + X = ind[0] * actor_vol.spacing[2] + Y = ind[1] * actor_vol.spacing[1] + Z = ind[2] * actor_vol.spacing[0] + + # calculer la position du voxel dans le ct + x = (X + decal[0]) / ct.spacing[0] + y = (Y + decal[1]) / ct.spacing[1] + z = (Z + decal[2]) / ct.spacing[2] + + return int(x), int(y), int(z) + + def find_emission_vector( + self, el, root_data + ): # to find the TH2D in the root_data corresponding to the element el + if el == "Phosphor": + el = "Phosphorus" + el = elements_name_symbol[el] + histo = root_data[el]["GammaZ"].to_hist() + w = histo.to_numpy()[0] + return w + + def density_mat( + self, mat, data_way + ): # read the datas to find the density of the material + with open(data_way, "r") as f: + mat = mat + ":" + for line in f: + words = line.split() + if len(words) < 1: + continue + if words[0] == mat: + rho = words[1] + if words[2] == "mg/cm3;": + return float(rho[2:]) / 1000 # to convert the mg/cm3 to g/cm3 + return float(rho[2:]) + return "DEFAULT" + + def voxel_to_mat_name(self, UH, mat_data): # from UH to material name + ind = 0 + for l in mat_data: + ind = ind + 1 + if ind == len(mat_data): + mat = l[2] + if UH > l[1]: + continue + else: + mat = l[2] + break + return mat # str + + def mat_to_UH(self, mat, mat_data): # from material to UH + mat = mat.lower() + for l in mat_data: + print(l[2]) + if l[2] == mat.capitalize(): + return l[0] + return "DEFAULT" + + def liste_el_frac( + self, mat, frac_data + ): # from material, extract a list of element and a list of fraction. Element and its corresponding fraction are stored with the same index in both lists + if mat[-3] == "_": + mat = mat[:-3] + else: + mat = mat[:-2] + liste = [] + frac = [] + for l in frac_data: + if l["name"] == mat: + for el in l: + if (el != "HU") and (el != "name"): + if l[el] != 0.0: + liste.append(el) + frac.append(l[el]) + return liste, frac + + def gamma_mat( + self, name, root_data + ): # construct the GammaZ corresponding to the material + Gamma = np.zeros((500, 250)) # Initialize a 2D array for gamma emission + + """Find the material and its composition corresponding to the Hounsfield Unit (UH)""" + rho_mat = self.density_mat(name, self.data_way) + (elements, fractions) = self.liste_el_frac(name, self.mat_fraction) + + """ elements: list of elements in the material""" + for el in elements: + w = fractions[elements.index(el)] / 100 # Convert percentage to fraction + vect = self.find_emission_vector(el, root_data) + Gamma += vect * w * rho_mat + return Gamma diff --git a/opengate/contrib/vpgTLE/stageX/time-patient.py b/opengate/contrib/vpgTLE/stageX/time-patient.py new file mode 100644 index 000000000..ceed5aafa --- /dev/null +++ b/opengate/contrib/vpgTLE/stageX/time-patient.py @@ -0,0 +1,78 @@ +import itk +import numpy as np +import matplotlib.pyplot as plt +import matplotlib.ticker as mtick + +dossier = "patient" +part = "prot" +X = 62 +Y = 66 +Z = 94 +case = "try" +# Read input image +itk_image = itk.imread("output/" + dossier + "/tle_patient_0_" + part + "_tof.nii.gz") +# Copy of itk.Image, pixel data is copied +array = itk.array_from_image(itk_image) +# Read input image +itk_image = itk.imread("output/" + dossier + "/ana_patient_0_" + part + "_tof.nii.gz") +# Copy of itk.Image, pixel data is copied +ana_array = itk.array_from_image(itk_image) +N = 1e7 + +ana_histogram = np.zeros(ana_array.shape[0]) +ana_histogram = ana_array[:, Z, Y, X] +# ana_histogram = ana_histogram[:-1] + +histogram = np.zeros(array.shape[0]) +histogram = array[:, Z, Y, X] + +ana_norma = np.sum(ana_histogram) +norma = np.sum(histogram) + +ana_histogram = ana_histogram / ana_norma +histogram = histogram / norma + +inc = np.sqrt(ana_histogram / N) +inc = inc / ana_norma +print(max(inc)) + +# Define the energy axis (assuming the histogram bins correspond to energy levels) +energy_axis = np.linspace( + 0, 5, len(ana_histogram) +) # Adjust the range and number of bins as needed + +# Plot the histogram +plt.figure(figsize=(10, 6)) +plt.plot(energy_axis, ana_histogram, label=f"analog MC", color="red", linewidth=1) +plt.plot(energy_axis, histogram, label=f"vpgtle MC", color="green", linewidth=1) + +# Add labels, title, and grid +plt.xlabel("Time of flight (ns)", fontsize=14) +plt.ylabel("PG yield / 20 ps / protons", fontsize=14) +if part == "prot": + plt.xlim(0, 5) +else: + plt.xlim(0, 5) +# Enable minor ticks for a more precise grid +plt.minorticks_on() +plt.title( + f"PG emission probability depending on " + part + "ons ToF [" + str(Y * 4) + " mm]", + fontsize=16, +) + +# Customize the grid +plt.grid(which="major", linestyle="-", linewidth=0.8, alpha=0.8) # Major grid lines +plt.grid(which="minor", linestyle=":", linewidth=0.5, alpha=0.5) # Minor grid lines +plt.ticklabel_format(axis="y", style="sci", scilimits=(0, 0)) + +# Add a legend +plt.legend(fontsize=12) +plt.savefig( + "output/" + dossier + "/ToF_" + part + "on_" + case + ".pdf", + dpi=300, + format="pdf", + bbox_inches="tight", +) +# Show the plot + +plt.show() diff --git a/opengate/image.py b/opengate/image.py index 4dc6c2d7e..2891ea120 100644 --- a/opengate/image.py +++ b/opengate/image.py @@ -59,6 +59,36 @@ def create_3d_image( return img +def create_3d_image_of_histogram( + size, spacing, bins, origin=None, pixel_type="float", allocate=True +): + if len(size) != 4: + size.append(bins) + spacing.append(1.0) + else: + size[3] = bins + if (origin is not None) and (len(origin) != 4): + origin.append(0.0) + return create_4d_image(size, spacing, origin, pixel_type, allocate) + + +def create_4d_image(size, spacing, origin=None, pixel_type="float", allocate=True): + dim = 4 + pixel_type = itk.ctype(pixel_type) + image_type = itk.Image[pixel_type, dim] + img = image_type.New() + region = itk.ImageRegion[dim]() + region.SetSize([int(s) for s in size]) + region.SetIndex([0, 0, 0, 0]) + img.SetRegions(region) + img.SetSpacing(spacing) + if origin is not None: + img.SetOrigin(origin) + if allocate: + img.Allocate() + return img + + def create_image_like(like_image, allocate=True, pixel_type=""): # TODO fix pixel_type -> copy from image rather than argument info = get_info_from_image(like_image) diff --git a/opengate/managers.py b/opengate/managers.py index e8f656414..90285cf4f 100644 --- a/opengate/managers.py +++ b/opengate/managers.py @@ -39,7 +39,7 @@ from .sources.generic import SourceBase, GenericSource from .sources.phspsources import PhaseSpaceSource -from .sources.voxelsources import VoxelSource +from .sources.voxelsources import VoxelSource, VoxelizedPromptGammaTLESource from .sources.gansources import GANSource, GANPairsSource from .sources.beamsources import IonPencilBeamSource, TreatmentPlanPBSource from .sources.phidsources import PhotonFromIonDecaySource @@ -54,6 +54,7 @@ "IonPencilBeamSource": IonPencilBeamSource, "PhotonFromIonDecaySource": PhotonFromIonDecaySource, "TreatmentPlanPBSource": TreatmentPlanPBSource, + "VoxelizedPromptGammaTLESource": VoxelizedPromptGammaTLESource, } from .geometry.volumes import ( @@ -89,6 +90,8 @@ EmCalculatorActor, ) +from .actors.tleactors import VoxelizedPromptGammaTLEActor + from .actors.dynamicactors import DynamicGeometryActor from .actors.arfactors import ARFActor, ARFTrainingDatasetActor from .actors.miscactors import ( @@ -128,6 +131,7 @@ # dose related "DoseActor": DoseActor, "TLEDoseActor": TLEDoseActor, + "VoxelizedPromptGammaTLEActor": VoxelizedPromptGammaTLEActor, "LETActor": LETActor, "ProductionAndStoppingActor": ProductionAndStoppingActor, "RBEActor": RBEActor, diff --git a/opengate/sources/voxelsources.py b/opengate/sources/voxelsources.py index ade7a3e79..55a272144 100644 --- a/opengate/sources/voxelsources.py +++ b/opengate/sources/voxelsources.py @@ -1,5 +1,4 @@ import itk - import opengate_core as g4 from .generic import GenericSource from ..image import ( @@ -86,4 +85,59 @@ def initialize(self, run_timing_intervals): GenericSource.initialize(self, run_timing_intervals) +class VoxelizedPromptGammaTLESource(VoxelSource, g4.GateVoxelizedPromptGammaTLESource): + """ + todo + """ + + user_info_defaults = { + "pg_image": ( + None, + { + "doc": "Filename of the image of the 3D activity distribution " + "(will be automatically normalized to sum=1)", + "is_input_file": True, + }, + ) + } + + def __init__(self, *args, **kwargs): + self.__initcpp__() + super().__init__(self, *args, **kwargs) + # the loaded image + self.itk_pg_image = None + # the image for the 3D distribution sampling : integral of the histogram + self.itk_image = None + + def __initcpp__(self): + g4.GateVoxelSource.__init__(self) + g4.GateVoxelizedPromptGammaTLESource.__init__(self) + + def initialize(self, run_timing_intervals): + # read source image + print("reading ", self.pg_image) + self.itk_pg_image = itk.imread(ensure_filename_is_str(self.pg_image)) + print( + "read image input", self.itk_pg_image.GetLargestPossibleRegion().GetSize() + ) + print("read image spacing", self.itk_pg_image.GetSpacing()) + print("before origin", self.itk_pg_image.GetOrigin()) + + # FIXME todo, compute itk_image with integrated histo for 3D sampling + + # compute position + # self.set_transform_from_user_info() + # print("after origin", self.itk_image.GetOrigin()) + + # create Cumulative Distribution Function + # self.cumulative_distribution_functions() + + # initialize standard options (particle energy, etc.) + # we temporarily set the position attribute to reuse + # the GenericSource verification + ## FIXME unsure here -> some options are fixed (particle is always gamma, etc), other maybe variable + GenericSource.initialize(self, run_timing_intervals) + + process_cls(VoxelSource) +process_cls(VoxelizedPromptGammaTLESource) diff --git a/opengate/tests/src/test085_free_flight_helpers.py b/opengate/tests/src/test085_free_flight_helpers.py old mode 100755 new mode 100644 diff --git a/opengate/tests/src/test087_multijobs.py b/opengate/tests/src/test087_multijobs.py new file mode 100644 index 000000000..7b3ef31cf --- /dev/null +++ b/opengate/tests/src/test087_multijobs.py @@ -0,0 +1,274 @@ +#!/usr/bin/env python +import argparse +import os +import itk +from multiprocessing import Pool + +from opengate.tests import utility +import matplotlib.pyplot as plt +import opengate as gate +import glob +import numpy as np +import pyvista as pv +from scipy.optimize import curve_fit + +from scipy.spatial.transform import Rotation as R +import spekpy as sp + +#import critical_angle +#import psutil +from test_088_vpg_tle_script import * +DEFAULT_OUTPUT = 'debug_new' #file name where single root file are stocked + +DEFAULT_NUMBER_OF_JOBS = 1 +DEFAULT_NUMBER_OF_PARTICLES = 1e6 +DEFAULT_POSITION = [0, 0, 0] +DEFAULT_STEP_SIZE = float(1) + +DEFAULT_WORLD = "G4_Galactic" #default material of the world volume +DEFAULT_SAMPLE = "Water3ppmPt" #default material of the sample volume + +DEFAULT_FILE_NAME = "prestep" +DEFAULT_BEAM_ENERGY = 130 +DEFAULT_ACTOR = "VoxelizedPromptGammaAnalogActor" + +path = utility.get_default_test_paths(__file__, output_folder=DEFAULT_OUTPUT) + +def opengate_run( + output=DEFAULT_OUTPUT, + job_id=0, + number_of_particles=DEFAULT_NUMBER_OF_PARTICLES, + visu=True, + verbose=True, + polarization=False, + energy_type=False, + world=DEFAULT_WORLD, + sample_material=DEFAULT_SAMPLE, + File_name=DEFAULT_FILE_NAME, + collimation=False, + energy = DEFAULT_BEAM_ENERGY, + position=DEFAULT_POSITION, + actor = DEFAULT_ACTOR, + ): + + if verbose: + print(f"Running MC… " + str(locals())) #locals = variable locales soit les paramètres de la fonction + + # Call the simulation function and retrieve sim and simulation_parameters + sim, ct, vpg_actor, source = simulation(output, File_name, job_id, number_of_particles, visu, verbose, actor) + + sim.run() + + #visualization + + # === Charger le fichier VRML avec PyVista === + """print("📂 Chargement du fichier VRML pour affichage...") + pl = pv.Plotter() + pl.import_vrml("simulation.wrl") # Charger la scène OpenGATE + + # === Ajouter des axes colorés pour l'orientation === + axes = pv.Axes() + axes.axes_actor.total_length = [1, 1, 1] # Taille des axes + axes.axes_actor.x_axis_shaft_properties.color = (1, 0, 0) # Rouge pour X + axes.axes_actor.y_axis_shaft_properties.color = (0, 1, 0) # Vert pour Y + axes.axes_actor.z_axis_shaft_properties.color = (0, 0, 1) # Bleu pour Z + pl.add_actor(axes.axes_actor) + + # === Paramètres d'affichage === + pl.background_color = "black" # Fond noir pour meilleur contraste + pl.show_grid() # Afficher une grille + pl.show() # Afficher la scène 3D""" + +def itk_merge( + output=DEFAULT_OUTPUT, + verbose=False, + File_name=DEFAULT_FILE_NAME, + number_of_jobs=DEFAULT_NUMBER_OF_JOBS, +): + def print_verbose(*args, **kwargs): + if verbose: + print(*args, **kwargs) + if not os.path.exists(output): + os.makedirs(output) + if number_of_jobs == 1 : + print_verbose("Skipping merge, only one job was run.") + return + try: + # Find all .nii.gz files matching the pattern + file_pattern = os.path.join(f"/home/vguittet/Documents/G10/stage1.5/output/{output}/{File_name}_*.nii.gz") + file_list = sorted(glob.glob(file_pattern)) # Get all matching files + count = number_of_jobs + if not file_list: + raise FileNotFoundError(f"No .nii.gz files found matching pattern: {file_pattern}") + actor_list = [] + type_list = [] + for file_path in file_list: + base_name = os.path.basename(file_path) + actor_name = base_name.split("_")[1] + type_name = base_name.split("_")[3] + "_" + base_name.split("_")[4] # e.g., neutr_e + + if actor_name not in actor_list: + actor_list.append(actor_name) + if type_name not in type_list: + type_list.append(type_name) + init = itk.imread(path.output/f"{File_name}_{actor_list[0]}_1_{type_list[0]}") + init_arr = itk.array_from_image(init) + merged_array = np.zeros_like(init_arr, dtype=np.float32) + for type_name in type_list: + for actor_name in actor_list: + merged_array.fill(0) # Initialize the merged array + for i in range(count): + file_path = path.output/f"{File_name}_{actor_name}_{i + 1}_{type_name}" + # Check if the file exists + if not os.path.exists(file_path): + print(f"File not found: {file_path}") + continue + image = itk.imread(file_path) + array = itk.array_from_image(image) + merged_array = merged_array + array / count # Accumulate the pixel values + merged_image = itk.image_from_array(merged_array) + merged_image.CopyInformation(init) # Copy metadata from the initial image + output_file = path.output/f"{File_name}_{actor_name}_merged_{type_name}" + print_verbose(f"Saving merged image to: {output_file}") + itk.imwrite(merged_image, output_file) + + + + except FileNotFoundError as e: + print(e) + + +def opengate_pool_run( + output, + number_of_jobs, #mettre autant que de positions sinon on ne simule pas toutes les particules + number_of_particles, + visu, + verbose, + polarization, + energy_type, + world, + sample_material, + File_name, + collimation, + energy, + #step, + #size, + +): + + with Pool(maxtasksperchild=1) as pool: + + results = [] + number_of_particles_per_job = int(number_of_particles / number_of_jobs) + mm = gate.g4_units.mm + um = gate.g4_units.um + cm = gate.g4_units.cm + + position = [0, 0, 20*cm] #position initiale à 30um -> trop proche de la source + #delta_x = 0.1*mm #pas du raster scan pour passer d'un pixel à l'autre + #delta_x = step*mm + #delta_y = -step*mm + job_id = 0 + #image_size = size + + + """ for y in range(image_size) : #lignes + for x in range(image_size) : #colonnes + job_id += 1 + # Calcule la nouvelle position à partir de la base + position = [ + x * delta_x, + y * delta_y, + position[2] + ] + #le carré décrit est balayé de gauche à droite et de haut en bas + + + copied_position = position.copy() + print(f"launching job #{job_id}/{number_of_jobs} with position x={copied_position[0]/mm:.2f} mm, y={copied_position[1]/mm:.2f} mm") + + result = pool.apply_async(opengate_run, kwds={ + 'output': output, + 'job_id': job_id, + 'number_of_particles': number_of_particles_per_job, + 'visu': visu, + 'verbose': verbose, + 'position': copied_position + + #'position' : position + }) + results.append(result)""" + + + + + for i in range(number_of_jobs): + + job_id = i+1 + #copied_position = position.copy() + print(f"launching job #{job_id}/{number_of_jobs}") + #print(f"launching job #{job_id}/{number_of_jobs} with position x={copied_position[0]/mm:.2f} mm") + result = pool.apply_async(opengate_run, kwds={ + 'output': output, + 'job_id': job_id, + 'number_of_particles': number_of_particles_per_job, + 'visu': visu, + 'verbose': verbose, + 'position' : position, + 'polarization': polarization, + 'energy_type': energy_type, + 'world' : world, + 'sample_material' : sample_material, + 'File_name': File_name, + 'collimation' : collimation, + 'energy' : energy, + #'position' : copied_position, + }) + results.append(result) + #position[0] += delta_x + + + + + + pool.close() + pool.join() + + for result in results: + result.wait() + if not result.successful(): + print("Failure in MC simulation") + exit(1) + + itk_merge( + output=output, + verbose=verbose, + File_name=File_name, + number_of_jobs=number_of_jobs, + ) + +def main(): + + parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter) + parser.add_argument('--output', help="Path of outputs", default=DEFAULT_OUTPUT) + parser.add_argument('-j', '--number-of-jobs', help="Number of jobs", default=DEFAULT_NUMBER_OF_JOBS, type=int) + parser.add_argument('-n', '--number-of-particles', help="Number of generated particles (total)", default=DEFAULT_NUMBER_OF_PARTICLES, type=float) + parser.add_argument('--visu', help="Visualize Monte Carlo simulation", default=False, action='store_true') + parser.add_argument('--verbose', '-v', help="Verbose execution", default=False, action='store_true') + parser.add_argument('-p', '--polarization', help="Polarization of the beam", default=False, action='store_true') + parser.add_argument('--energy_type', help="Type of energy distribution", default=False, action='store_true')#True active le mode polychromatique, rendre la commande plus clair après + parser.add_argument('-w', '--world', help="World's material", default=DEFAULT_WORLD, type=str)#pas besoin de mettre les guillemets au moment de la commande + parser.add_argument('-s', '--sample_material', help="sample's material", default=DEFAULT_SAMPLE, type=str) + parser.add_argument('-f', '--File_name', help="name of the output file", default=DEFAULT_FILE_NAME, type=str) + parser.add_argument('-c', '--collimation', help="collimation of the beam", default=False, action='store_true') + parser.add_argument('-e', '--energy', help="Energy of the beam in keV", default=DEFAULT_BEAM_ENERGY, type= int) + #parser.add_argument('-step', '--step', help="size of a step between two succesive positions en mm", default=DEFAULT_STEP_SIZE, type=float) + #parser.add_argument('-size', '--size', help="number of pixels in lines/columns", default=DEFAULT_STEP_SIZE, type=int) + #parser.add_argument('-pos', '--number-of-positions', help="Number of positions to simulate", default=DEFAULT_NUMBER_OF_POSITIONS, type=int) + #argument position initiale ? + #argument pour le nombre de position à couvrir (taille de l'image à balayer en gros) + args_info = parser.parse_args() + + opengate_pool_run(**vars(args_info)) + #opengate_run(output, 1, number_of_particles, visu, verbose) +if __name__ == '__main__': diff --git a/opengate/tests/src/test087_spectrum.py b/opengate/tests/src/test087_spectrum.py new file mode 100755 index 000000000..4bfaf65ba --- /dev/null +++ b/opengate/tests/src/test087_spectrum.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import itk +import uproot +import hist +import opengate as gate + +from opengate.tests import utility +import os +import glob +import numpy as np + +# import matplotlib.pyplot as plt +# import matplotlib.ticker as mticker +from variables import tle_sim +from simu_lecture import simulation +from multijobs2 import ( + DEFAULT_OUTPUT, + DEFAULT_FILE_NAME, + DEFAULT_NUMBER_OF_PARTICLES, + DEFAULT_ACTOR, +) + +simu, ct, vpg_actor, source = simulation( + output=DEFAULT_OUTPUT, + File_name=DEFAULT_FILE_NAME, + job_id=0, + number_of_particles=DEFAULT_NUMBER_OF_PARTICLES, + visu=False, + verbose=False, + actor=DEFAULT_ACTOR, +) + +tle_simu = tle_sim(simu, ct, vpg_actor, source) + +# FIRST STEP : retrieve the arrays and all the needed features +# CT volume : + + +ct_object = tle_simu.ct +UH_array = itk.array_from_image(ct_object.load_input_image()) + + +path = tle_simu.path +actor_object = tle_simu.vpg_actor +feature = ["prot_e", "neutr_e"] +File_name = DEFAULT_FILE_NAME + +# database + +data_protons = tle_simu.root_file +data_neutrons = tle_simu.root_file_neutron + +gamma_neutron = {} +gamma_proton = {} + +for x in range(UH_array.shape[2]): + for y in range(UH_array.shape[1]): + for z in range(UH_array.shape[0]): + UH = UH_array[z, y, x] + if UH not in gamma_neutron: + gamma_neutron[UH] = tle_simu.gamma_mat(UH, data_neutrons, False) + if UH not in gamma_proton: + gamma_proton[UH] = tle_simu.gamma_mat(UH, data_protons, True) + + +Ep = np.linspace(0, actor_object.energyrange, actor_object.energybins + 1) + +output_name = actor_object.name +for type_name in feature: + file_path = os.path.join( + path.output, f"{File_name}_{output_name}_1_{type_name}.nii.gz" + ) + + print(file_path) + # Check if the file exists + if not os.path.exists(file_path): + print(f"File {file_path} not found in output directory.") + continue # Skip to the next iteration if the file does not exist + # Read the image + img = itk.imread(file_path) + + if img is None: + print(f"Image {output_name}_merged_{type_name} not found in output directory.") + # Convert the image to a numpy array + array = itk.array_from_image(img) + treated_array = np.zeros( + (250, array.shape[1], array.shape[2], array.shape[3]) + ) # Initialize the treated array + for x in range(array.shape[3]): + for y in range(array.shape[2]): + for z in range(array.shape[1]): + # Get the Hounsfield Unit (HU) value at the current voxel + X, Y, Z = tle_simu.redim((x, y, z), ct_object, actor_object, array) + UH = UH_array[X, Y, Z] + histo_E = array[ + :, z, y, x + ] # Get the energy histogram for the current voxel + Gamma_m = ( + gamma_proton[UH] + if type_name == "prot_e.nii.gz" + else gamma_neutron[UH] + ) + spectrum = np.zeros( + 250 + ) # Initialize the spectrum for the current voxel + for i in range(len(histo_E) - 1): + print(histo_E[i]) + En = ( + i * actor_object.energyrange / actor_object.energybins + ) # Calculate the energy for the current bin + bin_index = int( + En / (200 / 500) + ) # Determine the bin index for the current energy value + spectrum = spectrum + Gamma_m[bin_index, :] * histo_E[i] + + treated_array[:, z, y, x] = treated_array[:, z, y, x] + spectrum +# Create a new ITK image from the treated array +itk_output = itk.image_from_array(treated_array) +itk_output.CopyInformation(img) + +itk.imwrite(itk_output, path.output / f"VPG_{output_name}_Energy.nii.gz") diff --git a/opengate/tests/src/test087_variables.py b/opengate/tests/src/test087_variables.py new file mode 100755 index 000000000..17938f065 --- /dev/null +++ b/opengate/tests/src/test087_variables.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import itk +import uproot +import hist +import opengate as gate +import xraylib +import periodictable +from opengate.tests import utility +from opengate.definitions import elements_name_symbol +import os +import numpy as np +from test_088_vpg_tle_script import * + +from test_088_multijobs import ( + DEFAULT_OUTPUT, + DEFAULT_FILE_NAME, + DEFAULT_NUMBER_OF_PARTICLES, + DEFAULT_ACTOR, +) + +gcm3 = gate.g4_units.g_cm3 + + +class tle_sim: + def __init__(self, sim, ct, actor, source): + paths = utility.get_default_test_paths(__file__, output_folder=DEFAULT_OUTPUT) + self.path = paths + self.sim = sim + self.ct = ct + self.vpg_actor = actor + f1 = str(paths.data / "Schneider2000MaterialsTable.txt") + f2 = str(paths.data / "Schneider2000DensitiesTable.txt") + tol = 0.05 * gcm3 + ( + voxel_materials, + materials, + ) = gate.geometry.materials.HounsfieldUnit_to_material(sim, tol, f1, f2) + (mat_fraction, el) = gate.geometry.materials.HU_read_materials_table(f1) + mat_fraction.pop() + database_mat = gate.geometry.materials.write_material_database( + sim, materials, str(paths.data / "database.db") + ) + + self.mat_fraction = mat_fraction + self.voxel_mat = voxel_materials + self.el = el + self.database_mat = database_mat + p = os.path.abspath(str(paths.data / "database.db")) + + self.data_way = p + f3 = str(paths.data / "data_merge.root") + f4 = str(paths.data / "data_merge_neutron.root") + self.root_file = uproot.open(f3) + self.root_file_neutron = uproot.open(f4) + + def redim( + self, ind, ct, actor_vol, array + ): # to convert the index from the actor volume to the ct volume + ct_trans = list(ct.translation) # centre du ct + vol_trans = list(actor_vol.translation) + # passer du centre au côtés droit voxel + ct_size = list(array.shape) # size du ct + + ct_size = array.shape + ct_space = list(ct.spacing) + ct_len = ct_size[0] * ct_space[0] + ct_wid = ct_size[1] * ct_space[1] + ct_hig = ct_size[2] * ct_space[2] + + # côté droit du vol + x_center = ct_trans[2] - ct_len / 2 + y_center = ct_trans[1] - ct_wid / 2 + z_center = ct_trans[0] - ct_hig / 2 + # distance entre le centre du ct et le centre du vol + decal = [ + vol_trans[0] - x_center, + vol_trans[1] - y_center, + vol_trans[2] - z_center, + ] + # calculer la position du voxel dans le vol + X = ind[0] * actor_vol.spacing[0] + Y = ind[1] * actor_vol.spacing[1] + Z = ind[2] * actor_vol.spacing[2] + # calculer la position du voxel dans le ct + + x = (X + decal[0]) / ct_space[0] + y = (Y + decal[1]) / ct_space[1] + z = (Z + decal[2]) / ct_space[2] + return int(x), int(y), int(z) + + def find_emission_vector(self, el, root_data, prot): + if el == "Phosphor": + el = "Phosphorus" + el = elements_name_symbol[el] + if prot: + histo = root_data[el][ + "PGs energy as a function of protons and GAMMAZ" + ].to_hist() + else: + histo = root_data[el][ + "PGs energy as a function of neutrons and GAMMAZ" + ].to_hist() + w = histo.to_numpy()[0] + return w + + def density_mat(self, mat, data_way): + with open(data_way, "r") as f: + mat = mat + ":" + for line in f: + words = line.split() + if len(words) < 1: + continue + if words[0] == mat: + rho = words[1] + if words[2] == "mg/cm3;": + return float(rho[2:]) / 1000 # to convert the mg/cm3 to g/cm3 + return float(rho[2:]) + return "DEFAULT" + + def voxel_to_mat_name(self, UH, mat_data): + ind = 0 + for l in mat_data: + ind = ind + 1 + if ind == len(mat_data): + mat = l[2] + if UH > l[1]: + continue + else: + mat = l[2] + break + return mat # str + + def mat_to_UH(self, mat, mat_data): + mat = mat.lower() + for l in mat_data: + print(l[2]) + if l[2] == mat.capitalize(): + return l[0] + return "DEFAULT" + + def liste_el_frac(self, mat, frac_data): + if mat[-3] == "_": + mat = mat[:-3] + else: + mat = mat[:-2] + liste = [] + frac = [] + for l in frac_data: + + if l["name"] == mat: + for el in l: + if (el != "HU") and (el != "name"): + if l[el] != 0.0: + + liste.append(el) + frac.append(l[el]) + return liste, frac + + def Volume_to_array(self, vol, actor): + size = vol.size + spacing = actor.spacing + array_el = np.empty( + ( + int(size[0] / spacing[0]), + int(size[1] / spacing[0]), + int(size[2] / spacing[0]), + ), + dtype=object, + ) + array_el.fill(vol.material) # fill the array with the material of the volume + return array_el # 3D array + + def gamma_mat(self, UH, root_data, prot): + Gamma = np.zeros((500, 250)) # Initialize a 2D array for gamma emission + """Find the material and its composition corresponding to the Hounsfield Unit (UH)""" + name = self.voxel_to_mat_name(UH, self.voxel_mat) + rho_mat = self.density_mat(name, self.data_way) + (elements, fractions) = self.liste_el_frac(name, self.mat_fraction) + """ elements: list of elements in the material""" + for el in elements: + w = fractions[elements.index(el)] / 100 # Convert percentage to fraction + vect = self.find_emission_vector(el, root_data, prot) + Gamma += vect * w * rho_mat + return Gamma diff --git a/opengate/tests/src/test087_vpg_tle_script.py b/opengate/tests/src/test087_vpg_tle_script.py new file mode 100755 index 000000000..88f9226fe --- /dev/null +++ b/opengate/tests/src/test087_vpg_tle_script.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# import itk +import uproot +import hist +import opengate as gate + +# import xraylib +# import periodictable +from opengate.tests import utility +import os +import numpy as np + +# import matplotlib.pyplot as plt +# import matplotlib.ticker as mticker +import test_088_variables + + +def simulation( + output, + File_name, + job_id, + number_of_particles, + visu=False, + verbose=False, + actor="VoxelizedPromptGammaTLEActor", +): + + paths = utility.get_default_test_paths(__file__, output_folder=output) + + # create the simulation + sim = gate.Simulation() + # main options + sim.visu = visu + sim.g4_verbose = verbose + sim.random_seed = "auto" # FIXME to be replaced by a fixed number at the end + sim.random_engine = "MersenneTwister" + sim.output_dir = paths.output + sim.number_of_threads = 1 + sim.progress_bar = True + + # units + mm = gate.g4_units.mm + cm = gate.g4_units.cm + m = gate.g4_units.m + gcm3 = gate.g4_units.g_cm3 + keV = gate.g4_units.keV + MeV = gate.g4_units.MeV + Bq = gate.g4_units.Bq + ns = gate.g4_units.ns + mg = gate.g4_units.mg + cm3 = gate.g4_units.cm3 + g = gate.g4_units.g + km = gate.g4_units.km + + # change world size + world = sim.world + world.size = [3 * m, 3 * m, 3 * m] + world.material = "G4_Galactic" + + # cas test = boite à un élément + ct = sim.add_volume("Box", "ct") + ct.mother = "world" + ct.translation = [0 * cm, 15 * cm, 0 * cm] + ct.size = [4 * cm, 20 * cm, 4 * cm] + ct.material = "G4_C" + ct.color = [1, 0, 0, 1] + + """# insert voxelized CT + ct = sim.add_volume("Image", "ct") + ct.image = paths.data / f"ct_4mm.mhd" + if sim.visu: + ct.image = paths.data / f"ct_40mm.mhd" + ct.material = "G4_AIR" + f1 = str(paths.data / "Schneider2000MaterialsTable.txt") + f2 = str(paths.data / "Schneider2000DensitiesTable.txt") + tol = 0.05 * gcm3 + ( + ct.voxel_materials, + materials, + ) = gate.geometry.materials.HounsfieldUnit_to_material(sim, tol, f1, f2) + print(f"tol = {tol/gcm3} g/cm3") + print(f"mat : {len(ct.voxel_materials)} materials") + + ct.dump_label_image = paths.output / "labels.mhd" + ct.mother = "world" + """ + + # physics + sim.physics_manager.physics_list_name = "QGSP_BIC_HP_EMY" + + # sim.physics_manager.set_production_cut("world", "gamma", 1 * km) + # sim.physics_manager.set_production_cut("world", "electron", 1 * km) + # sim.physics_manager.set_production_cut("world", "proton", 1 * km) + # sim.physics_manager.set_production_cut("world", "positron", 1 * km) + + sim.physics_manager.set_production_cut("ct", "gamma", 1 * mm) + # sim.physics_manager.set_production_cut("ct", "electron", 1 * mm) + sim.physics_manager.set_production_cut("ct", "proton", 1 * mm) + # sim.physics_manager.set_production_cut("ct", "positron", 1 * mm) + + sim.physics_manager.set_max_step_size("ct", 1 * mm) + sim.physics_manager.set_user_limits_particles(["proton"]) + + # source of proton + # FIXME to replace by a more realistic proton beam, see tests 044 + source = sim.add_source("GenericSource", "proton_beam") + source.energy.mono = 150 * MeV + source.particle = "proton" + source.position.type = "disc" + source.position.radius = 0 + source.position.translation = [0, 0, 0] + source.n = number_of_particles + source.direction.type = "momentum" + source.direction.momentum = [0, 1, 0] + + # add vpgtle actor + vpg_tle = sim.add_actor(actor, "vpg_tle") + vpg_tle.attached_to = "ct" + vpg_tle.output_filename = paths.output / f"{File_name}_appic_{job_id}.nii.gz" + vpg_tle.size = [40, 400, 40] + vpg_tle.spacing = [1 * mm, 1 * mm, 1 * mm] + vpg_tle.translation = [0, 0, 0] + vpg_tle.timebins = 200 + vpg_tle.timerange = 1 * ns + vpg_tle.energybins = 250 + vpg_tle.energyrange = 150 * MeV + vpg_tle.prot_E.active = False + + vpg_tle.neutr_E.active = True + vpg_tle.prot_tof.active = False + vpg_tle.neutr_tof.active = True + + # add stat actor + stats = sim.add_actor("SimulationStatisticsActor", "stats") + stats.track_types_flag = True + stats.output_filename = paths.output / f"stat_{job_id}.txt" + + return sim, ct, vpg_tle, source + + +"""output = "TEST" +File_name = "test" +number_of_particles = 1000 +actor = "VoxelizedPromptGammaTLEActor" + +if __name__ == "__main__": + sim = simulation(output, File_name, 0, number_of_particles,False, False, actor) + # start simulation + sim.run()""" diff --git a/opengate/tests/src/test087_vpg_tle_stage1_actor.py b/opengate/tests/src/test087_vpg_tle_stage1_actor.py new file mode 100755 index 000000000..a21d9ca19 --- /dev/null +++ b/opengate/tests/src/test087_vpg_tle_stage1_actor.py @@ -0,0 +1,91 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +from vtkmodules.vtkFiltersModeling import vtkGeodesicPath + +import opengate as gate +from opengate.tests import utility + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__, output_folder="test087_vpg_tle") + + # create the simulation + sim = gate.Simulation() + + # main options + # sim.visu = True + sim.visu_type = "qt" + sim.random_seed = "auto" # FIXME to be replaced by a fixed number at the end + sim.output_dir = paths.output + sim.progress_bar = True + sim.number_of_threads = 1 + + # units + mm = gate.g4_units.mm + cm = gate.g4_units.cm + m = gate.g4_units.m + gcm3 = gate.g4_units.g_cm3 + keV = gate.g4_units.keV + MeV = gate.g4_units.MeV + Bq = gate.g4_units.Bq + + # change world size + world = sim.world + world.size = [3 * m, 3 * m, 3 * m] + + # insert voxelized CT + ct = sim.add_volume("Image", "ct") + ct.image = paths.data / f"ct_4mm.mhd" + if sim.visu: + ct.image = paths.data / f"ct_40mm.mhd" + ct.material = "G4_AIR" + f1 = str(paths.data / "Schneider2000MaterialsTable.txt") + f2 = str(paths.data / "Schneider2000DensitiesTable.txt") + tol = 0.05 * gcm3 + ( + ct.voxel_materials, + materials, + ) = gate.geometry.materials.HounsfieldUnit_to_material(sim, tol, f1, f2) + print(f"tol = {tol/gcm3} g/cm3") + print(f"mat : {len(ct.voxel_materials)} materials") + ct.dump_label_image = paths.output / "labels.mhd" + + # physics + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.global_production_cuts.all = 1 * mm + + # source of proton + # FIXME to replace by a more realistic proton beam, see tests 044 + source = sim.add_source("GenericSource", "proton_beam") + source.energy.mono = 130 * MeV + source.particle = "proton" + source.position.type = "sphere" + source.position.radius = 10 * mm + source.position.translation = [0, 0 * cm, 0] + source.activity = 10000 * Bq + source.direction.type = "momentum" + source.direction.momentum = [0, 1, 0] + + # add vpgtle actor + vpg_tle = sim.add_actor("VoxelizedPromptGammaTLEActor", "vpgtle") + vpg_tle.attached_to = ct + vpg_tle.output_filename = "vpgtle.nii.gz" + vpg_tle.size = [100, 100, 100] + vpg_tle.bins = 50 + vpg_tle.spacing = [2 * mm, 2 * mm, 2 * mm] + vpg_tle.activity = 1000 * Bq + # FIXME to put elsewhere ? change the name ? + # vpg_tle.stage_0_database = paths.data / "vpgtle_stage0.db" + + # add stat actor + stats = sim.add_actor("SimulationStatisticsActor", "stats") + stats.track_types_flag = True + + # start simulation + sim.run() + + # print results at the end + print(stats) + + # FIXME to a real test + is_ok = False + utility.test_ok(is_ok) diff --git a/opengate/tests/src/test087_vpg_tle_stage2_source.py b/opengate/tests/src/test087_vpg_tle_stage2_source.py new file mode 100755 index 000000000..e6906ffd6 --- /dev/null +++ b/opengate/tests/src/test087_vpg_tle_stage2_source.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +import opengate as gate +from opengate.tests import utility + +if __name__ == "__main__": + paths = utility.get_default_test_paths(__file__, output_folder="test087_vpg_tle") + + # create the simulation + sim = gate.Simulation() + + # main options + sim.visu = True + sim.visu_type = "qt" + sim.random_seed = "auto" # FIXME to be replaced by a fixed number at the end + sim.output_dir = paths.output + sim.progress_bar = True + sim.number_of_threads = 1 + + # units + mm = gate.g4_units.mm + cm = gate.g4_units.cm + m = gate.g4_units.m + gcm3 = gate.g4_units.g_cm3 + keV = gate.g4_units.keV + MeV = gate.g4_units.MeV + Bq = gate.g4_units.Bq + + # change world size + world = sim.world + world.size = [3 * m, 3 * m, 3 * m] + + # insert voxelized CT + ct = sim.add_volume("Image", "ct") + ct.image = paths.data / f"ct_4mm.mhd" + if sim.visu: + ct.image = paths.data / f"ct_40mm.mhd" + ct.material = "G4_AIR" + f1 = str(paths.data / "Schneider2000MaterialsTable.txt") + f2 = str(paths.data / "Schneider2000DensitiesTable.txt") + tol = 0.05 * gcm3 + ( + ct.voxel_materials, + materials, + ) = gate.geometry.materials.HounsfieldUnit_to_material(sim, tol, f1, f2) + print(f"tol = {tol/gcm3} g/cm3") + print(f"mat : {len(ct.voxel_materials)} materials") + ct.dump_label_image = paths.output / "labels.mhd" + + # physics + sim.physics_manager.physics_list_name = "G4EmStandardPhysics_option3" + sim.physics_manager.global_production_cuts.all = 1 * mm + + # add vpgtle source + vpg_tle = sim.add_source("VoxelizedPromptGammaTLESource", "vpgtle") + vpg_tle.attached_to = ct + vpg_tle.pg_image = paths.output / "vpgtle.nii.gz" + vpg_tle.activity = 100 * Bq # (total in the whole image) + + # add stat actor + stats = sim.add_actor("SimulationStatisticsActor", "stats") + stats.track_types_flag = True + + # start simulation + sim.run() + + # print results at the end + print(stats) + + # FIXME to a real test + is_ok = False + utility.test_ok(is_ok)