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 beamAn
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)
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
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)
C12 ion 3 GeV#
SetParticleByName(app.generator, "C12")
SetParticleEnergy(app.generator, 3GeV)
beamOn(app, 100000)
do_plots(sc1)