KAHIBARO
Discord Login Register

Avoiding Hard-Coded Values

Scanner parameters

When you build GATE simulations it is tempting to write numbers directly into the code. For instance, you might write the ring radius, number of crystals, and crystal size directly in geometry creation calls. This works at first, but it quickly becomes difficult to maintain. If you want to build two slightly different scanners, or sweep over detector sizes, you end up searching through the script for every place where those numbers appear. This is what we call hard coded values, and it is exactly what you want to avoid in a well structured project.

Instead, collect all scanner related quantities into a small, explicit set of variables or configuration objects that you define once and then reuse everywhere. For a PET scanner example, you might define parameters such as ring radius, number of rings, axial field of view, crystal size, and material at the top of the script or in a separate configuration file. Every geometry function then reads from these parameters. If you later decide to change the ring diameter, you modify a single value and the entire geometry adapts automatically.

This approach makes it much easier to compare different scanner designs. You can create one configuration dictionary for a “small animal PET” and another for a “whole body PET” and pass the chosen configuration into your geometry building functions. The simulation logic stays identical, only the parameter set changes. You also reduce the risk of inconsistent setups, such as using one crystal size in the geometry definition and another when decoding detector IDs in the digitizer. If both the geometry and the digitizer read from the same scanner parameter object, they always stay in sync.

Whenever a number appears in more than one place, turn it into a named parameter. Good candidates include world size margins around the scanner, detector ring inner and outer radii, block and module counts, collimator hole sizes, and thicknesses of shielding. Even simple booleans such as “use_tof” or “use_scatter_window” can act as configuration switches. They help you keep all scanner options together and make it obvious which aspects of the system can be changed.

To keep the code readable, choose descriptive names instead of anonymous abbreviations. A line that uses pet_config["crystal_size_mm"] is much easier to understand than one that uses a raw number like 3.2. When you work in a team, this is critical for clarity. It also simplifies documentation, since the configuration block can be copied directly into a README file or a parameter table in a paper.

You can store scanner parameters in plain Python structures, such as dictionaries or simple classes, or in external configuration files. Text formats like JSON, YAML, or TOML are common choices. With an external file, you can run the same simulation code with different scanner designs simply by pointing to a different configuration file, without touching the Python logic. This fits very well with reproducible research, where you want to preserve exactly which scanner definition was used in each run.

Finally, avoid using “magic numbers” in formulas that relate to scanner geometry or performance. If you have a factor for converting from crystal indices to physical positions, define it as a named parameter. If you derive a quantity from base parameters, such as ring circumference or total number of channels, compute it explicitly from the stored scanner parameters, rather than retyping the underlying numbers. This prevents subtle errors when you change the base configuration.

Always define scanner geometry and detector options through a small set of named parameters and reuse them everywhere in the code. Never repeat the same numeric value in multiple places.

Source parameters

The same principle applies to radiation sources. In a realistic study you will rarely run a single fixed source configuration. You might explore several radionuclides, different activities, time windows, or spatial distributions. If you hard code activity, energy, or position values directly into the source creation calls, each change requires manual editing of the script and increases the chance of mistakes.

Instead, group all relevant source parameters into a dedicated structure. For imaging this might include radionuclide name, central energy or spectrum definition, total activity, activity distribution (point, line, volume, or image based), time interval, and source position and orientation relative to the scanner. For therapy you might collect beam energy, spot size, divergence, field size, and delivered monitor units or total protons. Your source building function then reads only from this parameter set.

When you keep source parameters together, you can quickly define multiple source scenarios. For example, you may create one configuration for a point F 18 source at the center of the field of view, another for a uniform phantom distribution, and another for a patient specific image based source. Switching between them can be as simple as choosing a different configuration object or pointing to a different configuration file, while the Python implementation of the source remains unchanged.

This is particularly important for simulations that involve time dependence or decay. Activity, start time, and acquisition duration should all be parameters, not numbers scattered throughout the code. When you rerun a study with a different acquisition length, you change one parameter and ensure that all related parts of the simulation, such as actors and digitizers that depend on time, remain coherent.

You also avoid inconsistencies between the source and the analysis. For instance, if your analysis script expects a certain photon energy range based on the radionuclide, but you later change the radionuclide in the simulation without updating the analysis, the results will not match your assumptions. If both simulation and analysis read the radionuclide and energy information from the same configuration, such mismatches are much less likely.

As with scanner parameters, external configuration files are useful for source definitions. They allow you to archive complete acquisition descriptions, including isotope, activity, and timing, alongside the simulation output. This is essential when you revisit a project or share it with collaborators, since they can see exactly what was simulated without reading through the full Python code.

When you find yourself typing literal numbers for energy, activity, or positions, consider whether they describe source properties. If they do, they belong in the source parameter block. Treat these values as part of the experimental protocol, not implementation details. If you later decide to scan an activity range, such as 1 to 10 MBq, you can generate a series of configurations programmatically and loop over them, instead of editing the script by hand each time.

Centralize all source related quantities such as radionuclide, energy, activity, spatial distribution, and timing into explicit parameters. Do not embed these values directly in the source creation code.

Views: 13

Comments

Please login to add a comment.

Don't have an account? Register now!