ViewVC Help
View File | Revision Log | Show Annotations | Root Listing
root/cvsroot/UserCode/RootMacros/overlayHists.py
Revision: 1.12
Committed: Fri Jan 15 04:20:33 2010 UTC (15 years, 3 months ago) by klukas
Content type: text/x-python
Branch: MAIN
Changes since 1.11: +66 -17 lines
Log Message:
Added ratio option

File Contents

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