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 array
4 import difflib
5 
6 import ROOT
7 ROOT.gROOT.SetBatch(True)
8 ROOT.PyConfig.IgnoreCommandLineOptions = True
9 
10 import html
11 
12 verbose=False
13 
14 def _getObject(tdirectory, name):
15  obj = tdirectory.Get(name)
16  if not obj:
17  if verbose:
18  print "Did not find {obj} from {dir}".format(obj=name, dir=tdirectory.GetPath())
19  return None
20  return obj
21 
22 def _getOrCreateObject(tdirectory, nameOrCreator):
23  if hasattr(nameOrCreator, "create"):
24  return nameOrCreator.create(tdirectory)
25  return _getObject(tdirectory, nameOrCreator)
26 
28  class FileNotExist: pass
30  class SubDirNotExist: pass
31 
32  @staticmethod
33  def codesToNone(code):
35  return None
36  return code
37 
38 def _getDirectoryDetailed(tfile, possibleDirs, subDir=None):
39  """Get TDirectory from TFile."""
40  if tfile is None:
42  for pdf in possibleDirs:
43  d = tfile.Get(pdf)
44  if d:
45  if subDir is not None:
46  # Pick associator if given
47  d = d.Get(subDir)
48  if d:
49  return d
50  else:
51  if verbose:
52  print "Did not find subdirectory '%s' from directory '%s' in file %s" % (subDir, pdf, tfile.GetName())
53 # if "Step" in subDir:
54 # raise Exception("Foo")
56  else:
57  return d
58  if verbose:
59  print "Did not find any of directories '%s' from file %s" % (",".join(possibleDirs), tfile.GetName())
61 
62 def _getDirectory(*args, **kwargs):
63  return GetDirectoryCode.codesToNone(_getDirectoryDetailed(*args, **kwargs))
64 
65 def _createCanvas(name, width, height):
66  # silence warning of deleting canvas with the same name
67  if not verbose:
68  backup = ROOT.gErrorIgnoreLevel
69  ROOT.gErrorIgnoreLevel = ROOT.kError
70  canvas = ROOT.TCanvas(name, name, width, height)
71  if not verbose:
72  ROOT.gErrorIgnoreLevel = backup
73  return canvas
74 
75 
76 def _getXmin(obj, limitToNonZeroContent=False):
77  if isinstance(obj, ROOT.TH1):
78  xaxis = obj.GetXaxis()
79  if limitToNonZeroContent:
80  for i in xrange(1, obj.GetNbinsX()+1):
81  if obj.GetBinContent(i) != 0:
82  return xaxis.GetBinLowEdge(i)
83  return xaxis.GetBinLowEdge(xaxis.GetLast())
84  else:
85  return xaxis.GetBinLowEdge(xaxis.GetFirst())
86  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
87  return min([obj.GetX()[i] for i in xrange(0, obj.GetN())])*0.9
88  raise Exception("Unsupported type %s" % str(obj))
89 
90 def _getXmax(obj, limitToNonZeroContent=False):
91  if isinstance(obj, ROOT.TH1):
92  xaxis = obj.GetXaxis()
93  if limitToNonZeroContent:
94  for i in xrange(obj.GetNbinsX(), 0, -1):
95  if obj.GetBinContent(i) != 0:
96  return xaxis.GetBinUpEdge(i)
97  return xaxis.GetBinUpEdge(xaxis.GetFirst())
98  else:
99  return xaxis.GetBinUpEdge(xaxis.GetLast())
100  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
101  return max([obj.GetX()[i] for i in xrange(0, obj.GetN())])*1.02
102  raise Exception("Unsupported type %s" % str(obj))
103 
104 def _getYmin(obj):
105  if isinstance(obj, ROOT.TH1):
106  return obj.GetMinimum()
107  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
108  return min([obj.GetY()[i] for i in xrange(0, obj.GetN())])
109  raise Exception("Unsupported type %s" % str(obj))
110 
111 def _getYmax(obj):
112  if isinstance(obj, ROOT.TH1):
113  return obj.GetMaximum()
114  elif isinstance(obj, ROOT.TGraph) or isinstance(obj, ROOT.TGraph2D):
115  return max([obj.GetY()[i] for i in xrange(0, obj.GetN())])
116  raise Exception("Unsupported type %s" % str(obj))
117 
119  return max([th1.GetBinContent(i)+th1.GetBinError(i) for i in xrange(1, th1.GetNbinsX()+1)])
120 
122  yvals = filter(lambda n: n>0, [th1.GetBinContent(i) for i in xrange(1, th1.GetNbinsX()+1)])
123  yvals.sort()
124  if len(yvals) == 0:
125  return th1.GetMinimum()
126  if len(yvals) == 1:
127  return yvals[0]
128 
129  # Define outlier as being x10 less than minimum of the 95 % of the non-zero largest values
130  ind_min = len(yvals)-1 - int(len(yvals)*0.95)
131  min_val = yvals[ind_min]
132  for i in xrange(0, ind_min):
133  if yvals[i] > 0.1*min_val:
134  return yvals[i]
135 
136  return min_val
137 
138 def _findBounds(th1s, ylog, xmin=None, xmax=None, ymin=None, ymax=None):
139  """Find x-y axis boundaries encompassing a list of TH1s if the bounds are not given in arguments.
140 
141  Arguments:
142  th1s -- List of TH1s
143  ylog -- Boolean indicating if y axis is in log scale or not (affects the automatic ymax)
144 
145  Keyword arguments:
146  xmin -- Minimum x value; if None, take the minimum of TH1s
147  xmax -- Maximum x value; if None, take the maximum of TH1s
148  xmin -- Minimum y value; if None, take the minimum of TH1s
149  xmax -- Maximum y value; if None, take the maximum of TH1s
150  """
151 
152  def y_scale_max(y):
153  if ylog:
154  return 1.5*y
155  return 1.05*y
156 
157  def y_scale_min(y):
158  # assuming log
159  return 0.9*y
160 
161  if xmin is None or xmax is None or ymin is None or ymax is None or \
162  isinstance(xmin, list) or isinstance(max, list) or isinstance(ymin, list) or isinstance(ymax, list):
163  xmins = []
164  xmaxs = []
165  ymins = []
166  ymaxs = []
167  for th1 in th1s:
168  xmins.append(_getXmin(th1, limitToNonZeroContent=isinstance(xmin, list)))
169  xmaxs.append(_getXmax(th1, limitToNonZeroContent=isinstance(xmax, list)))
170  if ylog and isinstance(ymin, list):
171  ymins.append(_getYminIgnoreOutlier(th1))
172  else:
173  ymins.append(_getYmin(th1))
174  ymaxs.append(_getYmax(th1))
175 # ymaxs.append(_getYmaxWithError(th1))
176 
177  if xmin is None:
178  xmin = min(xmins)
179  elif isinstance(xmin, list):
180  xm = min(xmins)
181  xmins_below = filter(lambda x: x<=xm, xmin)
182  if len(xmins_below) == 0:
183  xmin = min(xmin)
184  if xm < xmin:
185  if verbose:
186  print "Histogram minimum x %f is below all given xmin values %s, using the smallest one" % (xm, str(xmin))
187  else:
188  xmin = max(xmins_below)
189 
190  if xmax is None:
191  xmax = max(xmaxs)
192  elif isinstance(xmax, list):
193  xm = max(xmaxs)
194  xmaxs_above = filter(lambda x: x>xm, xmax)
195  if len(xmaxs_above) == 0:
196  xmax = max(xmax)
197  if xm > xmax:
198  if verbose:
199  print "Histogram maximum x %f is above all given xmax values %s, using the maximum one" % (xm, str(xmax))
200  else:
201  xmax = min(xmaxs_above)
202 
203  if ymin is None:
204  ymin = min(ymins)
205  elif isinstance(ymin, list):
206  ym_unscaled = min(ymins)
207  ym = y_scale_min(ym_unscaled)
208  ymins_below = filter(lambda y: y<=ym, ymin)
209  if len(ymins_below) == 0:
210  ymin = min(ymin)
211  if ym_unscaled < ymin:
212  if verbose:
213  print "Histogram minimum y %f is below all given ymin values %s, using the smallest one" % (ym, str(ymin))
214  else:
215  ymin = max(ymins_below)
216 
217  if ymax is None:
218  ymax = y_scale_max(max(ymaxs))
219  elif isinstance(ymax, list):
220  ym_unscaled = max(ymaxs)
221  ym = y_scale_max(ym_unscaled)
222  ymaxs_above = filter(lambda y: y>ym, ymax)
223  if len(ymaxs_above) == 0:
224  ymax = max(ymax)
225  if ym_unscaled > ymax:
226  if verbose:
227  print "Histogram maximum y %f is above all given ymax values %s, using the maximum one" % (ym_unscaled, str(ymax))
228  else:
229  ymax = min(ymaxs_above)
230 
231  for th1 in th1s:
232  th1.GetXaxis().SetRangeUser(xmin, xmax)
233  th1.GetYaxis().SetRangeUser(ymin, ymax)
234 
235  return (xmin, ymin, xmax, ymax)
236 
237 class Subtract:
238  """Class for subtracting two histograms"""
239  def __init__(self, name, nameA, nameB, title=""):
240  """Constructor
241 
242  Arguments:
243  name -- String for name of the resulting histogram (A-B)
244  nameA -- String for A histogram
245  nameB -- String for B histogram
246 
247  Keyword arguments:
248  title -- String for a title of the resulting histogram (default "")
249 
250  Uncertainties are calculated with the assumption that B is a
251  subset of A, and the histograms contain event counts.
252  """
253  self._name = name
254  self._nameA = nameA
255  self._nameB = nameB
256  self._title = title
257 
258  def __str__(self):
259  """String representation, returns the name"""
260  return self._name
261 
262  def create(self, tdirectory):
263  """Create and return the fake+duplicate histogram from a TDirectory"""
264  histoA = _getObject(tdirectory, self._nameA)
265  histoB = _getObject(tdirectory, self._nameB)
266 
267  if not histoA or not histoB:
268  return None
269 
270  ret = histoA.Clone(self._name)
271  ret.SetTitle(self._title)
272 
273  for i in xrange(1, histoA.GetNbinsX()+1):
274  val = histoA.GetBinContent(i)-histoB.GetBinContent(i)
275  ret.SetBinContent(i, val)
276  ret.SetBinError(i, math.sqrt(val))
277 
278  return ret
279 
281  """Class to calculate the fake+duplicate rate"""
282  def __init__(self, name, assoc, dup, reco, title=""):
283  """Constructor.
284 
285  Arguments:
286  name -- String for the name of the resulting efficiency histogram
287  assoc -- String for the name of the "associated" histogram
288  dup -- String for the name of the "duplicates" histogram
289  reco -- String for the name of the "reco" (denominator) histogram
290 
291  Keyword arguments:
292  title -- String for a title of the resulting histogram (default "")
293 
294  The result is calculated as 1 - (assoc - dup) / reco
295  """
296  self._name = name
297  self._assoc = assoc
298  self._dup = dup
299  self._reco = reco
300  self._title = title
301 
302  def __str__(self):
303  """String representation, returns the name"""
304  return self._name
305 
306  def create(self, tdirectory):
307  """Create and return the fake+duplicate histogram from a TDirectory"""
308  # Get the numerator/denominator histograms
309  hassoc = _getObject(tdirectory, self._assoc)
310  hdup = _getObject(tdirectory, self._dup)
311  hreco = _getObject(tdirectory, self._reco)
312 
313  # Skip if any of them does not exist
314  if not hassoc or not hdup or not hreco:
315  return None
316 
317  hfakedup = hreco.Clone(self._name)
318  hfakedup.SetTitle(self._title)
319 
320  for i in xrange(1, hassoc.GetNbinsX()+1):
321  numerVal = hassoc.GetBinContent(i) - hdup.GetBinContent(i)
322  denomVal = hreco.GetBinContent(i)
323 
324  fakedupVal = (1 - numerVal / denomVal) if denomVal != 0.0 else 0.0
325  errVal = math.sqrt(fakedupVal*(1-fakedupVal)/denomVal) if (denomVal != 0.0 and fakedupVal <= 1) else 0.0
326 
327  hfakedup.SetBinContent(i, fakedupVal)
328  hfakedup.SetBinError(i, errVal)
329 
330  return hfakedup
331 
333  """Class to create a histogram by aggregating bins of another histogram to a bin of the resulting histogram."""
334  def __init__(self, name, histoName, mapping, normalizeTo=None, scale=None, renameBin=None, ignoreMissingBins=False, minExistingBins=None):
335  """Constructor.
336 
337  Arguments:
338  name -- String for the name of the resulting histogram
339  histoName -- String for the name of the source histogram
340  mapping -- Dictionary or list for mapping the bins (see below)
341 
342  Keyword arguments:
343  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.
344  scale -- Optional number for scaling the histogram (passed to ROOT.TH1.Scale())
345  renameBin -- Optional function (string -> string) to rename the bins of the input histogram
346 
347  Mapping structure (mapping):
348 
349  Dictionary (you probably want to use collections.OrderedDict)
350  should be a mapping from the destination bin label to a list
351  of source bin labels ("dst -> [src]").
352 
353  If the mapping is a list, it should only contain the source
354  bin labels. In this case, the resulting histogram contains a
355  subset of the bins of the source histogram.
356  """
357  self._name = name
358  self._histoName = histoName
359  self._mapping = mapping
360  self._normalizeTo = normalizeTo
361  self._scale = scale
362  self._renameBin = renameBin
363  self._ignoreMissingBins = ignoreMissingBins
364  self._minExistingBins = minExistingBins
365 
366  def __str__(self):
367  """String representation, returns the name"""
368  return self._name
369 
370  def create(self, tdirectory):
371  """Create and return the histogram from a TDirectory"""
372  th1 = _getOrCreateObject(tdirectory, self._histoName)
373  if th1 is None:
374  return None
375 
376  binLabels = [""]*len(self._mapping)
377  binValues = [None]*len(self._mapping)
378 
379  # TH1 can't really be used as a map/dict, so convert it here:
380  values = {}
381  for i in xrange(1, th1.GetNbinsX()+1):
382  binLabel = th1.GetXaxis().GetBinLabel(i)
383  if self._renameBin is not None:
384  binLabel = self._renameBin(binLabel)
385  values[binLabel] = (th1.GetBinContent(i), th1.GetBinError(i))
386 
387  if isinstance(self._mapping, list):
388  for i, label in enumerate(self._mapping):
389  try:
390  binValues[i] = values[label]
391  except KeyError:
392  pass
393  binLabels[i] = label
394  else:
395  for i, (key, labels) in enumerate(self._mapping.iteritems()):
396  sumTime = 0.
397  sumErrorSq = 0.
398  nsum = 0
399  for l in labels:
400  try:
401  sumTime += values[l][0]
402  sumErrorSq += values[l][1]**2
403  nsum += 1
404  except KeyError:
405  pass
406 
407  if nsum > 0:
408  binValues[i] = (sumTime, math.sqrt(sumErrorSq))
409  binLabels[i] = key
410 
411  if self._minExistingBins is not None and (len(binValues)-binValues.count(None)) < self._minExistingBins:
412  return None
413 
414  if self._ignoreMissingBins:
415  for i, val in enumerate(binValues):
416  if val is None:
417  binLabels[i] = None
418  binValues = filter(lambda v: v is not None, binValues)
419  binLabels = filter(lambda v: v is not None, binLabels)
420  if len(binValues) == 0:
421  return None
422 
423  result = ROOT.TH1F(self._name, self._name, len(binValues), 0, len(binValues))
424  for i, (value, label) in enumerate(zip(binValues, binLabels)):
425  if value is not None:
426  result.SetBinContent(i+1, value[0])
427  result.SetBinError(i+1, value[1])
428  result.GetXaxis().SetBinLabel(i+1, label)
429 
430  if self._normalizeTo is not None:
431  bin = th1.GetXaxis().FindBin(self._normalizeTo)
432  if bin <= 0:
433  print "Trying to normalize {name} to {binlabel}, which does not exist".format(name=self._name, binlabel=self._normalizeTo)
434  sys.exit(1)
435  value = th1.GetBinContent(bin)
436  if value != 0:
437  result.Scale(1/value)
438 
439  if self._scale is not None:
440  result.Scale(self._scale)
441 
442  return result
443 
445  """Class to create a histogram by aggregaging integrals of another histoggrams."""
446  def __init__(self, name, mapping, normalizeTo=None):
447  """Constructor.
448 
449  Arguments:
450  name -- String for the name of the resulting histogram
451  mapping -- Dictionary for mapping the bin label to a histogram name
452 
453  Keyword arguments:
454  normalizeTo -- Optional string for a histogram. If given, all bins of the resulting histograqm are divided by the integral of this histogram.
455  """
456  self._name = name
457  self._mapping = mapping
458  self._normalizeTo = normalizeTo
459 
460  def __str__(self):
461  """String representation, returns the name"""
462  return self._name
463 
464  def create(self, tdirectory):
465  """Create and return the histogram from a TDirectory"""
466  result = []
467  for key, histoName in self._mapping.iteritems():
468  th1 = _getObject(tdirectory, histoName)
469  if th1 is None:
470  continue
471  result.append( (key, th1.Integral(0, th1.GetNbinsX()+1)) ) # include under- and overflow bins
472  if len(result) == 0:
473  return None
474 
475  res = ROOT.TH1F(self._name, self._name, len(result), 0, len(result))
476 
477  for i, (name, count) in enumerate(result):
478  res.SetBinContent(i+1, count)
479  res.GetXaxis().SetBinLabel(i+1, name)
480 
481  if self._normalizeTo is not None:
482  th1 = _getObject(tdirectory, self._normalizeTo)
483  if th1 is None:
484  return None
485  scale = th1.Integral(0, th1.GetNbinsX()+1)
486  res.Scale(1/scale)
487 
488  return res
489 
490 class ROC:
491  """Class to construct a ROC curve (e.g. efficiency vs. fake rate) from two histograms"""
492  def __init__(self, name, xhistoName, yhistoName, zaxis=False):
493  """Constructor.
494 
495  Arguments:
496  name -- String for the name of the resulting histogram
497  xhistoName -- String for the name of the x-axis histogram (or another "creator" object)
498  yhistoName -- String for the name of the y-axis histogram (or another "creator" object)
499 
500  Keyword arguments:
501  zaxis -- If set to True (default False), create a TGraph2D with z axis showing the cut value (recommended drawStyle 'pcolz')
502  """
503  self._name = name
504  self._xhistoName = xhistoName
505  self._yhistoName = yhistoName
506  self._zaxis = zaxis
507 
508  def __str__(self):
509  """String representation, returns the name"""
510  return self._name
511 
512  def create(self, tdirectory):
513  """Create and return the histogram from a TDirectory"""
514  xhisto = _getOrCreateObject(tdirectory, self._xhistoName)
515  yhisto = _getOrCreateObject(tdirectory, self._yhistoName);
516  if xhisto is None or yhisto is None:
517  return None
518 
519  x = []
520  xerrup = []
521  xerrdown = []
522  y = []
523  yerrup = []
524  yerrdown = []
525  z = []
526 
527  for i in xrange(1, xhisto.GetNbinsX()+1):
528  x.append(xhisto.GetBinContent(i))
529  xerrup.append(xhisto.GetBinError(i))
530  xerrdown.append(xhisto.GetBinError(i))
531 
532  y.append(yhisto.GetBinContent(i))
533  yerrup.append(yhisto.GetBinError(i))
534  yerrdown.append(yhisto.GetBinError(i))
535 
536  z.append(xhisto.GetXaxis().GetBinUpEdge(i))
537 
538  # If either axis has only zeroes, no graph makes no point
539  if x.count(0.0) == len(x) or y.count(0.0) == len(y):
540  return None
541 
542  arr = lambda v: array.array("d", v)
543  gr = None
544  if self._zaxis:
545  gr = ROOT.TGraph2D(len(x), arr(x), arr(y), arr(z))
546  else:
547  gr = ROOT.TGraphAsymmErrors(len(x), arr(x), arr(y), arr(xerrdown), arr(xerrup), arr(yerrdown), arr(yerrup))
548  gr.SetTitle("")
549  return gr
550 
551 
552 # Plot styles
553 _plotStylesColor = [4, 2, ROOT.kBlack, ROOT.kOrange+7, ROOT.kMagenta-3]
554 _plotStylesMarker = [21, 20, 22, 34, 33]
555 
556 def _drawFrame(pad, bounds, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None, suffix=""):
557  """Function to draw a frame
558 
559  Arguments:
560  pad -- TPad to where the frame is drawn
561  bounds -- List or 4-tuple for (xmin, ymin, xmax, ymax)
562 
563  Keyword arguments:
564  xbinlabels -- Optional list of strings for x axis bin labels
565  xbinlabelsize -- Optional number for the x axis bin label size
566  xbinlabeloption -- Optional string for the x axis bin options (passed to ROOT.TH1.LabelsOption())
567  suffix -- Optional string for a postfix of the frame name
568  """
569  if xbinlabels is None:
570  frame = pad.DrawFrame(*bounds)
571  else:
572  # Special form needed if want to set x axis bin labels
573  nbins = len(xbinlabels)
574  frame = ROOT.TH1F("hframe"+suffix, "", nbins, bounds[0], bounds[2])
575  frame.SetBit(ROOT.TH1.kNoStats)
576  frame.SetBit(ROOT.kCanDelete)
577  frame.SetMinimum(bounds[1])
578  frame.SetMaximum(bounds[3])
579  frame.GetYaxis().SetLimits(bounds[1], bounds[3])
580  frame.Draw("")
581 
582  xaxis = frame.GetXaxis()
583  for i in xrange(nbins):
584  xaxis.SetBinLabel(i+1, xbinlabels[i])
585  if xbinlabelsize is not None:
586  xaxis.SetLabelSize(xbinlabelsize)
587  if xbinlabeloption is not None:
588  frame.LabelsOption(xbinlabeloption)
589 
590  return frame
591 
592 class Frame:
593  """Class for creating and managing a frame for a simple, one-pad plot"""
594  def __init__(self, pad, bounds, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None):
595  self._pad = pad
596  self._frame = _drawFrame(pad, bounds, xbinlabels, xbinlabelsize, xbinlabeloption)
597 
598  yoffsetFactor = 1
599  xoffsetFactor = 1
600  if nrows == 2:
601  yoffsetFactor *= 2
602  xoffsetFactor *= 2
603  elif nrows >= 3:
604  yoffsetFactor *= 4
605  xoffsetFactor *= 3
606 
607  self._frame.GetYaxis().SetTitleOffset(self._frame.GetYaxis().GetTitleOffset()*yoffsetFactor)
608  self._frame.GetXaxis().SetTitleOffset(self._frame.GetXaxis().GetTitleOffset()*xoffsetFactor)
609 
610 
611  def setLogx(self, log):
612  self._pad.SetLogx(log)
613 
614  def setLogy(self, log):
615  self._pad.SetLogy(log)
616 
617  def setGridx(self, grid):
618  self._pad.SetGridx(grid)
619 
620  def setGridy(self, grid):
621  self._pad.SetGridy(grid)
622 
623  def adjustMarginLeft(self, adjust):
624  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
625  # Need to redraw frame after adjusting the margin
626  self._pad.cd()
627  self._frame.Draw("")
628 
629  def adjustMarginRight(self, adjust):
630  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
631  # Need to redraw frame after adjusting the margin
632  self._pad.cd()
633  self._frame.Draw("")
634 
635  def setTitle(self, title):
636  self._frame.SetTitle(title)
637 
638  def setXTitle(self, title):
639  self._frame.GetXaxis().SetTitle(title)
640 
641  def setXTitleSize(self, size):
642  self._frame.GetXaxis().SetTitleSize(size)
643 
644  def setXTitleOffset(self, offset):
645  self._frame.GetXaxis().SetTitleOffset(offset)
646 
647  def setXLabelSize(self, size):
648  self._frame.GetXaxis().SetLabelSize(size)
649 
650  def setYTitle(self, title):
651  self._frame.GetYaxis().SetTitle(title)
652 
653  def setYTitleSize(self, size):
654  self._frame.GetYaxis().SetTitleSize(size)
655 
656  def setYTitleOffset(self, offset):
657  self._frame.GetYaxis().SetTitleOffset(offset)
658 
659  def redrawAxis(self):
660  self._pad.RedrawAxis()
661 
663  """Class for creating and managing a frame for a ratio plot with two subpads"""
664  def __init__(self, pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels=None, xbinlabelsize=None, xbinlabeloption=None):
665  self._parentPad = pad
666  self._pad = pad.cd(1)
667  if xbinlabels is not None:
668  self._frame = _drawFrame(self._pad, bounds, [""]*len(xbinlabels))
669  else:
670  self._frame = _drawFrame(self._pad, bounds)
671  self._padRatio = pad.cd(2)
672  self._frameRatio = _drawFrame(self._padRatio, ratioBounds, xbinlabels, xbinlabelsize, xbinlabeloption)
673 
674  self._frame.GetXaxis().SetLabelSize(0)
675  self._frame.GetXaxis().SetTitleSize(0)
676 
677  yoffsetFactor = ratioFactor
678  divisionPoint = 1-1/ratioFactor
679  xoffsetFactor = 1/divisionPoint #* 0.6
680 
681  if nrows == 1:
682  xoffsetFactor *= 0.6
683  elif nrows == 2:
684  yoffsetFactor *= 2
685  xoffsetFactor *= 1.5
686  elif nrows == 3:
687  yoffsetFactor *= 4
688  xoffsetFactor *= 2.3
689  elif nrows >= 4:
690  yoffsetFactor *= 5
691  xoffsetFactor *= 3
692 
693  self._frame.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
694  self._frameRatio.GetYaxis().SetLabelSize(int(self._frameRatio.GetYaxis().GetLabelSize()*0.8))
695  self._frameRatio.GetYaxis().SetTitleOffset(self._frameRatio.GetYaxis().GetTitleOffset()*yoffsetFactor)
696  self._frameRatio.GetXaxis().SetTitleOffset(self._frameRatio.GetXaxis().GetTitleOffset()*xoffsetFactor)
697 
698  self._frameRatio.GetYaxis().SetNdivisions(4, 5, 0)
699 
700  self._frameRatio.GetYaxis().SetTitle("Ratio")
701 
702  def setLogx(self, log):
703  self._pad.SetLogx(log)
704  self._padRatio.SetLogx(log)
705 
706  def setLogy(self, log):
707  self._pad.SetLogy(log)
708 
709  def setGridx(self, grid):
710  self._pad.SetGridx(grid)
711  self._padRatio.SetGridx(grid)
712 
713  def setGridy(self, grid):
714  self._pad.SetGridy(grid)
715  self._padRatio.SetGridy(grid)
716 
717  def adjustMarginLeft(self, adjust):
718  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
719  self._padRatio.SetLeftMargin(self._padRatio.GetLeftMargin()+adjust)
720  # Need to redraw frame after adjusting the margin
721  self._pad.cd()
722  self._frame.Draw("")
723  self._padRatio.cd()
724  self._frameRatio.Draw("")
725 
726  def adjustMarginRight(self, adjust):
727  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
728  self._padRatio.SetRightMargin(self._padRatio.GetRightMargin()+adjust)
729  # Need to redraw frames after adjusting the margin
730  self._pad.cd()
731  self._frame.Draw("")
732  self._padRatio.cd()
733  self._frameRatio.Draw("")
734 
735  def setTitle(self, title):
736  self._frame.SetTitle(title)
737 
738  def setXTitle(self, title):
739  self._frameRatio.GetXaxis().SetTitle(title)
740 
741  def setXTitleSize(self, size):
742  self._frameRatio.GetXaxis().SetTitleSize(size)
743 
744  def setYTitleOffset(self, offset):
745  self._frameRatio.GetXaxis().SetTitleOffset(offset)
746 
747  def setXLabelSize(self, size):
748  self._frameRatio.GetXaxis().SetLabelSize(size)
749 
750  def setYTitle(self, title):
751  self._frame.GetYaxis().SetTitle(title)
752 
753  def setYTitleRatio(self, title):
754  self._frameRatio.GetYaxis().SetTitle(title)
755 
756  def setYTitleSize(self, size):
757  self._frame.GetYaxis().SetTitleSize(size)
758  self._frameRatio.GetYaxis().SetTitleSize(size)
759 
760  def setYTitleOffset(self, offset):
761  self._frame.GetYaxis().SetTitleOffset(offset)
762  self._frameRatio.GetYaxis().SetTitleOffset(offset)
763 
764  def redrawAxis(self):
765  self._padRatio.RedrawAxis()
766  self._pad.RedrawAxis()
767 
768  self._parentPad.cd()
769 
770  # pad to hide the lowest y axis label of the main pad
771  xmin=0.065
772  ymin=0.285
773  xmax=0.128
774  ymax=0.33
775  self._coverPad = ROOT.TPad("coverpad", "coverpad", xmin, ymin, xmax, ymax)
776  self._coverPad.SetBorderMode(0)
777  self._coverPad.Draw()
778 
779  self._pad.cd()
780  self._pad.Pop() # Move the first pad on top
781 
783  """Class for creating and managing a frame for a plot from TGraph2D"""
784  def __init__(self, pad, bounds, histos, ratioOrig, ratioFactor):
785  self._pad = pad
786  if ratioOrig:
787  self._pad = pad.cd(1)
788 
789  # adjust margins because of not having the ratio, we want
790  # the same bottom margin, so some algebra gives this
791  (xlow, ylow, width, height) = (self._pad.GetXlowNDC(), self._pad.GetYlowNDC(), self._pad.GetWNDC(), self._pad.GetHNDC())
792  xup = xlow+width
793  yup = ylow+height
794 
795  bottomMargin = self._pad.GetBottomMargin()
796  bottomMarginNew = ROOT.gStyle.GetPadBottomMargin()
797 
798  ylowNew = yup - (1-bottomMargin)/(1-bottomMarginNew) * (yup-ylow)
799  topMarginNew = self._pad.GetTopMargin() * (yup-ylow)/(yup-ylowNew)
800 
801  self._pad.SetPad(xlow, ylowNew, xup, yup)
802  self._pad.SetTopMargin(topMarginNew)
803  self._pad.SetBottomMargin(bottomMarginNew)
804 
805  self._view = ROOT.TView.CreateView()
806  self._view.SetRange(bounds[0], bounds[1], 0, bounds[2], bounds[3], 20) # 20 is from Harrison-Stetson, may need tuning?
807  self._view.Top()
808  self._view.ShowAxis()
809 
810  self._xtitleoffset = 1.8
811  self._ytitleoffset = 2.3
812 
813  self._firstHisto = histos[0]
814 
815  def setLogx(self, log):
816  pass
817 
818  def setLogy(self, log):
819  pass
820 
821  def setGridx(self, grid):
822  pass
823 
824  def setGridy(self, grid):
825  pass
826 
827  def adjustMarginLeft(self, adjust):
828  self._pad.SetLeftMargin(self._pad.GetLeftMargin()+adjust)
829  self._pad.cd()
830 
831  def adjustMarginRight(self, adjust):
832  self._pad.SetRightMargin(self._pad.GetRightMargin()+adjust)
833  self._pad.cd()
834 
835  def setTitle(self, title):
836  pass
837 
838  def setXTitle(self, title):
839  self._xtitle = title
840 
841  def setXTitleSize(self, size):
842  self._xtitlesize = size
843 
844  def setXTitleOffset(self, size):
845  self._xtitleoffset = size
846 
847  def setXLabelSize(self, size):
848  self._xlabelsize = size
849 
850  def setYTitle(self, title):
851  self._ytitle = title
852 
853  def setYTitleSize(self, size):
854  self._ytitlesize = size
855 
856  def setYTitleOffset(self, offset):
857  self._ytitleoffset = offset
858 
859  def setZTitle(self, title):
860  self._firstHisto.GetZaxis().SetTitle(title)
861 
862  def setZTitleOffset(self, offset):
863  self._firstHisto.GetZaxis().SetTitleOffset(offset)
864 
865  def redrawAxis(self):
866  # Disabling and enabled the 3D rulers somehow magically moves the axes to their proper places
867  ROOT.TAxis3D.ToggleRulers()
868  ROOT.TAxis3D.ToggleRulers()
869  axis = ROOT.TAxis3D.GetPadAxis()
870  axis.SetLabelColor(ROOT.kBlack);
871  axis.SetAxisColor(ROOT.kBlack);
872 
873  axis.GetXaxis().SetTitleOffset(self._xtitleoffset)
874  axis.GetYaxis().SetTitleOffset(self._ytitleoffset)
875 
876  if hasattr(self, "_xtitle"):
877  axis.GetXaxis().SetTitle(self._xtitle)
878  if hasattr(self, "_xtitlesize"):
879  axis.GetXaxis().SetTitleSize(self._xtitlesize)
880  if hasattr(self, "_xlabelsize"):
881  axis.GetXaxis().SetLabelSize(self._labelsize)
882  if hasattr(self, "_ytitle"):
883  axis.GetYaxis().SetTitle(self._ytitle)
884  if hasattr(self, "_ytitlesize"):
885  axis.GetYaxis().SetTitleSize(self._ytitlesize)
886  if hasattr(self, "_ytitleoffset"):
887  axis.GetYaxis().SetTitleOffset(self._ytitleoffset)
888 
889 def _copyStyle(src, dst):
890  properties = []
891  if hasattr(src, "GetLineColor") and hasattr(dst, "SetLineColor"):
892  properties.extend(["LineColor", "LineStyle", "LineWidth"])
893  if hasattr(src, "GetFillColor") and hasattr(dst, "SetFillColor"):
894  properties.extend(["FillColor", "FillStyle"])
895  if hasattr(src, "GetMarkerColor") and hasattr(dst, "SetMarkerColor"):
896  properties.extend(["MarkerColor", "MarkerSize", "MarkerStyle"])
897 
898  for prop in properties:
899  getattr(dst, "Set"+prop)(getattr(src, "Get"+prop)())
900 
901 class Plot:
902  """Represents one plot, comparing one or more histograms."""
903  def __init__(self, name, **kwargs):
904  """ Constructor.
905 
906  Arguments:
907  name -- String for name of the plot, or Efficiency object
908 
909  Keyword arguments:
910  fallback -- Dictionary for specifying fallback (default None)
911  title -- String for a title of the plot (default None)
912  xtitle -- String for x axis title (default None)
913  xtitlesize -- Float for x axis title size (default None)
914  xtitleoffset -- Float for x axis title offset (default None)
915  xlabelsize -- Float for x axis label size (default None)
916  ytitle -- String for y axis title (default None)
917  ytitlesize -- Float for y axis title size (default None)
918  ytitleoffset -- Float for y axis title offset (default None)
919  ztitle -- String for z axis title (default None)
920  ztitleoffset -- Float for z axis title offset (default None)
921  xmin -- Float for x axis minimum (default None, i.e. automatic)
922  xmax -- Float for x axis maximum (default None, i.e. automatic)
923  ymin -- Float for y axis minimum (default 0)
924  ymax -- Float for y axis maximum (default None, i.e. automatic)
925  xlog -- Bool for x axis log status (default False)
926  ylog -- Bool for y axis log status (default False)
927  xgrid -- Bool for x axis grid status (default True)
928  ygrid -- Bool for y axis grid status (default True)
929  stat -- Draw stat box? (default False)
930  fit -- Do gaussian fit? (default False)
931  statx -- Stat box x coordinate (default 0.65)
932  staty -- Stat box y coordinate (default 0.8)
933  statyadjust -- List of floats for stat box y coordinate adjustments (default None)
934  normalizeToUnitArea -- Normalize histograms to unit area? (default False)
935  profileX -- Take histograms via ProfileX()? (default False)
936  fitSlicesY -- Take histograms via FitSlicesY() (default False)
937  rebinX -- rebin x axis (default None)
938  scale -- Scale histograms by a number (default None)
939  xbinlabels -- List of x axis bin labels (if given, default None)
940  xbinlabelsize -- Size of x axis bin labels (default None)
941  xbinlabeloption -- Option string for x axis bin labels (default None)
942  drawStyle -- If "hist", draw as line instead of points (default None)
943  drawCommand -- Deliver this to Draw() (default: None for same as drawStyle)
944  lineWidth -- If drawStyle=="hist", the width of line (default 2)
945  legendDx -- Float for moving TLegend in x direction for separate=True (default None)
946  legendDy -- Float for moving TLegend in y direction for separate=True (default None)
947  legendDw -- Float for changing TLegend width for separate=True (default None)
948  legendDh -- Float for changing TLegend height for separate=True (default None)
949  adjustMarginRight -- Float for adjusting right margin (default None)
950  ratioYmin -- Float for y axis minimum in ratio pad (default 0.9)
951  ratioYmax -- Float for y axis maximum in ratio pad (default 1.1)
952  ratioUncertainty -- Plot uncertainties on ratio? (default True)
953  histogramModifier -- Function to be called in create() to modify the histograms (default None)
954  """
955  self._name = name
956 
957  def _set(attr, default):
958  setattr(self, "_"+attr, kwargs.get(attr, default))
959 
960  _set("fallback", None)
961 
962  _set("title", None)
963  _set("xtitle", None)
964  _set("xtitlesize", None)
965  _set("xtitleoffset", None)
966  _set("xlabelsize", None)
967  _set("ytitle", None)
968  _set("ytitlesize", None)
969  _set("ytitleoffset", None)
970  _set("ztitle", None)
971  _set("ztitleoffset", None)
972 
973  _set("xmin", None)
974  _set("xmax", None)
975  _set("ymin", 0.)
976  _set("ymax", None)
977 
978  _set("xlog", False)
979  _set("ylog", False)
980  _set("xgrid", True)
981  _set("ygrid", True)
982 
983  _set("stat", False)
984  _set("fit", False)
985 
986  _set("statx", 0.65)
987  _set("staty", 0.8)
988  _set("statyadjust", None)
989 
990  _set("normalizeToUnitArea", False)
991  _set("profileX", False)
992  _set("fitSlicesY", False)
993  _set("rebinX", None)
994 
995  _set("scale", None)
996  _set("xbinlabels", None)
997  _set("xbinlabelsize", None)
998  _set("xbinlabeloption", None)
999 
1000  _set("drawStyle", "EP")
1001  _set("drawCommand", None)
1002  _set("lineWidth", 2)
1003 
1004  _set("legendDx", None)
1005  _set("legendDy", None)
1006  _set("legendDw", None)
1007  _set("legendDh", None)
1008 
1009  _set("adjustMarginRight", None)
1010 
1011  _set("ratioYmin", 0.9)
1012  _set("ratioYmax", 1.1)
1013  _set("ratioUncertainty", True)
1014 
1015  _set("histogramModifier", None)
1016 
1017  self._histograms = []
1018 
1019  def setProperties(self, **kwargs):
1020  for name, value in kwargs.iteritems():
1021  if not hasattr(self, "_"+name):
1022  raise Exception("No attribute '%s'" % name)
1023  setattr(self, "_"+name, value)
1024 
1026  """Return number of existing histograms."""
1027  return len(filter(lambda h: h is not None, self._histograms))
1028 
1029  def isEmpty(self):
1030  """Return true if there are no histograms created for the plot"""
1031  return self.getNumberOfHistograms() == 0
1032 
1033  def isTGraph2D(self):
1034  for h in self._histograms:
1035  if isinstance(h, ROOT.TGraph2D):
1036  return True
1037  return False
1038 
1039  def getName(self):
1040  if isinstance(self._name, list):
1041  return str(self._name[0])
1042  else:
1043  return str(self._name)
1044 
1046  """Return true if the ratio uncertainty should be drawn"""
1047  return self._ratioUncertainty
1048 
1049  def _createOne(self, name, index, tdir):
1050  """Create one histogram from a TDirectory."""
1051  if tdir == None:
1052  return None
1053 
1054  # If name is a list, pick the name by the index
1055  if isinstance(name, list):
1056  name = name[index]
1057 
1058  return _getOrCreateObject(tdir, name)
1059 
1060  def create(self, tdirs, requireAllHistograms=False):
1061  """Create histograms from list of TDirectories"""
1062  self._histograms = [self._createOne(self._name, i, tdir) for i, tdir in enumerate(tdirs)]
1063 
1064  if self._fallback is not None:
1065  profileX = [self._profileX]*len(self._histograms)
1066  for i in xrange(0, len(self._histograms)):
1067  if self._histograms[i] is None:
1068  self._histograms[i] = self._createOne(self._fallback["name"], i, tdirs[i])
1069  profileX[i] = self._fallback.get("profileX", self._profileX)
1070 
1071  if self._histogramModifier is not None:
1072  self._histograms = self._histogramModifier(self._histograms)
1073 
1074  if len(self._histograms) > len(_plotStylesColor):
1075  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)))
1076 
1077  # Modify histograms here in case self._name returns numbers
1078  # and self._histogramModifier creates the histograms from
1079  # these numbers
1080  def _modifyHisto(th1, profileX):
1081  if th1 is None:
1082  return None
1083 
1084  if profileX:
1085  th1 = th1.ProfileX()
1086 
1087  if self._fitSlicesY:
1088  ROOT.TH1.AddDirectory(True)
1089  th1.FitSlicesY()
1090  th1 = ROOT.gDirectory.Get(th1.GetName()+"_2")
1091  th1.SetDirectory(None)
1092  #th1.SetName(th1.GetName()+"_ref")
1093  ROOT.TH1.AddDirectory(False)
1094 
1095  if self._title is not None:
1096  th1.SetTitle(self._title)
1097 
1098  if self._scale is not None:
1099  th1.Scale(self._scale)
1100 
1101  return th1
1102 
1103  if self._fallback is not None:
1104  self._histograms = map(_modifyHisto, self._histograms, profileX)
1105  else:
1106  self._histograms = map(lambda h: _modifyHisto(h, self._profileX), self._histograms)
1107  if requireAllHistograms and None in self._histograms:
1108  self._histograms = [None]*len(self._histograms)
1109 
1110  def _setStats(self, histos, startingX, startingY):
1111  """Set stats box."""
1112  if not self._stat:
1113  for h in histos:
1114  if h is not None and hasattr(h, "SetStats"):
1115  h.SetStats(0)
1116  return
1117 
1118  def _doStats(h, col, dy):
1119  if h is None:
1120  return
1121  h.SetStats(True)
1122 
1123  if self._fit and h.GetEntries() > 0.5:
1124  h.Fit("gaus", "Q")
1125  f = h.GetListOfFunctions().FindObject("gaus")
1126  if f == None:
1127  h.SetStats(0)
1128  return
1129  f.SetLineColor(col)
1130  f.SetLineWidth(1)
1131  h.Draw()
1132  ROOT.gPad.Update()
1133  st = h.GetListOfFunctions().FindObject("stats")
1134  if self._fit:
1135  st.SetOptFit(0o010)
1136  st.SetOptStat(1001)
1137  st.SetX1NDC(startingX)
1138  st.SetX2NDC(startingX+0.3)
1139  st.SetY1NDC(startingY+dy)
1140  st.SetY2NDC(startingY+dy+0.15)
1141  st.SetTextColor(col)
1142 
1143  dy = 0.0
1144  for i, h in enumerate(histos):
1145  if self._statyadjust is not None and i < len(self._statyadjust):
1146  dy += self._statyadjust[i]
1147 
1148  _doStats(h, _plotStylesColor[i], dy)
1149  dy -= 0.19
1150 
1151  def _normalize(self):
1152  """Normalise histograms to unit area"""
1153 
1154  for h in self._histograms:
1155  if h is None:
1156  continue
1157  i = h.Integral()
1158  if i == 0:
1159  continue
1160  h.Sumw2()
1161  h.Scale(1.0/i)
1162 
1163  def draw(self, pad, ratio, ratioFactor, nrows):
1164  """Draw the histograms using values for a given algorithm."""
1165 # if len(self._histograms) == 0:
1166 # print "No histograms for plot {name}".format(name=self._name)
1167 # return
1168 
1169  isTGraph2D = self.isTGraph2D()
1170  if isTGraph2D:
1171  # Ratios for the TGraph2Ds is not that interesting
1172  ratioOrig = ratio
1173  ratio = False
1174 
1175  if self._normalizeToUnitArea:
1176  self._normalize()
1177 
1178  if self._rebinX is not None:
1179  for h in self._histograms:
1180  h.Rebin(self._rebinX)
1181 
1182  def _styleMarker(h, msty, col):
1183  h.SetMarkerStyle(msty)
1184  h.SetMarkerColor(col)
1185  h.SetMarkerSize(0.7)
1186  h.SetLineColor(1)
1187  h.SetLineWidth(1)
1188 
1189  def _styleHist(h, msty, col):
1190  _styleMarker(h, msty, col)
1191  h.SetLineColor(col)
1192  h.SetLineWidth(self._lineWidth)
1193 
1194  # Use marker or hist style
1195  style = _styleMarker
1196  if "hist" in self._drawStyle.lower():
1197  style = _styleHist
1198  if len(self._histograms) > 0 and isinstance(self._histograms[0], ROOT.TGraph):
1199  if "l" in self._drawStyle.lower():
1200  style = _styleHist
1201 
1202  # Apply style to histograms, filter out Nones
1203  histos = []
1204  for i, h in enumerate(self._histograms):
1205  if h is None:
1206  continue
1207  style(h, _plotStylesMarker[i], _plotStylesColor[i])
1208  histos.append(h)
1209  if len(histos) == 0:
1210  if verbose:
1211  print "No histograms for plot {name}".format(name=self.getName())
1212  return
1213 
1214  # Extract x bin labels, make sure that only bins with same
1215  # label are compared with each other
1216  xbinlabels = self._xbinlabels
1217  if xbinlabels is None:
1218  if len(histos[0].GetXaxis().GetBinLabel(1)) > 0:
1219  xbinlabels = [histos[0].GetXaxis().GetBinLabel(i) for i in xrange(1, histos[0].GetNbinsX()+1)]
1220  # Merge bin labels with difflib
1221  for h in histos[1:]:
1222  labels = [h.GetXaxis().GetBinLabel(i) for i in xrange(1, h.GetNbinsX()+1)]
1223  diff = difflib.ndiff(xbinlabels, labels)
1224  xbinlabels = []
1225  for item in diff:
1226  xbinlabels.append(item[2:])
1227 
1228  histos_new = []
1229  for h in histos:
1230  h_new = h.Clone(h.GetName()+"_xbinlabels")
1231  h_new.SetBins(len(xbinlabels), h.GetBinLowEdge(1), h.GetBinLowEdge(1)+len(xbinlabels))
1232  for i, label in enumerate(xbinlabels):
1233  bin = h.GetXaxis().FindFixBin(label)
1234  if bin >= 0:
1235  h_new.SetBinContent(i+1, h.GetBinContent(bin))
1236  h_new.SetBinError(i+1, h.GetBinError(bin))
1237  else:
1238  h_new.SetBinContent(i+1, 0)
1239  h_new.SetBinError(i+1, 0)
1240  histos_new.append(h_new)
1241  self._tmp_histos = histos_new # need to keep these in memory too ...
1242  histos = histos_new
1243 
1244  bounds = _findBounds(histos, self._ylog,
1245  xmin=self._xmin, xmax=self._xmax,
1246  ymin=self._ymin, ymax=self._ymax)
1247 
1248  # Create bounds before stats in order to have the
1249  # SetRangeUser() calls made before the fit
1250  #
1251  # stats is better to be called before frame, otherwise get
1252  # mess in the plot (that frame creation cleans up)
1253  if ratio:
1254  pad.cd(1)
1255  self._setStats(histos, self._statx, self._staty)
1256 
1257  # Create frame
1258  if isTGraph2D:
1259  frame = FrameTGraph2D(pad, bounds, histos, ratioOrig, ratioFactor)
1260  else:
1261  if ratio:
1262  ratioBounds = (bounds[0], self._ratioYmin, bounds[2], self._ratioYmax)
1263  frame = FrameRatio(pad, bounds, ratioBounds, ratioFactor, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
1264  else:
1265  frame = Frame(pad, bounds, nrows, xbinlabels, self._xbinlabelsize, self._xbinlabeloption)
1266 
1267  # Set log and grid
1268  frame.setLogx(self._xlog)
1269  frame.setLogy(self._ylog)
1270  frame.setGridx(self._xgrid)
1271  frame.setGridy(self._ygrid)
1272 
1273  # Construct draw option string
1274  opt = "sames" # s for statbox or something?
1275  ds = ""
1276  if self._drawStyle is not None:
1277  ds = self._drawStyle
1278  if self._drawCommand is not None:
1279  ds = self._drawCommand
1280  if len(ds) > 0:
1281  opt += " "+ds
1282 
1283  # Set properties of frame
1284  frame.setTitle(histos[0].GetTitle())
1285  if self._xtitle is not None:
1286  frame.setXTitle(self._xtitle)
1287  if self._xtitlesize is not None:
1288  frame.setXTitleSize(self._xtitlesize)
1289  if self._xtitleoffset is not None:
1290  frame.setXTitleOffset(self._xtitleoffset)
1291  if self._xlabelsize is not None:
1292  frame.setXLabelSize(self._xlabelsize)
1293  if self._ytitle is not None:
1294  frame.setYTitle(self._ytitle)
1295  if self._ytitlesize is not None:
1296  frame.setYTitleSize(self._ytitlesize)
1297  if self._ytitleoffset is not None:
1298  frame.setYTitleOffset(self._ytitleoffset)
1299  if self._ztitle is not None:
1300  frame.setZTitle(self._ztitle)
1301  if self._ztitleoffset is not None:
1302  frame.setZTitleOffset(self._ztitleoffset)
1303  if self._adjustMarginRight is not None:
1304  frame.adjustMarginRight(self._adjustMarginRight)
1305  elif "z" in opt:
1306  frame.adjustMarginLeft(0.03)
1307  frame.adjustMarginRight(0.08)
1308 
1309  # Draw histograms
1310  if ratio:
1311  frame._pad.cd()
1312 
1313  for h in histos:
1314  h.Draw(opt)
1315 
1316  # Draw ratios
1317  if ratio and len(histos) > 0:
1318  frame._padRatio.cd()
1319  self._ratios = self._calculateRatios(histos) # need to keep these in memory too ...
1320  if self._ratioUncertainty and self._ratios[0]._ratio is not None:
1321  self._ratios[0]._ratio.SetFillStyle(1001)
1322  self._ratios[0]._ratio.SetFillColor(ROOT.kGray)
1323  self._ratios[0]._ratio.SetLineColor(ROOT.kGray)
1324  self._ratios[0]._ratio.SetMarkerColor(ROOT.kGray)
1325  self._ratios[0]._ratio.SetMarkerSize(0)
1326  self._ratios[0].draw("E2")
1327  frame._padRatio.RedrawAxis("G") # redraw grid on top of the uncertainty of denominator
1328  for r in self._ratios[1:]:
1329  r.draw()
1330 
1331  frame.redrawAxis()
1332  self._frame = frame # keep the frame in memory for sure
1333 
1334  def addToLegend(self, legend, legendLabels, denomUncertainty):
1335  """Add histograms to a legend.
1336 
1337  Arguments:
1338  legend -- TLegend
1339  legendLabels -- List of strings for the legend labels
1340  """
1341  first = denomUncertainty
1342  for h, label in zip(self._histograms, legendLabels):
1343  if h is None:
1344  first = False
1345  continue
1346  if first:
1347  self._forLegend = h.Clone()
1348  self._forLegend.SetFillStyle(1001)
1349  self._forLegend.SetFillColor(ROOT.kGray)
1350  entry = legend.AddEntry(self._forLegend, label, "lpf")
1351  first = False
1352  else:
1353  legend.AddEntry(h, label, "LP")
1354 
1355  def _calculateRatios(self, histos):
1356  """Calculate the ratios for a list of histograms"""
1357 
1358  def _divideOrZero(numerator, denominator):
1359  if denominator == 0:
1360  return 0
1361  return numerator/denominator
1362 
1363  def equal(a, b):
1364  if a == 0. and b == 0.:
1365  return True
1366  return abs(a-b)/max(abs(a),abs(b)) < 1e-3
1367 
1368  def findBins(wrap, bins_xvalues):
1369  ret = []
1370  currBin = wrap.begin()
1371  i = 0
1372  while i < len(bins_xvalues) and currBin < wrap.end():
1373  (xcenter, xlow, xhigh) = bins_xvalues[i]
1374  xlowEdge = xcenter-xlow
1375  xupEdge = xcenter+xhigh
1376 
1377  (curr_center, curr_low, curr_high) = wrap.xvalues(currBin)
1378  curr_lowEdge = curr_center-curr_low
1379  curr_upEdge = curr_center+curr_high
1380 
1381  if equal(xlowEdge, curr_lowEdge) and equal(xupEdge, curr_upEdge):
1382  ret.append(currBin)
1383  currBin += 1
1384  i += 1
1385  elif curr_upEdge <= xlowEdge:
1386  currBin += 1
1387  elif curr_lowEdge >= xupEdge:
1388  ret.append(None)
1389  i += 1
1390  else:
1391  ret.append(None)
1392  currBin += 1
1393  i += 1
1394  if len(ret) != len(bins_xvalues):
1395  ret.extend([None]*( len(bins_xvalues) - len(ret) ))
1396  return ret
1397 
1398  # Define wrappers for TH1/TGraph/TGraph2D to have uniform interface
1399  # TODO: having more global wrappers would make some things simpler also elsewhere in the code
1400  class WrapTH1:
1401  def __init__(self, th1, uncertainty):
1402  self._th1 = th1
1403  self._uncertainty = uncertainty
1404 
1405  xaxis = th1.GetXaxis()
1406  xaxis_arr = xaxis.GetXbins()
1407  if xaxis_arr.GetSize() > 0: # unequal binning
1408  lst = [xaxis_arr[i] for i in xrange(0, xaxis_arr.GetSize())]
1409  arr = array.array("d", lst)
1410  self._ratio = ROOT.TH1F("foo", "foo", xaxis.GetNbins(), arr)
1411  else:
1412  self._ratio = ROOT.TH1F("foo", "foo", xaxis.GetNbins(), xaxis.GetXmin(), xaxis.GetXmax())
1413  _copyStyle(th1, self._ratio)
1414  self._ratio.SetStats(0)
1415  self._ratio.SetLineColor(ROOT.kBlack)
1416  self._ratio.SetLineWidth(1)
1417  def draw(self, style=None):
1418  st = style
1419  if st is None:
1420  if self._uncertainty:
1421  st = "EP"
1422  else:
1423  st = "HIST P"
1424  self._ratio.Draw("same "+st)
1425  def begin(self):
1426  return 1
1427  def end(self):
1428  return self._th1.GetNbinsX()+1
1429  def xvalues(self, bin):
1430  xval = self._th1.GetBinCenter(bin)
1431  xlow = xval-self._th1.GetXaxis().GetBinLowEdge(bin)
1432  xhigh = self._th1.GetXaxis().GetBinUpEdge(bin)-xval
1433  return (xval, xlow, xhigh)
1434  def yvalues(self, bin):
1435  yval = self._th1.GetBinContent(bin)
1436  yerr = self._th1.GetBinError(bin)
1437  return (yval, yerr, yerr)
1438  def y(self, bin):
1439  return self._th1.GetBinContent(bin)
1440  def divide(self, bin, scale):
1441  self._ratio.SetBinContent(bin, _divideOrZero(self._th1.GetBinContent(bin), scale))
1442  self._ratio.SetBinError(bin, _divideOrZero(self._th1.GetBinError(bin), scale))
1443  def makeRatio(self):
1444  pass
1445 
1446  class WrapTGraph:
1447  def __init__(self, gr, uncertainty):
1448  self._gr = gr
1449  self._uncertainty = uncertainty
1450  self._xvalues = []
1451  self._xerrslow = []
1452  self._xerrshigh = []
1453  self._yvalues = []
1454  self._yerrshigh = []
1455  self._yerrslow = []
1456  def draw(self, style=None):
1457  if self._ratio is None:
1458  return
1459  st = style
1460  if st is None:
1461  if self._uncertainty:
1462  st = "PZ"
1463  else:
1464  st = "PX"
1465  self._ratio.Draw("same "+st)
1466  def begin(self):
1467  return 0
1468  def end(self):
1469  return self._gr.GetN()
1470  def xvalues(self, bin):
1471  return (self._gr.GetX()[bin], self._gr.GetErrorXlow(bin), self._gr.GetErrorXhigh(bin))
1472  def yvalues(self, bin):
1473  return (self._gr.GetY()[bin], self._gr.GetErrorYlow(bin), self._gr.GetErrorYhigh(bin))
1474  def y(self, bin):
1475  return self._gr.GetY()[bin]
1476  def divide(self, bin, scale):
1477  # Ignore bin if denominator is zero
1478  if scale == 0:
1479  return
1480  # No more items in the numerator
1481  if bin >= self._gr.GetN():
1482  return
1483  # denominator is missing an item
1484  xvals = self.xvalues(bin)
1485  xval = xvals[0]
1486 
1487  self._xvalues.append(xval)
1488  self._xerrslow.append(xvals[1])
1489  self._xerrshigh.append(xvals[2])
1490  yvals = self.yvalues(bin)
1491  self._yvalues.append(yvals[0] / scale)
1492  if self._uncertainty:
1493  self._yerrslow.append(yvals[1] / scale)
1494  self._yerrshigh.append(yvals[2] / scale)
1495  else:
1496  self._yerrslow.append(0)
1497  self._yerrshigh.append(0)
1498  def makeRatio(self):
1499  if len(self._xvalues) == 0:
1500  self._ratio = None
1501  return
1502  self._ratio = ROOT.TGraphAsymmErrors(len(self._xvalues), array.array("d", self._xvalues), array.array("d", self._yvalues),
1503  array.array("d", self._xerrslow), array.array("d", self._xerrshigh),
1504  array.array("d", self._yerrslow), array.array("d", self._yerrshigh))
1505  _copyStyle(self._gr, self._ratio)
1506  class WrapTGraph2D(WrapTGraph):
1507  def __init__(self, gr, uncertainty):
1508  WrapTGraph.__init__(self, gr, uncertainty)
1509  def xvalues(self, bin):
1510  return (self._gr.GetX()[bin], self._gr.GetErrorX(bin), self._gr.GetErrorX(bin))
1511  def yvalues(self, bin):
1512  return (self._gr.GetY()[bin], self._gr.GetErrorY(bin), self._gr.GetErrorY(bin))
1513 
1514  def wrap(o):
1515  if isinstance(o, ROOT.TH1):
1516  return WrapTH1(o, self._ratioUncertainty)
1517  elif isinstance(o, ROOT.TGraph):
1518  return WrapTGraph(o, self._ratioUncertainty)
1519  elif isinstance(o, ROOT.TGraph2D):
1520  return WrapTGraph2D(o, self._ratioUncertainty)
1521 
1522  wrappers = [wrap(h) for h in histos]
1523  ref = wrappers[0]
1524 
1525  wrappers_bins = []
1526  ref_bins = [ref.xvalues(b) for b in xrange(ref.begin(), ref.end())]
1527  for w in wrappers:
1528  wrappers_bins.append(findBins(w, ref_bins))
1529 
1530  for i, bin in enumerate(xrange(ref.begin(), ref.end())):
1531  (scale, ylow, yhigh) = ref.yvalues(bin)
1532  for w, bins in zip(wrappers, wrappers_bins):
1533  if bins[i] is None:
1534  continue
1535  w.divide(bins[i], scale)
1536 
1537  for w in wrappers:
1538  w.makeRatio()
1539 
1540  return wrappers
1541 
1543  """Group of plots, results a TCanvas"""
1544  def __init__(self, name, plots, **kwargs):
1545  """Constructor.
1546 
1547  Arguments:
1548  name -- String for name of the TCanvas, used also as the basename of the picture files
1549  plots -- List of Plot objects
1550 
1551  Keyword arguments:
1552  ncols -- Number of columns (default 2)
1553  legendDx -- Float for moving TLegend in x direction (default None)
1554  legendDy -- Float for moving TLegend in y direction (default None)
1555  legendDw -- Float for changing TLegend width (default None)
1556  legendDh -- Float for changing TLegend height (default None)
1557  legend -- Bool for disabling legend (default True for legend being enabled)
1558  overrideLegendLabels -- List of strings for legend labels, if given, these are used instead of the ones coming from Plotter (default None)
1559  onlyForPileup -- Plots this group only for pileup samples
1560  """
1561  self._name = name
1562  self._plots = plots
1563 
1564  def _set(attr, default):
1565  setattr(self, "_"+attr, kwargs.get(attr, default))
1566 
1567  _set("ncols", 2)
1568 
1569  _set("legendDx", None)
1570  _set("legendDy", None)
1571  _set("legendDw", None)
1572  _set("legendDh", None)
1573  _set("legend", True)
1574 
1575  _set("overrideLegendLabels", None)
1576 
1577  _set("onlyForPileup", False)
1578 
1579  self._ratioFactor = 1.25
1580 
1581  def getName(self):
1582  return self._name
1583 
1584  def getPlots(self):
1585  return self._plots
1586 
1587  def remove(self, name):
1588  for i, plot in enumerate(self._plots):
1589  if plot.getName() == name:
1590  del self._plots[i]
1591  return
1592  raise Exception("Did not find Plot '%s' from PlotGroup '%s'" % (name, self._name))
1593 
1594  def append(self, plot):
1595  self._plots.append(plot)
1596 
1597  def getPlot(self, name):
1598  for plot in self._plots:
1599  if plot.getName() == name:
1600  return plot
1601  raise Exception("No Plot named '%s'" % name)
1602 
1603  def onlyForPileup(self):
1604  """Return True if the PlotGroup is intended only for pileup samples"""
1605  return self._onlyForPileup
1606 
1607  def create(self, tdirectories, requireAllHistograms=False):
1608  """Create histograms from a list of TDirectories.
1609 
1610  Arguments:
1611  tdirectories -- List of TDirectory objects
1612  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
1613  """
1614  for plot in self._plots:
1615  plot.create(tdirectories, requireAllHistograms)
1616 
1617  def draw(self, legendLabels, prefix=None, separate=False, saveFormat=".pdf", ratio=False):
1618  """Draw the histograms using values for a given algorithm.
1619 
1620  Arguments:
1621  legendLabels -- List of strings for legend labels (corresponding to the tdirectories in create())
1622  prefix -- Optional string for file name prefix (default None)
1623  separate -- Save the plots of a group to separate files instead of a file per group (default False)
1624  saveFormat -- String specifying the plot format (default '.pdf')
1625  ratio -- Add ratio to the plot (default False)
1626  """
1627 
1628  if self._overrideLegendLabels is not None:
1629  legendLabels = self._overrideLegendLabels
1630 
1631  # Do not draw the group if it would be empty
1632  onlyEmptyPlots = True
1633  for plot in self._plots:
1634  if not plot.isEmpty():
1635  onlyEmptyPlots = False
1636  break
1637  if onlyEmptyPlots:
1638  return []
1639 
1640  if separate:
1641  return self._drawSeparate(legendLabels, prefix, saveFormat, ratio)
1642 
1643  cwidth = 500*self._ncols
1644  nrows = int((len(self._plots)+self._ncols-1)/self._ncols) # this should work also for odd n
1645  cheight = 500 * nrows
1646 
1647  if ratio:
1648  cheight = int(cheight*self._ratioFactor)
1649 
1650  canvas = _createCanvas(self._name, cwidth, cheight)
1651 
1652  canvas.Divide(self._ncols, nrows)
1653  if ratio:
1654  for i in xrange(0, len(self._plots)):
1655  pad = canvas.cd(i+1)
1656  self._modifyPadForRatio(pad)
1657 
1658  # Draw plots to canvas
1659  for i, plot in enumerate(self._plots):
1660  pad = canvas.cd(i+1)
1661  if not plot.isEmpty():
1662  plot.draw(pad, ratio, self._ratioFactor, nrows)
1663 
1664  # Setup legend
1665  canvas.cd()
1666  if len(self._plots) <= 4:
1667  lx1 = 0.2
1668  lx2 = 0.9
1669  ly1 = 0.48
1670  ly2 = 0.53
1671  else:
1672  lx1 = 0.1
1673  lx2 = 0.9
1674  ly1 = 0.64
1675  ly2 = 0.67
1676  if self._legendDx is not None:
1677  lx1 += self._legendDx
1678  lx2 += self._legendDx
1679  if self._legendDy is not None:
1680  ly1 += self._legendDy
1681  ly2 += self._legendDy
1682  if self._legendDw is not None:
1683  lx2 += self._legendDw
1684  if self._legendDh is not None:
1685  ly1 -= self._legendDh
1686  plot = max(self._plots, key=lambda p: p.getNumberOfHistograms())
1687  denomUnc = sum([p.drawRatioUncertainty() for p in self._plots]) > 0
1688  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2,
1689  denomUncertainty=(ratio and denomUnc))
1690 
1691  return self._save(canvas, saveFormat, prefix=prefix)
1692 
1693  def _drawSeparate(self, legendLabels, prefix, saveFormat, ratio):
1694  """Internal method to do the drawing to separate files per Plot instead of a file per PlotGroup"""
1695  width = 500
1696  height = 500
1697  if ratio:
1698  height = int(height*self._ratioFactor)
1699 
1700  canvas = _createCanvas(self._name+"Single", width, height)
1701  # from TDRStyle
1702  canvas.SetTopMargin(0.05)
1703  canvas.SetBottomMargin(0.13)
1704  canvas.SetLeftMargin(0.16)
1705  canvas.SetRightMargin(0.05)
1706 
1707  lx1def = 0.6
1708  lx2def = 0.95
1709  ly1def = 0.85
1710  ly2def = 0.95
1711 
1712  ret = []
1713 
1714  for plot in self._plots:
1715  if plot.isEmpty():
1716  continue
1717 
1718  if ratio:
1719  canvas.cd()
1720  self._modifyPadForRatio(canvas)
1721 
1722  # Draw plot to canvas
1723  canvas.cd()
1724  plot.draw(canvas, ratio, self._ratioFactor, 1)
1725 
1726 
1727  # Setup legend
1728  lx1 = lx1def
1729  lx2 = lx2def
1730  ly1 = ly1def
1731  ly2 = ly2def
1732 
1733  if plot._legendDx is not None:
1734  lx1 += plot._legendDx
1735  lx2 += plot._legendDx
1736  if plot._legendDy is not None:
1737  ly1 += plot._legendDy
1738  ly2 += plot._legendDy
1739  if plot._legendDw is not None:
1740  lx2 += plot._legendDw
1741  if plot._legendDh is not None:
1742  ly1 -= plot._legendDh
1743 
1744  canvas.cd()
1745  legend = self._createLegend(plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.03,
1746  denomUncertainty=(ratio and plot.drawRatioUncertainty))
1747 
1748  ret.extend(self._save(canvas, saveFormat, prefix=prefix, postfix="_"+plot.getName(), single=True))
1749  return ret
1750 
1751  def _modifyPadForRatio(self, pad):
1752  """Internal method to set divide a pad to two for ratio plots"""
1753  pad.Divide(1, 2)
1754 
1755  divisionPoint = 1-1/self._ratioFactor
1756 
1757  topMargin = pad.GetTopMargin()
1758  bottomMargin = pad.GetBottomMargin()
1759  divisionPoint += (1-divisionPoint)*bottomMargin # correct for (almost-)zeroing bottom margin of pad1
1760  divisionPointForPad1 = 1-( (1-divisionPoint) / (1-0.02) ) # then correct for the non-zero bottom margin, but for pad1 only
1761 
1762  # Set the lower point of the upper pad to divisionPoint
1763  pad1 = pad.cd(1)
1764  yup = 1.0
1765  ylow = divisionPointForPad1
1766  xup = 1.0
1767  xlow = 0.0
1768  pad1.SetPad(xlow, ylow, xup, yup)
1769  pad1.SetFillStyle(4000) # transparent
1770  pad1.SetBottomMargin(0.02) # need some bottom margin here for eps/pdf output (at least in ROOT 5.34)
1771 
1772  # Set the upper point of the lower pad to divisionPoint
1773  pad2 = pad.cd(2)
1774  yup = divisionPoint
1775  ylow = 0.0
1776  pad2.SetPad(xlow, ylow, xup, yup)
1777  pad2.SetFillStyle(4000) # transparent
1778  pad2.SetTopMargin(0.0)
1779  pad2.SetBottomMargin(bottomMargin/(self._ratioFactor*divisionPoint))
1780 
1781  def _createLegend(self, plot, legendLabels, lx1, ly1, lx2, ly2, textSize=0.016, denomUncertainty=True):
1782  if not self._legend:
1783  return None
1784 
1785  l = ROOT.TLegend(lx1, ly1, lx2, ly2)
1786  l.SetTextSize(textSize)
1787  l.SetLineColor(1)
1788  l.SetLineWidth(1)
1789  l.SetLineStyle(1)
1790  l.SetFillColor(0)
1791  l.SetMargin(0.07)
1792 
1793  plot.addToLegend(l, legendLabels, denomUncertainty)
1794  l.Draw()
1795  return l
1796 
1797  def _save(self, canvas, saveFormat, prefix=None, postfix=None, single=False):
1798  # Save the canvas to file and clear
1799  name = self._name
1800  if prefix is not None:
1801  name = prefix+name
1802  if postfix is not None:
1803  name = name+postfix
1804 
1805  if not verbose: # silence saved file printout
1806  backup = ROOT.gErrorIgnoreLevel
1807  ROOT.gErrorIgnoreLevel = ROOT.kWarning
1808  canvas.SaveAs(name+saveFormat)
1809  if not verbose:
1810  ROOT.gErrorIgnoreLevel = backup
1811 
1812  if single:
1813  canvas.Clear()
1814  canvas.SetLogx(False)
1815  canvas.SetLogy(False)
1816  else:
1817  canvas.Clear("D") # keep subpads
1818 
1819  return [name+saveFormat]
1820 
1822  """Represents a collection of PlotGroups, produced from a single folder in a DQM file"""
1823  def __init__(self, *plotGroups, **kwargs):
1824  """Constructor.
1825 
1826  Arguments:
1827  plotGroups -- List of PlotGroup objects
1828 
1829  Keyword arguments
1830  loopSubFolders -- Should the subfolders be looped over? (default: True)
1831  onlyForPileup -- Plots this folder only for pileup samples
1832  purpose -- html.PlotPurpose member class for the purpose of the folder, used for grouping of the plots to the HTML pages
1833  page -- Optional string for the page in HTML generatin
1834  section -- Optional string for the section within a page in HTML generation
1835  """
1836  self._plotGroups = list(plotGroups)
1837  self._loopSubFolders = kwargs.pop("loopSubFolders", True)
1838  self._onlyForPileup = kwargs.pop("onlyForPileup", False)
1839  self._purpose = kwargs.pop("purpose", None)
1840  self._page = kwargs.pop("page", None)
1841  self._section = kwargs.pop("section", None)
1842  if len(kwargs) > 0:
1843  raise Exception("Got unexpected keyword arguments: "+ ",".join(kwargs.keys()))
1844 
1845  def loopSubFolders(self):
1846  """Return True if the PlotGroups of this folder should be applied to the all subfolders"""
1847  return self._loopSubFolders
1848 
1849  def onlyForPileup(self):
1850  """Return True if the folder is intended only for pileup samples"""
1851  return self._onlyForPileup
1852 
1853  def getPurpose(self):
1854  return self._purpose
1855 
1856  def getPage(self):
1857  return self._page
1858 
1859  def getSection(self):
1860  return self._section
1861 
1862  def append(self, plotGroup):
1863  self._plotGroups.append(plotGroup)
1864 
1865  def set(self, plotGroups):
1866  self._plotGroups = plotGroups
1867 
1868  def getPlotGroups(self):
1869  return self._plotGroups
1870 
1871  def getPlotGroup(self, name):
1872  for pg in self._plotGroups:
1873  if pg.getName() == name:
1874  return pg
1875  raise Exception("No PlotGroup named '%s'" % name)
1876 
1877  def create(self, dirs, labels, isPileupSample=True, requireAllHistograms=False):
1878  """Create histograms from a list of TFiles.
1879 
1880  Arguments:
1881  dirs -- List of TDirectories
1882  labels -- List of strings for legend labels corresponding the files
1883  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
1884  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
1885  """
1886 
1887  if len(dirs) != len(labels):
1888  raise Exception("len(files) should be len(labels), now they are %d and %d" % (len(files), len(labels)))
1889 
1890  self._labels = labels
1891 
1892  for pg in self._plotGroups:
1893  if pg.onlyForPileup() and not isPileupSample:
1894  continue
1895  pg.create(dirs, requireAllHistograms)
1896 
1897  def draw(self, prefix=None, separate=False, saveFormat=".pdf", ratio=False):
1898  """Draw and save all plots using settings of a given algorithm.
1899 
1900  Arguments:
1901  prefix -- Optional string for file name prefix (default None)
1902  separate -- Save the plots of a group to separate files instead of a file per group (default False)
1903  saveFormat -- String specifying the plot format (default '.pdf')
1904  ratio -- Add ratio to the plot (default False)
1905  """
1906  ret = []
1907 
1908  for pg in self._plotGroups:
1909  ret.extend(pg.draw(self._labels, prefix=prefix, separate=separate, saveFormat=saveFormat, ratio=ratio))
1910  return ret
1911 
1912 
1913  # These are to be overridden by derived classes for customisation
1914  def translateSubFolder(self, dqmSubFolderName):
1915  """Method called to (possibly) translate a subfolder name to more 'readable' form
1916 
1917  The implementation in this (base) class just returns the
1918  argument. The idea is that a deriving class might want to do
1919  something more complex (like trackingPlots.TrackingPlotFolder
1920  does)
1921  """
1922  return dqmSubFolderName
1923 
1924  def iterSelectionName(self, plotFolderName, translatedDqmSubFolder):
1925  """Iterate over possible selections name (used in output directory name and legend) from the name of PlotterFolder, and a return value of translateSubFolder"""
1926  ret = ""
1927  if plotFolderName != "":
1928  ret += "_"+plotFolderName
1929  if translatedDqmSubFolder is not None:
1930  ret += "_"+translatedDqmSubFolder
1931  yield ret
1932 
1933  def limitSubFolder(self, limitOnlyTo, translatedDqmSubFolder):
1934  """Return True if this subfolder should be processed
1935 
1936  Arguments:
1937  limitOnlyTo -- List/set/similar containing the translatedDqmSubFolder
1938  translatedDqmSubFolder -- Return value of translateSubFolder
1939  """
1940  return translatedDqmSubFolder in limitOnlyTo
1941 
1943  """Class to hold the original name and a 'translated' name of a subfolder in the DQM ROOT file"""
1944  def __init__(self, subfolder, translated):
1945  self.subfolder = subfolder
1946  self.translated = translated
1947 
1948  def equalTo(self, other):
1949  """Equality is defined by the 'translated' name"""
1950  return self.translated == other.translated
1951 
1953  """Plotter for one DQM folder.
1954 
1955  This class is supposed to be instantiated by the Plotter class (or
1956  PlotterItem, to be more specific), and not used directly by the
1957  user.
1958  """
1959  def __init__(self, name, possibleDqmFolders, dqmSubFolders, plotFolder, fallbackNames, fallbackDqmSubFolders, tableCreators):
1960  """
1961  Constructor
1962 
1963  Arguments:
1964  name -- Name of the folder (is used in the output directory naming)
1965  possibleDqmFolders -- List of strings for possible directories of histograms in TFiles
1966  dqmSubFolders -- List of lists of strings for list of subfolders per input file, or None if no subfolders
1967  plotFolder -- PlotFolder object
1968  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.
1969  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.
1970  tableCreators -- List of PlotterTableItem objects for tables to be created from this folder
1971  """
1972  self._name = name
1973  self._possibleDqmFolders = possibleDqmFolders
1974  self._plotFolder = plotFolder
1975  #self._dqmSubFolders = [map(lambda sf: DQMSubFolder(sf, self._plotFolder.translateSubFolder(sf)), lst) for lst in dqmSubFolders]
1976  if dqmSubFolders is None:
1977  self._dqmSubFolders = None
1978  else:
1979  # Match the subfolders between files in case the lists differ
1980  # equality is by the 'translated' name
1981  subfolders = {}
1982  for sf_list in dqmSubFolders:
1983  for sf in sf_list:
1984  sf_translated = self._plotFolder.translateSubFolder(sf)
1985  if sf_translated is not None and not sf_translated in subfolders:
1986  subfolders[sf_translated] = DQMSubFolder(sf, sf_translated)
1987 
1988  self._dqmSubFolders = subfolders.values()
1989  self._dqmSubFolders.sort(key=lambda sf: sf.subfolder)
1990 
1991  self._fallbackNames = fallbackNames
1992  self._fallbackDqmSubFolders = fallbackDqmSubFolders
1993  self._tableCreators = tableCreators
1994 
1995  def getName(self):
1996  return self._name
1997 
1998  def getPurpose(self):
1999  return self._plotFolder.getPurpose()
2000 
2001  def getPage(self):
2002  return self._plotFolder.getPage()
2003 
2004  def getSection(self):
2005  return self._plotFolder.getSection()
2006 
2007  def onlyForPileup(self):
2008  return self._plotFolder.onlyForPileup()
2009 
2011  return self._possibleDqmFolders
2012 
2013  def getDQMSubFolders(self, limitOnlyTo=None):
2014  """Get list of subfolders, possibly limiting to some of them.
2015 
2016  Keyword arguments:
2017  limitOnlyTo -- Object depending on the PlotFolder type for limiting the set of subfolders to be processed
2018  """
2019 
2020  if self._dqmSubFolders is None:
2021  return [None]
2022 
2023  if limitOnlyTo is None:
2024  return self._dqmSubFolders
2025 
2026  return filter(lambda s: self._plotFolder.limitSubFolder(limitOnlyTo, s.translated), self._dqmSubFolders)
2027 
2028  def getTableCreators(self):
2029  return self._tableCreators
2030 
2031  def getSelectionNameIterator(self, dqmSubFolder):
2032  """Get a generator for the 'selection name', looping over the name and fallbackNames"""
2033  for name in [self._name]+self._fallbackNames:
2034  for selname in self._plotFolder.iterSelectionName(name, dqmSubFolder.translated if dqmSubFolder is not None else None):
2035  yield selname
2036 
2037  def getSelectionName(self, dqmSubFolder):
2038  return next(self.getSelectionNameIterator(dqmSubFolder))
2039 
2040  def create(self, files, labels, dqmSubFolder, isPileupSample=True, requireAllHistograms=False):
2041  """Create histograms from a list of TFiles.
2042  Arguments:
2043  files -- List of TFiles
2044  labels -- List of strings for legend labels corresponding the files
2045  dqmSubFolder -- DQMSubFolder object for a subfolder (or None for no subfolder)
2046  isPileupSample -- Is sample pileup (some PlotGroups may limit themselves to pileup)
2047  requireAllHistograms -- If True, a plot is produced if histograms from all files are present (default: False)
2048  """
2049 
2050  subfolder = dqmSubFolder.subfolder if dqmSubFolder is not None else None
2051  dirs = []
2052 
2053  for tfile in files:
2054  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, subfolder)
2055  # If file and any of possibleDqmFolders exist but subfolder does not, try the fallbacks
2057  for fallbackFunc in self._fallbackDqmSubFolders:
2058  fallback = fallbackFunc(subfolder)
2059  if fallback is not None:
2060  ret = _getDirectoryDetailed(tfile, self._possibleDqmFolders, fallback)
2061  if ret is not GetDirectoryCode.SubDirNotExist:
2062  break
2063  dirs.append(GetDirectoryCode.codesToNone(ret))
2064 
2065  self._plotFolder.create(dirs, labels, isPileupSample, requireAllHistograms)
2066 
2067  def draw(self, *args, **kwargs):
2068  """Draw and save all plots using settings of a given algorithm."""
2069  return self._plotFolder.draw(*args, **kwargs)
2070 
2071 
2073  """Instance of plotter that knows the directory content, holds many folders."""
2074  def __init__(self, folders):
2075  self._plotterFolders = filter(lambda f: f is not None, folders)
2076 
2077  def iterFolders(self, limitSubFoldersOnlyTo=None):
2078  for plotterFolder in self._plotterFolders:
2079  limitOnlyTo = None
2080  if limitSubFoldersOnlyTo is not None:
2081  limitOnlyTo = limitSubFoldersOnlyTo.get(plotterFolder.getName(), None)
2082 
2083  for dqmSubFolder in plotterFolder.getDQMSubFolders(limitOnlyTo=limitOnlyTo):
2084  yield plotterFolder, dqmSubFolder
2085 
2086 # Helper for Plotter
2088  def __init__(self, name, possibleDirs, plotFolder, fallbackNames=[], fallbackDqmSubFolders=[]):
2089  """ Constructor
2090 
2091  Arguments:
2092  name -- Name of the folder (is used in the output directory naming)
2093  possibleDirs -- List of strings for possible directories of histograms in TFiles
2094  plotFolder -- PlotFolder object
2095 
2096  Keyword arguments
2097  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.
2098  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.
2099  """
2100  self._name = name
2101  self._possibleDirs = possibleDirs
2102  self._plotFolder = plotFolder
2103  self._fallbackNames = fallbackNames
2104  self._fallbackDqmSubFolders = fallbackDqmSubFolders
2105  self._tableCreators = []
2106 
2107  def getName(self):
2108  return self._name
2109 
2110  def getPlotFolder(self):
2111  return self._plotFolder
2112 
2113  def appendTableCreator(self, tc):
2114  self._tableCreators.append(tc)
2115 
2116  def readDirs(self, files):
2117  """Read available subfolders from the files
2118 
2119  Arguments:
2120  files -- List of strings for paths to files, or list of TFiles
2121 
2122  For each file, loop over 'possibleDirs', and read the
2123  subfolders of first one that exists.
2124 
2125  Returns a PlotterFolder if at least one file for which one of
2126  'possibleDirs' exists. Otherwise, return None to signal that
2127  there is nothing available for this PlotFolder.
2128  """
2129  subFolders = None
2130  if self._plotFolder.loopSubFolders():
2131  subFolders = []
2132  possibleDirFound = False
2133  for fname in files:
2134  isOpenFile = isinstance(fname, ROOT.TFile)
2135  if isOpenFile:
2136  tfile = fname
2137  else:
2138  tfile = ROOT.TFile.Open(fname)
2139  for pd in self._possibleDirs:
2140  d = tfile.Get(pd)
2141  if d:
2142  possibleDirFound = True
2143  if subFolders is not None:
2144  subf = []
2145  for key in d.GetListOfKeys():
2146  if isinstance(key.ReadObj(), ROOT.TDirectory):
2147  subf.append(key.GetName())
2148  subFolders.append(subf)
2149  break
2150 
2151  if not isOpenFile:
2152  tfile.Close()
2153 
2154  if not possibleDirFound:
2155  return None
2156 
2157  return PlotterFolder(self._name, self._possibleDirs, subFolders, self._plotFolder, self._fallbackNames, self._fallbackDqmSubFolders, self._tableCreators)
2158 
2160  def __init__(self, possibleDirs, tableCreator):
2161  self._possibleDirs = possibleDirs
2162  self._tableCreator = tableCreator
2163 
2164  def create(self, openFiles, legendLabels, dqmSubFolder):
2165  if isinstance(dqmSubFolder, list):
2166  if len(dqmSubFolder) != len(openFiles):
2167  raise Exception("When dqmSubFolder is a list, len(dqmSubFolder) should be len(openFiles), now they are %d and %d" % (len(dqmSubFolder), len(openFiles)))
2168  else:
2169  dqmSubFolder = [dqmSubFolder]*len(openFiles)
2170  dqmSubFolder = [sf.subfolder if sf is not None else None for sf in dqmSubFolder]
2171 
2172  tbl = []
2173  for f, sf in zip(openFiles, dqmSubFolder):
2174  data = None
2175  tdir = _getDirectory(f, self._possibleDirs, sf)
2176  if tdir is not None:
2177  data = self._tableCreator.create(tdir)
2178  tbl.append(data)
2179 
2180  # Check if we have any content
2181  allNones = True
2182  colLen = 0
2183  for col in tbl:
2184  if col is not None:
2185  allNones = False
2186  colLen = len(col)
2187  break
2188  if allNones:
2189  return None
2190 
2191  # Replace all None columns with lists of column length
2192  for i in xrange(len(tbl)):
2193  if tbl[i] is None:
2194  tbl[i] = [None]*colLen
2195 
2196  return html.Table(columnHeaders=legendLabels, rowHeaders=self._tableCreator.headers(), table=tbl,
2197  purpose=self._tableCreator.getPurpose(), page=self._tableCreator.getPage(), section=self._tableCreator.getSection(dqmSubFolder[0]))
2198 
2199 class Plotter:
2200  """Contains PlotFolders, i.e. the information what plots to do, and creates a helper object to actually produce the plots."""
2201  def __init__(self):
2202  self._plots = []
2203 
2204  _absoluteSize = True
2205  if _absoluteSize:
2206  font = 43
2207  titleSize = 22
2208  labelSize = 22
2209  statSize = 14
2210  else:
2211  font = 42
2212  titleSize = 0.05
2213  labelSize = 0.05
2214  statSize = 0.025
2215 
2216  ROOT.gROOT.SetStyle("Plain")
2217  ROOT.gStyle.SetPadRightMargin(0.07)
2218  ROOT.gStyle.SetPadLeftMargin(0.13)
2219  ROOT.gStyle.SetTitleFont(font, "XYZ")
2220  ROOT.gStyle.SetTitleSize(titleSize, "XYZ")
2221  ROOT.gStyle.SetTitleOffset(1.2, "Y")
2222  #ROOT.gStyle.SetTitleFontSize(0.05)
2223  ROOT.gStyle.SetLabelFont(font, "XYZ")
2224  ROOT.gStyle.SetLabelSize(labelSize, "XYZ")
2225  ROOT.gStyle.SetTextSize(labelSize)
2226  ROOT.gStyle.SetStatFont(font)
2227  ROOT.gStyle.SetStatFontSize(statSize)
2228 
2229  ROOT.TH1.AddDirectory(False)
2230  ROOT.TGaxis.SetMaxDigits(4)
2231 
2232  def append(self, *args, **kwargs):
2233  """Append a plot folder to the plotter.
2234 
2235  All arguments are forwarded to the constructor of PlotterItem.
2236  """
2237  self._plots.append(PlotterItem(*args, **kwargs))
2238 
2239  def appendTable(self, attachToFolder, *args, **kwargs):
2240  for plotterItem in self._plots:
2241  if plotterItem.getName() == attachToFolder:
2242  plotterItem.appendTableCreator(PlotterTableItem(*args, **kwargs))
2243  return
2244  raise Exception("Did not find plot folder '%s' when trying to attach a table creator to it" % attachToFolder)
2245 
2246  def clear(self):
2247  """Remove all plot folders and tables"""
2248  self._plots = []
2249 
2251  return [item.getName() for item in self._plots]
2252 
2253  def getPlotFolders(self):
2254  return [item.getPlotFolder() for item in self._plots]
2255 
2256  def getPlotFolder(self, name):
2257  for item in self._plots:
2258  if item.getName() == name:
2259  return item.getPlotFolder()
2260  raise Exception("No PlotFolder named '%s'" % name)
2261 
2262  def readDirs(self, *files):
2263  """Returns PlotterInstance object, which knows how exactly to produce the plots for these files"""
2264  return PlotterInstance([plotterItem.readDirs(files) for plotterItem in self._plots])
def _getYmin
Definition: plotting.py:104
def _getXmax
Definition: plotting.py:90
def setXLabelSize
Definition: plotting.py:647
def _getYmaxWithError
Definition: plotting.py:118
def setXTitleOffset
Definition: plotting.py:644
def _getYminIgnoreOutlier
Definition: plotting.py:121
def _getXmin
Definition: plotting.py:76
def _getYmax
Definition: plotting.py:111
def _createCanvas
Definition: plotting.py:65
bool equal(const T &first, const T &second)
Definition: Equal.h:34
def adjustMarginLeft
Definition: plotting.py:623
def _findBounds
Definition: plotting.py:138
def _drawFrame
Definition: plotting.py:556
def drawRatioUncertainty
Definition: plotting.py:1045
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:629
def __init__
Definition: plotting.py:492
def _getObject
Definition: plotting.py:14
static std::string join(char **cmd)
Definition: RemoteFile.cc:18
def _getDirectory
Definition: plotting.py:62
def getNumberOfHistograms
Definition: plotting.py:1025
def _getOrCreateObject
Definition: plotting.py:22
def _copyStyle
Definition: plotting.py:889
#define begin
Definition: vmac.h:30
def _calculateRatios
Definition: plotting.py:1355
def setYTitleSize
Definition: plotting.py:653
def setYTitleOffset
Definition: plotting.py:656
def _getDirectoryDetailed
Definition: plotting.py:38
auto wrap(F iFunc) -> decltype(iFunc())
def setXTitleSize
Definition: plotting.py:641
def setProperties
Definition: plotting.py:1019
def getPlotFolderNames
Definition: plotting.py:2250
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