6 Transate arbitrary root file into a han config file
7 @author: ponyisi@utexas.edu
9 Adapted for physics validation 14 May 2014
12 from __future__
import print_function
14 from DQConfMakerBase.DQElements
import DQRegion, DQReference, DQAlgorithm, DQAlgorithmParameter
15 from DQConfMakerBase.Helpers
import make_thresholds
16 from DataQualityUtils.hanwriter
import writeHanConfiguration
17 from DataQualityUtils
import HanMetadata
20 repeatalgorithm = DQAlgorithm(id=
'RepeatAlgorithm',
21 libname=
'libdqm_algorithms.so')
22 worst = DQAlgorithm(id=
'WorstCaseSummary',libname=
'libdqm_summaries.so')
28 algorithmparameters = [DQAlgorithmParameter(
'AuxAlgName--Chi2Test_Chi2_per_NDF', 1),
29 DQAlgorithmParameter(
'RepeatAlgorithm--ResultsNEntries', 1)]
32 norefalgorithm = DQAlgorithm(id=
'GatherData',
33 libname=
'libdqm_algorithms.so')
36 thresh = make_thresholds(
'Chi2_per_NDF', 1.0, 1.50,
'Chi2Thresholds')
39 def recurse(rdir, dqregion, ignorepath, modelrefs=[], displaystring='Draw=PE', displaystring2D='Draw=COLZ', regex=None, startpath=None, hists=None, manglefunc=None):
40 if manglefunc
is None:
41 manglefunc =
lambda a, b: a
42 for key
in rdir.GetListOfKeys():
43 cl = key.GetClassName(); rcl = ROOT.TClass.GetClass(cl)
44 if ' ' in key.GetName():
45 print(
'WARNING: cannot have spaces in histogram names for han config; not including %s %s' % (cl, key.GetName()))
47 if rcl.InheritsFrom(
'TH1')
or rcl.InheritsFrom(
'TGraph')
or rcl.InheritsFrom(
'TEfficiency'):
48 if '/' in key.GetName():
49 print(
'WARNING: cannot have slashes in histogram names, encountered in directory %s, histogram %s' % (rdir.GetPath(), key.GetName()))
51 if key.GetName() ==
'summary':
52 print(
'WARNING: cannot have histogram named summary, encountered in %s' % rdir.GetPath())
54 fpath = rdir.GetPath().
replace(ignorepath,
'')
55 name = (fpath +
'/' + key.GetName()).lstrip(
'/')
62 if not match:
continue
64 if not regex.match(name):
continue
65 dqpargs = {
'id' : (
'' if fpath
else 'top_level/') + name,
66 'inputdatasource': (startpath +
'/' if startpath
else '') + name,
70 for mref
in modelrefs:
71 newref = DQReference(manglefunc(mref.getReference().
replace(
'same_name', (startpath +
'/' if startpath
else '') + name), mref.id))
72 newref.addAnnotation(
'info', mref.id)
73 lnewrefs.append(newref)
74 dqpargs.update({
'algorithm': repeatalgorithm,
75 'algorithmparameters': algorithmparameters,
77 'references': lnewrefs
80 dqpargs[
'algorithm'] = norefalgorithm
81 dqpar = dqregion.newDQParameter( **dqpargs)
83 if not options.normalize: drawstrs.append(
'NoNorm')
84 if options.logy
and (cl.startswith(
'TH1')
or cl==
'TProfile'): drawstrs.append(
'LogY')
85 if options.logy
and (cl.startswith(
'TH2')
or cl==
'TProfile2D'): drawstrs.append(
'LogZ')
86 if cl.startswith(
'TH1'): drawstrs.append(displaystring)
87 if cl ==
'TProfile': drawstrs.append(displaystring)
88 if cl.startswith(
'TH2')
or cl==
'TProfile2D': drawstrs.append(displaystring2D)
89 if options.scaleref != 1: drawstrs.append(
'ScaleRef=%f' % options.scaleref)
90 if options.ratio: drawstrs.append(
'RatioPad')
92 if options.ratio2D: drawstrs.append(
'Ref2DRatio')
93 if options.ratiorange
is not None:
94 drawstrs.append(
'delta(%f)' % options.ratiorange)
96 drawstrs.append(
'DataName=%s' % options.title)
97 dqpar.addAnnotation(
'display',
','.
join(drawstrs))
99 elif rcl.InheritsFrom(
'TDirectory'):
100 newregion = dqregion.newDQRegion( key.GetName(), algorithm=worst )
101 recurse(key.ReadObj(), newregion, ignorepath, modelrefs, displaystring, displaystring2D, regex, startpath, hists, manglefunc)
105 returns True if we should kill this node
106 False if we should not
108 params = dqregion.getDQParameters()
111 subregions = dqregion.getSubRegions()
112 if subregions
is None:
115 subregions = subregions[:]
117 for sr
in subregions:
121 dqregion.delRelation(
'DQRegions', sr)
122 subregions = dqregion.getSubRegions()
123 if subregions
is None:
125 if len(subregions) + len(params) == 0:
131 params = dqregion.getDQParameters()
134 subregions = dqregion.getSubRegions()
135 if subregions
is None:
138 return len(params) +
sum([
paramcount(region)
for region
in subregions])
140 def process(infname, confname, options, refs=None):
142 f = ROOT.TFile.Open(infname,
'READ')
144 print(
'ERROR: cannot open %s' % infname)
147 top_level = DQRegion(id=
'topRegion',algorithm=worst)
148 print(
'Building tree...')
149 refpairs = refs.split(
',')
if refs
else []
151 refdict = dict(_.split(
':', 1)
for _
in refpairs)
152 except Exception
as e:
155 dqrs = [DQReference(reference=
'%s:same_name' % v, id=k)
156 for k, v
in list(refdict.items())]
157 displaystring = options.drawopt
158 if options.refdrawopt:
159 displaystring +=
',' + (
','.
join(
'DrawRef=%s' % _
for _
in options.refdrawopt.split(
',')))
160 displaystring2D = options.drawopt2D
161 if options.drawrefopt2D:
162 displaystring2D +=
',' + (
','.
join(
'DrawRef2D=%s' % _
for _
in options.drawrefopt2D.split(
',')))
164 if options.startpath:
165 topindir = f.Get(options.startpath)
167 raise ValueError(
"Path %s doesn't exist in input file" % options.startpath)
168 topindirname = f.GetPath() + options.startpath.strip(
'/')
169 startpath = options.startpath.strip(
'/')
172 topindirname = f.GetPath()
176 refstartpaths = options.refstartpath.split(
',')
if options.refstartpath
else []
178 refstartpathdict = dict(_.split(
':')
for _
in refstartpaths)
179 for k, v
in refstartpathdict.items():
180 refstartpathdict[k] = v.strip(
'/')
181 except Exception
as e:
183 def refpath_manglefunc(path, id):
185 pfx = refstartpathdict[id]
187 return path.replace(
':' + (startpath +
'/' if startpath
else ''),
':' + (pfx +
'/' if pfx
else ''), 1)
192 if options.histlistfile:
193 hists = [re.compile(line.rstrip(
'\n'))
for line
in open(options.histlistfile)]
194 if options.pathregex:
print(
"histlistfile given, pathregex is ignored")
195 if options.refmangle:
197 sys.path.append(os.getcwd())
199 manglefunc = importlib.import_module(options.refmangle).mangle
201 manglefunc = refpath_manglefunc
202 recurse(topindir, top_level, topindirname, dqrs, displaystring, displaystring2D,
203 re.compile(options.pathregex), startpath, hists, manglefunc=manglefunc)
204 print(
'Pruning dead branches...')
208 sublevel = top_level.getSubRegions()[:]
210 top_level.delRelation(
'DQRegions', x)
212 print(
'Writing output')
217 import shutil, os, sys, contextlib
219 han_is_found = (ROOT.gSystem.Load(
'libDataQualityInterfaces') != 1)
221 print(
'ERROR: unable to load offline DQMF; unable to proceed')
223 bname = os.path.basename(fname)
230 prebuilt_hcfg =
False
232 @contextlib.contextmanager
235 td = tempfile.mkdtemp()
239 with tmpdir()
as hantmpdir:
241 print(
'====> Processing file %s' % (fname))
242 print(
'====> Generating han configuration file')
243 hantmpinput = os.path.join(hantmpdir, bname)
244 shutil.copyfile(fname, hantmpinput)
245 haninput = hantmpinput
246 hanconfig = os.path.join(hantmpdir,
'han.config')
247 rv =
process(hantmpinput, hanconfig, options, options.reffile)
252 print(
'No histograms to display; exiting with code 0')
255 print(
'====> Compiling han configuration')
256 hanhcfg = os.path.join(hantmpdir,
'han.hcfg')
257 ROOT.dqi.HanConfig().AssembleAndSave( hanconfig, hanhcfg )
258 print(
'====> Executing han')
260 memlimit = resource.getrlimit(resource.RLIMIT_AS)
261 resource.setrlimit(resource.RLIMIT_AS, (memlimit[1], memlimit[1]))
262 hanoutput = haninput.rpartition(
'.')[0] +
'_han.root'
264 rv = ROOT.dqi.HanApp().Analyze( hanhcfg, haninput, hanoutput )
266 raise Exception(
'failure in han')
268 rf = ROOT.TFile.Open(hanoutput,
'UPDATE')
269 HanMetadata.addMetadata(rf,
'AMI', {
'AMI Tag': options.amitag})
271 if not options.hanonly:
272 print(
'====> Dumping web display output')
273 from DataQualityUtils
import handimod
274 handimod.handiWithComparisons( options.title,
278 'https://atlasdqm.web.cern.ch/atlasdqm/js/',
279 3
if options.jsRoot
else 1)
280 if options.hanoutput:
281 from pathlib
import Path
282 print(
'====> Copying han output to', options.hanoutput)
283 target = Path(options.hanoutput)
285 target.parent.mkdir(parents=
True, exist_ok=
True)
286 except Exception
as e:
287 print(
'Unable to create %s for some reason: %s' % (target.parent, e))
288 raise Exception(
'Error during execute')
from e
289 shutil.copy2(hanoutput, options.hanoutput)
290 print(
'====> Cleaning up')
292 except Exception
as e:
295 traceback.print_exc()
296 if 'canonical format' not in str(e):
300 if not prebuilt_hcfg:
301 os.unlink(hantmpinput)
311 if __name__==
"__main__":
312 import sys, optparse, os
313 os.environ[
'TDAQ_ERS_NO_SIGNAL_HANDLERS']=
'1'
314 parser = optparse.OptionParser(usage=
'usage: %prog [options] inputfile')
315 parser.add_option(
'--reffile', default=
None,
316 help=
'Reference files to use. Must have same structure as inputfile. Format: tag1:reffile1.root,tag2:reffile2.root,...')
317 parser.add_option(
'--outdir', default=
'./handi',
318 help=
'Directory for web ouptut')
319 parser.add_option(
'--hanoutput', default=
None,
320 help=
'Filename to save han output to (will not save if not set)')
321 parser.add_option(
'--hanonly', action=
'store_true',
322 help=
'Only save han output file, do not write HTML/PNG')
323 parser.add_option(
'--normalize', default=
False, action=
'store_true',
324 help=
'Normalize reference histograms for display')
325 parser.add_option(
'--title', default=
'Summary',
326 help=
'Title for histograms being tested')
327 parser.add_option(
'--drawopt', default=
'Draw=PE',
328 help=
'Draw options for tested histograms (only use if you know what you are doing)')
329 parser.add_option(
'--refdrawopt',
330 help=
'ROOT Draw option for reference histograms (e.g. HIST)')
331 parser.add_option(
'--drawopt2D', default=
'Draw=COLZ',
332 help=
'Draw options for tested TH2 histograms (only use if you know what you are doing)')
333 parser.add_option(
'--drawrefopt2D', default=
None,
334 help=
'Draw options for reference TH2 histograms. If nothing is specified, no 2D reference histograms are drawn. If you want to draw both test and reference histo, recommended settings are --drawopt2D="Draw=BOX" --drawrefopt2D="COLZ"')
335 parser.add_option(
'--logy', action=
'store_true',
336 help=
'Display on log Y scale')
337 parser.add_option(
'--pathregex', default=
'.*',
338 help=
'Specify regex to match histograms, e.g. "(Btag|Jets)"')
339 parser.add_option(
'--startpath', default=
None,
340 help=
'Start from this subdirectory of the file')
341 parser.add_option(
'--refstartpath', default=
None,
342 help=
'Start from this subdirectory of reference files. By default is the same as startpath. Format: tag1:dir1,tag2:dir2,...')
343 parser.add_option(
'--histlistfile',
344 help=
'text file with a list of regexes/histogram names')
345 parser.add_option(
'--scaleref', type=
"float", default=1,
346 help=
'Scale references by this value')
347 parser.add_option(
'--Kolmogorov', default=
False, action=
'store_true',
348 help=
'Run Kolmogorov test instead of Chi2 test')
349 parser.add_option(
'--ratio', default=
False, action=
'store_true',
350 help=
'Draw histograms with ratio plots')
351 parser.add_option(
'--ratio2D', default=
False, action=
'store_true',
352 help=
'Draw 2D histograms with ratio plots')
353 parser.add_option(
'--jsRoot',action=
'store_true', default=
False,
354 help=
"make interactive jsRoot displays")
355 parser.add_option(
'--ratiorange', default=
None, type=
"float",
356 help=
'set range for ratio plots (as delta to 1.0)')
357 parser.add_option(
'--refmangle', default=
None, type=
"string",
358 help=
'provide a Python module to translate histogram names between test and reference files. Module should provide\na function mangle(testhistoname, reflabel)')
359 parser.add_option(
'--amitag', default=
None,
360 help=
'AMI tag to add as metadata')
362 options, args = parser.parse_args()
364 if not 1 == len(args):
368 if options.Kolmogorov:
369 algorithmparameters = [DQAlgorithmParameter(
'AuxAlgName--KolmogorovTest_Prob', 1),
370 DQAlgorithmParameter(
'RepeatAlgorithm--ResultsNEntries', 1)]
371 thresh = make_thresholds(
'P', 0.05, 0.01,
'pThresholds')