Water Phantom Simulation with Scoring
Example originated from the example in Geant4Py. It is using a very simple phantom model (a box of water) and exercises the scoring interface.
using Geant4
using Geant4.SystemOfUnits
using Plots
Detector description
The main parameters are stored in the RE03Detector
struct with some default values.
mutable struct RE03Detector <: G4JLDetector
const worldXY::Float64
const worldZ::Float64
const phantomXY::Float64
const phantomZ::Float64
RE03Detector(;worldXY=100cm, worldZ=300cm, phantomXY=30.5cm, phantomZ=30cm) = new(worldXY, worldZ, phantomXY, phantomZ)
end
function construct(det::RE03Detector)::CxxPtr{G4VPhysicalVolume}
#---Materials----------------------------------------------------------------------------------
nist = G4NistManager!Instance()
fAir = FindOrBuildMaterial(nist, "G4_AIR")
fWater = FindOrBuildMaterial(nist, "G4_WATER")
#---World--------------------------------------------------------------------------------------
worldSolid = G4Box("World",det.worldXY/2, det.worldXY/2, det.worldZ/2)
worldLogical = G4LogicalVolume(worldSolid,fAir,"World")
worldPhys = G4PVPlacement(nothing, G4ThreeVector(), worldLogical, "World", nothing,false,0)
#---Phantom------------------------------------------------------------------------------------
phantomSolid = G4Box("Phantom", det.phantomXY/2, det.phantomXY/2, det.phantomZ/2)
phantomLogical = G4LogicalVolume(phantomSolid, fWater, "Phantom");
phantomPhys = G4PVPlacement(nothing, G4ThreeVector(), phantomLogical, "Phantom", worldLogical, false, 0)
#---Visualization attributes-------------------------------------------------------------------
SetVisAttributes(worldLogical, G4VisAttributes!GetInvisible())
simpleBoxVisAtt = G4VisAttributes(G4Colour(1.0, 1.0, 1.0))
SetVisibility(simpleBoxVisAtt, true)
SetVisAttributes(phantomLogical, simpleBoxVisAtt)
return worldPhys
end
Geant4.getConstructor(::RE03Detector)::Function = construct
Define the primary particle generator
We define the MedicalBeam
particle generator. This is similar to the particle gun but with direction of the particles randomly distributed within some aperture cone. It consists of
MedicalBeamData
data structure with the parameters of the beam- An
init
function that will be called at initialization time. - An
generate
function hat is called at each event and create the primary particle and primary vertex. - A set of setter functions (
SetParticleByName
,SetParticleEnergy
) to change parameters at run time
The function to construct the generator is MedicalBeam(...)
.
mutable struct MedicalBeamData <: G4JLGeneratorData
particleName::String
particlePtr::CxxPtr{G4ParticleDefinition}
energy::Float64
ssd::Float64
fieldXY::Float64
surfaceZ::Float64
end
function generateBeamDir(ssd::Float64, fxy::Float64)
dr = √2/2*fxy
R = √(ssd^2 + dr^2)
cos0 = ssd/R
xymax = fxy/ssd/2
dx = dy = dz = 0.
while true
dz = rand()*(1-cos0)+ cos0
dsin = √(1-dz^2)
dphi = rand()*2π
dx = dsin * cos(dphi)
dy = dsin * sin(dphi)
if abs(dx) < xymax*dz && abs(dy) < xymax*dz
break
end
end
G4ThreeVector(dx, dy, dz)
end
function MedicalBeam(;particle="e-", energy=10MeV, ssd=100cm, fieldXY=10cm)
data = MedicalBeamData(particle, CxxPtr{G4ParticleDefinition}(C_NULL), energy, ssd, fieldXY, 0.)
function init(data::MedicalBeamData, app::G4JLApplication)
data.particlePtr = FindParticle(data.particleName)
data.surfaceZ = -app.detector.phantomZ/2
end
function generate( evt::G4Event, data::MedicalBeamData)::Nothing
mass = data.particlePtr |> GetPDGMass
momemtum = √((mass + data.energy)^2 - mass^2)
pvec = momemtum * generateBeamDir(data.ssd, data.fieldXY);
primary = G4PrimaryParticle(data.particlePtr, pvec |> x, pvec |> y, pvec |> z )
vertex = G4PrimaryVertex(G4ThreeVector(0, 0, data.surfaceZ - data.ssd), 0ns)
SetPrimary(vertex, move!(primary)) # note that we give up ownership of the objects just created
AddPrimaryVertex(evt, move!(vertex)) # note that we give up ownership of the objects just created
end
G4JLPrimaryGenerator("MedicalBeam", data; init_method=init, generate_method=generate)
end
function SetParticleByName(gen::G4JLPrimaryGenerator{MedicalBeamData}, particle::String)
gen.data.particleName = particle
gen.data.particlePtr = FindParticle(particle)
return
end
function SetParticleEnergy(gen::G4JLPrimaryGenerator{MedicalBeamData}, energy::Float64)
gen.data.energy = energy
return
end
SetParticleEnergy (generic function with 1 method)
Setup the scoring with the the scoring interface
Create a box shaped mesh and define the number of bins. The quantity to be monitor is dose
. Later accessing the attribute dose
on the scoring mesh will return a tuple with the sum of dose, sum square and number of entries for each mesh cell.
sc1 = G4JLScoringMesh("boxMesh_1",
BoxMesh(15.25cm, 15.25cm, 15cm),
bins = (61, 61, 150),
quantities = [ doseDeposit("dose") ]
);
Instantiate the Geant4 Application
app = G4JLApplication(;detector = RE03Detector(), # detector with parameters
generator = MedicalBeam(), # promary partcile generator
nthreads = 8, # define the number of threads
physics_type = FTFP_BERT, # what physics list to instantiate
scorers = [sc1] # list of scorers
);
**************************************************************
Geant4 version Name: geant4-11-02-patch-01 [MT] (16-February-2024)
<< in Multi-threaded mode >>
Copyright : Geant4 Collaboration
References : NIM A 506 (2003), 250-303
: IEEE-TNS 53 (2006), 270-278
: NIM A 835 (2016), 186-225
WWW : http://geant4.org/
**************************************************************
Configure, initiliaze and run
configure(app)
initialize(app)
beamOn(app, 0)
--- G4CoupledTransportation is used
.... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4ScoringManager has 1 scoring meshes.
G4ScoringBox : boxMesh_1 --- Shape: Box mesh
Size (x, y, z): (15.25, 15.25, 15) [cm]
# of segments: (61, 61, 150)
displacement: (0, 0, 0) [cm]
registered primitve scorers :
0 dose
G4WT3 > .... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4WT5 > .... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4WT3 > G4ScoringManager has 1 scoring meshes.
G4WT3 > G4ScoringBox : boxMesh_1 --- Shape: Box mesh
G4WT6 > .... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4WT3 > Size (x, y, z): (15.25, 15.25, 15) [cm]
G4WT3 > # of segments: (61, 61, 150)
G4WT3 > displacement: (0, 0, 0) [cm]
G4WT3 > registered primitve scorers :
G4WT3 > 0 dose
G4WT5 > G4ScoringManager has 1 scoring meshes.
G4WT5 > G4ScoringBox : boxMesh_1 --- Shape: Box mesh
G4WT5 > Size (x, y, z): (15.25, 15.25, 15) [cm]
G4WT5 > # of segments: (61, 61, 150)
G4WT5 > displacement: (0, 0, 0) [cm]
G4WT1 > .... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4WT6 > G4ScoringManager has 1 scoring meshes.
G4WT5 > registered primitve scorers :
G4WT5 > 0 dose
G4WT6 > G4ScoringBox : boxMesh_1 --- Shape: Box mesh
G4WT6 > Size (x, y, z): (15.25, 15.25, 15) [cm]
G4WT6 > # of segments: (61, 61, 150)
G4WT6 > displacement: (0, 0, 0) [cm]
G4WT6 > registered primitve scorers :
G4WT6 > 0 dose
G4WT7 > .... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4WT7 > G4ScoringManager has 1 scoring meshes.
G4WT7 > G4ScoringBox : boxMesh_1 --- Shape: Box mesh
G4WT7 > Size (x, y, z): (15.25, 15.25, 15) [cm]
G4WT7 > # of segments: (61, 61, 150)
G4WT7 > displacement: (0, 0, 0) [cm]
G4WT7 > registered primitve scorers :
G4WT7 > 0 dose
G4WT4 > .... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4WT0 > .... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4WT4 > G4ScoringManager has 1 scoring meshes.
G4WT4 > G4ScoringBox : boxMesh_1 --- Shape: Box mesh
G4WT4 > Size (x, y, z): (15.25, 15.25, 15) [cm]
G4WT4 > # of segments: (61, 61, 150)
G4WT4 > displacement: (0, 0, 0) [cm]
G4WT4 > registered primitve scorers :
G4WT4 > 0 dose
G4WT0 > G4ScoringManager has 1 scoring meshes.
G4WT0 > G4ScoringBox : boxMesh_1 --- Shape: Box mesh
G4WT0 > Size (x, y, z): (15.25, 15.25, 15) [cm]
G4WT0 > # of segments: (61, 61, 150)
G4WT0 > displacement: (0, 0, 0) [cm]
G4WT0 > registered primitve scorers :
G4WT0 > 0 dose
G4WT2 > .... G4ScoringMessenger::MeshBinCommand - G4ScoringBox
G4WT2 > G4ScoringManager has 1 scoring meshes.
G4WT2 > G4ScoringBox : boxMesh_1 --- Shape: Box mesh
G4WT2 > Size (x, y, z): (15.25, 15.25, 15) [cm]
G4WT2 > # of segments: (61, 61, 150)
G4WT2 > displacement: (0, 0, 0) [cm]
G4WT2 > registered primitve scorers :
G4WT2 > 0 dose
G4WT1 > G4ScoringManager has 1 scoring meshes.
G4WT1 > G4ScoringBox : boxMesh_1 --- Shape: Box mesh
G4WT1 > Size (x, y, z): (15.25, 15.25, 15) [cm]
G4WT1 > # of segments: (61, 61, 150)
G4WT1 > displacement: (0, 0, 0) [cm]
G4WT1 > registered primitve scorers :
G4WT1 > 0 dose
Visualize the Detector Setup
Define plotting functions
function do_plots(sc)
dose, dose2, nentry = sc.dose
xaxisvalues = range(0., sc.mesh.dx*2/cm, sc.bins[1])
zaxisvalues = range(0., sc.mesh.dz*2/cm, sc.bins[3])
cbin = round(Int, (sc.bins[1]+1)/2)
fig = plot( layout=(2,1), size=(800,800),
heatmap(zaxisvalues, xaxisvalues, dose[cbin,:,:], title="Dose (XZ)", color=:thermal, xlabel="Z (cm)", ylabel="X (cm)"),
plot(zaxisvalues, dose[cbin,cbin,:], title="Depth Dose (center)", xlabel="Z (cm)", label="dose")
)
display("image/png", fig)
end
do_plots (generic function with 1 method)
Electron 20 Mev
SetParticleByName(app.generator, "e-")
SetParticleEnergy(app.generator, 20MeV)
beamOn(app, 100000)
do_plots(sc1)
Proton 200 MeV
SetParticleByName(app.generator, "proton")
SetParticleEnergy(app.generator, 200MeV)
beamOn(app, 100000)
do_plots(sc1)
C<sub>12</sub> ion 3 GeV
SetParticleByName(app.generator, "C12")
SetParticleEnergy(app.generator, 3GeV)
beamOn(app, 100000)
do_plots(sc1)