Reading GATE ROOT Files
Table of Contents
ROOT files
GATE can write most simulation results to ROOT files, which are binary files specifically designed for high energy and medical physics data analysis. When you work with GATE output, reading these ROOT files correctly is often the first step toward any detailed analysis.
A ROOT file usually has the extension .root. Inside it, GATE stores one or more data structures such as trees and histograms. For typical medical physics simulations, the most important objects are ROOT trees that contain event-by-event information about hits, singles, coincidences, phase space, and statistics.
You normally open ROOT files with the ROOT framework itself, which is written in C++, but it also provides a convenient C++ command line called root and Python bindings known as PyROOT. The basic idea is always the same: you open the file, inspect its contents, then attach to the tree that contains the data you want.
In C++ or in the ROOT command line, you open a file like this:
TFile *f = TFile::Open("my_simulation.root");
f->ls(); // list contentsThis prints the objects stored in the file, including any trees and histograms. With PyROOT in Python, the code looks similar:
import ROOT
f = ROOT.TFile.Open("my_simulation.root")
f.ls()
Most GATE simulation outputs follow naming conventions. For example, a hits actor might create a tree called something like Hits, a singles chain might create Singles, coincidences might be in Coincidences, and statistics actors often write a small tree or a set of leaves with scalar information. The exact names can be configured in your GATE script, so you should always verify what is actually stored by listing the file contents.
A ROOT file can store several trees at once. For instance, a PET simulation can have in a single file a tree of hits for each detector, a tree of singles, and a tree of coincidences. Understanding that the ROOT file is just a container is essential: you still need to identify which tree corresponds to the data product you want to analyze.
If you change your GATE configuration or add new actors, the structure of the ROOT file may also change. It is a good habit to inspect every new simulation output, list the objects contained in the file, and note which trees you will use in later analysis. For reproducibility, keep track of which version of your GATE script created which ROOT file, and avoid overwriting older results unless you are sure they are no longer needed.
Important rule: Always inspect a new GATE ROOT file with ls or an equivalent command to identify which trees and branches it contains before starting your analysis.
Trees
Inside a ROOT file, the main container for event-level data is the TTree. A tree is conceptually similar to a table with rows and columns, but it is optimized for very large datasets. Each row is called an entry, and each column is called a branch. In the context of GATE, each entry typically corresponds to one physical entity, such as a hit, a single, or a coincidence, depending on the actor that wrote the tree.
You usually retrieve a tree from a ROOT file like this:
TTree *t = (TTree*) f->Get("Hits");
t->Print(); // show structureor in PyROOT:
t = f.Get("Hits")
t.Print()
The Print function lists all branches, their names, and their data types. For example, a hits tree might have branches for deposited energy, position coordinates, time, event ID, and volume ID.
An important property of trees is that they can contain millions or even billions of entries without loading everything into memory at once. You can loop over the entries one by one or in small groups. This is crucial for large GATE simulations, where the number of hits or singles can be very large.
In ROOT C++, a typical loop over a tree looks like:
Long64_t n = t->GetEntries();
for (Long64_t i = 0; i < n; i++) {
t->GetEntry(i);
// read branch values for this entry
}
In PyROOT, the logic is the same. You can also use convenience methods, such as Draw, to quickly create histograms from branches without writing an explicit loop:
t->Draw("energy");
This creates an in-memory histogram of the energy branch, useful for quick checks of spectra, distributions, and data ranges. For example, you can immediately check that your energy window, time distribution, or position range looks reasonable.
Trees can also be chained. If you have several ROOT files, for example from simulations run with different random seeds, you can create a TChain to treat all their trees as if they were one long tree. This is particularly useful in GATE when you run large simulations in parts and then want to analyze them as a combined dataset.
Although this chapter focuses on using ROOT directly, you will later see that the same tree data can be accessed using Python libraries such as uproot, which allow you to work with GATE ROOT files using NumPy and Pandas.
Branches
Branches are the individual data fields stored in a tree. Each branch has a name and a data type, and often corresponds directly to one column in your output table. In GATE, the branches are configured by the actors or digitizers that write the data. For instance, a hits actor can be set to record deposited energy, position, time, and particle type, each of which becomes a branch in the hits tree.
You can inspect the branches in a tree with:
t->Print();ROOT will list something similar to:
*Br 0 :EventID : EventID/I
*Br 1 :Edep : Edep/D
*Br 2 :X : X/D
*Br 3 :Y : Y/D
*Br 4 :Z : Z/D
*Br 5 :Time : Time/D
Here, EventID is an integer, while Edep, X, Y, Z, and Time are double precision floating point values. The exact names differ depending on your GATE configuration, but the idea is always the same: each branch stores one piece of information for every entry.
To read branch values entry by entry in C++, you typically set addresses:
Int_t eventID;
Double_t edep, x, y, z, time;
t->SetBranchAddress("EventID", &eventID);
t->SetBranchAddress("Edep", &edep);
t->SetBranchAddress("X", &x);
t->SetBranchAddress("Y", &y);
t->SetBranchAddress("Z", &z);
t->SetBranchAddress("Time", &time);
Long64_t n = t->GetEntries();
for (Long64_t i = 0; i < n; ++i) {
t->GetEntry(i);
// now use eventID, edep, x, y, z, time
}In PyROOT, the pattern is similar, but you usually access branches through attributes of the tree object.
Branches can contain simple scalar values, arrays, or even more complex objects. For standard GATE medical physics output you will usually deal with scalar branches, for example one energy value and one set of coordinates per hit or per single. Some actors, such as phase space actors, may also store particle direction components as three separate branches like dirX, dirY, dirZ.
Understanding the branch names is crucial because later analysis often requires selecting subsets of entries based on cuts on these branches. For instance, you might want to keep only singles with energies inside a photopeak window, or coincidences where the time difference lies inside a coincidence timing window.
ROOT provides convenient methods to apply such cuts directly using branch names as variables. For example:
t->Draw("Edep", "Edep > 400 && Edep < 600");
creates a histogram of Edep only for entries that fall between 400 and 600, which is a simple way to visualize an energy window.
In GATE, the choice of which branches to write has direct implications for file size and analysis flexibility. If you record many branches for every hit, your ROOT files can become very large, but you gain more information for detailed studies. If you restrict the number of branches, files will be smaller and faster to process but some analyses may not be possible. You will need to balance these aspects according to the goals of your simulation.
Important rule: Make sure you know the meaning and units of each branch in your GATE ROOT trees. Misinterpreting a branch name or its units can lead to completely wrong physical conclusions.
Views: 11
KAHIBARO