KAHIBARO
Discord Login Register

26.2. Creating the Water Phantom

Concept of a Water Phantom

In this example the water phantom represents a simple, uniform volume of liquid water that mimics a patient or tissue-equivalent medium. The goal is not anatomical realism but a clean and controllable geometry that allows you to study how protons lose energy in water as a function of depth.

In practice you will create the phantom as a single Geant4 volume with material set to water, large enough so that the entire proton Bragg peak is contained inside it. All later scoring of energy deposition and depth dose will happen inside this phantom.

Choosing Phantom Size and Placement

Before you write any code, decide on the basic dimensions and where to place the phantom in the world.

For a monoenergetic proton beam, the range in water depends on the energy. For example, a 150 MeV proton has a range of about 15 cm in water. To be safe, the phantom should extend beyond the expected range so that nearly all protons stop in the water.

A typical beginner choice is:

You can position the phantom so that its entrance face is near the world origin and the beam starts just in front of it. A simple option is to center the phantom at $z = 0$ and let the beam start at some negative $z$ position. Another common approach is to put the entrance face at $z = 0$ and extend the phantom in the positive $z$ direction.

Whatever you choose, keep the coordinate system simple, because you will later interpret depth in water directly from the $z$ coordinate.

Choose phantom dimensions so that the proton range is fully contained in water with extra margin, for example at least 2 times the expected range along the beam direction.

Implementing the Phantom in DetectorConstruction

The water phantom is defined in your DetectorConstruction class, typically derived from G4VUserDetectorConstruction. Inside the Construct() method you will first build the world volume, then place the water phantom inside it.

In code, the phantom is represented by a solid, a logical volume, and a physical placement.

A minimal sequence is:

  1. Create the world box and its logical and physical volumes.
  2. Obtain the water material.
  3. Create a box solid for the phantom with half-lengths in $x$, $y$, and $z$.
  4. Create a G4LogicalVolume for the phantom using the water material.
  5. Place the phantom logical volume inside the world using G4PVPlacement.

You do not need to connect any sensitive detectors yet. The phantom at this stage is only a passive volume that will host the future scoring of energy deposition.

Every volume must follow the pattern: solid → logical volume → physical placement. Forgetting any of these steps will prevent the phantom from appearing in the geometry.

Using Water from the NIST Database

For a realistic water material you should use the NIST material database provided by Geant4 rather than defining water manually. This guarantees that the density and composition are consistent with physics models and with other examples.

Inside DetectorConstruction::Construct(), after including the appropriate headers, you can obtain water with:

cpp
auto nist = G4NistManager::Instance();
G4Material* water = nist->FindOrBuildMaterial("G4_WATER");

The string "G4_WATER" is a predefined material name in the NIST database. You do not need to specify elements or density by hand.

If you want to double-check, you can print material properties during initialization using methods from G4Material, but this belongs to general material handling and not specifically to the phantom.

Always prefer G4NistManager materials like "G4_WATER" for standard materials to ensure correct density and composition.

World and Phantom Geometry in Code

A concrete implementation of the world and phantom might look like this, focusing only on the essential geometry:

cpp
G4VPhysicalVolume* DetectorConstruction::Construct()
{
  // Get NIST manager
  auto nist = G4NistManager::Instance();
  // World parameters
  G4double worldSizeXY = 1.0*m;
  G4double worldSizeZ  = 1.0*m;
  G4Material* worldMat = nist->FindOrBuildMaterial("G4_AIR");
  // World solid / logical / physical
  auto solidWorld =
    new G4Box("World",                         // name
              0.5*worldSizeXY,                 // half x
              0.5*worldSizeXY,                 // half y
              0.5*worldSizeZ);                 // half z
  auto logicWorld =
    new G4LogicalVolume(solidWorld,            // solid
                        worldMat,              // material
                        "World");              // name
  auto physWorld =
    new G4PVPlacement(nullptr,                 // no rotation
                      G4ThreeVector(),         // at (0,0,0)
                      logicWorld,              // logical volume
                      "World",                 // name
                      nullptr,                 // no mother volume
                      false,                   // no boolean operation
                      0,                       // copy number
                      true);                   // check overlaps
  // Phantom dimensions
  G4double phantomSizeXY = 20.0*cm;
  G4double phantomSizeZ  = 30.0*cm;
  // Phantom material
  G4Material* water = nist->FindOrBuildMaterial("G4_WATER");
  // Phantom solid / logical
  auto solidPhantom =
    new G4Box("WaterPhantom",
              0.5*phantomSizeXY,
              0.5*phantomSizeXY,
              0.5*phantomSizeZ);
  auto logicPhantom =
    new G4LogicalVolume(solidPhantom,
                        water,
                        "WaterPhantom");
  // Place phantom at center of world
  new G4PVPlacement(nullptr,
                    G4ThreeVector(0., 0., 0.),
                    logicPhantom,
                    "WaterPhantom",
                    logicWorld,
                    false,
                    0,
                    true);
  return physWorld;
}

In this example the phantom is centered at the origin. With this choice, depth in water runs from negative to positive $z$, and you can map the Bragg peak location by converting $z$ to an absolute depth using $z + 0.5 \times \text{phantomSizeZ}$ if needed.

If you prefer depth to match directly with positive $z$ from the entrance face, you could shift the phantom so that its entrance face is at $z = 0$:

cpp
G4double zPos = 0.5*phantomSizeZ; // center at z = +half length
new G4PVPlacement(nullptr,
                  G4ThreeVector(0., 0., zPos),
                  logicPhantom,
                  "WaterPhantom",
                  logicWorld,
                  false,
                  0,
                  true);

Now the entrance face is at $z = 0$, and depth in water is simply the $z$ coordinate inside the phantom.

Always ensure that the phantom fits entirely inside the world volume. World half-lengths must be larger than the phantom half-lengths in every dimension.

Checking the Phantom with Visualization

Once the phantom is implemented, you should verify it visually. After you have configured a visualization driver, you can run a few basic commands in a macro to display the geometry:

Use for example:

/vis/open OGL
/vis/drawVolume
/vis/viewer/setStyle wireframe

You should see a large world volume and inside it a smaller rectangular volume which is the water phantom. Later, when you fire the proton beam, tracks should enter this phantom and stop inside it.

If the phantom does not appear, common issues include:

Checking the geometry now will save time when you start scoring energy deposition in later steps of the example.

Views: 10

Comments

Please login to add a comment.

Don't have an account? Register now!