test
CMS 3D CMS Logo

 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Pages
plotting.py
Go to the documentation of this file.
1 import sys
2 import math
3 import copy
4 import array
5 import difflib
6 import collections
7 
8 import ROOT
9 ROOT.gROOT.SetBatch(True)
10 ROOT.PyConfig.IgnoreCommandLineOptions = True
11 
12 import html
13 
14 verbose=False
15 ratioYTitle = "Ratio"
16 
17 def _setStyle():
18  _absoluteSize = True
19  if _absoluteSize:
20  font = 43
21  titleSize = 22
22  labelSize = 22
23  statSize = 14
24  else:
25  font = 42
26  titleSize = 0.05
27  labelSize = 0.05
28  statSize = 0.025
29 
30  ROOT.gROOT.SetStyle("Plain")
31  ROOT.gStyle.SetPadRightMargin(0.07)
32  ROOT.gStyle.SetPadLeftMargin(0.13)
33  ROOT.gStyle.SetTitleFont(font, "XYZ")
34  ROOT.gStyle.SetTitleSize(titleSize, "XYZ")
35  ROOT.gStyle.SetTitleOffset(1.2, "Y")
36  #ROOT.gStyle.SetTitleFontSize(0.05)
37  ROOT.gStyle.SetLabelFont(font, "XYZ")
38  ROOT.gStyle.SetLabelSize(labelSize, "XYZ")
39  ROOT.gStyle.SetTextSize(labelSize)
40  ROOT.gStyle.SetStatFont(font)
41  ROOT.gStyle.SetStatFontSize(statSize)
42 
43  ROOT.TGaxis.SetMaxDigits(4)
44 
45 def _getObject(tdirectory, name):
46  obj = tdirectory.Get(name)
47  if not obj:
48  if verbose:
49  print "Did not find {obj} from {dir}".format(obj=name, dir=tdirectory.GetPath())
50  return None
51  return obj
52 
53 def _getOrCreateObject(tdirectory, nameOrCreator):
54  if hasattr(nameOrCreator, "create"):
55  return nameOrCreator.create(tdirectory)
56  return _getObject(tdirectory, nameOrCreator)
57 
59  class FileNotExist: pass
61  class SubDirNotExist: pass
62 
63  @staticmethod
64  def codesToNone(code):
66  return None
67  return code
68 
69 def _getDirectoryDetailed(tfile, possibleDirs, subDir=None):
70  """Get TDirectory from TFile."""
71  if tfile is None:
73  for pdf in possibleDirs:
74  d = tfile.Get(pdf)
75  if d:
76  if subDir is not None:
77  # Pick associator if given
78  d = d.Get(subDir)
79  if d:
80  return d
81  else:
82  if verbose:
83  print "Did not find subdirectory '%s' from directory '%s' in file %s" % (subDir, pdf, tfile.GetName())
84 # if "Step" in subDir:
85 # raise Exception("Foo")
87  else:
88  return d
89  if verbose:
90  print "Did not find any of directories '%s' from file %s" % (",".join(possibleDirs), tfile.GetName())
92 
93 def _getDirectory(*args, **kwargs):
94  return GetDirectoryCode.codesToNone(_getDirectoryDetailed(*args, **kwargs))
95 
96 def _th1ToOrderedDict(th1, renameBin=None):
97  values = collections.OrderedDict()
98  for i in xrange(1, th1.GetNbinsX()+1):
99  binLabel = th1.GetXaxis().GetBinLabel(i)
100  if renameBin is not None:
101  binLabel = renameBin(binLabel)
102  values[binLabel] = (th1.GetBinContent(i), th1.GetBinError(i))
103  return values
104 
105 def _createCanvas(name, width, height):
106  # silence warning of deleting canvas with the same name
107  if not verbose:
108  backup = ROOT.gErrorIgnoreLevel
109  ROOT.gErrorIgnoreLevel = ROOT.kError
110  canvas = ROOT.TCanvas(name, name, width, height)
111  if not verbose:
112  ROOT.gErrorIgnoreLevel = backup
113  return canvas
114 
115 def _modifyPadForRatio(pad, ratioFactor):
116  pad.Divide(1, 2)
117 
118  divisionPoint = 1-1/ratioFactor
119 
120  topMargin = pad.GetTopMargin()
121  bottomMargin = pad.GetBottomMargin()
122  divisionPoint += (1-divisionPoint)*bottomMargin # correct for (almost-)zeroing bottom margin of pad1
123  divisionPointForPad1 = 1-( (1-divisionPoint) / (1-0.02) ) # then correct for the non-zero bottom margin, but for pad1 only
124 
125  # Set the lower point of the upper pad to divisionPoint
126  pad1 = pad.cd(1)
127  yup = 1.0
128  ylow = divisionPointForPad1
129  xup = 1.0
130  xlow = 0.0
131  pad1.SetPad(xlow, ylow, xup, yup)
132  pad1.SetFillStyle(4000) # transparent
133  pad1.SetBottomMargin(0.02) # need some bottom margin here for eps/pdf output (at least in ROOT 5.34)
134 
135  # Set the upper point of the lower pad to divisionPoint
136  pad2 = pad.cd(2)
137  yup = divisionPoint
138  ylow = 0.0
139  pad2.SetPad(xlow, ylow, xup, yup)
140  pad2.SetFillStyle(4000) # transparent
141  pad2.SetTopMargin(0.0)
142  pad2.SetBottomMargin(bottomMargin/(ratioFactor*divisionPoint))
143 
144 def _calculateRatios(histos, ratioUncertainty=False):
145  """Calculate the ratios for a list of histograms"""
146 
147  def _divideOrZero(numerator, denominator):
148  if denominator == 0:
149  return 0
150  return numerator/denominator
151 
152  def equal(a, b):
153  if a == 0. and b == 0.:
154  return True
155  return abs(a-b)/max(abs(a),abs(b)) < 1e-3
156 
157  def findBins(wrap, bins_xvalues):
158  ret = []
159  currBin = wrap.begin()
160  i = 0
161  while i < len(bins_xvalues) and currBin < wrap.end():
162  (xcenter, xlow, xhigh) = bins_xvalues[i]
163  xlowEdge = xcenter-xlow
164  xupEdge = xcenter+xhigh
165 
166  (curr_center, curr_low, curr_high) = wrap.xvalues(currBin)
167  curr_lowEdge = curr_center-curr_low
168  curr_upEdge = curr_center+curr_high
169 
170  if equal(xlowEdge, curr_lowEdge) and equal(xupEdge, curr_upEdge):
171  ret.append(currBin)
172  currBin += 1
173  i += 1
174  elif curr_upEdge <= xlowEdge:
175  currBin += 1
176  elif curr_lowEdge >= xupEdge:
177  ret.append(None)
178  i += 1
179  else:
180  ret.append(None)
181  currBin += 1
182  i += 1
183  if len(ret) != len(bins_xvalues):
184  ret.extend([None]*( len(bins_xvalues) - len(ret) ))
185  return ret
186 
187  # Define wrappers for TH1/TGraph/TGraph2D to have uniform interface
188  # TODO: having more global wrappers would make some things simpler also elsewhere in the code
189  class WrapTH1:
190  def __init__(self, th1, uncertainty):
191  self._th1 = th1
192  self._uncertainty = uncertainty
193 
194  xaxis = th1.GetXaxis()
195  xaxis_arr = xaxis.GetXbins()
196  if xaxis_arr.GetSize() > 0: # unequal binning
197  lst = [xaxis_arr[i] for i in xrange(0, xaxis_arr.GetSize())]
198  arr = array.array("d", lst)
199  self._ratio = ROOT.TH1F("foo", "foo", xaxis.GetNbins(), arr)
200  else:
201  self._ratio = ROOT.TH1F("foo", "foo", xaxis.GetNbins(), xaxis.GetXmin(), xaxis.GetXmax())
202  _copyStyle(th1, self._ratio)
203  self._ratio.SetStats(0)
204  self._ratio.SetLineColor(ROOT.kBlack)
205  self._ratio.SetLineWidth(1)
206  def draw(self, style=None):
207  st = style
208  if st is None:
209  if self._uncertainty:
210  st = "EP"
211  else:
212  st = "HIST P"
213  self._ratio.Draw("same "+st)
214  def begin(self):
215  return 1
216  def end(self):
217  return self._th1.GetNbinsX()+1
218  def xvalues(self, bin):
219  xval = self._th1.GetBinCenter(bin)
220  xlow = xval-self._th1.GetXaxis().GetBinLowEdge(bin)
221  xhigh = self._th1.GetXaxis().GetBinUpEdge(bin)-xval
222  return (xval, xlow, xhigh)
223  def yvalues(self, bin):
224  yval = self._th1.GetBinContent(bin)
225  yerr = self._th1.GetBinError(bin)
226  return (yval, yerr, yerr)
227  def y(self, bin):
228  return self._th1.GetBinContent(bin)
229  def divide(self, bin, scale):
230  self._ratio.SetBinContent(bin, _divideOrZero(self._th1.GetBinContent(bin), scale))
231  self._ratio.SetBinError(bin, _divideOrZero(self._th1.GetBinError(bin), scale))
232  def makeRatio(self):
233  pass
234  def getRatio(self):
235  return self._ratio
236 
237  class WrapTGraph:
238  def __init__(self, gr, uncertainty):
239  self._gr = gr
240  self._uncertainty = uncertainty
241  self._xvalues = []
242  self._xerrslow = []
243  self._xerrshigh = []
244  self._yvalues = []
245  self._yerrshigh = []
246  self._yerrslow = []
247  def draw(self, style=None):
248  if self._ratio is None:
249  return
250  st = style
251  if st is None:
252  if self._uncertainty:
253  st = "PZ"
254  else:
255  st = "PX"
256  self._ratio.Draw("same "+st)
257  def begin(self):
258  return 0
259  def end(self):
260  return self._gr.GetN()
261  def xvalues(self, bin):
262  return (self._gr.GetX()[bin], self._gr.GetErrorXlow(bin), self._gr.GetErrorXhigh(bin))
263  def yvalues(self, bin):
264  return (self._gr.GetY()[bin], self._gr.GetErrorYlow(bin), self._gr.GetErrorYhigh(bin))
265  def y(self, bin):
266  return self._gr.GetY()[bin]
267  def divide(self, bin, scale):
268  # Ignore bin if denominator is zero
269  if scale == 0:
270  return
271  # No more items in the numerator
272  if bin >= self._gr.GetN():
273  return
274  # denominator is missing an item
275  xvals = self.xvalues(bin)
276  xval = xvals[0]
277 
278  self._xvalues.append(xval)
279  self._xerrslow.append(xvals[1])
280  self._xerrshigh.append(xvals[2])
281  yvals = self.yvalues(bin)
282  self._yvalues.append(yvals[0] / scale)
283  if self._uncertainty:
284  self._yerrslow.append(yvals[1] / scale)
285  self._yerrshigh.append(yvals[2] / scale)
286  else:
287  self._yerrslow.append(0)
288  self._yerrshigh.append(0)
289  def makeRatio(self):
290  if len(self._xvalues) == 0:
291  self._ratio = None
292  return
293  self._ratio = ROOT.TGraphAsymmErrors(len(self._xvalues), array.array("d", self._xvalues), array.array("d", self._yvalues),
294  array.array("d", self._xerrslow), array.array("d", self._xerrshigh),
295  array.array("d", self._yerrslow), array.array("d", self._yerrshigh))
296  _copyStyle(self._gr, self._ratio)
297  def getRatio(self):
298  return self._ratio
299  class WrapTGraph2D(WrapTGraph):
300  def __init__(self, gr, uncertainty):
301  WrapTGraph.__init__(self, gr, uncertainty)
302  def xvalues(self, bin):
303  return (self._gr.GetX()[bin], self._gr.GetErrorX(bin), self._gr.GetErrorX(bin))
304  def yvalues(self, bin):
305  return (self._gr.GetY()[bin], self._gr.GetErrorY(bin), self._gr.GetErrorY(bin))
306 
307  def wrap(o):
308  if isinstance(o, ROOT.TH1):
309  return WrapTH1(o, ratioUncertainty)
310  elif isinstance(o, ROOT.TGraph):
311  return WrapTGraph(o, ratioUncertainty)
312  elif isinstance(o, ROOT.TGraph2D):
313  return WrapTGraph2D(o, ratioUncertainty)
314 
315  wrappers = [wrap(h) for h in histos]
316  ref = wrappers[0]
317 
318  wrappers_bins = []
319  ref_bins = [ref.xvalues(b) for b in xrange(ref.begin(), ref.end())]
320  for w in wrappers:
321  wrappers_bins.append(findBins(w, ref_bins))
322 
323  for i, bin in enumerate(xrange(ref.begin(), ref.end())):
324  (scale, ylow, yhigh) = ref.yvalues(bin)
325  for w, bins in zip(wrappers, wrappers_bins):
326  if bins[i] is None:
327  continue
328  w.divide(bins[i], scale)
329 
330  for w in wrappers:
331  w.makeRatio()
332 
333  return wrappers
334 
335 
336 def _getXmin(obj, limitToNonZeroContent=False):
337  if isinstance(obj, ROOT.TH1):
338  xaxis = obj.GetXaxis()
339  if limitToNonZeroContent:
340  for i in xrange(1, obj.GetNbinsX()+1):
341  if obj.GetBinContent(i) != 0:
342  return xaxis.GetBinLowEdge(i)
343  # None for all bins being zero
344  return None
345  else:
346  return xaxis.GetBinLowEdge(xaxis.GetFirst())
347  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
348  m = min([obj.GetX()[i] for i in xrange(0, obj.GetN())])
349  return m*0.9 if m > 0 else m*1.1
350  raise Exception("Unsupported type %s" % str(obj))
351 
352 def _getXmax(obj, limitToNonZeroContent=False):
353  if isinstance(obj, ROOT.TH1):
354  xaxis = obj.GetXaxis()
355  if limitToNonZeroContent:
356  for i in xrange(obj.GetNbinsX(), 0, -1):
357  if obj.GetBinContent(i) != 0:
358  return xaxis.GetBinUpEdge(i)
359  # None for all bins being zero
360  return None
361  else:
362  return xaxis.GetBinUpEdge(xaxis.GetLast())
363  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
364  m = max([obj.GetX()[i] for i in xrange(0, obj.GetN())])
365  return m*1.1 if m > 0 else m*0.9
366  raise Exception("Unsupported type %s" % str(obj))
367 
368 def _getYmin(obj):
369  if isinstance(obj, ROOT.TH2):
370  yaxis = obj.GetYaxis()
371  return yaxis.GetBinLowEdge(yaxis.GetFirst())
372  elif isinstance(obj, ROOT.TH1):
373  return obj.GetMinimum()
374  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
375  m = min([obj.GetY()[i] for i in xrange(0, obj.GetN())])
376  return m*0.9 if m > 0 else m*1.1
377  raise Exception("Unsupported type %s" % str(obj))
378 
379 def _getYmax(obj):
380  if isinstance(obj, ROOT.TH2):
381  yaxis = obj.GetYaxis()
382  return yaxis.GetBinUpEdge(yaxis.GetLast())
383  elif isinstance(obj, ROOT.TH1):
384  return obj.GetMaximum()
385  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
386  m = max([obj.GetY()[i] for i in xrange(0, obj.GetN())])
387  return m*1.1 if m > 0 else m*0.9
388  raise Exception("Unsupported type %s" % str(obj))
389 
391  return max([th1.GetBinContent(i)+th1.GetBinError(i) for i in xrange(1, th1.GetNbinsX()+1)])
392 
394  yvals = filter(lambda n: n>0, [th1.GetBinContent(i) for i in xrange(1, th1.GetNbinsX()+1)])
395  yvals.sort()
396  if len(yvals) == 0:
397  return th1.GetMinimum()
398  if len(yvals) == 1:
399  return yvals[0]
400 
401  # Define outlier as being x10 less than minimum of the 95 % of the non-zero largest values
402  ind_min = len(yvals)-1 - int(len(yvals)*0.95)
403  min_val = yvals[ind_min]
404  for i in xrange(0, ind_min):
405  if yvals[i] > 0.1*min_val:
406  return yvals[i]
407 
408  return min_val
409 
410 def _getYminMaxAroundMedian(obj, coverage, coverageRange=None):
411  inRange = lambda x: True
412  inRange2 = lambda xmin,xmax: True
413  if coverageRange:
414  inRange = lambda x: coverageRange[0] <= x <= coverageRange[1]
415  inRange2 = lambda xmin,xmax: coverageRange[0] <= xmin and xmax <= coverageRange[1]
416 
417  if isinstance(obj, ROOT.TH1):
418  yvals = [obj.GetBinContent(i) for i in xrange(1, obj.GetNbinsX()+1) if inRange2(obj.GetXaxis().GetBinLowEdge(i), obj.GetXaxis().GetBinUpEdge(i))]
419  yvals = filter(lambda x: x != 0, yvals)
420  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
421  yvals = [obj.GetY()[i] for i in xrange(0, obj.GetN()) if inRange(obj.GetX()[i])]
422  else:
423  raise Exception("Unsupported type %s" % str(obj))
424  if len(yvals) == 0:
425  return (0, 0)
426  if len(yvals) == 1:
427  return (yvals[0], yvals[0])
428  if len(yvals) == 2:
429  return (yvals[0], yvals[1])
430 
431  yvals.sort()
432  nvals = int(len(yvals)*coverage)
433  if nvals < 2:
434  # Take median and +- 1 values
435  if len(yvals) % 2 == 0:
436  half = len(yvals)/2
437  return ( yvals[half-1], yvals[half] )
438  else:
439  middle = len(yvals)/2
440  return ( yvals[middle-1], yvals[middle+1] )
441  ind_min = (len(yvals)-nvals)/2
442  ind_max = len(yvals)-1 - ind_min
443 
444  return (yvals[ind_min], yvals[ind_max])
445 
446 def _findBounds(th1s, ylog, xmin=None, xmax=None, ymin=None, ymax=None):
447  """Find x-y axis boundaries encompassing a list of TH1s if the bounds are not given in arguments.
448 
449  Arguments:
450  th1s -- List of TH1s
451  ylog -- Boolean indicating if y axis is in log scale or not (affects the automatic ymax)
452 
453  Keyword arguments:
454  xmin -- Minimum x value; if None, take the minimum of TH1s
455  xmax -- Maximum x value; if None, take the maximum of TH1s
456  ymin -- Minimum y value; if None, take the minimum of TH1s
457  ymax -- Maximum y value; if None, take the maximum of TH1s
458  """
459 
460  (ymin, ymax) = _findBoundsY(th1s, ylog, ymin, ymax)
461 
462  if xmin is None or xmax is None or isinstance(xmin, list) or isinstance(max, list):
463  xmins = []
464  xmaxs = []
465  for th1 in th1s:
466  xmins.append(_getXmin(th1, limitToNonZeroContent=isinstance(xmin, list)))
467  xmaxs.append(_getXmax(th1, limitToNonZeroContent=isinstance(xmax, list)))
468 
469  # Filter out cases where histograms have zero content
470  xmins = filter(lambda h: h is not None, xmins)
471  xmaxs = filter(lambda h: h is not None, xmaxs)
472 
473  if xmin is None:
474  xmin = min(xmins)
475  elif isinstance(xmin, list):
476  if len(xmins) == 0: # all histograms zero
477  xmin = min(xmin)
478  if verbose:
479  print "Histogram is zero, using the smallest given value for xmin from", str(xmin)
480  else:
481  xm = min(xmins)
482  xmins_below = filter(lambda x: x<=xm, xmin)
483  if len(xmins_below) == 0:
484  xmin = min(xmin)
485  if xm < xmin:
486  if verbose:
487  print "Histogram minimum x %f is below all given xmin values %s, using the smallest one" % (xm, str(xmin))
488  else:
489  xmin = max(xmins_below)
490 
491  if xmax is None:
492  xmax = max(xmaxs)
493  elif isinstance(xmax, list):
494  if len(xmaxs) == 0: # all histograms zero
495  xmax = max(xmax)
496  if verbose:
497  print "Histogram is zero, using the smallest given value for xmax from", str(xmin)
498  else:
499  xm = max(xmaxs)
500  xmaxs_above = filter(lambda x: x>xm, xmax)
501  if len(xmaxs_above) == 0:
502  xmax = max(xmax)
503  if xm > xmax:
504  if verbose:
505  print "Histogram maximum x %f is above all given xmax values %s, using the maximum one" % (xm, str(xmax))
506  else:
507  xmax = min(xmaxs_above)
508 
509  for th1 in th1s:
510  th1.GetXaxis().SetRangeUser(xmin, xmax)
511 
512  return (xmin, ymin, xmax, ymax)
513 
514 def _findBoundsY(th1s, ylog, ymin=None, ymax=None, coverage=None, coverageRange=None):
515  """Find y axis boundaries encompassing a list of TH1s if the bounds are not given in arguments.
516 
517  Arguments:
518  th1s -- List of TH1s
519  ylog -- Boolean indicating if y axis is in log scale or not (affects the automatic ymax)
520 
521  Keyword arguments:
522  ymin -- Minimum y value; if None, take the minimum of TH1s
523  ymax -- Maximum y value; if None, take the maximum of TH1s
524  coverage -- If set, use only values within the 'coverage' part around the median are used for min/max (useful for ratio)
525  coverageRange -- If coverage and this are set, use only the x axis specified by an (xmin,xmax) pair for the coverage
526  """
527  if coverage is not None:
528  # the only use case for coverage for now is ratio, for which
529  # the scalings are not needed (actually harmful), so let's
530  # just ignore them if 'coverage' is set
531  y_scale_max = lambda y: y
532  y_scale_min = lambda y: y
533  else:
534  if ylog:
535  y_scale_max = lambda y: y*1.5
536  else:
537  y_scale_max = lambda y: y*1.05
538  y_scale_min = lambda y: y*0.9 # assuming log
539 
540  if ymin is None or ymax is None or isinstance(ymin, list) or isinstance(ymax, list):
541  ymins = []
542  ymaxs = []
543  for th1 in th1s:
544  if coverage is not None:
545  (_ymin, _ymax) = _getYminMaxAroundMedian(th1, coverage, coverageRange)
546  else:
547  if ylog and isinstance(ymin, list):
548  _ymin = _getYminIgnoreOutlier(th1)
549  else:
550  _ymin = _getYmin(th1)
551  _ymax = _getYmax(th1)
552 # _ymax = _getYmaxWithError(th1)
553 
554  ymins.append(_ymin)
555  ymaxs.append(_ymax)
556 
557  if ymin is None:
558  ymin = min(ymins)
559  elif isinstance(ymin, list):
560  ym_unscaled = min(ymins)
561  ym = y_scale_min(ym_unscaled)
562  ymins_below = filter(lambda y: y<=ym, ymin)
563  if len(ymins_below) == 0:
564  ymin = min(ymin)
565  if ym_unscaled < ymin:
566  if verbose:
567  print "Histogram minimum y %f is below all given ymin values %s, using the smallest one" % (ym, str(ymin))
568  else:
569  ymin = max(ymins_below)
570 
571  if ymax is None:
572  # in case ymax is automatic, ymin is set by list, and the
573  # histograms are zero, ensure here that ymax > ymin
574  ymax = y_scale_max(max(ymaxs+[ymin]))
575  elif isinstance(ymax, list):
576  ym_unscaled = max(ymaxs)
577  ym = y_scale_max(ym_unscaled)
578  ymaxs_above = filter(lambda y: y>ym, ymax)
579  if len(ymaxs_above) == 0:
580  ymax = max(ymax)
581  if ym_unscaled > ymax:
582  if verbose:
583  print "Histogram maximum y %f is above all given ymax values %s, using the maximum one" % (ym_unscaled, str(ymax))
584  else:
585  ymax = min(ymaxs_above)
586 
587  for th1 in th1s:
588  th1.GetYaxis().SetRangeUser(ymin, ymax)
589 
590  return (ymin, ymax)
591 
592 class Subtract:
593  """Class for subtracting two histograms"""
594  def __init__(self, name, nameA, nameB, title=""):
595  """Constructor
596 
597  Arguments:
598  name -- String for name of the resulting histogram (A-B)
599  nameA -- String for A histogram
600  nameB -- String for B histogram
601 
602  Keyword arguments:
603  title -- String for a title of the resulting histogram (default "")
604 
605  Uncertainties are calculated with the assumption that B is a
606  subset of A, and the histograms contain event counts.
607  """
608  self._name = name
609  self._nameA = nameA
610  self._nameB = nameB
611  self._title = title
612 
613  def __str__(self):
614  """String representation, returns the name"""
615  return self._name
616 
617  def create(self, tdirectory):
618  """Create and return the fake+duplicate histogram from a TDirectory"""
619  histoA = _getObject(tdirectory, self._nameA)
620  histoB = _getObject(tdirectory, self._nameB)
621 
622  if not histoA or not histoB:
623  return None
624 
625  ret = histoA.Clone(self._name)
626  ret.SetTitle(self._title)
627 
628  for i in xrange(0, histoA.GetNbinsX()+2): # include under- and overflow too
629  val = histoA.GetBinContent(i)-histoB.GetBinContent(i)
630  ret.SetBinContent(i, val)
631  ret.SetBinError(i, math.sqrt(val))
632 
633  return ret
634 
635 class Transform:
636  """Class to transform bin contents in an arbitrary way."""
637  def __init__(self, name, histo, func, title=""):
638  """Constructor.
639 
640  Argument:
641  name -- String for name of the resulting histogram
642  histo -- String for a source histogram (needs to be cumulative)
643  func -- Function to operate on the bin content
644  """
645  self._name = name
646  self._histo = histo
647  self._func = func
648  self._title = title
649 
650  def __str__(self):
651  """String representation, returns the name"""
652  return self._name
653 
654  def create(self, tdirectory):
655  """Create and return the transformed histogram from a TDirectory"""
656  histo = _getOrCreateObject(tdirectory, self._histo)
657  if not histo:
658  return None
659 
660  ret = histo.Clone(self._name)
661  ret.SetTitle(self._title)
662 
663  for i in xrange(0, histo.GetNbinsX()+2):
664  ret.SetBinContent(i, self._func(histo.GetBinContent(i)))
665  return ret
666 
668  """Class to calculate the fake+duplicate rate"""
669  def __init__(self, name, assoc, dup, reco, title=""):
670  """Constructor.
671 
672  Arguments:
673  name -- String for the name of the resulting efficiency histogram
674  assoc -- String for the name of the "associated" histogram
675  dup -- String for the name of the "duplicates" histogram
676  reco -- String for the name of the "reco" (denominator) histogram
677 
678  Keyword arguments:
679  title -- String for a title of the resulting histogram (default "")
680 
681  The result is calculated as 1 - (assoc - dup) / reco
682  """
683  self._name = name
684  self._assoc = assoc
685  self._dup = dup
686  self._reco = reco
687  self._title = title
688 
689  def __str__(self):
690  """String representation, returns the name"""
691  return self._name
692 
693  def create(self, tdirectory):
694  """Create and return the fake+duplicate histogram from a TDirectory"""
695  # Get the numerator/denominator histograms
696  hassoc = _getObject(tdirectory, self._assoc)
697  hdup = _getObject(tdirectory, self._dup)
698  hreco = _getObject(tdirectory, self._reco)
699 
700  # Skip if any of them does not exist
701  if not hassoc or not hdup or not hreco:
702  return None
703 
704  hfakedup = hreco.Clone(self._name)
705  hfakedup.SetTitle(self._title)
706 
707  for i in xrange(1, hassoc.GetNbinsX()+1):
708  numerVal = hassoc.GetBinContent(i) - hdup.GetBinContent(i)
709  denomVal = hreco.GetBinContent(i)
710 
711  fakedupVal = (1 - numerVal / denomVal) if denomVal != 0.0 else 0.0
712  errVal = math.sqrt(fakedupVal*(1-fakedupVal)/denomVal) if (denomVal != 0.0 and fakedupVal <= 1) else 0.0
713 
714  hfakedup.SetBinContent(i, fakedupVal)
715  hfakedup.SetBinError(i, errVal)
716 
717  return hfakedup
718 
720  """Class for making a cut efficiency histograms.
721 
722  N after cut
723  eff = -----------
724  N total
725  """
726  def __init__(self, name, histo, title=""):
727  """Constructor
728 
729  Arguments:
730  name -- String for name of the resulting histogram
731  histo -- String for a source histogram (needs to be cumulative)
732  """
733  self._name = name
734  self._histo = histo
735  self._title = title
736 
737  def __str__(self):
738  """String representation, returns the name"""
739  return self._name
740 
741  def create(self, tdirectory):
742  """Create and return the cut efficiency histogram from a TDirectory"""
743  histo = _getOrCreateObject(tdirectory, self._histo)
744  if not histo:
745  return None
746 
747  # infer cumulative direction from the under/overflow bins
748  ascending = histo.GetBinContent(0) < histo.GetBinContent(histo.GetNbinsX())
749  if ascending:
750  n_tot = histo.GetBinContent(histo.GetNbinsX())
751  else:
752  n_tot = histo.GetBinContent(0)
753 
754  if n_tot == 0:
755  return histo
756 
757  ret = histo.Clone(self._name)
758  ret.SetTitle(self._title)
759 
760  # calculate efficiency
761  for i in xrange(1, histo.GetNbinsX()+1):
762  n = histo.GetBinContent(i)
763  val = n/n_tot
764  errVal = math.sqrt(val*(1-val)/n_tot)
765  ret.SetBinContent(i, val)
766  ret.SetBinError(i, errVal)
767  return ret
768 
770  """Class to create a histogram by aggregating bins of another histogram to a bin of the resulting histogram."""
771  def __init__(self, name, histoName, mapping, normalizeTo=None, scale=None, renameBin=None, ignoreMissingBins=False, minExistingBins=None, originalOrder=False):
772  """Constructor.
773 
774  Arguments:
775  name -- String for the name of the resulting histogram
776  histoName -- String for the name of the source histogram
777  mapping -- Dictionary for mapping the bins (see below)
778 
779  Keyword arguments:
780  normalizeTo -- Optional string of a bin label in the source histogram. If given, all bins of the resulting histogram are divided by the value of this bin.
781  scale -- Optional number for scaling the histogram (passed to ROOT.TH1.Scale())
782  renameBin -- Optional function (string -> string) to rename the bins of the input histogram
783  originalOrder -- Boolean for using the order of bins in the histogram (default False)
784 
785  Mapping structure (mapping):
786 
787  Dictionary (you probably want to use collections.OrderedDict)
788  should be a mapping from the destination bin label to a list
789  of source bin labels ("dst -> [src]").
790  """
791  self._name = name
792  self._histoName = histoName
793  self._mapping = mapping
794  self._normalizeTo = normalizeTo
795  self._scale = scale
796  self._renameBin = renameBin
797  self._ignoreMissingBins = ignoreMissingBins
798  self._minExistingBins = minExistingBins
799  self._originalOrder = originalOrder
800 
801  def __str__(self):
802  """String representation, returns the name"""
803  return self._name
804 
805  def create(self, tdirectory):
806  """Create and return the histogram from a TDirectory"""
807  th1 = _getOrCreateObject(tdirectory, self._histoName)
808  if th1 is None:
809  return None
810 
811  binLabels = [""]*len(self._mapping)
812  binValues = [None]*len(self._mapping)
813 
814  # TH1 can't really be used as a map/dict, so convert it here:
815  values = _th1ToOrderedDict(th1, self._renameBin)
816 
817  binIndexOrder = [] # for reordering bins if self._originalOrder is True
818  for i, (key, labels) in enumerate(self._mapping.iteritems()):
819  sumTime = 0.
820  sumErrorSq = 0.
821  nsum = 0
822  for l in labels:
823  try:
824  sumTime += values[l][0]
825  sumErrorSq += values[l][1]**2
826  nsum += 1
827  except KeyError:
828  pass
829 
830  if nsum > 0:
831  binValues[i] = (sumTime, math.sqrt(sumErrorSq))
832  binLabels[i] = key
833 
834  ivalue = len(values)+1
835  if len(labels) > 0:
836  # first label doesn't necessarily exist (especially for
837  # the iteration timing plots), so let's test them all
838  for lab in labels:
839  if lab in values:
840  ivalue = values.keys().index(lab)
841  break
842  binIndexOrder.append( (ivalue, i) )
843 
844  if self._originalOrder:
845  binIndexOrder.sort(key=lambda t: t[0])
846  tmpVal = []
847  tmpLab = []
848  for i in xrange(0, len(binValues)):
849  fromIndex = binIndexOrder[i][1]
850  tmpVal.append(binValues[fromIndex])
851  tmpLab.append(binLabels[fromIndex])
852  binValues = tmpVal
853  binLabels = tmpLab
854 
855  if self._minExistingBins is not None and (len(binValues)-binValues.count(None)) < self._minExistingBins:
856  return None
857 
858  if self._ignoreMissingBins:
859  for i, val in enumerate(binValues):
860  if val is None:
861  binLabels[i] = None
862  binValues = filter(lambda v: v is not None, binValues)
863  binLabels = filter(lambda v: v is not None, binLabels)
864  if len(binValues) == 0:
865  return None
866 
867  result = ROOT.TH1F(self._name, self._name, len(binValues), 0, len(binValues))
868  for i, (value, label) in enumerate(zip(binValues, binLabels)):
869  if value is not None:
870  result.SetBinContent(i+1, value[0])
871  result.SetBinError(i+1, value[1])
872  result.GetXaxis().SetBinLabel(i+1, label)
873 
874  if self._normalizeTo is not None:
875  bin = th1.GetXaxis().FindBin(self._normalizeTo)
876  if bin <= 0:
877  print "Trying to normalize {name} to {binlabel}, which does not exist".format(name=self._name, binlabel=self._normalizeTo)
878  sys.exit(1)
879  value = th1.GetBinContent(bin)
880  if value != 0:
881  result.Scale(1/value)
882 
883  if self._scale is not None:
884  result.Scale(self._scale)
885 
886  return result
887 
889  """Class to create a histogram by aggregaging integrals of another histoggrams."""
890  def __init__(self, name, mapping, normalizeTo=None):
891  """Constructor.
892 
893  Arguments:
894  name -- String for the name of the resulting histogram
895  mapping -- Dictionary for mapping the bin label to a histogram name
896 
897  Keyword arguments:
898  normalizeTo -- Optional string for a histogram. If given, all bins of the resulting histograqm are divided by the integral of this histogram.
899  """
900  self._name = name
901  self._mapping = mapping
902  self._normalizeTo = normalizeTo
903 
904  def __str__(self):
905  """String representation, returns the name"""
906  return self._name
907 
908  def create(self, tdirectory):
909  """Create and return the histogram from a TDirectory"""
910  result = []
911  for key, histoName in self._mapping.iteritems():
912  th1 = _getObject(tdirectory, histoName)
913  if th1 is None:
914  continue
915  result.append( (key, th1.Integral(0, th1.GetNbinsX()+1)) ) # include under- and overflow bins
916  if len(result) == 0:
917  return None
918 
919  res = ROOT.TH1F(self._name, self._name, len(result), 0, len(result))
920 
921  for i, (name, count) in enumerate(result):
922  res.SetBinContent(i+1, count)
923  res.GetXaxis().SetBinLabel(i+1, name)
924 
925  if self._normalizeTo is not None:
926  th1 = _getObject(tdirectory, self._normalizeTo)
927  if th1 is None:
928  return None
929  scale = th1.Integral(0, th1.GetNbinsX()+1)
930  res.Scale(1/scale)
931 
932  return res
933 
934 class ROC:
935  """Class to construct a ROC curve (e.g. efficiency vs. fake rate) from two histograms"""
936  def __init__(self, name, xhistoName, yhistoName, zaxis=False):
937  """Constructor.
938 
939  Arguments:
940  name -- String for the name of the resulting histogram
941  xhistoName -- String for the name of the x-axis histogram (or another "creator" object)
942  yhistoName -- String for the name of the y-axis histogram (or another "creator" object)
943 
944  Keyword arguments:
945  zaxis -- If set to True (default False), create a TGraph2D with z axis showing the cut value (recommended drawStyle 'pcolz')
946  """
947  self._name = name
948  self._xhistoName = xhistoName
949  self._yhistoName = yhistoName
950  self._zaxis = zaxis
951 
952  def __str__(self):
953  """String representation, returns the name"""
954  return self._name
955 
956  def create(self, tdirectory):
957  """Create and return the histogram from a TDirectory"""
958  xhisto = _getOrCreateObject(tdirectory, self._xhistoName)
959  yhisto = _getOrCreateObject(tdirectory, self._yhistoName);
960  if xhisto is None or yhisto is None:
961  return None
962 
963  x = []
964  xerrup = []
965  xerrdown = []
966  y = []
967  yerrup = []
968  yerrdown = []
969  z = []
970 
971  for i in xrange(1, xhisto.GetNbinsX()+1):
972  x.append(xhisto.GetBinContent(i))
973  xerrup.append(xhisto.GetBinError(i))
974  xerrdown.append(xhisto.GetBinError(i))
975 
976  y.append(yhisto.GetBinContent(i))
977  yerrup.append(yhisto.GetBinError(i))
978  yerrdown.append(yhisto.GetBinError(i))
979 
980  z.append(xhisto.GetXaxis().GetBinUpEdge(i))
981 
982  # If either axis has only zeroes, no graph makes no point
983  if x.count(0.0) == len(x) or y.count(0.0) == len(y):
984  return None
985 
986  arr = lambda v: array.array("d", v)
987  gr = None
988  if self._zaxis:
989  gr = ROOT.TGraph2D(len(x), arr(x), arr(y), arr(z))
990  else:
991  gr = ROOT.TGraphAsymmErrors(len(x), arr(x), arr(y), arr(xerrdown), arr(xerrup), arr(yerrdown), arr(yerrup))
992  gr.SetTitle("")
993  return gr
994 
995 
996 # Plot styles
997 _plotStylesColor = [4, 2, ROOT.kBlack, ROOT.kOrange+7, ROOT.kMagenta-3]
998 _plotStylesMarker = [21, 20, 22, 34, 33]
999 
1000 def _drawFrame(pad, bounds, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, suffix=""):
1001  """Function to draw a frame
1002 
1003  Arguments:
1004  pad -- TPad to where the frame is drawn
1005  bounds -- List or 4-tuple for (xmin, ymin, xmax, ymax)
1006 
1007  Keyword arguments:
1008  xbinlabels -- Optional list of strings for x axis bin labels
1009  xbinlabelsize -- Optional number for the x axis bin label size
1010  xbinlabeloption -- Optional string for the x axis bin options (passed to ROOT.TH1.LabelsOption())
1011  suffix -- Optional string for a postfix of the frame name
1012  """
1013  if xbinlabels is None:
1014  frame = pad.DrawFrame(*bounds)
1015  else:
1016  # Special form needed if want to set x axis bin labels
1017  nbins = len(xbinlabels)
1018  frame = ROOT.TH1F("hframe"+suffix, "", nbins, bounds[0], bounds[2])
1019  frame.SetBit(ROOT.TH1.kNoStats)
1020  frame.SetBit(ROOT.kCanDelete)
1021  frame.SetMinimum(bounds[1])
1022  frame.SetMaximum(bounds[3])
1023  frame.GetYaxis().SetLimits(bounds[1], bounds[3])
1024  frame.Draw("")
1025 
1026  xaxis = frame.GetXaxis()
1027  for i in xrange(nbins):
1028  xaxis.SetBinLabel(i+1, xbinlabels[i])
1029  if xbinlabelsize is not None:
1030  xaxis.SetLabelSize(xbinlabelsize)
1031  if xbinlabeloption is not None:
1032  frame.LabelsOption(xbinlabeloption)
1033 
1034  return frame
1035 
1036 class Frame:
1037  """Class for creating and managing a frame for a simple, one-pad plot"""
1038  def __init__(self, pad, bounds, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None):
1039  self._pad = pad
1040  self._frame = _drawFrame(pad, bounds, xbinlabels, xbinlabelsize, xbinlabeloption)
1041 
1042  yoffsetFactor = 1
1043  xoffsetFactor = 1
1044  if nrows == 2:
1045  yoffsetFactor *= 2
1046  xoffsetFactor *= 2
1047  elif nrows >= 3:
1048  yoffsetFactor *= 4
1049  xoffsetFactor *= 3
1050 
1051  self._frame.GetYaxis().SetTitleOffset(self._frame.GetYaxis().GetTitleOffset()*yoffsetFactor)
1052  self._frame.GetXaxis().SetTitleOffset(self._frame.GetXaxis().GetTitleOffset()*xoffsetFactor)
1053 
1054 
1055  def setLogx(self, log):
1056  self._pad.SetLogx(log)
1057 
1058  def setLogy(self, log):
1059  self._pad.SetLogy(log)
1060 
1061  def setGridx(self, grid):
1062  self._pad.SetGridx(grid)
1063 
1064  def setGridy(self, grid):
1065  self._pad.SetGridy(grid)
1066 
1067  def adjustMarginLeft(self, adjust):
1068  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1069  # Need to redraw frame after adjusting the margin
1070  self._pad.cd()
1071  self._frame.Draw("")
1072 
1073  def adjustMarginRight(self, adjust):
1074  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1075  # Need to redraw frame after adjusting the margin
1076  self._pad.cd()
1077  self._frame.Draw("")
1078 
1079  def setTitle(self, title):
1080  self._frame.SetTitle(title)
1081 
1082  def setXTitle(self, title):
1083  self._frame.GetXaxis().SetTitle(title)
1084 
1085  def setXTitleSize(self, size):
1086  self._frame.GetXaxis().SetTitleSize(size)
1087 
1088  def setXTitleOffset(self, offset):
1089  self._frame.GetXaxis().SetTitleOffset(offset)
1090 
1091  def setXLabelSize(self, size):
1092  self._frame.GetXaxis().SetLabelSize(size)
1093 
1094  def setYTitle(self, title):
1095  self._frame.GetYaxis().SetTitle(title)
1096 
1097  def setYTitleSize(self, size):
1098  self._frame.GetYaxis().SetTitleSize(size)
1099 
1100  def setYTitleOffset(self, offset):
1101  self._frame.GetYaxis().SetTitleOffset(offset)
1102 
1103  def redrawAxis(self):
1104  self._pad.RedrawAxis()
1105 
1107  """Class for creating and managing a frame for a ratio plot with two subpads"""
1108  def __init__(self, pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None):
1109  self._parentPad = pad
1110  self._pad = pad.cd(1)
1111  if xbinlabels is not None:
1112  self._frame = _drawFrame(self._pad, bounds, [""]*len(xbinlabels))
1113  else:
1114  self._frame = _drawFrame(self._pad, bounds)
1115  self._padRatio = pad.cd(2)
1116  self._frameRatio = _drawFrame(self._padRatio, ratioBounds, xbinlabels, xbinlabelsize, xbinlabeloption)
1117 
1118  self._frame.GetXaxis().SetLabelSize(0)
1119  self._frame.GetXaxis().SetTitleSize(0)
1120 
1121  yoffsetFactor = ratioFactor
1122  divisionPoint = 1-1/ratioFactor
1123  xoffsetFactor = 1/divisionPoint #* 0.6
1124 
1125  if nrows == 1:
1126  xoffsetFactor *= 0.6
1127  elif nrows == 2:
1128  yoffsetFactor *= 2
1129  xoffsetFactor *= 1.5
1130  elif nrows == 3:
1131  yoffsetFactor *= 4
1132  xoffsetFactor *= 2.3
1133  elif nrows >= 4:
1134  yoffsetFactor *= 5
1135  xoffsetFactor *= 3
1136 
1137  self._frame.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
1138  self._frameRatio.GetYaxis().SetLabelSize(int(self._frameRatio.GetYaxis().GetLabelSize()*0.8))
1139  self._frameRatio.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
1140  self._frameRatio.GetXaxis().SetTitleOffset(self._frameRatio.GetXaxis().GetTitleOffset()*xoffsetFactor)
1141 
1142  self._frameRatio.GetYaxis().SetNdivisions(4, 5, 0)
1143 
1144  self._frameRatio.GetYaxis().SetTitle(ratioYTitle)
1145 
1146  def setLogx(self, log):
1147  self._pad.SetLogx(log)
1148  self._padRatio.SetLogx(log)
1149 
1150  def setLogy(self, log):
1151  self._pad.SetLogy(log)
1152 
1153  def setGridx(self, grid):
1154  self._pad.SetGridx(grid)
1155  self._padRatio.SetGridx(grid)
1156 
1157  def setGridy(self, grid):
1158  self._pad.SetGridy(grid)
1159  self._padRatio.SetGridy(grid)
1160 
1161  def adjustMarginLeft(self, adjust):
1162  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1163  self._padRatio.SetLeftMargin(self._padRatio.GetLeftMargin()+adjust)
1164  # Need to redraw frame after adjusting the margin
1165  self._pad.cd()
1166  self._frame.Draw("")
1167  self._padRatio.cd()
1168  self._frameRatio.Draw("")
1169 
1170  def adjustMarginRight(self, adjust):
1171  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1172  self._padRatio.SetRightMargin(self._padRatio.GetRightMargin()+adjust)
1173  # Need to redraw frames after adjusting the margin
1174  self._pad.cd()
1175  self._frame.Draw("")
1176  self._padRatio.cd()
1177  self._frameRatio.Draw("")
1178 
1179  def setTitle(self, title):
1180  self._frame.SetTitle(title)
1181 
1182  def setXTitle(self, title):
1183  self._frameRatio.GetXaxis().SetTitle(title)
1184 
1185  def setXTitleSize(self, size):
1186  self._frameRatio.GetXaxis().SetTitleSize(size)
1187 
1188  def setXTitleOffset(self, offset):
1189  self._frameRatio.GetXaxis().SetTitleOffset(offset)
1190 
1191  def setXLabelSize(self, size):
1192  self._frameRatio.GetXaxis().SetLabelSize(size)
1193 
1194  def setYTitle(self, title):
1195  self._frame.GetYaxis().SetTitle(title)
1196 
1197  def setYTitleRatio(self, title):
1198  self._frameRatio.GetYaxis().SetTitle(title)
1199 
1200  def setYTitleSize(self, size):
1201  self._frame.GetYaxis().SetTitleSize(size)
1202  self._frameRatio.GetYaxis().SetTitleSize(size)
1203 
1204  def setYTitleOffset(self, offset):
1205  self._frame.GetYaxis().SetTitleOffset(offset)
1206  self._frameRatio.GetYaxis().SetTitleOffset(offset)
1207 
1208  def redrawAxis(self):
1209  self._padRatio.RedrawAxis()
1210  self._pad.RedrawAxis()
1211 
1212  self._parentPad.cd()
1213 
1214  # pad to hide the lowest y axis label of the main pad
1215  xmin=0.065
1216  ymin=0.285
1217  xmax=0.128
1218  ymax=0.33
1219  self._coverPad = ROOT.TPad("coverpad", "coverpad", xmin, ymin, xmax, ymax)
1220  self._coverPad.SetBorderMode(0)
1221  self._coverPad.Draw()
1222 
1223  self._pad.cd()
1224  self._pad.Pop() # Move the first pad on top
1225 
1227  """Class for creating and managing a frame for a plot from TGraph2D"""
1228  def __init__(self, pad, bounds, histos, ratioOrig, ratioFactor):
1229  self._pad = pad
1230  if ratioOrig:
1231  self._pad = pad.cd(1)
1232 
1233  # adjust margins because of not having the ratio, we want
1234  # the same bottom margin, so some algebra gives this
1235  (xlow, ylow, width, height) = (self._pad.GetXlowNDC(), self._pad.GetYlowNDC(), self._pad.GetWNDC(), self._pad.GetHNDC())
1236  xup = xlow+width
1237  yup = ylow+height
1238 
1239  bottomMargin = self._pad.GetBottomMargin()
1240  bottomMarginNew = ROOT.gStyle.GetPadBottomMargin()
1241 
1242  ylowNew = yup - (1-bottomMargin)/(1-bottomMarginNew) * (yup-ylow)
1243  topMarginNew = self._pad.GetTopMargin() * (yup-ylow)/(yup-ylowNew)
1244 
1245  self._pad.SetPad(xlow, ylowNew, xup, yup)
1246  self._pad.SetTopMargin(topMarginNew)
1247  self._pad.SetBottomMargin(bottomMarginNew)
1248 
1249  self._view = ROOT.TView.CreateView()
1250  self._view.SetRange(bounds[0], bounds[1], 0, bounds[2], bounds[3], 20) # 20 is from Harrison-Stetson, may need tuning?
1251  self._view.Top()
1252  self._view.ShowAxis()
1253 
1254  self._xtitleoffset = 1.8
1255  self._ytitleoffset = 2.3
1256 
1257  self._firstHisto = histos[0]
1258 
1259  def setLogx(self, log):
1260  pass
1261 
1262  def setLogy(self, log):
1263  pass
1264 
1265  def setGridx(self, grid):
1266  pass
1267 
1268  def setGridy(self, grid):
1269  pass
1270 
1271  def adjustMarginLeft(self, adjust):
1272  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
1273  self._pad.cd()
1274 
1275  def adjustMarginRight(self, adjust):
1276  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
1277  self._pad.cd()
1278 
1279  def setTitle(self, title):
1280  pass
1281 
1282  def setXTitle(self, title):
1283  self._xtitle = title
1284 
1285  def setXTitleSize(self, size):
1286  self._xtitlesize = size
1287 
1288  def setXTitleOffset(self, size):
1289  self._xtitleoffset = size
1290 
1291  def setXLabelSize(self, size):
1292  self._xlabelsize = size
1293 
1294  def setYTitle(self, title):
1295  self._ytitle = title
1296 
1297  def setYTitleSize(self, size):
1298  self._ytitlesize = size
1299 
1300  def setYTitleOffset(self, offset):
1301  self._ytitleoffset = offset
1302 
1303  def setZTitle(self, title):
1304  self._firstHisto.GetZaxis().SetTitle(title)
1305 
1306  def setZTitleOffset(self, offset):
1307  self._firstHisto.GetZaxis().SetTitleOffset(offset)
1308 
1309  def redrawAxis(self):
1310  # Disabling and enabled the 3D rulers somehow magically moves the axes to their proper places
1311  ROOT.TAxis3D.ToggleRulers()
1312  ROOT.TAxis3D.ToggleRulers()
1313  axis = ROOT.TAxis3D.GetPadAxis()
1314  axis.SetLabelColor(ROOT.kBlack);
1315  axis.SetAxisColor(ROOT.kBlack);
1316 
1317  axis.GetXaxis().SetTitleOffset(self._xtitleoffset)
1318  axis.GetYaxis().SetTitleOffset(self._ytitleoffset)
1319 
1320  if hasattr(self, "_xtitle"):
1321  axis.GetXaxis().SetTitle(self._xtitle)
1322  if hasattr(self, "_xtitlesize"):
1323  axis.GetXaxis().SetTitleSize(self._xtitlesize)
1324  if hasattr(self, "_xlabelsize"):
1325  axis.GetXaxis().SetLabelSize(self._labelsize)
1326  if hasattr(self, "_ytitle"):
1327  axis.GetYaxis().SetTitle(self._ytitle)
1328  if hasattr(self, "_ytitlesize"):
1329  axis.GetYaxis().SetTitleSize(self._ytitlesize)
1330  if hasattr(self, "_ytitleoffset"):
1331  axis.GetYaxis().SetTitleOffset(self._ytitleoffset)
1332 
1333 class PlotText:
1334  """Abstraction on top of TLatex"""
1335  def __init__(self, x, y, text, size=None, bold=True, align="left", color=ROOT.kBlack, font=None):
1336  """Constructor.
1337 
1338  Arguments:
1339  x -- X coordinate of the text (in NDC)
1340  y -- Y coordinate of the text (in NDC)
1341  text -- String to draw
1342  size -- Size of text (None for the default value, taken from gStyle)
1343  bold -- Should the text be bold?
1344  align -- Alignment of text (left, center, right)
1345  color -- Color of the text
1346  font -- Specify font explicitly
1347  """
1348  self._x = x
1349  self._y = y
1350  self._text = text
1351 
1352  self._l = ROOT.TLatex()
1353  self._l.SetNDC()
1354  if not bold:
1355  self._l.SetTextFont(self._l.GetTextFont()-20) # bold -> normal
1356  if font is not None:
1357  self._l.SetTextFont(font)
1358  if size is not None:
1359  self._l.SetTextSize(size)
1360  if isinstance(align, basestring):
1361  if align.lower() == "left":
1362  self._l.SetTextAlign(11)
1363  elif align.lower() == "center":
1364  self._l.SetTextAlign(21)
1365  elif align.lower() == "right":
1366  self._l.SetTextAlign(31)
1367  else:
1368  raise Exception("Error: Invalid option '%s' for text alignment! Options are: 'left', 'center', 'right'."%align)
1369  else:
1370  self._l.SetTextAlign(align)
1371  self._l.SetTextColor(color)
1372 
1373  def Draw(self, options=None):
1374  """Draw the text to the current TPad.
1375 
1376  Arguments:
1377  options -- For interface compatibility, ignored
1378 
1379  Provides interface compatible with ROOT's drawable objects.
1380  """
1381  self._l.DrawLatex(self._x, self._y, self._text)
1382 
1383 
1385  """Class for drawing text and a background box."""
1386  def __init__(self, xmin, ymin, xmax, ymax, lineheight=0.04, fillColor=ROOT.kWhite, transparent=True, **kwargs):
1387  """Constructor
1388 
1389  Arguments:
1390  xmin -- X min coordinate of the box (NDC)
1391  ymin -- Y min coordinate of the box (NDC) (if None, deduced automatically)
1392  xmax -- X max coordinate of the box (NDC)
1393  ymax -- Y max coordinate of the box (NDC)
1394  lineheight -- Line height
1395  fillColor -- Fill color of the box
1396  transparent -- Should the box be transparent? (in practive the TPave is not created)
1397 
1398  Keyword arguments are forwarded to constructor of PlotText
1399  """
1400  # ROOT.TPave Set/GetX1NDC() etc don't seem to work as expected.
1401  self._xmin = xmin
1402  self._xmax = xmax
1403  self._ymin = ymin
1404  self._ymax = ymax
1405  self._lineheight = lineheight
1406  self._fillColor = fillColor
1407  self._transparent = transparent
1408  self._texts = []
1409  self._textArgs = {}
1410  self._textArgs.update(kwargs)
1411 
1412  self._currenty = ymax
1413 
1414  def addText(self, text):
1415  """Add text to current position"""
1416  self._currenty -= self._lineheight
1417  self._texts.append(PlotText(self._xmin+0.01, self._currenty, text, **self._textArgs))
1418 
1419  def width(self):
1420  return self._xmax-self._xmin
1421 
1422  def move(self, dx=0, dy=0, dw=0, dh=0):
1423  """Move the box and the contained text objects
1424 
1425  Arguments:
1426  dx -- Movement in x (positive is to right)
1427  dy -- Movement in y (positive is to up)
1428  dw -- Increment of width (negative to decrease width)
1429  dh -- Increment of height (negative to decrease height)
1430 
1431  dx and dy affect to both box and text objects, dw and dh
1432  affect the box only.
1433  """
1434  self._xmin += dx
1435  self._xmax += dx
1436  if self._ymin is not None:
1437  self._ymin += dy
1438  self._ymax += dy
1439 
1440  self._xmax += dw
1441  if self._ymin is not None:
1442  self._ymin -= dh
1443 
1444  for t in self._texts:
1445  t._x += dx
1446  t._y += dy
1447 
1448  def Draw(self, options=""):
1449  """Draw the box and the text to the current TPad.
1450 
1451  Arguments:
1452  options -- Forwarded to ROOT.TPave.Draw(), and the Draw() of the contained objects
1453  """
1454  if not self._transparent:
1455  ymin = self.ymin
1456  if ymin is None:
1457  ymin = self.currenty - 0.01
1458  self._pave = ROOT.TPave(self.xmin, self.ymin, self.xmax, self.ymax, 0, "NDC")
1459  self._pave.SetFillColor(self.fillColor)
1460  self._pave.Draw(options)
1461  for t in self._texts:
1462  t.Draw(options)
1463 
1464 def _copyStyle(src, dst):
1465  properties = []
1466  if hasattr(src, "GetLineColor") and hasattr(dst, "SetLineColor"):
1467  properties.extend(["LineColor", "LineStyle", "LineWidth"])
1468  if hasattr(src, "GetFillColor") and hasattr(dst, "SetFillColor"):
1469  properties.extend(["FillColor", "FillStyle"])
1470  if hasattr(src, "GetMarkerColor") and hasattr(dst, "SetMarkerColor"):
1471  properties.extend(["MarkerColor", "MarkerSize", "MarkerStyle"])
1472 
1473  for prop in properties:
1474  getattr(dst, "Set"+prop)(getattr(src, "Get"+prop)())
1475 
1476 class Plot:
1477  """Represents one plot, comparing one or more histograms."""
1478  def __init__(self, name, **kwargs):
1479  """ Constructor.
1480 
1481  Arguments:
1482  name -- String for name of the plot, or Efficiency object
1483 
1484  Keyword arguments:
1485  fallback -- Dictionary for specifying fallback (default None)
1486  outname -- String for an output name of the plot (default None for the same as 'name')
1487  title -- String for a title of the plot (default None)
1488  xtitle -- String for x axis title (default None)
1489  xtitlesize -- Float for x axis title size (default None)
1490  xtitleoffset -- Float for x axis title offset (default None)
1491  xlabelsize -- Float for x axis label size (default None)
1492  ytitle -- String for y axis title (default None)
1493  ytitlesize -- Float for y axis title size (default None)
1494  ytitleoffset -- Float for y axis title offset (default None)
1495  ztitle -- String for z axis title (default None)
1496  ztitleoffset -- Float for z axis title offset (default None)
1497  xmin -- Float for x axis minimum (default None, i.e. automatic)
1498  xmax -- Float for x axis maximum (default None, i.e. automatic)
1499  ymin -- Float for y axis minimum (default 0)
1500  ymax -- Float for y axis maximum (default None, i.e. automatic)
1501  xlog -- Bool for x axis log status (default False)
1502  ylog -- Bool for y axis log status (default False)
1503  xgrid -- Bool for x axis grid status (default True)
1504  ygrid -- Bool for y axis grid status (default True)
1505  stat -- Draw stat box? (default False)
1506  fit -- Do gaussian fit? (default False)
1507  statx -- Stat box x coordinate (default 0.65)
1508  staty -- Stat box y coordinate (default 0.8)
1509  statyadjust -- List of floats for stat box y coordinate adjustments (default None)
1510  normalizeToUnitArea -- Normalize histograms to unit area? (default False)
1511  normalizeToNumberOfEvents -- Normalize histograms to number of events? If yes, the PlotFolder needs 'numberOfEventsHistogram' set to a histogram filled once per event (default False)
1512  profileX -- Take histograms via ProfileX()? (default False)
1513  fitSlicesY -- Take histograms via FitSlicesY() (default False)
1514  rebinX -- rebin x axis (default None)
1515  scale -- Scale histograms by a number (default None)
1516  xbinlabels -- List of x axis bin labels (if given, default None)
1517  xbinlabelsize -- Size of x axis bin labels (default None)
1518  xbinlabeloption -- Option string for x axis bin labels (default None)
1519  removeEmptyBins -- Bool for removing empty bins, but only if histogram has bin labels (default False)
1520  printBins -- Bool for printing bin values, but only if histogram has bin labels (default False)
1521  drawStyle -- If "hist", draw as line instead of points (default None)
1522  drawCommand -- Deliver this to Draw() (default: None for same as drawStyle)
1523  lineWidth -- If drawStyle=="hist", the width of line (default 2)
1524  legendDx -- Float for moving TLegend in x direction for separate=True (default None)
1525  legendDy -- Float for moving TLegend in y direction for separate=True (default None)
1526  legendDw -- Float for changing TLegend width for separate=True (default None)
1527  legendDh -- Float for changing TLegend height for separate=True (default None)
1528  legend -- Bool to enable/disable legend (default True)
1529  adjustMarginRight -- Float for adjusting right margin (default None)
1530  ratio -- Possibility to disable ratio for this particular plot (default None)
1531  ratioYmin -- Float for y axis minimum in ratio pad (default: list of values)
1532  ratioYmax -- Float for y axis maximum in ratio pad (default: list of values)
1533  ratioFit -- Fit straight line in ratio? (default None)
1534  ratioUncertainty -- Plot uncertainties on ratio? (default True)
1535  ratioCoverageXrange -- Range of x axis values (xmin,xmax) to limit the automatic ratio y axis range calculation to (default None for disabled)
1536  histogramModifier -- Function to be called in create() to modify the histograms (default None)
1537  """
1538  self._name = name
1539 
1540  def _set(attr, default):
1541  setattr(self, "_"+attr, kwargs.get(attr, default))
1542 
1543  _set("fallback", None)
1544  _set("outname", None)
1545 
1546  _set("title", None)
1547  _set("xtitle", None)
1548  _set("xtitlesize", None)
1549  _set("xtitleoffset", None)
1550  _set("xlabelsize", None)
1551  _set("ytitle", None)
1552  _set("ytitlesize", None)
1553  _set("ytitleoffset", None)
1554  _set("ztitle", None)
1555  _set("ztitleoffset", None)
1556 
1557  _set("xmin", None)
1558  _set("xmax", None)
1559  _set("ymin", 0.)
1560  _set("ymax", None)
1561 
1562  _set("xlog", False)
1563  _set("ylog", False)
1564  _set("xgrid", True)
1565  _set("ygrid", True)
1566 
1567  _set("stat", False)
1568  _set("fit", False)
1569 
1570  _set("statx", 0.65)
1571  _set("staty", 0.8)
1572  _set("statyadjust", None)
1573 
1574  _set("normalizeToUnitArea", False)
1575  _set("normalizeToNumberOfEvents", False)
1576  _set("profileX", False)
1577  _set("fitSlicesY", False)
1578  _set("rebinX", None)
1579 
1580  _set("scale", None)
1581  _set("xbinlabels", None)
1582  _set("xbinlabelsize", None)
1583  _set("xbinlabeloption", None)
1584  _set("removeEmptyBins", False)
1585  _set("printBins", False)
1586 
1587  _set("drawStyle", "EP")
1588  _set("drawCommand", None)
1589  _set("lineWidth", 2)
1590 
1591  _set("legendDx", None)
1592  _set("legendDy", None)
1593  _set("legendDw", None)
1594  _set("legendDh", None)
1595  _set("legend", True)
1596 
1597  _set("adjustMarginRight", None)
1598 
1599  _set("ratio", None)
1600  _set("ratioYmin", [0, 0.2, 0.5, 0.7, 0.8, 0.9, 0.95])
1601  _set("ratioYmax", [1.05, 1.1, 1.2, 1.3, 1.5, 1.8, 2, 2.5, 3, 4, 5])
1602  _set("ratioFit", None)
1603  _set("ratioUncertainty", True)
1604  _set("ratioCoverageXrange", None)
1605 
1606  _set("histogramModifier", None)
1607 
1608  self._histograms = []
1609 
1610  def setProperties(self, **kwargs):
1611  for name, value in kwargs.iteritems():
1612  if not hasattr(self, "_"+name):
1613  raise Exception("No attribute '%s'" % name)
1614  setattr(self, "_"+name, value)
1615 
1616  def clone(self, **kwargs):
1617  if not self.isEmpty():
1618  raise Exception("Plot can be cloned only before histograms have been created")
1619  cl = copy.copy(self)
1620  cl.setProperties(**kwargs)
1621  return cl
1622 
1624  """Return number of existing histograms."""
1625  return len(filter(lambda h: h is not None, self._histograms))
1626 
1627  def isEmpty(self):
1628  """Return true if there are no histograms created for the plot"""
1629  return self.getNumberOfHistograms() == 0
1630 
1631  def isTGraph2D(self):
1632  for h in self._histograms:
1633  if isinstance(h, ROOT.TGraph2D):
1634  return True
1635  return False
1636 
1637  def isRatio(self, ratio):
1638  if self._ratio is None:
1639  return ratio
1640  return ratio and self._ratio
1641 
1642  def getName(self):
1643  if self._outname is not None:
1644  return self._outname
1645  if isinstance(self._name, list):
1646  return str(self._name[0])
1647  else:
1648  return str(self._name)
1649 
1651  """Return true if the ratio uncertainty should be drawn"""
1652  return self._ratioUncertainty
1653 
1654  def _createOne(self, name, index, tdir, nevents):
1655  """Create one histogram from a TDirectory."""
1656  if tdir == None:
1657  return None
1658 
1659  # If name is a list, pick the name by the index
1660  if isinstance(name, list):
1661  name = name[index]
1662 
1663  h = _getOrCreateObject(tdir, name)
1664  if h is not None and self._normalizeToNumberOfEvents and nevents is not None and nevents != 0:
1665  h.Scale(1.0/nevents)
1666  return h
1667 
1668  def create(self, tdirNEvents, requireAllHistograms=False):
1669  """Create histograms from list of TDirectories"""
1670  self._histograms = [self._createOne(self._name, i, tdirNEvent[0], tdirNEvent[1]) for i, tdirNEvent in enumerate(tdirNEvents)]
1671 
1672  if self._fallback is not None:
1673  profileX = [self._profileX]*len(self._histograms)
1674  for i in xrange(0, len(self._histograms)):
1675  if self._histograms[i] is None:
1676  self._histograms[i] = self._createOne(self._fallback["name"], i, tdirNEvents[i][0], tdirNEvents[i][1])
1677  profileX[i] = self._fallback.get("profileX", self._profileX)
1678 
1679  if self._histogramModifier is not None:
1680  self._histograms = self._histogramModifier(self._histograms)
1681 
1682  if len(self._histograms) > len(_plotStylesColor):
1683  raise Exception("More histograms (%d) than there are plot styles (%d) defined. Please define more plot styles in this file" % (len(self._histograms), len(_plotStylesColor)))
1684 
1685  # Modify histograms here in case self._name returns numbers
1686  # and self._histogramModifier creates the histograms from
1687  # these numbers
1688  def _modifyHisto(th1, profileX):
1689  if th1 is None:
1690  return None
1691 
1692  if profileX:
1693  th1 = th1.ProfileX()
1694 
1695  if self._fitSlicesY:
1696  ROOT.TH1.AddDirectory(True)
1697  th1.FitSlicesY()
1698  th1 = ROOT.gDirectory.Get(th1.GetName()+"_2")
1699  th1.SetDirectory(None)
1700  #th1.SetName(th1.GetName()+"_ref")
1701  ROOT.TH1.AddDirectory(False)
1702 
1703  if self._title is not None:
1704  th1.SetTitle(self._title)
1705 
1706  if self._scale is not None:
1707  th1.Scale(self._scale)
1708 
1709  return th1
1710 
1711  if self._fallback is not None:
1712  self._histograms = map(_modifyHisto, self._histograms, profileX)
1713  else:
1714  self._histograms = map(lambda h: _modifyHisto(h, self._profileX), self._histograms)
1715  if requireAllHistograms and None in self._histograms:
1716  self._histograms = [None]*len(self._histograms)
1717 
1718  def _setStats(self, histos, startingX, startingY):
1719  """Set stats box."""
1720  if not self._stat:
1721  for h in histos:
1722  if h is not None and hasattr(h, "SetStats"):
1723  h.SetStats(0)
1724  return
1725 
1726  def _doStats(h, col, dy):
1727  if h is None:
1728  return
1729  h.SetStats(True)
1730 
1731  if self._fit and h.GetEntries() > 0.5:
1732  h.Fit("gaus", "Q")
1733  f = h.GetListOfFunctions().FindObject("gaus")
1734  if f == None:
1735  h.SetStats(0)
1736  return
1737  f.SetLineColor(col)
1738  f.SetLineWidth(1)
1739  h.Draw()
1740  ROOT.gPad.Update()
1741  st = h.GetListOfFunctions().FindObject("stats")
1742  if self._fit:
1743  st.SetOptFit(0o010)
1744  st.SetOptStat(1001)
1745  st.SetX1NDC(startingX)
1746  st.SetX2NDC(startingX+0.3)
1747  st.SetY1NDC(startingY+dy)
1748  st.SetY2NDC(startingY+dy+0.15)
1749  st.SetTextColor(col)
1750 
1751  dy = 0.0
1752  for i, h in enumerate(histos):
1753  if self._statyadjust is not None and i < len(self._statyadjust):
1754  dy += self._statyadjust[i]
1755 
1756  _doStats(h, _plotStylesColor[i], dy)
1757  dy -= 0.19
1758 
1759  def _normalize(self):
1760  """Normalise histograms to unit area"""
1761 
1762  for h in self._histograms:
1763  if h is None:
1764  continue
1765  i = h.Integral()
1766  if i == 0:
1767  continue
1768  if h.GetSumw2().fN <= 0: # to suppress warning
1769  h.Sumw2()
1770  h.Scale(1.0/i)
1771 
1772  def draw(self, pad, ratio, ratioFactor, nrows):
1773  """Draw the histograms using values for a given algorithm."""
1774 # if len(self._histograms) == 0:
1775 # print "No histograms for plot {name}".format(name=self._name)
1776 # return
1777 
1778  isTGraph2D = self.isTGraph2D()
1779  if isTGraph2D:
1780  # Ratios for the TGraph2Ds is not that interesting
1781  ratioOrig = ratio
1782  ratio = False
1783 
1784  if self._normalizeToUnitArea:
1785  self._normalize()
1786 
1787  if self._rebinX is not None:
1788  for h in self._histograms:
1789  h.Rebin(self._rebinX)
1790 
1791  def _styleMarker(h, msty, col):
1792  h.SetMarkerStyle(msty)
1793  h.SetMarkerColor(col)
1794  h.SetMarkerSize(0.7)
1795  h.SetLineColor(1)
1796  h.SetLineWidth(1)
1797 
1798  def _styleHist(h, msty, col):
1799  _styleMarker(h, msty, col)
1800  h.SetLineColor(col)
1801  h.SetLineWidth(self._lineWidth)
1802 
1803  # Use marker or hist style
1804  style = _styleMarker
1805  if "hist" in self._drawStyle.lower():
1806  style = _styleHist
1807  if len(self._histograms) > 0 and isinstance(self._histograms[0], ROOT.TGraph):
1808  if "l" in self._drawStyle.lower():
1809  style = _styleHist
1810 
1811  # Apply style to histograms, filter out Nones
1812  histos = []
1813  for i, h in enumerate(self._histograms):
1814  if h is None:
1815  continue
1816  style(h, _plotStylesMarker[i], _plotStylesColor[i])
1817  histos.append(h)
1818  if len(histos) == 0:
1819  if verbose:
1820  print "No histograms for plot {name}".format(name=self.getName())
1821  return
1822 
1823  # Extract x bin labels, make sure that only bins with same
1824  # label are compared with each other
1825  histosHaveBinLabels = len(histos[0].GetXaxis().GetBinLabel(1)) > 0
1826  xbinlabels = self._xbinlabels
1827  if xbinlabels is None:
1828  if histosHaveBinLabels:
1829  xbinlabels = [histos[0].GetXaxis().GetBinLabel(i) for i in xrange(1, histos[0].GetNbinsX()+1)]
1830  # Merge bin labels with difflib
1831  for h in histos[1:]:
1832  labels = [h.GetXaxis().GetBinLabel(i) for i in xrange(1, h.GetNbinsX()+1)]
1833  diff = difflib.unified_diff(xbinlabels, labels, n=max(len(xbinlabels), len(labels)))
1834  xbinlabels = []
1835  operation = []
1836  for item in diff: # skip the "header" lines
1837  if item[:2] == "@@":
1838  break
1839  for item in diff:
1840  operation.append(item[0])
1841  lab = item[1:]
1842  if lab in xbinlabels:
1843  # pick the last addition of the bin
1844  ind = xbinlabels.index(lab)
1845  if operation[ind] == "-" and operation[-1] == "+":
1846  xbinlabels.remove(lab)
1847  del operation[ind] # to keep xbinlabels and operation indices in sync
1848  elif operation[ind] == "+" and operation[-1] == "-":
1849  del operation[-1] # to keep xbinlabels and operation indices in sync
1850  continue
1851  else:
1852  raise Exception("This should never happen")
1853  xbinlabels.append(lab)
1854  # unified_diff returns empty diff if xbinlabels and labels are equal
1855  # so if xbinlabels is empty here, it can be just set to labels
1856  if len(xbinlabels) == 0:
1857  xbinlabels = labels
1858 
1859  histos_new = []
1860  for h in histos:
1861  h_new = h.Clone(h.GetName()+"_xbinlabels")
1862  h_new.SetBins(len(xbinlabels), h.GetBinLowEdge(1), h.GetBinLowEdge(1)+len(xbinlabels))
1863  for i, label in enumerate(xbinlabels):
1864  bin = h.GetXaxis().FindFixBin(label)
1865  if bin >= 0:
1866  h_new.SetBinContent(i+1, h.GetBinContent(bin))
1867  h_new.SetBinError(i+1, h.GetBinError(bin))
1868  else:
1869  h_new.SetBinContent(i+1, 0)
1870  h_new.SetBinError(i+1, 0)
1871  histos_new.append(h_new)
1872  self._tmp_histos = histos_new # need to keep these in memory too ...
1873  histos = histos_new
1874 
1875  # Remove empty bins, but only if histograms have bin labels
1876  if self._removeEmptyBins and histosHaveBinLabels:
1877  # at this point, all histograms have been "equalized" by their x binning and labels
1878  # therefore remove bins which are empty in all histograms
1879  binsToRemove = set()
1880  for b in xrange(1, histos[0].GetNbinsX()+1):
1881  binEmpty = True
1882  for h in histos:
1883  if h.GetBinContent(b) > 0:
1884  binEmpty = False
1885  break
1886  if binEmpty:
1887  binsToRemove.add(b)
1888 
1889  if len(binsToRemove) > 0:
1890  # filter xbinlabels
1891  xbinlab_new = []
1892  for i in xrange(len(xbinlabels)):
1893  if (i+1) not in binsToRemove:
1894  xbinlab_new.append(xbinlabels[i])
1895  xbinlabels = xbinlab_new
1896 
1897  # filter histogram bins
1898  histos_new = []
1899  for h in histos:
1900  values = []
1901  for b in xrange(1, h.GetNbinsX()+1):
1902  if b not in binsToRemove:
1903  values.append( (h.GetXaxis().GetBinLabel(b), h.GetBinContent(b), h.GetBinError(b)) )
1904 
1905  if len(values) > 0:
1906  h_new = h.Clone(h.GetName()+"_empty")
1907  h_new.SetBins(len(values), h.GetBinLowEdge(1), h.GetBinLowEdge(1)+len(values))
1908  for b, (l, v, e) in enumerate(values):
1909  h_new.GetXaxis().SetBinLabel(b+1, l)
1910  h_new.SetBinContent(b+1, v)
1911  h_new.SetBinError(b+1, e)
1912 
1913  histos_new.append(h_new)
1914 
1915  self._tmp_histos = histos_new # need to keep these in memory too ...
1916  histos = histos_new
1917  if len(histos) == 0:
1918  if verbose:
1919  print "No histograms with non-empty bins for plot {name}".format(name=self.getName())
1920  return
1921 
1922  if self._printBins and histosHaveBinLabels:
1923  print "####################"
1924  print self._name
1925  width = max([len(l) for l in xbinlabels])
1926  tmp = "%%-%ds " % width
1927  for b in xrange(1, histos[0].GetNbinsX()+1):
1928  s = tmp % xbinlabels[b-1]
1929  for h in histos:
1930  s += "%.3f " % h.GetBinContent(b)
1931  print s
1932  print
1933 
1934  bounds = _findBounds(histos, self._ylog,
1935  xmin=self._xmin, xmax=self._xmax,
1936  ymin=self._ymin, ymax=self._ymax)
1937 
1938  # need to keep these in memory
1941 
1942  if ratio:
1943  self._ratios = _calculateRatios(histos, self._ratioUncertainty) # need to keep these in memory too ...
1944  ratioHistos = filter(lambda h: h is not None, [r.getRatio() for r in self._ratios[1:]])
1945 
1946  if len(ratioHistos) > 0:
1947  ratioBoundsY = _findBoundsY(ratioHistos, ylog=False, ymin=self._ratioYmin, ymax=self._ratioYmax, coverage=0.68, coverageRange=self._ratioCoverageXrange)
1948  else:
1949  ratioBoundsY = (0.9, 1,1) # hardcoded default in absence of valid ratio calculations
1950 
1951  if self._ratioFit is not None:
1952  for i, rh in enumerate(ratioHistos):
1953  tf_line = ROOT.TF1("line%d"%i, "[0]+x*[1]")
1954  tf_line.SetRange(self._ratioFit["rangemin"], self._ratioFit["rangemax"])
1955  fitres = rh.Fit(tf_line, "RINSQ")
1956  tf_line.SetLineColor(rh.GetMarkerColor())
1957  tf_line.SetLineWidth(2)
1958  self._ratioAdditional.append(tf_line)
1959  box = PlotTextBox(xmin=self._ratioFit.get("boxXmin", 0.14), ymin=None, # None for automatix
1960  xmax=self._ratioFit.get("boxXmax", 0.35), ymax=self._ratioFit.get("boxYmax", 0.09),
1961  color=rh.GetMarkerColor(), font=43, size=11, lineheight=0.02)
1962  box.move(dx=(box.width()+0.01)*i)
1963  #box.addText("Const: %.4f" % fitres.Parameter(0))
1964  #box.addText("Slope: %.4f" % fitres.Parameter(1))
1965  box.addText("Const: %.4f#pm%.4f" % (fitres.Parameter(0), fitres.ParError(0)))
1966  box.addText("Slope: %.4f#pm%.4f" % (fitres.Parameter(1), fitres.ParError(1)))
1967  self._mainAdditional.append(box)
1968 
1969 
1970  # Create bounds before stats in order to have the
1971  # SetRangeUser() calls made before the fit
1972  #
1973  # stats is better to be called before frame, otherwise get
1974  # mess in the plot (that frame creation cleans up)
1975  if ratio:
1976  pad.cd(1)
1977  self._setStats(histos, self._statx, self._staty)
1978 
1979  # Create frame
1980  if isTGraph2D:
1981  frame = FrameTGraph2D(pad, bounds, histos, ratioOrig, ratioFactor)
1982  else:
1983  if ratio:
1984  ratioBounds = (bounds[0], ratioBoundsY[0], bounds[2], ratioBoundsY[1])
1985  frame = FrameRatio(pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
1986  else:
1987  frame = Frame(pad, bounds, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
1988 
1989  # Set log and grid
1990  frame.setLogx(self._xlog)
1991  frame.setLogy(self._ylog)
1992  frame.setGridx(self._xgrid)
1993  frame.setGridy(self._ygrid)
1994 
1995  # Construct draw option string
1996  opt = "sames" # s for statbox or something?
1997  ds = ""
1998  if self._drawStyle is not None:
1999  ds = self._drawStyle
2000  if self._drawCommand is not None:
2001  ds = self._drawCommand
2002  if len(ds) > 0:
2003  opt += " "+ds
2004 
2005  # Set properties of frame
2006  frame.setTitle(histos[0].GetTitle())
2007  if self._xtitle is not None:
2008  frame.setXTitle(self._xtitle)
2009  if self._xtitlesize is not None:
2010  frame.setXTitleSize(self._xtitlesize)
2011  if self._xtitleoffset is not None:
2012  frame.setXTitleOffset(self._xtitleoffset)
2013  if self._xlabelsize is not None:
2014  frame.setXLabelSize(self._xlabelsize)
2015  if self._ytitle is not None:
2016  frame.setYTitle(self._ytitle)
2017  if self._ytitlesize is not None:
2018  frame.setYTitleSize(self._ytitlesize)
2019  if self._ytitleoffset is not None:
2020  frame.setYTitleOffset(self._ytitleoffset)
2021  if self._ztitle is not None:
2022  frame.setZTitle(self._ztitle)
2023  if self._ztitleoffset is not None:
2024  frame.setZTitleOffset(self._ztitleoffset)
2025  if self._adjustMarginRight is not None:
2026  frame.adjustMarginRight(self._adjustMarginRight)
2027  elif "z" in opt:
2028  frame.adjustMarginLeft(0.03)
2029  frame.adjustMarginRight(0.08)
2030 
2031  # Draw histograms
2032  if ratio:
2033  frame._pad.cd()
2034 
2035  for h in histos:
2036  h.Draw(opt)
2037 
2038  for addl in self._mainAdditional:
2039  addl.Draw("same")
2040 
2041  # Draw ratios
2042  if ratio and len(histos) > 0:
2043  frame._padRatio.cd()
2044  firstRatio = self._ratios[0].getRatio()
2045  if self._ratioUncertainty and firstRatio is not None:
2046  firstRatio.SetFillStyle(1001)
2047  firstRatio.SetFillColor(ROOT.kGray)
2048  firstRatio.SetLineColor(ROOT.kGray)
2049  firstRatio.SetMarkerColor(ROOT.kGray)
2050  firstRatio.SetMarkerSize(0)
2051  self._ratios[0].draw("E2")
2052  frame._padRatio.RedrawAxis("G") # redraw grid on top of the uncertainty of denominator
2053  for r in self._ratios[1:]:
2054  r.draw()
2055 
2056  for addl in self._ratioAdditional:
2057  addl.Draw("same")
2058 
2059  frame.redrawAxis()
2060  self._frame = frame # keep the frame in memory for sure
2061 
2062  def addToLegend(self, legend, legendLabels, denomUncertainty):
2063  """Add histograms to a legend.
2064 
2065  Arguments:
2066  legend -- TLegend
2067  legendLabels -- List of strings for the legend labels
2068  """
2069  first = denomUncertainty
2070  for h, label in zip(self._histograms, legendLabels):
2071  if h is None:
2072  first = False
2073  continue
2074  if first:
2075  self._forLegend = h.Clone()
2076  self._forLegend.SetFillStyle(1001)
2077  self._forLegend.SetFillColor(ROOT.kGray)
2078  entry = legend.AddEntry(self._forLegend, label, "lpf")
2079  first = False
2080  else:
2081  legend.AddEntry(h, label, "LP")
2082 
2084  """Group of plots, results a TCanvas"""
2085  def __init__(self, name, plots, **kwargs):
2086  """Constructor.
2087 
2088  Arguments:
2089  name -- String for name of the TCanvas, used also as the basename of the picture files
2090  plots -- List of Plot objects
2091 
2092  Keyword arguments:
2093  ncols -- Number of columns (default 2)
2094  legendDx -- Float for moving TLegend in x direction (default None)
2095  legendDy -- Float for moving TLegend in y direction (default None)
2096  legendDw -- Float for changing TLegend width (default None)
2097  legendDh -- Float for changing TLegend height (default None)
2098  legend -- Bool for disabling legend (default True for legend being enabled)
2099  overrideLegendLabels -- List of strings for legend labels, if given, these are used instead of the ones coming from Plotter (default None)
2100  onlyForPileup -- Plots this group only for pileup samples
2101  """
2102  self._name = name
2103  self._plots = plots
2104 
2105  def _set(attr, default):
2106  setattr(self, "_"+attr, kwargs.get(attr, default))
2107 
2108  _set("ncols", 2)
2109 
2110  _set("legendDx", None)
2111  _set("legendDy", None)
2112  _set("legendDw", None)
2113  _set("legendDh", None)
2114  _set("legend", True)
2115 
2116  _set("overrideLegendLabels", None)
2117 
2118  _set("onlyForPileup", False)
2119 
2120  self._ratioFactor = 1.25
2121 
2122  def setProperties(self, **kwargs):
2123  for name, value in kwargs.iteritems():
2124  if not hasattr(self, "_"+name):
2125  raise Exception("No attribute '%s'" % name)
2126  setattr(self, "_"+name, value)
2127 
2128  def getName(self):
2129  return self._name
2130 
2131  def getPlots(self):
2132  return self._plots
2133 
2134  def remove(self, name):
2135  for i, plot in enumerate(self._plots):
2136  if plot.getName() == name:
2137  del self._plots[i]
2138  return
2139  raise Exception("Did not find Plot '%s' from PlotGroup '%s'" % (name, self._name))
2140 
2141  def clear(self):
2142  self._plots = []
2143 
2144  def append(self, plot):
2145  self._plots.append(plot)
2146 
2147  def getPlot(self, name):
2148  for plot in self._plots:
2149  if plot.getName() == name:
2150  return plot
2151  raise Exception("No Plot named '%s'" % name)
2152 
2153  def onlyForPileup(self):
2154  """Return True if the PlotGroup is intended only for pileup samples"""
2155  return self._onlyForPileup
2156 
2157  def create(self, tdirectoryNEvents, requireAllHistograms=False):
2158  """Create histograms from a list of TDirectories.
2159 
2160  Arguments:
2161  tdirectoryNEvents -- List of (TDirectory, nevents) pairs
2162  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2163  """
2164  for plot in self._plots:
2165  plot.create(tdirectoryNEvents, requireAllHistograms)
2166 
2167  def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=True):
2168  """Draw the histograms using values for a given algorithm.
2169 
2170  Arguments:
2171  legendLabels -- List of strings for legend labels (corresponding to the tdirectories in create())
2172  prefix -- Optional string for file name prefix (default None)
2173  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2174  saveFormat -- String specifying the plot format (default '.pdf')
2175  ratio -- Add ratio to the plot (default True)
2176  """
2177 
2178  if self._overrideLegendLabels is not None:
2179  legendLabels = self._overrideLegendLabels
2180 
2181  # Do not draw the group if it would be empty
2182  onlyEmptyPlots = True
2183  for plot in self._plots:
2184  if not plot.isEmpty():
2185  onlyEmptyPlots = False
2186  break
2187  if onlyEmptyPlots:
2188  return []
2189 
2190  if separate:
2191  return self._drawSeparate(legendLabels, prefix, saveFormat, ratio)
2192 
2193  cwidth = 500*self._ncols
2194  nrows = int((len(self._plots)+self._ncols-1)/self._ncols) # this should work also for odd n
2195  cheight = 500 * nrows
2196 
2197  if ratio:
2198  cheight = int(cheight*self._ratioFactor)
2199 
2200  canvas = _createCanvas(self._name, cwidth, cheight)
2201 
2202  canvas.Divide(self._ncols, nrows)
2203  if ratio:
2204  for i, plot in enumerate(self._plots):
2205  pad = canvas.cd(i+1)
2206  self._modifyPadForRatio(pad)
2207 
2208  # Draw plots to canvas
2209  for i, plot in enumerate(self._plots):
2210  pad = canvas.cd(i+1)
2211  if not plot.isEmpty():
2212  plot.draw(pad, ratio, self._ratioFactor, nrows)
2213 
2214  # Setup legend
2215  canvas.cd()
2216  if len(self._plots) <= 4:
2217  lx1 = 0.2
2218  lx2 = 0.9
2219  ly1 = 0.48
2220  ly2 = 0.53
2221  else:
2222  lx1 = 0.1
2223  lx2 = 0.9
2224  ly1 = 0.64
2225  ly2 = 0.67
2226  if self._legendDx is not None:
2227  lx1 += self._legendDx
2228  lx2 += self._legendDx
2229  if self._legendDy is not None:
2230  ly1 += self._legendDy
2231  ly2 += self._legendDy
2232  if self._legendDw is not None:
2233  lx2 += self._legendDw
2234  if self._legendDh is not None:
2235  ly1 -= self._legendDh
2236  plot = max(self._plots, key=lambda p: p.getNumberOfHistograms())
2237  denomUnc = sum([p.drawRatioUncertainty() for p in self._plots]) > 0
2238  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2,
2239  denomUncertainty=(ratio and denomUnc))
2240 
2241  return self._save(canvas, saveFormat, prefix=prefix)
2242 
2243  def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio):
2244  """Internal method to do the drawing to separate files per Plot instead of a file per PlotGroup"""
2245  width = 500
2246  height = 500
2247 
2248  canvas = _createCanvas(self._name+"Single", width, height)
2249  canvasRatio = _createCanvas(self._name+"SingleRatio", width, int(height*self._ratioFactor))
2250 
2251  # from TDRStyle
2252  for c in [canvas, canvasRatio]:
2253  c.SetTopMargin(0.05)
2254  c.SetBottomMargin(0.13)
2255  c.SetLeftMargin(0.16)
2256  c.SetRightMargin(0.05)
2257 
2258  lx1def = 0.6
2259  lx2def = 0.95
2260  ly1def = 0.85
2261  ly2def = 0.95
2262 
2263  ret = []
2264 
2265  for plot in self._plots:
2266  if plot.isEmpty():
2267  continue
2268 
2269  ratioForThisPlot = plot.isRatio(ratio)
2270  c = canvas
2271  if ratioForThisPlot:
2272  c = canvasRatio
2273  c.cd()
2274  self._modifyPadForRatio(c)
2275 
2276  # Draw plot to canvas
2277  c.cd()
2278  plot.draw(c, ratioForThisPlot, self._ratioFactor, 1)
2279 
2280  if plot._legend:
2281  # Setup legend
2282  lx1 = lx1def
2283  lx2 = lx2def
2284  ly1 = ly1def
2285  ly2 = ly2def
2286 
2287  if plot._legendDx is not None:
2288  lx1 += plot._legendDx
2289  lx2 += plot._legendDx
2290  if plot._legendDy is not None:
2291  ly1 += plot._legendDy
2292  ly2 += plot._legendDy
2293  if plot._legendDw is not None:
2294  lx2 += plot._legendDw
2295  if plot._legendDh is not None:
2296  ly1 -= plot._legendDh
2297 
2298  c.cd()
2299  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.03,
2300  denomUncertainty=(ratioForThisPlot and plot.drawRatioUncertainty))
2301 
2302  ret.extend(self._save(c, saveFormat, prefix=prefix, postfix="_"+plot.getName(), single=True))
2303  return ret
2304 
2305  def _modifyPadForRatio(self, pad):
2306  """Internal method to set divide a pad to two for ratio plots"""
2307  _modifyPadForRatio(pad, self._ratioFactor)
2308 
2309  def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True):
2310  if not self._legend:
2311  return None
2312 
2313  l = ROOT.TLegend(lx1, ly1, lx2, ly2)
2314  l.SetTextSize(textSize)
2315  l.SetLineColor(1)
2316  l.SetLineWidth(1)
2317  l.SetLineStyle(1)
2318  l.SetFillColor(0)
2319  l.SetMargin(0.07)
2320 
2321  plot.addToLegend(l, legendLabels, denomUncertainty)
2322  l.Draw()
2323  return l
2324 
2325  def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False):
2326  # Save the canvas to file and clear
2327  name = self._name
2328  if prefix is not None:
2329  name = prefix+name
2330  if postfix is not None:
2331  name = name+postfix
2332 
2333  if not verbose: # silence saved file printout
2334  backup = ROOT.gErrorIgnoreLevel
2335  ROOT.gErrorIgnoreLevel = ROOT.kWarning
2336  canvas.SaveAs(name+saveFormat)
2337  if not verbose:
2338  ROOT.gErrorIgnoreLevel = backup
2339 
2340  if single:
2341  canvas.Clear()
2342  canvas.SetLogx(False)
2343  canvas.SetLogy(False)
2344  else:
2345  canvas.Clear("D") # keep subpads
2346 
2347  return [name+saveFormat]
2348 
2350  """Represents a collection of PlotGroups, produced from a single folder in a DQM file"""
2351  def __init__(self, *plotGroups, **kwargs):
2352  """Constructor.
2353 
2354  Arguments:
2355  plotGroups -- List of PlotGroup objects
2356 
2357  Keyword arguments
2358  loopSubFolders -- Should the subfolders be looped over? (default: True)
2359  onlyForPileup -- Plots this folder only for pileup samples
2360  onlyForElectron -- Plots this folder only for electron samples
2361  onlyForConversion -- Plots this folder only for conversion samples
2362  purpose -- html.PlotPurpose member class for the purpose of the folder, used for grouping of the plots to the HTML pages
2363  page -- Optional string for the page in HTML generatin
2364  section -- Optional string for the section within a page in HTML generation
2365  numberOfEventsHistogram -- Optional path to histogram filled once per event. Needed if there are any plots normalized by number of events. Path is relative to "possibleDqmFolders".
2366  """
2367  self._plotGroups = list(plotGroups)
2368  self._loopSubFolders = kwargs.pop("loopSubFolders", True)
2369  self._onlyForPileup = kwargs.pop("onlyForPileup", False)
2370  self._onlyForElectron = kwargs.pop("onlyForElectron", False)
2371  self._onlyForConversion = kwargs.pop("onlyForConversion", False)
2372  self._purpose = kwargs.pop("purpose", None)
2373  self._page = kwargs.pop("page", None)
2374  self._section = kwargs.pop("section", None)
2375  self._numberOfEventsHistogram = kwargs.pop("numberOfEventsHistogram", None)
2376  if len(kwargs) > 0:
2377  raise Exception("Got unexpected keyword arguments: "+ ",".join(kwargs.keys()))
2378 
2379  def loopSubFolders(self):
2380  """Return True if the PlotGroups of this folder should be applied to the all subfolders"""
2381  return self._loopSubFolders
2382 
2383  def onlyForPileup(self):
2384  """Return True if the folder is intended only for pileup samples"""
2385  return self._onlyForPileup
2386 
2387  def onlyForElectron(self):
2388  return self._onlyForElectron
2389 
2391  return self._onlyForConversion
2392 
2393  def getPurpose(self):
2394  return self._purpose
2395 
2396  def getPage(self):
2397  return self._page
2398 
2399  def getSection(self):
2400  return self._section
2401 
2403  return self._numberOfEventsHistogram
2404 
2405  def append(self, plotGroup):
2406  self._plotGroups.append(plotGroup)
2407 
2408  def set(self, plotGroups):
2409  self._plotGroups = plotGroups
2410 
2411  def getPlotGroups(self):
2412  return self._plotGroups
2413 
2414  def getPlotGroup(self, name):
2415  for pg in self._plotGroups:
2416  if pg.getName() == name:
2417  return pg
2418  raise Exception("No PlotGroup named '%s'" % name)
2419 
2420  def create(self, dirsNEvents, labels, isPileupSample=True, requireAllHistograms=False):
2421  """Create histograms from a list of TFiles.
2422 
2423  Arguments:
2424  dirsNEvents -- List of (TDirectory, nevents) pairs
2425  labels -- List of strings for legend labels corresponding the files
2426  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2427  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2428  """
2429 
2430  if len(dirsNEvents) != len(labels):
2431  raise Exception("len(dirsNEvents) should be len(labels), now they are %d and %d" % (len(dirsNEvents), len(labels)))
2432 
2433  self._labels = labels
2434 
2435  for pg in self._plotGroups:
2436  if pg.onlyForPileup() and not isPileupSample:
2437  continue
2438  pg.create(dirsNEvents, requireAllHistograms)
2439 
2440  def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=True):
2441  """Draw and save all plots using settings of a given algorithm.
2442 
2443  Arguments:
2444  prefix -- Optional string for file name prefix (default None)
2445  separate -- Save the plots of a group to separate files instead of a file per group (default False)
2446  saveFormat -- String specifying the plot format (default '.pdf')
2447  ratio -- Add ratio to the plot (default True)
2448  """
2449  ret = []
2450 
2451  for pg in self._plotGroups:
2452  ret.extend(pg.draw(self._labels, prefix=prefix, separate=separate, saveFormat=saveFormat, ratio=ratio))
2453  return ret
2454 
2455 
2456  # These are to be overridden by derived classes for customisation
2457  def translateSubFolder(self, dqmSubFolderName):
2458  """Method called to (possibly) translate a subfolder name to more 'readable' form
2459 
2460  The implementation in this (base) class just returns the
2461  argument. The idea is that a deriving class might want to do
2462  something more complex (like trackingPlots.TrackingPlotFolder
2463  does)
2464  """
2465  return dqmSubFolderName
2466 
2467  def iterSelectionName(self, plotFolderName, translatedDqmSubFolder):
2468  """Iterate over possible selections name (used in output directory name and legend) from the name of PlotterFolder, and a return value of translateSubFolder"""
2469  ret = ""
2470  if plotFolderName != "":
2471  ret += "_"+plotFolderName
2472  if translatedDqmSubFolder is not None:
2473  ret += "_"+translatedDqmSubFolder
2474  yield ret
2475 
2476  def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder):
2477  """Return True if this subfolder should be processed
2478 
2479  Arguments:
2480  limitOnlyTo -- List/set/similar containing the translatedDqmSubFolder
2481  translatedDqmSubFolder -- Return value of translateSubFolder
2482  """
2483  return translatedDqmSubFolder in limitOnlyTo
2484 
2486  """Class to hold the original name and a 'translated' name of a subfolder in the DQM ROOT file"""
2487  def __init__(self, subfolder, translated):
2488  self.subfolder = subfolder
2489  self.translated = translated
2490 
2491  def equalTo(self, other):
2492  """Equality is defined by the 'translated' name"""
2493  return self.translated == other.translated
2494 
2496  """Plotter for one DQM folder.
2497 
2498  This class is supposed to be instantiated by the Plotter class (or
2499  PlotterItem, to be more specific), and not used directly by the
2500  user.
2501  """
2502  def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators):
2503  """
2504  Constructor
2505 
2506  Arguments:
2507  name -- Name of the folder (is used in the output directory naming)
2508  possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
2509  dqmSubFolders -- List of lists of strings for list of subfolders per input file, or None if no subfolders
2510  plotFolder -- PlotFolder object
2511  fallbackNames -- List of names for backward compatibility (can be empty). These are used only by validation.Validation (class responsible of the release validation workflow) in case the reference file pointed by 'name' does not exist.
2512  fallbackDqmSubFolders -- List of dicts of (string->string) for mapping the subfolder names found in the first file to another names. Use case is comparing files that have different iteration naming convention.
2513  tableCreators -- List of PlotterTableItem objects for tables to be created from this folder
2514  """
2515  self._name = name
2516  self._possibleDqmFolders = possibleDqmFolders
2517  self._plotFolder = plotFolder
2518  #self._dqmSubFolders = [map(lambda sf: DQMSubFolder(sf, self._plotFolder.translateSubFolder(sf)), lst) for lst in dqmSubFolders]
2519  if dqmSubFolders is None:
2520  self._dqmSubFolders = None
2521  else:
2522  # Match the subfolders between files in case the lists differ
2523  # equality is by the 'translated' name
2524  subfolders = {}
2525  for sf_list in dqmSubFolders:
2526  for sf in sf_list:
2527  sf_translated = self._plotFolder.translateSubFolder(sf)
2528  if sf_translated is not None and not sf_translated in subfolders:
2529  subfolders[sf_translated] = DQMSubFolder(sf, sf_translated)
2530 
2531  self._dqmSubFolders = subfolders.values()
2532  self._dqmSubFolders.sort(key=lambda sf: sf.subfolder)
2533 
2534  self._fallbackNames = fallbackNames
2535  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2536  self._tableCreators = tableCreators
2537 
2538  def getName(self):
2539  return self._name
2540 
2541  def getPurpose(self):
2542  return self._plotFolder.getPurpose()
2543 
2544  def getPage(self):
2545  return self._plotFolder.getPage()
2546 
2547  def getSection(self):
2548  return self._plotFolder.getSection()
2549 
2550  def onlyForPileup(self):
2551  return self._plotFolder.onlyForPileup()
2552 
2553  def onlyForElectron(self):
2554  return self._plotFolder.onlyForElectron()
2555 
2557  return self._plotFolder.onlyForConversion()
2558 
2560  return self._possibleDqmFolders
2561 
2562  def getDQMSubFolders(self, limitOnlyTo=None):
2563  """Get list of subfolders, possibly limiting to some of them.
2564 
2565  Keyword arguments:
2566  limitOnlyTo -- Object depending on the PlotFolder type for limiting the set of subfolders to be processed
2567  """
2568 
2569  if self._dqmSubFolders is None:
2570  return [None]
2571 
2572  if limitOnlyTo is None:
2573  return self._dqmSubFolders
2574 
2575  return filter(lambda s: self._plotFolder.limitSubFolder(limitOnlyTo, s.translated), self._dqmSubFolders)
2576 
2577  def getTableCreators(self):
2578  return self._tableCreators
2579 
2580  def getSelectionNameIterator(self, dqmSubFolder):
2581  """Get a generator for the 'selection name', looping over the name and fallbackNames"""
2582  for name in [self._name]+self._fallbackNames:
2583  for selname in self._plotFolder.iterSelectionName(name, dqmSubFolder.translated if dqmSubFolder is not None else None):
2584  yield selname
2585 
2586  def getSelectionName(self, dqmSubFolder):
2587  return next(self.getSelectionNameIterator(dqmSubFolder))
2588 
2589  def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False):
2590  """Create histograms from a list of TFiles.
2591  Arguments:
2592  files -- List of TFiles
2593  labels -- List of strings for legend labels corresponding the files
2594  dqmSubFolder -- DQMSubFolder object for a subfolder (or None for no subfolder)
2595  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2596  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2597  """
2598 
2599  subfolder = dqmSubFolder.subfolder if dqmSubFolder is not None else None
2600  neventsHisto = self._plotFolder.getNumberOfEventsHistogram()
2601  dirsNEvents = []
2602 
2603  for tfile in files:
2604  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, subfolder)
2605  # If file and any of possibleDqmFolders exist but subfolder does not, try the fallbacks
2607  for fallbackFunc in self._fallbackDqmSubFolders:
2608  fallback = fallbackFunc(subfolder)
2609  if fallback is not None:
2610  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, fallback)
2611  if ret is not GetDirectoryCode.SubDirNotExist:
2612  break
2613  d = GetDirectoryCode.codesToNone(ret)
2614  nev = None
2615  if neventsHisto is not None and tfile is not None:
2616  hnev = _getObject(tfile, neventsHisto)
2617  if hnev is not None:
2618  nev = hnev.GetEntries()
2619  dirsNEvents.append( (d, nev) )
2620 
2621  self._plotFolder.create(dirsNEvents, labels, isPileupSample, requireAllHistograms)
2622 
2623  def draw(self, *args, **kwargs):
2624  """Draw and save all plots using settings of a given algorithm."""
2625  return self._plotFolder.draw(*args, **kwargs)
2626 
2627 
2629  """Instance of plotter that knows the directory content, holds many folders."""
2630  def __init__(self, folders):
2631  self._plotterFolders = filter(lambda f: f is not None, folders)
2632 
2633  def iterFolders(self, limitSubFoldersOnlyTo=None):
2634  for plotterFolder in self._plotterFolders:
2635  limitOnlyTo = None
2636  if limitSubFoldersOnlyTo is not None:
2637  limitOnlyTo = limitSubFoldersOnlyTo.get(plotterFolder.getName(), None)
2638 
2639  for dqmSubFolder in plotterFolder.getDQMSubFolders(limitOnlyTo=limitOnlyTo):
2640  yield plotterFolder, dqmSubFolder
2641 
2642 # Helper for Plotter
2644  def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[]):
2645  """ Constructor
2646 
2647  Arguments:
2648  name -- Name of the folder (is used in the output directory naming)
2649  possibleDirs -- List of strings for possible directories of histograms in TFiles
2650  plotFolder -- PlotFolder object
2651 
2652  Keyword arguments
2653  fallbackNames -- Optional list of names for backward compatibility. These are used only by validation.Validation (class responsible of the release validation workflow) in case the reference file pointed by 'name' does not exist.
2654  fallbackDqmSubFolders -- Optional list of functions for (string->string) mapping the subfolder names found in the first file to another names (function should return None for no mapping). Use case is comparing files that have different iteration naming convention.
2655  """
2656  self._name = name
2657  self._possibleDirs = possibleDirs
2658  self._plotFolder = plotFolder
2659  self._fallbackNames = fallbackNames
2660  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2661  self._tableCreators = []
2662 
2663  def getName(self):
2664  return self._name
2665 
2666  def getPlotFolder(self):
2667  return self._plotFolder
2668 
2669  def appendTableCreator(self, tc):
2670  self._tableCreators.append(tc)
2671 
2672  def readDirs(self, files):
2673  """Read available subfolders from the files
2674 
2675  Arguments:
2676  files -- List of strings for paths to files, or list of TFiles
2677 
2678  For each file, loop over 'possibleDirs', and read the
2679  subfolders of first one that exists.
2680 
2681  Returns a PlotterFolder if at least one file for which one of
2682  'possibleDirs' exists. Otherwise, return None to signal that
2683  there is nothing available for this PlotFolder.
2684  """
2685  subFolders = None
2686  if self._plotFolder.loopSubFolders():
2687  subFolders = []
2688  possibleDirFound = False
2689  for fname in files:
2690  if fname is None:
2691  continue
2692 
2693  isOpenFile = isinstance(fname, ROOT.TFile)
2694  if isOpenFile:
2695  tfile = fname
2696  else:
2697  tfile = ROOT.TFile.Open(fname)
2698  for pd in self._possibleDirs:
2699  d = tfile.Get(pd)
2700  if d:
2701  possibleDirFound = True
2702  if subFolders is not None:
2703  subf = []
2704  for key in d.GetListOfKeys():
2705  if isinstance(key.ReadObj(), ROOT.TDirectory):
2706  subf.append(key.GetName())
2707  subFolders.append(subf)
2708  break
2709 
2710  if not isOpenFile:
2711  tfile.Close()
2712 
2713  if not possibleDirFound:
2714  return None
2715 
2716  return PlotterFolder(self._name, self._possibleDirs, subFolders, self._plotFolder, self._fallbackNames, self._fallbackDqmSubFolders, self._tableCreators)
2717 
2719  def __init__(self, possibleDirs, tableCreator):
2720  self._possibleDirs = possibleDirs
2721  self._tableCreator = tableCreator
2722 
2723  def create(self, openFiles, legendLabels, dqmSubFolder):
2724  if isinstance(dqmSubFolder, list):
2725  if len(dqmSubFolder) != len(openFiles):
2726  raise Exception("When dqmSubFolder is a list, len(dqmSubFolder) should be len(openFiles), now they are %d and %d" % (len(dqmSubFolder), len(openFiles)))
2727  else:
2728  dqmSubFolder = [dqmSubFolder]*len(openFiles)
2729  dqmSubFolder = [sf.subfolder if sf is not None else None for sf in dqmSubFolder]
2730 
2731  tbl = []
2732  for f, sf in zip(openFiles, dqmSubFolder):
2733  data = None
2734  tdir = _getDirectory(f, self._possibleDirs, sf)
2735  if tdir is not None:
2736  data = self._tableCreator.create(tdir)
2737  tbl.append(data)
2738 
2739  # Check if we have any content
2740  allNones = True
2741  colLen = 0
2742  for col in tbl:
2743  if col is not None:
2744  allNones = False
2745  colLen = len(col)
2746  break
2747  if allNones:
2748  return None
2749 
2750  # Replace all None columns with lists of column length
2751  for i in xrange(len(tbl)):
2752  if tbl[i] is None:
2753  tbl[i] = [None]*colLen
2754 
2755  return html.Table(columnHeaders=legendLabels, rowHeaders=self._tableCreator.headers(), table=tbl,
2756  purpose=self._tableCreator.getPurpose(), page=self._tableCreator.getPage(), section=self._tableCreator.getSection(dqmSubFolder[0]))
2757 
2758 class Plotter:
2759  """Contains PlotFolders, i.e. the information what plots to do, and creates a helper object to actually produce the plots."""
2760  def __init__(self):
2761  self._plots = []
2762  _setStyle()
2763  ROOT.TH1.AddDirectory(False)
2764 
2765  def append(self, *args, **kwargs):
2766  """Append a plot folder to the plotter.
2767 
2768  All arguments are forwarded to the constructor of PlotterItem.
2769  """
2770  self._plots.append(PlotterItem(*args, **kwargs))
2771 
2772  def appendTable(self, attachToFolder, *args, **kwargs):
2773  for plotterItem in self._plots:
2774  if plotterItem.getName() == attachToFolder:
2775  plotterItem.appendTableCreator(PlotterTableItem(*args, **kwargs))
2776  return
2777  raise Exception("Did not find plot folder '%s' when trying to attach a table creator to it" % attachToFolder)
2778 
2779  def clear(self):
2780  """Remove all plot folders and tables"""
2781  self._plots = []
2782 
2784  return [item.getName() for item in self._plots]
2785 
2786  def getPlotFolders(self):
2787  return [item.getPlotFolder() for item in self._plots]
2788 
2789  def getPlotFolder(self, name):
2790  for item in self._plots:
2791  if item.getName() == name:
2792  return item.getPlotFolder()
2793  raise Exception("No PlotFolder named '%s'" % name)
2794 
2795  def readDirs(self, *files):
2796  """Returns PlotterInstance object, which knows how exactly to produce the plots for these files"""
2797  return PlotterInstance([plotterItem.readDirs(files) for plotterItem in self._plots])
def _getYmin
Definition: plotting.py:368
def _getYminMaxAroundMedian
Definition: plotting.py:410
def _getXmax
Definition: plotting.py:352
def _getYmaxWithError
Definition: plotting.py:390
def setXTitleOffset
Definition: plotting.py:1088
def _getYminIgnoreOutlier
Definition: plotting.py:393
def _getXmin
Definition: plotting.py:336
def _th1ToOrderedDict
Definition: plotting.py:96
def _getYmax
Definition: plotting.py:379
def _createCanvas
Definition: plotting.py:105
bool equal(const T &first, const T &second)
Definition: Equal.h:34
def adjustMarginLeft
Definition: plotting.py:1067
def _findBounds
Definition: plotting.py:446
def _drawFrame
Definition: plotting.py:1000
Definition: style.py:1
def drawRatioUncertainty
Definition: plotting.py:1650
OutputIterator zip(InputIterator1 first1, InputIterator1 last1, InputIterator2 first2, InputIterator2 last2, OutputIterator result, Compare comp)
Abs< T >::type abs(const T &t)
Definition: Abs.h:22
#define end
Definition: vmac.h:37
T min(T a, T b)
Definition: MathUtil.h:58
def adjustMarginRight
Definition: plotting.py:1073
def __init__
Definition: plotting.py:936
def _getObject
Definition: plotting.py:45
def _modifyPadForRatio
Definition: plotting.py:115
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def _getDirectory
Definition: plotting.py:93
def getNumberOfHistograms
Definition: plotting.py:1623
def _getOrCreateObject
Definition: plotting.py:53
def _copyStyle
Definition: plotting.py:1464
#define begin
Definition: vmac.h:30
def _setStyle
Definition: plotting.py:17
def getNumberOfEventsHistogram
Definition: plotting.py:2402
def setYTitleOffset
Definition: plotting.py:1100
def _getDirectoryDetailed
Definition: plotting.py:69
auto wrap(F iFunc) -> decltype(iFunc())
def _findBoundsY
Definition: plotting.py:514
def setProperties
Definition: plotting.py:1610
def getPlotFolderNames
Definition: plotting.py:2783
How EventSelector::AcceptEvent() decides whether to accept an event for output otherwise it is excluding the probing of A single or multiple positive and the trigger will pass if any such matching triggers are PASS or EXCEPTION[A criterion thatmatches no triggers at all is detected and causes a throw.] A single negative with an expectation of appropriate bit checking in the decision and the trigger will pass if any such matching triggers are FAIL or EXCEPTION A wildcarded negative criterion that matches more than one trigger in the trigger list("!*","!HLTx*"if it matches 2 triggers or more) will accept the event if all the matching triggers are FAIL.It will reject the event if any of the triggers are PASS or EXCEPTION(this matches the behavior of"!*"before the partial wildcard feature was incorporated).Triggers which are in the READY state are completely ignored.(READY should never be returned since the trigger paths have been run
def _calculateRatios
Definition: plotting.py:144