ATLAS Offline Software
physval_make_web_display.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 # Copyright (C) 2002-2022 CERN for the benefit of the ATLAS collaboration
4 
5 """
6 Transate arbitrary root file into a han config file
7 @author: ponyisi@utexas.edu
8 9 Oct 2008
9 Adapted for physics validation 14 May 2014
10 """
11 
12 from __future__ import print_function
13 
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
18 import ROOT
19 
20 repeatalgorithm = DQAlgorithm(id='RepeatAlgorithm',
21  libname='libdqm_algorithms.so')
22 worst = DQAlgorithm(id='WorstCaseSummary',libname='libdqm_summaries.so')
23 
24 
28 algorithmparameters = [DQAlgorithmParameter('AuxAlgName--Chi2Test_Chi2_per_NDF', 1),
29  DQAlgorithmParameter('RepeatAlgorithm--ResultsNEntries', 1)]
30 
31 # this will be used if no references are provided
32 norefalgorithm = DQAlgorithm(id='GatherData',
33  libname='libdqm_algorithms.so')
34 
35 # Edit this to change thresholds
36 thresh = make_thresholds('Chi2_per_NDF', 1.0, 1.50, 'Chi2Thresholds')
37 
38 
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 # noqa: E731
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()))
46  continue
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()))
50  continue
51  if key.GetName() == 'summary':
52  print('WARNING: cannot have histogram named summary, encountered in %s' % rdir.GetPath())
53  continue
54  fpath = rdir.GetPath().replace(ignorepath, '')
55  name = (fpath + '/' + key.GetName()).lstrip('/')
56  #print rdir.GetPath(), ignorepath, name
57  if hists:
58  match = False
59  for hist in hists:
60  if hist.match(name):
61  match = True
62  if not match: continue
63  elif regex:
64  if not regex.match(name): continue
65  dqpargs = { 'id' : ('' if fpath else 'top_level/') + name,
66  'inputdatasource': (startpath + '/' if startpath else '') + name,
67  }
68  if modelrefs:
69  lnewrefs = []
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,
76  'thresholds': thresh,
77  'references': lnewrefs
78  })
79  else:
80  dqpargs['algorithm'] = norefalgorithm
81  dqpar = dqregion.newDQParameter( **dqpargs)
82  drawstrs = []
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')
91  #if options.ratio: drawstrs.append('Ref2DSignif')
92  if options.ratio2D: drawstrs.append('Ref2DRatio')
93  if options.ratiorange is not None:
94  drawstrs.append('delta(%f)' % options.ratiorange)
95 
96  drawstrs.append('DataName=%s' % options.title)
97  dqpar.addAnnotation('display', ','.join(drawstrs))
98 
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)
102 
103 def prune(dqregion):
104  """
105  returns True if we should kill this node
106  False if we should not
107  """
108  params = dqregion.getDQParameters()
109  if params is None:
110  params = []
111  subregions = dqregion.getSubRegions()
112  if subregions is None:
113  subregions = []
114  else:
115  subregions = subregions[:]
116  # kill subregions
117  for sr in subregions:
118  if sr is None:
119  continue
120  if prune(sr):
121  dqregion.delRelation('DQRegions', sr)
122  subregions = dqregion.getSubRegions()
123  if subregions is None:
124  subregions = []
125  if len(subregions) + len(params) == 0:
126  return True
127  else:
128  return False
129 
130 def paramcount(dqregion):
131  params = dqregion.getDQParameters()
132  if params is None:
133  params = []
134  subregions = dqregion.getSubRegions()
135  if subregions is None:
136  subregions = []
137 
138  return len(params) + sum([paramcount(region) for region in subregions])
139 
140 def process(infname, confname, options, refs=None):
141  import re
142  f = ROOT.TFile.Open(infname, 'READ')
143  if not f.IsOpen():
144  print('ERROR: cannot open %s' % infname)
145  return
146 
147  top_level = DQRegion(id='topRegion',algorithm=worst)
148  print('Building tree...')
149  refpairs = refs.split(',') if refs else []
150  try:
151  refdict = dict(_.split(':', 1) for _ in refpairs)
152  except Exception as e:
153  print(e)
154  # "Model" references
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(',')))
163 
164  if options.startpath:
165  topindir = f.Get(options.startpath)
166  if not topindir:
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('/')
170  else:
171  topindir = f
172  topindirname = f.GetPath()
173  startpath = None
174 
175  # make a map for the reference path names
176  refstartpaths = options.refstartpath.split(',') if options.refstartpath else []
177  try:
178  refstartpathdict = dict(_.split(':') for _ in refstartpaths)
179  for k, v in refstartpathdict.items():
180  refstartpathdict[k] = v.strip('/')
181  except Exception as e:
182  print(e)
183  def refpath_manglefunc(path, id):
184  try:
185  pfx = refstartpathdict[id]
186  # consider also the case where pfx is ''
187  return path.replace(':' + (startpath + '/' if startpath else ''), ':' + (pfx +'/' if pfx else ''), 1)
188  except KeyError:
189  return path
190 
191  hists = []
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:
196  import sys
197  sys.path.append(os.getcwd())
198  import importlib
199  manglefunc = importlib.import_module(options.refmangle).mangle
200  else:
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...')
205  prune(top_level)
206  pc = paramcount(top_level)
207 
208  sublevel = top_level.getSubRegions()[:]
209  for x in sublevel:
210  top_level.delRelation('DQRegions', x)
211 
212  print('Writing output')
213  writeHanConfiguration( filename = confname , roots = sublevel)
214  return pc
215 
216 def super_process(fname, options):
217  import shutil, os, sys, contextlib
218  import ROOT
219  han_is_found = (ROOT.gSystem.Load('libDataQualityInterfaces') != 1)
220  if not han_is_found:
221  print('ERROR: unable to load offline DQMF; unable to proceed')
222  sys.exit(1)
223  bname = os.path.basename(fname)
224 
225  hanconfig = None
226  hanhcfg = None
227  hanoutput = None
228 
229  failed = False
230  prebuilt_hcfg = False
231 
232  @contextlib.contextmanager
233  def tmpdir():
234  import tempfile
235  td = tempfile.mkdtemp()
236  yield td
237  shutil.rmtree(td)
238 
239  with tmpdir() as hantmpdir:
240  try:
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)
248  #shutil.copy(hanconfig, os.getcwd())
249 
250  # bad hack. rv = number of histogram nodes
251  if rv == 0:
252  print('No histograms to display; exiting with code 0')
253  sys.exit(0)
254 
255  print('====> Compiling han configuration')
256  hanhcfg = os.path.join(hantmpdir, 'han.hcfg')
257  ROOT.dqi.HanConfig().AssembleAndSave( hanconfig, hanhcfg )
258  print('====> Executing han')
259  import resource
260  memlimit = resource.getrlimit(resource.RLIMIT_AS)
261  resource.setrlimit(resource.RLIMIT_AS, (memlimit[1], memlimit[1]))
262  hanoutput = haninput.rpartition('.')[0] + '_han.root'
263 
264  rv = ROOT.dqi.HanApp().Analyze( hanhcfg, haninput, hanoutput )
265  if rv != 0:
266  raise Exception('failure in han')
267  if options.amitag:
268  rf = ROOT.TFile.Open(hanoutput, 'UPDATE')
269  HanMetadata.addMetadata(rf, 'AMI', {'AMI Tag': options.amitag})
270  rf.Close()
271  if not options.hanonly:
272  print('====> Dumping web display output')
273  from DataQualityUtils import handimod
274  handimod.handiWithComparisons( options.title,
275  hanoutput,
276  options.outdir,
277  '', False, False,
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)
284  try:
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')
291  os.unlink(hanoutput)
292  except Exception as e:
293  print(e)
294  import traceback
295  traceback.print_exc()
296  if 'canonical format' not in str(e):
297  failed = True
298  finally:
299  try:
300  if not prebuilt_hcfg:
301  os.unlink(hantmpinput)
302  os.unlink(hanconfig)
303  os.unlink(hanhcfg)
304  os.unlink(hanoutput)
305  except Exception:
306  pass
307 
308  return not failed
309 
310 
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')
361 
362  options, args = parser.parse_args()
363 
364  if not 1 == len(args):
365  parser.print_help()
366  sys.exit(1)
367  fname = args[0]
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')
372 
373  rv = super_process(fname, options)
374  if rv:
375  sys.exit(0)
376  else:
377  sys.exit(1)
replace
std::string replace(std::string s, const std::string &s2, const std::string &s3)
Definition: hcg.cxx:307
physval_make_web_display.super_process
def super_process(fname, options)
Definition: physval_make_web_display.py:216
convertTimingResiduals.sum
sum
Definition: convertTimingResiduals.py:55
physval_make_web_display.recurse
def recurse(rdir, dqregion, ignorepath, modelrefs=[], displaystring='Draw=PE', displaystring2D='Draw=COLZ', regex=None, startpath=None, hists=None, manglefunc=None)
Definition: physval_make_web_display.py:39
histSizes.list
def list(name, path='/')
Definition: histSizes.py:38
beamspotman.tmpdir
string tmpdir
Definition: beamspotman.py:412
physval_make_web_display.prune
def prune(dqregion)
Definition: physval_make_web_display.py:103
physval_make_web_display.paramcount
def paramcount(dqregion)
Definition: physval_make_web_display.py:130
print
void print(char *figname, TCanvas *c1)
Definition: TRTCalib_StrawStatusPlots.cxx:25
TCS::join
std::string join(const std::vector< std::string > &v, const char c=',')
Definition: Trigger/TrigT1/L1Topo/L1TopoCommon/Root/StringUtils.cxx:10
Trk::open
@ open
Definition: BinningType.h:40
physval_make_web_display.process
def process(infname, confname, options, refs=None)
Definition: physval_make_web_display.py:140
str
Definition: BTagTrackIpAccessor.cxx:11
python.hanwriter.writeHanConfiguration
def writeHanConfiguration(filename='dq.han.config', roots=[])
Definition: hanwriter.py:724