Belle II Software development
root_utils.py
1
8
9"""Various functions to interact with ROOT objects and the runtime environment"""
10
11import ROOT
12import contextlib
13from functools import singledispatch
14
15
16def root_walk(tdirectory):
17 """Walks the content of a TDirectory similar to os.walk.
18
19 Yields 3-tuples of current TDirectories, contained TObjects and contained TDirectories
20 for each of the directories nested inside the given TDirectory in a depth first manner.
21
22 Yields
23 ------
24 (TDirectory, list(TObject), list(TDirectory))
25 """
26 tkeys = tdirectory.GetListOfKeys()
27
28 tobjects = []
29 tdirectories = []
30 for tkey in tkeys:
31 tobject = tdirectory.Get(tkey.GetName())
32 if isinstance(tobject, ROOT.TDirectory):
33 tdirectories.append(tobject)
34 else:
35 tobjects.append(tobject)
36
37 yield tdirectory, tobjects, tdirectories
38
39 # Continue depth first
40 for sub_tdirectory in tdirectories:
41 for tdirectory, tobjects, tdirectories in root_walk(sub_tdirectory):
42 yield tdirectory, tobjects, tdirectories
43
44
45@contextlib.contextmanager
46def root_open(tfile_or_file_path, tfile_options=None):
47 """Context manager to open a TFile.
48
49 If a file path is given open the TFile and close it after the context is left.
50 If an already opened TFile is received simply return it and do not close on exit.
51
52 Parameters
53 ----------
54 tfile_or_file_path : str or ROOT.TFile
55 Path to the file or the TFile that should be activated.
56 tfile_options : str
57 Option string forwarded to the ROOT.TFile constructor
58 Typical options as "RECREATE", "READ" or "UPDATE".
59 """
60 if isinstance(tfile_or_file_path, ROOT.TFile):
61 tfile = tfile_or_file_path
62 with root_cd(tfile) as tfile:
63 yield tfile
64 else:
65 save_tdirectory = ROOT.gROOT.CurrentDirectory().load()
66
67 if tfile_options is None:
68 tfile = ROOT.TFile(tfile_or_file_path)
69 else:
70 tfile = ROOT.TFile(tfile_or_file_path, tfile_options)
71
72 try:
73 yield tfile
74 finally:
75 tfile.Close()
76 save_tdirectory.cd()
77
78
79@contextlib.contextmanager
80def root_cd(tdirectory):
81 """Context manager that temporarily switches the current global ROOT directory while in the context.
82
83 If a string as the name of a directory is given as the argument
84 try to switch to the directory with that name in the current ROOT folder.
85
86 If it is not present create it.
87
88 Parameters
89 ----------
90 tdirectory : ROOT.TDirectory or str
91 ROOT directory to switch to or name of a folder to switch.
92
93 Returns
94 -------
95 ROOT.TDirectory
96 The new current ROOT directory.
97 """
98
99 # Do not use ROOT.gDirectory here.
100 # Since ROOT.gDirectory gets transported as a reference it changes on a call to cd() as well,
101 # and can therefore not serve to save the former directory.
102 save_tdirectory = ROOT.gROOT.CurrentDirectory().load()
103
104 if not tdirectory or "." == tdirectory:
105 tdirectory = save_tdirectory
106
107 elif isinstance(tdirectory, str):
108 tdirectory_name = tdirectory
109
110 # Look for the tdirectory with the name
111 # before trying to create it
112 tdirectory = save_tdirectory.GetDirectory(tdirectory_name)
113 if not tdirectory:
114 tdirectory = save_tdirectory.mkdir(tdirectory_name, tdirectory_name)
115 if not tdirectory:
116 raise RuntimeError(f"Could not create or find folder {tdirectory_name}")
117
118 # If tdirectory_name is as hierarchy like a/b/c make sure we select the most nested folder
119 # (and not a as the documentation of TDirectory.mkdir suggests).
120 tdirectory = save_tdirectory.GetDirectory(tdirectory_name)
121
122 try:
123 if tdirectory is not None:
124 tdirectory.cd()
125 yield tdirectory
126
127 finally:
128 save_tdirectory.cd()
129
130
131def root_save_name(name):
132 """Strips all meta characters that might be unsafe to use as a ROOT name.
133
134 Parameters
135 ----------
136 name : str
137 A name that should be transformed
138
139 Returns
140 -------
141 str
142 Name with potentially harmful characters deleted / replaced.
143 """
144 deletechars = str.maketrans("", "", r"/$\#{}()[]=")
145 name = name.replace(' ', '_').replace('-', '_').replace(',', '_').translate(deletechars)
146 return name
147
148
149def root_browse(tobject):
150 """Open a browser and show the given object.
151
152 Parameters
153 ----------
154 tobject : ROOT.TObject
155 The object to be shown
156
157 Returns
158 -------
159 ROOT.TBrowser
160 The new TBrowser used to show the object.
161 """
162 # Set the style to the style desired by the validation plots.
165
166 tbrowser = ROOT.TBrowser()
167 if isinstance(tobject, ROOT.TObject):
168 if hasattr(tobject, "Browse"):
169 tobject.Browse(tbrowser)
170 else:
171 tbrowser.BrowseObject(tobject)
172 else:
173 raise ValueError("Can only browse ROOT objects inheriting from TObject.")
174 return tbrowser
175
176
177@singledispatch
178def root_ls(obj):
179 """Returns a list of names that are contained in the given obj.
180
181 This is a convenience function to investigate the content of ROOT objects,
182 that dispatches on to object type and retrieves different things depending on the type.
183 If the obj is a string it is interpreted as a filename.
184 """
185 return list(obj)
186
187
188@root_ls.register(str)
189def __(filename):
190 """Overloaded function for root_ls for filenames (e.g. opens the file and ls its content)."""
191 rootFile = ROOT.TFile(filename)
192 result = root_ls(rootFile)
193
194 rootFile.Close()
195 del rootFile
196 return result
197
198
199@root_ls.register(ROOT.TDirectory)
200def __(tDirectory): # noqa
201 """Overloaded function for root_ls for ROOT directories (e.g. list the keys in the directory)."""
202 tKeys = list(tDirectory.GetListOfKeys())
203 result = sorted([tKey.GetName() for tKey in tKeys])
204 return result
205
206
207@root_ls.register(ROOT.TTree)
208@root_ls.register(ROOT.TNtuple)
209def __(tTree): # noqa
210 """Overloaded function for root_ls for trees and ntuples (e.g. list the keys in the tuple/tree)."""
211 tBranches = list(tTree.GetListOfBranches())
212 result = sorted([tBranch.GetName() for tBranch in tBranches])
213 return result