18.4. Filling Ntuples
Table of Contents
Energy
In a Geant4 analysis, an ntuple is a table where each row usually corresponds to one logical unit of data, such as one event, one hit, or one step. Columns hold specific quantities. To store energy values you first create an ntuple and its columns in your analysis setup, then you fill those columns whenever appropriate user actions are called.
You typically obtain deposited energy in a sensitive detector from a G4Step through its G4StepPoint or, more directly, from the G4Step itself using GetTotalEnergyDeposit(). This returns the energy deposited on that step in internal Geant4 units. When you want to store per event quantities, you usually accumulate step energies in your EventAction, then write a single row at the end of the event.
A common pattern is the following. In your initialization code, you define an ntuple with columns for event-level energies, for example total deposited energy or energy in each detector element. Later, in your event or stepping actions, you call G4AnalysisManager::FillNtupleDColumn (or the type that matches your column) to place values into the correct columns, then you call AddNtupleRow() exactly once per row.
The rule is: fill all desired columns for a given row, then call AddNtupleRow() once to commit that row to the file. Forgetting AddNtupleRow() means your filled values are not written to the output.
Another important detail is consistency of units. Inside the simulation you should use Geant4 units, for example energyDeposit / MeV to convert to MeV before writing to the ntuple. This gives you human readable energy values in the output file and avoids confusion when you analyze the data later.
You can choose between storing step level energy information or event level summaries. Step level information can create very large ntuples but allows fine grained analysis. Event level summaries create much smaller files and are usually sufficient for typical spectra or dose distributions. Decide this before you design your ntuple, because the structure of the ntuple should match the level of detail you want to analyze.
Position
Position information is usually obtained from G4Step or G4Track. The G4Step provides pre step and post step points, and from those you can access a G4ThreeVector that holds the coordinates. You can choose to store global coordinates, which are defined in the world reference frame, or local coordinates, which are defined relative to a particular volume. The choice depends on how you want to analyze your detector response.
To write positions into an ntuple, you typically store the three components separately as different columns, for example x, y, and z. When you define your ntuple you create three double columns. Later, when a hit occurs or a step deposits energy, you extract the position vector, convert each component to suitable units, for example millimeters, then fill each column before adding the row.
When filling position columns, always convert from internal units to a consistent external unit, such as millimeters, for example pos.x() / mm, pos.y() / mm, pos.z() / mm. Mixing units between events or columns makes downstream analysis very error prone.
If you are recording positions for many hits within one event, you can give each hit its own ntuple row, with columns for event ID, detector ID, and position. This allows you to reconstruct spatial patterns during data analysis, for example hit maps or 2D projections. Alternatively, you can compute aggregated quantities, such as center of gravity of energy deposition per event, and store only these derived quantities in your ntuple.
When working with complex detectors, local coordinates can be more convenient. You can transform global coordinates into local coordinates using the touchable history from the step. Whichever system you choose, keep it consistent across your ntuple so that later analysis does not need to guess which frame each coordinate uses.
Time
Timing in Geant4 is handled through the global time of each track. You can get the global time at a given point using GetGlobalTime() from a G4StepPoint or G4Track. This time is measured from the start of the event and is typically in internal time units, which you can convert to nanoseconds or another convenient unit before storage.
To fill time into an ntuple, you again define a dedicated column, for example t, and then, whenever a relevant interaction happens, you retrieve the global time, convert it, and fill the column. For time of flight studies, you might store both the time of creation and the time of detection, and later compute the difference during analysis. Alternatively, you can compute the difference inside Geant4 and directly store the resulting time of flight value into the ntuple.
Use a single time reference within your ntuple, for example always global time since the beginning of the event, and always store in the same physical unit, such as nanoseconds. Mixing different time definitions or units in one ntuple can easily lead to incorrect timing analysis.
In detectors where precise timing is critical, like PET or time of flight systems, you may want to record multiple time related columns, for example raw detection time, smeared detection time that includes realistic resolution effects, and coincidence window information. Each of these quantities becomes its own ntuple column. As always, you must fill the appropriate columns before calling AddNtupleRow().
You can also choose different time granularity. For example, storing the time of every hit yields a detailed time structure at the cost of larger files. Storing only per event times, such as the earliest hit time per detector element, greatly reduces output size while still supporting many timing studies.
Particle information
Particle related information typically comes from G4Track or from the G4ParticleDefinition associated with a track. Common quantities you may want to store include particle name or PDG code, initial or current kinetic energy, charge, and track or parent IDs. Recording some of these values in your ntuple allows later selection and filtering, for example separating electrons from gammas during offline analysis.
To add particle information to an ntuple, you first decide which type of column fits each piece of data. Numerical quantities, such as kinetic energy, PDG code, track ID, and parent ID, are usually stored as integer or double columns. Descriptive quantities, such as particle name, are stored as string columns if your chosen analysis backend supports strings. When filling a row, you access the relevant information from the current track, for example track->GetDefinition()->GetParticleName() or track->GetDefinition()->GetPDGEncoding(), and place those values into the predetermined columns.
Select a minimal but sufficient set of particle properties to store. Recording every possible property for every step can create huge ntuples and slow down your simulation. Choose only what you actually need for downstream analysis.
Particle identity and genealogy are especially important. Including both trackID and parentID columns lets you reconstruct decay chains or secondary production trees. For example, you can distinguish primary particles from secondaries by checking whether parentID is zero. You can also separate hits created by primaries from those created by secondaries during ROOT analysis.
Kinetic energy is often useful to record together with other particle data. At the moment of a hit or at the beginning of a track, you can query the kinetic energy and store it in a column, again converting it to a convenient unit such as MeV. Combined with deposited energy columns, this allows you to study quantities like detection efficiency or energy loss distributions for different particle types.
By carefully choosing and filling particle information columns in your ntuple, you create a flexible dataset that can answer many physics questions without having to rerun the simulation every time you change the analysis approach.
Views: 8
KAHIBARO