7.6. Reusable Geometry Functions
Table of Contents
Creating detector functions
In GATE, any nontrivial detector quickly becomes a repetition of the same basic building blocks. Writing the same geometry code again and again is error prone and makes your simulations difficult to maintain. Instead, you can encapsulate geometry creation into Python functions that you call whenever you need that detector.
You already know how to create volumes and organize parent child relationships from earlier chapters. Here, the key difference is that you will put those steps inside Python functions with well defined inputs and outputs. The goal is not to invent new geometry capabilities, but to package existing ones into reusable building blocks.
A typical detector construction function takes at least three kinds of arguments. First, a reference to the simulation object, usually called sim. Second, some description of where the detector should be attached in the geometry, typically a parent volume name. Third, a set of parameters that control the size, material, and layout of the detector. The function then creates all necessary volumes and returns useful identifiers, for example the name of the top level volume it created.
A very simple pattern looks like this in pseudocode.
Reusable detector function pattern:
- Define a function, for example
def build_detector(sim, parent, params): - Inside the function, create one or more volumes attached to
parent. - Use
paramsto set dimensions, materials, and positions. - Optionally return names or IDs of created volumes so they can be used later.
As a concrete example, imagine a rectangular scintillation detector made of a single crystal, wrapped in a reflector, and attached to a support volume. You could write a function that takes the parent volume name and a dictionary of dimensions and materials, and then creates these three pieces with consistent names, for instance det_crystal, det_wrap, and det_support. Whenever you need another detector with the same structure, you call the function again, possibly with different dimensions.
It is important to keep naming consistent and predictable inside such functions. A common strategy is to give the caller control over a base name, for example detector_name, and then derive all subvolume names from it by appending suffixes like _crystal or _shield. This way, if you call the function multiple times with different base names, you avoid name collisions while still being able to infer relationships among volumes by reading their names.
Reusable detector functions also provide a natural place to keep related geometry logic together. For example, if your detector needs to include small air gaps, or a specific offset between the crystal and the photodetector, you encode those offsets once inside the function instead of repeating magic numbers in many scripts. When you later need to correct a thickness or a distance, you change it in only one place.
Once you organize your geometry creation as functions, you can build larger systems by composition. A scanner ring function can call a module function, which in turn calls a crystal function. Each level focuses only on arranging its immediate children. This hierarchy mirrors the physical structure of many medical imaging devices and makes your geometry easier to understand.
Parameterized geometry
Simple reusable functions become truly powerful when you design them to accept parameters that fully control the geometry they create. A parameterized geometry function does not hard code the dimensions, positions, or counts of volumes inside it. Instead, all relevant quantities are provided as input, often collected into a Python dictionary or a small class.
The main idea is that one function can represent an entire family of geometries. For example, a single function can build detector modules with different crystal sizes and different numbers of crystals along each axis, simply by adjusting parameters like crystal_size, n_x, and n_y. You do not write separate functions for a 4 by 4 array and an 8 by 8 array. You write one parameterized function and call it with different values.
A simple way to structure such parameters is shown in the following table.
| Category | Example parameters |
|---|---|
| Dimensions | crystal_size, module_size, gap |
| Counts | n_crystals_x, n_crystals_y, n_rings |
| Materials | crystal_material, housing_material |
| Placement | radius, axial_offset, rotation_angle |
Inside the function, you use these inputs to compute positions and sizes with basic formulas. For a linear array of crystals along the x axis, for instance, you might compute the center position of the $i$-th crystal as
$$
x_i = \left( i + 0.5 \right) \cdot p_x - \frac{N \cdot p_x}{2},
$$
where $N$ is the number of crystals and $p_x$ is the pitch in x, equal to the crystal width plus any gap. You can reuse this same expression with different $N$ and $p_x$ for different detectors.
Key principle for parameterized geometry:
Design geometry functions so that:
- All important dimensions and counts are parameters.
- Positions are computed from simple formulas using those parameters.
- No critical dimensions are hard coded inside the function.
Parameterized geometry also helps when you want to explore design variations. Suppose you have a PET detector module function that accepts parameters for crystal length and ring radius. To compare different designs, you can loop over a set of parameter values, build a geometry for each, and simulate them one by one. You do not need to modify the geometry code itself between runs. This approach is useful for optimization and sensitivity studies.
Consistent units are essential in parameterized geometry. Since earlier chapters cover units in detail, here you only need to remember to always express your inputs in the same unit system you use when building volumes. If you pass dimensions in millimeters, ensure that any derived values, such as pitches or radii, are also computed in millimeters before you use them to create GATE volumes.
When writing parameterized functions, keep the interface as simple as possible. Group related settings together, and avoid passing very long lists of positional arguments. Named arguments or a configuration dictionary make it easier to see which parameters are set in each call, and they reduce the risk of subtle geometry errors from swapped arguments.
Finally, parameterized geometry functions form the basis for more advanced patterns, such as using configuration files or command line options to select scanner sizes or phantom shapes without touching the code. Later chapters build on this idea for project organization, but the practical starting point is here. By turning your geometry into functions that accept clear parameters, you gain flexibility, reduce duplication, and make complex GATE simulations much easier to control.
Views: 13
KAHIBARO