ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/UserCode/RootMacros/overlayHists.py
Revision: 1.13
Committed: Wed Feb 3 19:09:56 2010 UTC (15 years, 2 months ago) by klukas
Content type: text/x-python
Branch: MAIN
CVS Tags: HEAD
Changes since 1.12: +169 -130 lines
Log Message:
Massive clean-up and documentation; added normalize-to-file option; removed timing option due to reorganization

File Contents

# User Rev Content
1 klukas 1.1 #!/usr/bin/env python
2 klukas 1.13 """Overlay the histograms from several root files with identical structure"""
3     __version__ = "1.0"
4 klukas 1.1
5     ## Created by Jeff Klukas (klukas@wisc.edu), November 2009
6 klukas 1.13 ## Updated February 2010
7 klukas 1.1
8 klukas 1.13 ######## Import python libraries #############################################
9 klukas 1.1
10     import sys
11     import optparse
12 klukas 1.9 import shutil
13 klukas 1.1 import os
14     import re
15    
16 klukas 1.13 ## If we actually plan to do something other than show the help menu,
17     ## import the PyROOT package
18 klukas 1.7 if '-h' not in sys.argv and len(sys.argv) > 1:
19 klukas 1.13 import ROOT
20     # ROOT parses options when the first ROOT command is called, so we must
21     # add '-b' before that to get batch mode, but we must immediately remove
22     # it to avoid interference with option parsing for this script.
23 klukas 1.1 sys.argv.append('-b')
24 klukas 1.13 ROOT.gErrorIgnoreLevel = ROOT.kWarning
25 klukas 1.1 sys.argv.remove('-b')
26    
27 klukas 1.10
28 klukas 1.1
29 klukas 1.13 ######## Feel free to change these style options to suit you ################
30    
31     def add_style_options(options):
32     """Define a set of global variables storing style information, etc."""
33     GetColor = ROOT.TColor.GetColor
34     options.colors = [
35     ## a default set of contrasting colors the author happens to like
36     GetColor( 82, 124, 219), # blue
37     GetColor(212, 58, 143), # red
38     GetColor(231, 139, 77), # orange
39     GetColor(145, 83, 207), # purple
40     GetColor(114, 173, 117), # green
41     GetColor( 67, 77, 83), # dark grey
42     ]
43     options.marker_styles = [
44     ## some of the more clear markers in root
45     3, # asterisk
46     4, # circle
47     5, # x
48     25, # square
49     26, # triangle
50     27, # diamond
51     28, # cross
52     30, # five-pointed star
53     ]
54     return options
55    
56    
57    
58     ######## Define classes and generators #######################################
59 klukas 1.1
60     class RootFile:
61 klukas 1.10 """A wrapper for TFiles, allowing quick access to the name and Get."""
62 klukas 1.1 def __init__(self, file_name):
63 klukas 1.9 self.name = file_name[0:-5]
64 klukas 1.1 self.file = ROOT.TFile(file_name, "read")
65     if self.file.IsZombie():
66     print "Error opening %s, exiting..." % file_name
67     sys.exit(1)
68     def Get(self, object_name):
69     return self.file.Get(object_name)
70    
71 klukas 1.10 def counter_generator():
72     """Incremement the counter used to number plots."""
73     k = 0
74     while True:
75     k += 1
76     yield k
77     next_counter = counter_generator().next
78    
79 klukas 1.1
80    
81 klukas 1.13 ######## These functions are the meat of this program #########################
82 klukas 1.1
83 klukas 1.13 #### A recursive function to drill down through directories
84 klukas 1.1 def process_directory(path, files):
85 klukas 1.10 """Loop through all histograms in the directory and plot them."""
86 klukas 1.13 dir_to_make = "%s/%s" % (options.plot_dir, path)
87 klukas 1.1 if not os.path.exists(dir_to_make):
88     os.mkdir(dir_to_make)
89     keys = files[0].file.GetDirectory(path).GetListOfKeys()
90     key = keys[0]
91     while key:
92     obj = key.ReadObj()
93     key = keys.After(key)
94     new_path = "%s/%s" % (path, obj.GetName())
95     if obj.IsA().InheritsFrom("TDirectory"):
96     process_directory(new_path, files)
97 klukas 1.10 #### If obj is a desired histogram, process it
98 klukas 1.13 if (options.regex.search(new_path) and
99 klukas 1.1 obj.IsA().InheritsFrom("TH1") and
100     not obj.IsA().InheritsFrom("TH2") and
101     not obj.IsA().InheritsFrom("TH3")):
102 klukas 1.10 process_hist(path, new_path, files, obj)
103    
104 klukas 1.1
105 klukas 1.10 #### This is where all the plotting actually happens
106     def process_hist(path, new_path, files, obj):
107 klukas 1.13 """Overlay all the instances of this plot and apply the options."""
108 klukas 1.12 counter = next_counter() # used for page numbers
109 klukas 1.10 name = obj.GetName()
110     hist = files[0].file.GetDirectory(path).Get(name)
111     title = hist.GetTitle()
112     x_title = hist.GetXaxis().GetTitle()
113     y_title = hist.GetYaxis().GetTitle()
114 klukas 1.13 if options.normalize or (options.sticky and "Norm" in name):
115 klukas 1.10 y_title = "Fraction of Events in Bin"
116 klukas 1.13 if options.normalize_to_file:
117     file_name = files[int(options.normalize_to_file) - 1].name
118     y_title = "Events Normalized to %s" % file_name
119 klukas 1.12 hists = []
120     #### Apply options to hist from each file
121 klukas 1.10 for i, file in enumerate(files):
122     hist = file.file.GetDirectory(path).Get(name)
123     if not hist: continue
124     hist.SetTitle(file.name)
125 klukas 1.13 color = options.colors[i % len(options.colors)]
126 klukas 1.10 hist.SetLineColor(color)
127 klukas 1.13 # if options.fill:
128     # r, g, b = plot_colors_rgb[i % len(colors)]
129     # #fill_color = ROOT.TColor.GetColor(r * 1.2, g * 1.2, b * 1.2)
130     # fill_color = color
131     # hist.SetFillColor(fill_color)
132     # hist.SetFillStyle(1001)
133     # print "Hist ", hist.GetFillColor()
134 klukas 1.10 if options.markers:
135     hist.SetMarkerColor(color)
136     hist.SetMarkerStyle(marker_styles[i])
137     else:
138     hist.SetMarkerSize(0)
139 klukas 1.13 if options.overflow or (options.sticky and "Overflow" in name):
140 klukas 1.10 nbins = hist.GetNbinsX()
141     overflow = hist.GetBinContent(nbins + 1)
142     hist.AddBinContent(nbins, overflow)
143 klukas 1.13 if options.underflow or (options.sticky and "Underflow" in name):
144 klukas 1.10 underflow = hist.GetBinContent(0)
145     hist.AddBinContent(1, underflow)
146 klukas 1.13 if options.normalize or (options.sticky and "Norm" in name):
147 klukas 1.10 integral = hist.Integral()
148 klukas 1.11 if integral: hist.Scale(1. / integral)
149 klukas 1.12 hists.append(hist)
150 klukas 1.13 if options.normalize_to_file:
151     integral = hists[int(options.normalize_to_file) - 1].Integral()
152     if integral:
153     for hist in hists:
154     hist.Scale(hist.Integral() / integral)
155 klukas 1.12 #### Combine hists in a THStack and draw
156     pads = [canvas]
157     stack = ROOT.THStack("st%.3i" % int(counter), title)
158     legend_height = 0.04 * len(files) + 0.02
159     legend = ROOT.TLegend(0.65, 0.89 - legend_height, 0.87, 0.89)
160     for hist in hists:
161 klukas 1.10 stack.Add(hist)
162     legend.AddEntry(hist)
163 klukas 1.13 stack.Draw(options.opt)
164 klukas 1.12 stack.GetXaxis().SetTitle(x_title)
165     stack.GetYaxis().SetTitle(y_title)
166 klukas 1.13 if options.ratio or (options.sticky and "Ratio" in name):
167 klukas 1.12 pads, stack, stack_ratio = add_ratio_plot(hists, stack, counter)
168     pads[1].cd()
169 klukas 1.13 stack_ratio.Draw(options.opt)
170 klukas 1.12 pads[0].cd()
171 klukas 1.13 pads[0].SetLogx(options.logx or (options.sticky and "Logx" in name))
172     pads[0].SetLogy(options.logy or (options.sticky and "Logy" in name))
173     stack.Draw(options.opt)
174 klukas 1.10 if options.numbering:
175     display_page_number(counter)
176 klukas 1.13 if options.efficiency or (options.sticky and "Eff" in name):
177     stack.Draw(options.opt + "e")
178 klukas 1.10 stack.SetMaximum(1.)
179     stack.SetMinimum(0.)
180 klukas 1.13 if options.overflow or (options.sticky and "Overflow" in name):
181 klukas 1.10 display_overflow(stack, hist)
182 klukas 1.13 if options.underflow or (options.sticky and "Underflow" in name):
183 klukas 1.10 display_underflow(stack, hist)
184     legend.Draw()
185 klukas 1.13 save_plot(stack, options.plot_dir, path, name, counter)
186    
187 klukas 1.1
188 klukas 1.10
189 klukas 1.13 ######## Define some supporting functions #####################################
190    
191 klukas 1.8 def save_plot(stack, plot_dir, path, name, counter):
192 klukas 1.13 """Save the canvas to the output format defined by --ext."""
193 klukas 1.9 output_file_name = "%s/%s/%s.%s" % (plot_dir, path, name, options.ext)
194     canvas.SaveAs(output_file_name)
195 klukas 1.8 if options.ext == "pdf":
196 klukas 1.9 numbered_pdf_name = "%.3i.pdf" % counter
197     shutil.copy(output_file_name, numbered_pdf_name)
198 klukas 1.8 report_progress(counter, 1)
199    
200     def report_progress(counter, divisor):
201 klukas 1.13 """Print the current number of finished plots."""
202 klukas 1.8 if counter % divisor == 0:
203     print "\r%i plots written to %s" % (counter, options.output),
204     sys.stdout.flush()
205    
206     def merge_pdf():
207 klukas 1.13 """Merge together all the produced plots into one pdf file."""
208 klukas 1.8 print "Writing merged pdf..."
209     os.system("gs -q -dBATCH -dNOPAUSE -sDEVICE=pdfwrite "
210     "-dAutoRotatePages=/All "
211     "-sOutputFile=%s.pdf " % options.output +
212     "[0-9][0-9][0-9].pdf")
213     os.system("rm [0-9]*.pdf")
214    
215 klukas 1.10 def display_page_number(page_number):
216 klukas 1.13 """Add a page number to the top corner of the canvas."""
217 klukas 1.10 page_text = ROOT.TText()
218     page_text.SetTextSize(0.03)
219     page_text.SetTextAlign(33)
220     page_text.DrawTextNDC(0.97, 0.985, "%i" % page_number)
221    
222     def display_overflow(stack, hist):
223 klukas 1.13 """Add the overflow to the last bin and print 'Overflow' on the bin."""
224 klukas 1.10 nbins = hist.GetNbinsX()
225     x = 0.5 * (hist.GetBinLowEdge(nbins) +
226     hist.GetBinLowEdge(nbins + 1))
227     y = stack.GetMinimum("nostack")
228     display_bin_text(x, y, nbins, "Overflow")
229    
230     def display_underflow(stack, hist):
231 klukas 1.13 """Add the underflow to the first bin and print 'Underflow' on the bin."""
232 klukas 1.10 nbins = hist.GetNbinsX()
233     x = 0.5 * (hist.GetBinLowEdge(1) +
234     hist.GetBinLowEdge(2))
235     y = stack.GetMinimum("nostack")
236     display_bin_text(x, y, nbins, "Underflow")
237    
238     def display_bin_text(x, y, nbins, text):
239 klukas 1.13 """Overlay TEXT on this bin."""
240 klukas 1.10 bin_text = ROOT.TText()
241     bin_text.SetTextSize(min(1. / nbins, 0.04))
242     bin_text.SetTextAlign(12)
243     bin_text.SetTextAngle(90)
244     bin_text.SetTextColor(13)
245     bin_text.SetTextFont(42)
246     bin_text.DrawText(x, y, text)
247 klukas 1.1
248 klukas 1.12 def add_ratio_plot(hists, stack, counter):
249     """Divide canvas into two parts, and plot the ratio on the bottom."""
250     ## Both pads are set to the full canvas size to maintain font sizes
251     ## Fill style 4000 used to ensure pad transparency because of this
252     div = 0.3 # portion of canvas to use for ratio plot
253     margins = [ROOT.gStyle.GetPadTopMargin(), ROOT.gStyle.GetPadBottomMargin()]
254     useable_height = 1 - (margins[0] + margins[1])
255     canvas.Clear()
256     pad = ROOT.TPad("mainPad", "mainPad", 0., 0., 1., 1.)
257     pad.SetFillStyle(4000)
258     pad.Draw()
259     pad.SetBottomMargin(margins[1] + div * useable_height)
260     pad_ratio = ROOT.TPad("ratioPad", "ratioPad", 0., 0., 1., 1.);
261     pad_ratio.SetFillStyle(4000)
262     pad_ratio.Draw()
263     pad_ratio.SetTopMargin(margins[0] + (1 - div) * useable_height)
264     pad.cd()
265     stack.Draw()
266     stack_ratio = ROOT.THStack("stRatio%.3i" % int(counter),
267     ";%s;Ratio" % stack.GetXaxis().GetTitle())
268     for hist in hists[1:]:
269     ratio_hist = hist.Clone()
270     ratio_hist.Divide(hists[0])
271     stack_ratio.Add(ratio_hist)
272     stack_ratio.Draw()
273     stack_ratio.GetYaxis().SetNdivisions(507) # Avoids crowded labels
274     stack.GetXaxis().SetBinLabel(1, "") # Don't show numbers below top plot
275     stack.GetXaxis().SetTitle("")
276     if stack.GetYaxis().GetTitle() == "":
277     stack.GetYaxis().SetTitle("Content")
278     # Avoid overlap of y-axis numbers by supressing zero
279     if stack.GetMinimum() / stack.GetMaximum() < 0.25:
280     stack.SetMinimum(stack.GetMaximum() / 10000)
281     return [pad, pad_ratio], stack, stack_ratio
282    
283    
284 klukas 1.13
285     ######## Define the main program #############################################
286    
287     def main():
288     usage="""usage: %prog [options] file1.root file2.root file3.root ...
289    
290     function: overlays corresponding histograms from several files, dumping the
291     images into an identical directory structure in the local directory
292     and also merging all images into a single file (if output is pdf);
293     most style options can be controlled from your rootlogon.C macro"""
294    
295     parser = optparse.OptionParser(usage=usage)
296     parser.add_option('-e', '--ext', default="pdf",
297     help="choose an output extension; default is pdf")
298     parser.add_option('-o', '--opt', default="nostack p H",
299     help="pass OPT to the Draw command; default is "
300     "'nostack p H', add 'e' for error bars")
301     parser.add_option('-m', '--markers', action="store_true", default=False,
302     help="add markers to histograms")
303     parser.add_option('-s', '--sticky', action="store_true", default=False,
304     help="enable name-based special plotting options "
305     "(see below)")
306     # parser.add_option('-f', '--fill', action="store_true", default=False,
307     # help="Fill histograms with a color")
308     parser.add_option('--output', default="overlaidHists", metavar="NAME",
309     help="name of output directory; default is 'overlaidHists'")
310     parser.add_option('--numbering', action="store_true", default=False,
311     help="add a page number in the upper right of each plot")
312     parser.add_option('--match', default="", metavar="REGEX",
313     help="only make plots for paths containing the specified "
314     "regular expression (use '.*' for wildcard)")
315     parser.add_option('--normalize-to-file', default="", metavar="FILENUM",
316     help="normalize to the FILENUMth file")
317     group1 = optparse.OptionGroup(
318     parser,
319     "special plotting options",
320     "Use the command line options given below to apply changes to all "
321     "plots. If you only wish to apply an option to a specific plot, "
322     "you can use '-s' "
323     "to turn on sticky keywords (such as 'Norm'). Any plot that includes "
324     "the given keyword in its ROOT name will have the option applied "
325     "regardless of its presence or absence on the command line."
326     )
327     group1.add_option('-n', '--normalize', action="store_true", default=False,
328     help="'Norm': area normalize the histograms")
329     group1.add_option('--efficiency', action="store_true", default=False,
330     help="'Eff' : force y axis scale to run from 0 to 1")
331     group1.add_option('--logx', action="store_true", default=False,
332     help="'Logx': force log scale for x axis")
333     group1.add_option('--logy', action="store_true", default=False,
334     help="'Logy': force log scale for y axis")
335     group1.add_option('--overflow', action="store_true", default=False,
336     help="'Overflow' : display overflow content in "
337     "highest bin")
338     group1.add_option('--underflow', action="store_true", default=False,
339     help="'Underflow': display underflow content in "
340     "lowest bin")
341     group1.add_option('--ratio', action="store_true", default=False,
342     help="'Ratio': display a ratio plot below the normal "
343     "plot")
344     parser.add_option_group(group1)
345     global options
346     options, arguments = parser.parse_args()
347     options.plot_dir = "%s/%s" % (os.path.abspath('.'), options.output)
348     options.regex = re.compile(options.match)
349     files = [RootFile(filename) for filename in arguments]
350     ## if no arguments provided, just display the help message
351     if len(files) == 0:
352     parser.print_help()
353     sys.exit(0)
354     ## add style options and create the canvas
355     options = add_style_options(options)
356     global canvas
357     canvas = ROOT.TCanvas()
358     ## here, we decend into the files to start plotting
359     process_directory("", files)
360     print ""
361     if options.ext == "pdf":
362     merge_pdf()
363    
364    
365 klukas 1.1 if __name__ == "__main__":
366 klukas 1.13 main()
367 klukas 1.1