KAHIBARO
Discord Login Register

12.6. Directories Inside ROOT Files

TDirectory

When you start to store more than a few objects in a single ROOT file, it becomes useful to group them logically. ROOT provides directories inside ROOT files for this purpose, implemented by the class TDirectory. A ROOT file itself, TFile, is a subclass of TDirectory, so you can think of the file as the top level directory, with subdirectories inside it.

A TDirectory behaves similarly to a folder inside a file system. It can contain histograms, graphs, trees, canvases, and also other directories. This lets you build a hierarchy that mirrors the structure of your analysis, detector subsystems, or data-taking periods.

You normally do not construct a TDirectory directly. Instead, you create subdirectories from an open TFile or from another TDirectory. The most common way is to call mkdir on the file or directory object. For example, inside a ROOT macro or interactive session, after opening or creating a file with

cpp
TFile *f = new TFile("analysis.root", "RECREATE");

you can make subdirectories like this:

cpp
TDirectory *dirEvents = f->mkdir("events");
TDirectory *dirPlots  = f->mkdir("plots");

Each directory has a name and a title, just like other ROOT objects. The name must be unique at a given directory level. The title is a more descriptive string and is optional.

To place an object inside a directory, you usually follow two steps. First, you change the current directory in memory by calling cd() on the target directory. Then you create or write the object. ROOT uses the current directory to decide where to keep the object:

cpp
dirPlots->cd();
TH1F *hEnergy = new TH1F("hEnergy", "Energy spectrum;E [MeV];Counts", 100, 0, 1000);
hEnergy->Fill(100);
hEnergy->Write();  // written into "plots" directory inside the file

If you call cd() on the file instead, new objects will go into the top level of the file. You can always check the current directory with gDirectory, a global pointer that ROOT keeps updated. For example,

cpp
gDirectory->pwd();

prints the path of the current directory. This is helpful when you are not sure which directory is active.

The directory hierarchy inside a file uses a path notation similar to a file system, with / as separator. For example, if you have a file analysis.root that contains a directory plots which itself contains a directory muons, a histogram hPt inside that last directory can have the full path

cpp
"analysis.root:/plots/muons/hPt"

You can retrieve a directory from an open file with Get or GetDirectory. For example,

cpp
TDirectory *dir = f->GetDirectory("plots/muons");

Once you have the directory pointer, you can Get objects from it, or call cd() to make it current. The method ls() on a directory prints its contents. Combined with the ROOT browser, which displays directories as a tree on the left side, this makes it easy to explore the structure of a ROOT file.

Inside a ROOT file, every object must have a unique name within its own directory. If you write an object with a name that already exists in that directory, the old object is usually overwritten. Always check that you are writing to the correct directory by using cd() and gDirectory->pwd().

You can create nested directories by calling mkdir on an existing subdirectory, just like you did on the file. This allows deeply nested structures when needed, although for simple analyses you will typically use only one or two levels.

When you close a file with f->Close(), the directory hierarchy and all stored objects are written to disk. The structure will be preserved when you open the file again later. Directories, once written, are part of the file. You do not normally delete or rename directories often, and if you need to reorganize a file, you usually do so by creating a new file and copying objects into a new directory layout.

Organizing objects

Directories are primarily useful as an organizational tool. With a clear directory structure, you can keep different parts of your analysis separated, avoid name clashes, and make files easier to navigate for you and other users.

A simple and common approach is to create top level directories that match the main components of your work. For instance, you might have one directory for raw histograms directly filled from data, another for processed or normalized histograms, and another for final publication plots. Within each of these, you can create subdirectories for specific channels, detector subsystems, or run periods.

Suppose you analyze events with electrons and muons, and you want to keep their plots apart. You could create a structure like

cpp
TFile *f = new TFile("analysis.root", "RECREATE");
TDirectory *dirRaw    = f->mkdir("raw");
TDirectory *dirFinal  = f->mkdir("final");
TDirectory *dirRawEl  = dirRaw->mkdir("electrons");
TDirectory *dirRawMu  = dirRaw->mkdir("muons");
TDirectory *dirFinEl  = dirFinal->mkdir("electrons");
TDirectory *dirFinMu  = dirFinal->mkdir("muons");

Then, during the analysis, you choose the right directory before creating or writing each object. For example, when you fill a raw electron transverse momentum histogram, you can do

cpp
dirRawEl->cd();
TH1F *hPtElRaw = new TH1F("hPt", "Electron p_{T};p_{T} [GeV];Counts", 100, 0, 100);
...
hPtElRaw->Write();

Later, when you produce processed or fitted results, you move to dirFinEl or dirFinMu and write new histograms with the same or different names. Even if the histogram name is the same, it lives in a different directory, so there is no conflict.

To see how objects are grouped, you can run in ROOT:

cpp
f->cd("raw/electrons");
gDirectory->ls();  // lists only raw electron objects

This isolates the part of the file that you are interested in, and the listing is more readable than if every histogram in the analysis lived at the top level. Complex projects benefit significantly from this separation.

A common pattern is to combine directory organization with naming conventions. For example, you might name histograms inside raw/electrons with a simple pattern such as hE, hPt, hEta, while using more descriptive names in final directories, because the directory path already tells you the context. A clean directory layout reduces the need for very long object names.

Directories also help when looping over groups of objects. For instance, if all histograms related to a specific study are inside one directory, you can obtain a list of keys in that directory and process them in a loop. Although you will see more advanced ROOT collection techniques elsewhere, the key idea here is that directory boundaries give you natural subsets of the file content.

Another important use of directories is separating different stages of analysis or different versions. You might have v1, v2, and v3 directories to hold results from different code versions, or mc and data directories to keep simulation and real data in the same file but clearly separated.

When reading a file, it is often convenient to rely on directory paths rather than full object paths in every Get call. You can cd once into a directory that corresponds to the dataset you want, and afterwards simple names are sufficient. For instance:

cpp
f->cd("final/muons");
TH1F *hMass = (TH1F*) gDirectory->Get("hMass");

This code does not need to repeat the full path each time, and it is easier to maintain than strings that repeat directory prefixes everywhere.

Decide on a directory structure early in your analysis and keep it consistent. Changing directory layout midway, or mixing unrelated objects at the same level, quickly leads to confusion. Use separate directories for different data types, analysis stages, or channels, and avoid placing many unrelated objects in the file root directory.

In summary, TDirectory and its hierarchy inside a TFile give you a flexible way to organize objects. By planning a simple logical structure, using mkdir, cd, and ls, you can keep complex ROOT files manageable and make it straightforward to find, read, and reinterpret the contents later.

Views: 12

Comments

Please login to add a comment.

Don't have an account? Register now!