4 from __future__
import absolute_import
5 from __future__
import print_function
8 Copyright (c) 2009-2010 Jeff Klukas <klukas@wisc.edu> 10 Permission is hereby granted, free of charge, to any person obtaining a copy 11 of this software and associated documentation files (the "Software"), to deal 12 in the Software without restriction, including without limitation the rights 13 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 copies of the Software, and to permit persons to whom the Software is 15 furnished to do so, subject to the following conditions: 17 The above copyright notice and this permission notice shall be included in 18 all copies or substantial portions of the Software. 20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 41 from .
import argparse
42 from os.path
import join
as joined
43 from .utilities
import rootglob, loadROOT
50 from .version
import __version__
58 def __init__(self, filename, path='', scale=1., scale_error=None):
66 def newadd(outfile, targets, dest_path=""):
68 if allsame([x.filename
for x
in targets]):
69 f = ROOT.TFile(targets[0].filename,
'read')
70 paths = [x.path
for x
in targets]
71 scales = [x.scale
for x
in targets]
72 scale_errors = [x.scale_error
for x
in targets]
73 if f.GetDirectory(paths[0]):
75 for h
in [os.path.basename(x)
for x
in 77 hists = [f.GetDirectory(x).Get(h)
for x
in paths]
78 if not alltrue([x
and x.InheritsFrom(
'TH1')
for x
in hists]):
80 dest = joined(destdir, h)
81 add(outfile, dest, hists, scales, dest_path, scale_errors=scale_errors)
83 hists = [f.Get(x)
for x
in paths]
84 if alltrue([x
and x.InheritsFrom(
'TH1')
for x
in hists]):
86 add(outfile, dest, hists, scales, scale_errors=scale_errors)
90 for target
in targets:
91 dict_targets.setdefault(target.filename, []).
append((target.path, target.scale))
92 if (target.filename
not in dict_tfiles):
94 dict_tfiles[target.filename] = ROOT.TFile(target.filename,
'read')
98 f = ROOT.TFile(targets[0].filename,
'read')
99 if f.GetDirectory(targets[0].path):
102 histnames = [os.path.basename(x)
for x
in 103 rootglob(f, targets[0].path +
'/*')]
107 for histname
in histnames:
110 for filename
in dict_targets:
111 tfile_cur = dict_tfiles[filename]
112 for path, scale
in dict_targets[filename]:
113 hists.append(tfile_cur.GetDirectory(path).Get(histname))
116 if not alltrue([x
and x.InheritsFrom(
'TH1')
for x
in hists]):
118 dest = joined(destdir, histname)
119 add(outfile, dest, hists, scales, dest_path)
121 print(
"Code not written yet to add histograms from multiple files")
131 keys = rootfile.GetDirectory(path).GetListOfKeys()
132 folders, objects = [], []
135 classname = key.GetClassName()
136 newpath = joined(path, name)
138 if 'TDirectory' in classname:
142 yield path, folders, objects
143 for folder
in folders:
148 for element
in iterable:
149 if element != iterable[0]:
154 for element
in iterable:
161 Return the appropriate destination for an object. 163 In all cases, the result will be placed in the deepest directory shared by 164 all paths. If the histogram names are the same, the result will be named 165 based on the first directories that they do not share. Otherwise, the 166 result will be named based on the names of the other histograms. 168 >>> pathdiff(['/dirA/dirB/dirX/hist', '/dirA/dirB/dirY/hist'], '_div_') 169 '/dirA/dirB/dirX_div_dirY' 170 >>> pathdiff(['/dirA/hist1', '/dirA/hist2', '/dirA/hist3'], '_plus_') 171 '/dirA/hist1_plus_hist2_plus_hist3' 172 >>> pathdiff(['/hist1', '/dirA/hist2'], '_minus_') 175 paths = [x.split(
'/')
for x
in paths]
177 for i
in range(len(paths[0])):
178 if allsame([p[i]
for p
in paths]):
179 dest = joined(dest, paths[0][i])
182 name = joiner.join([p[-1]
for p
in paths])
183 if allsame([p[-1]
for p
in paths]):
184 for i
in range(len(paths[0])):
185 if not allsame([p[i]
for p
in paths]):
186 name = joiner.join([p[i]
for p
in paths])
187 return joined(dest, name)
193 paths = [x.split(
'/')
for x
in paths]
195 for i
in range(len(paths[0])):
196 if allsame([p[i]
for p
in paths]):
197 commonbeginning = joined(commonbeginning, paths[0][i])
201 for i
in range(-1, -1 * len(paths[0]), -1):
202 if allsame([p[i]
for p
in paths]):
203 commonending = joined(paths[0][i], commonending)
210 return joined(commonbeginning, commonending)
214 Return the appropriate destination for an object. 216 If the final objects in each path match, then the return value will be the 217 matching part of the paths. Otherwise, the output path will simply be those 218 names joined together with *joiner*. See the examples below. 220 >>> pathdiff3(['/dirA/dirX/hist', '/dirA/dirY/hist']) 222 >>> pathdiff3(['/dirA/dirX/dirB/hist', '/dirA/dirY/dirB/hist']) 224 >>> pathdiff3(['/dirA/hist1', '/dirA/hist2', '/dirA/hist3'], '_plus_') 225 '/hist1_plus_hist2_plus_hist3' 226 >>> pathdiff3(['/hist1', '/dirA/hist2'], '_div_') 229 paths = [x.split(
'/')
for x
in paths]
230 if allsame([x[-1]
for x
in paths]):
232 for i
in range(-2,
min([len(x)
for x
in paths]) * -1, -1):
233 if allsame([p[i]
for p
in paths]):
234 dest = joined(paths[0][i], dest)
239 return '/' + joiner.join([x[-1]
for x
in paths])
242 def newfunc(outfile, dest, hists, scales=None, dest_path="", scale_errors=None):
244 for d
in os.path.dirname(dest).
split(
'/'):
245 if not ROOT.gDirectory.GetDirectory(d):
246 ROOT.gDirectory.mkdir(d)
247 ROOT.gDirectory.cd(d)
248 fn(outfile, dest, hists, scales, dest_path, scale_errors)
252 '''Scale a histogram by a scale factor that has an error. 253 This takes into account the scale error to set new error bars.''' 254 hist_new = hist.Clone()
256 for i
in range(hist_new.GetNbinsX()+2):
257 hist_new.SetBinContent(i, scale)
258 hist_new.SetBinError(i, scale_error)
259 hist_new.Multiply(hist)
261 hist_new.Scale(scale)
265 def add(outfile, dest, hists, scales=None, dest_path="", scale_errors=None):
267 scales = [1.
for i
in range(len(hists))]
269 scale_errors = [
None for i
in range(len(hists))]
270 sumhist = hists[0].Clone(os.path.basename(dest))
273 for i
in range(1,len(hists)):
278 if not ROOT.gDirectory.GetDirectory(dest_path):
279 ROOT.gDirectory.mkdir(dest_path)
280 ROOT.gDirectory.cd(dest_path)
282 ROOT.gDirectory.cd(
"/")
286 diffhist = hists[0].Clone(os.path.basename(dest))
287 for hist
in hists[1:]:
288 diffhist.Add(hist, -1)
293 quotient = numer.Clone(os.path.basename(dest))
294 quotient.Divide(numer, denom)
299 quotient = ROOT.TGraphAsymmErrors()
300 quotient.SetName(os.path.basename(dest))
301 quotient.BayesDivide(numer, denom)
305 parser = argparse.ArgumentParser()
306 parser.add_argument(
'filenames', type=str, nargs=
'+',
307 help=
'root files to process')
308 parser.add_argument(
'--dirs', type=str, nargs=
'+', default=[
'/'],
309 help=
'target directories in the root files; paths to ' 310 'histograms will be relative to these')
311 parser.add_argument(
'--add', default=[], action=
'append', nargs=
'*',
312 help=
'a list of directories or histograms to add')
313 parser.add_argument(
'--subtract', default=[], action=
'append', nargs=
'*',
314 help=
'a list of directories or histograms to subtract')
315 parser.add_argument(
'--divide', default=[], action=
'append', nargs=
'*',
316 help=
'2 directories or histograms to divide')
317 parser.add_argument(
'--bayes-divide', default=[], action=
'append', nargs=
'*',
318 help=
'2 directories or histograms from which to make ' 319 'an efficiency plot')
320 args = parser.parse_args()
321 separators = {
'add' :
'_plus_',
322 'subtract' :
'_minus_',
324 'bayes_divide' :
'_eff_'}
326 files = [ROOT.TFile(x,
'read')
for x
in args.filenames]
327 outfile = ROOT.TFile(
'out.root',
'recreate')
335 for operation_type, separator
in separators.items():
336 for arg_set
in getattr(args, operation_type):
337 paths = [joined(thisdir, x)
for x
in arg_set]
338 if f.GetDirectory(paths[0]):
339 destdir =
pathdiff(paths, separator)
340 for target
in [os.path.basename(x)
for x
in 342 hists = [f.GetDirectory(x).Get(target)
344 if not alltrue([x
and x.InheritsFrom(
'TH1')
347 dest = joined(destdir, target)
348 math_func = globals()[operation_type]
349 math_func(outfile, dest, hists)
351 hists = [f.GetDirectory(thisdir).Get(x)
for x
in paths]
352 if not alltrue([x
and x.InheritsFrom(
'TH1')
356 math_func = globals()[operation_type]
357 math_func(outfile, dest, hists)
359 for operation_type, separator
in separators.items():
360 arg_sets = getattr(args, operation_type)
361 if arg_sets
and arg_sets != [[]]:
362 raise ValueError(
"No arguments to --%s allowed when multiple " 363 "files are specified" % operation_type)
365 if 'divide' in operation_type
and len(files) != 2:
366 raise ValueError(
"Exactly 2 files are expected with --%s; " 367 "%i given" % (operation_type, len(files)))
370 hists = [x.GetDirectory(path).Get(obj)
for x
in files]
371 if not alltrue([x
and x.InheritsFrom(
'TH1')
374 math_func = globals()[operation_type]
375 math_func(outfile, joined(path, obj), hists)
379 if __name__ ==
'__main__':
def loadROOT(batch=True)
Define additional helping functions.
def pathdiff2(paths, joiner='__', truncate=False)
def bayes_divide(outfile, dest, numer, denom)
def pathdiff3(paths, joiner='__')
def divide(outfile, dest, numer, denom)
S & print(S &os, JobReport::InputFile const &f)
def pathdiff(paths, joiner)
def scale_with_error(hist, scale, scale_error=None)
def __init__(self, filename, path='', scale=1., scale_error=None)
def subtract(outfile, dest, hists)
def add(outfile, dest, hists, scales=None, dest_path="", scale_errors=None)
def walk_rootfile(rootfile, path='')
Implementation ######################################################.
Classes #############################################################.
def rootglob(tdirectory, pathname)
def newadd(outfile, targets, dest_path="")