File indexing completed on 2023-03-17 11:16:37
0001 """
0002 Utilities for rootplot including histogram classes.
0003 """
0004 from __future__ import print_function
0005
0006 from builtins import range
0007 __license__ = '''\
0008 Copyright (c) 2009-2010 Jeff Klukas <klukas@wisc.edu>
0009
0010 Permission is hereby granted, free of charge, to any person obtaining a copy
0011 of this software and associated documentation files (the "Software"), to deal
0012 in the Software without restriction, including without limitation the rights
0013 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
0014 copies of the Software, and to permit persons to whom the Software is
0015 furnished to do so, subject to the following conditions:
0016
0017 The above copyright notice and this permission notice shall be included in
0018 all copies or substantial portions of the Software.
0019
0020 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
0021 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
0022 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
0023 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
0024 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
0025 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
0026 THE SOFTWARE.
0027 '''
0028
0029
0030
0031 import math
0032 import ROOT
0033 import re
0034 import copy
0035 import array
0036 import os.path
0037 import sys
0038 import fnmatch
0039 from random import gauss
0040
0041
0042
0043 class Hist2D(object):
0044 """A container to hold the parameters from a 2D ROOT histogram."""
0045 def __init__(self, hist, label="__nolabel__", title=None,
0046 xlabel=None, ylabel=None):
0047 try:
0048 if not hist.InheritsFrom("TH2"):
0049 raise TypeError("%s does not inherit from TH2" % hist)
0050 except:
0051 raise TypeError("%s is not a ROOT object" % hist)
0052 self.rootclass = hist.ClassName()
0053 self.name = hist.GetName()
0054 self.nbinsx = nx = hist.GetNbinsX()
0055 self.nbinsy = ny = hist.GetNbinsY()
0056 self.binlabelsx = process_bin_labels([hist.GetXaxis().GetBinLabel(i)
0057 for i in range(1, nx + 1)])
0058 if self.binlabelsx:
0059 self.nbinsx = nx = self.binlabelsx.index('')
0060 self.binlabelsx = self.binlabelsx[:ny]
0061 self.binlabelsy = process_bin_labels([hist.GetYaxis().GetBinLabel(i)
0062 for i in range(1, ny + 1)])
0063 if self.binlabelsy:
0064 self.nbinsy = ny = self.binlabelsy.index('')
0065 self.binlabelsy = self.binlabelsy[:ny]
0066 self.entries = hist.GetEntries()
0067 self.content = [[hist.GetBinContent(i, j) for i in range(1, nx + 1)]
0068 for j in range(1, ny + 1)]
0069 self.xedges = [hist.GetXaxis().GetBinLowEdge(i)
0070 for i in range(1, nx + 2)]
0071 self.yedges = [hist.GetYaxis().GetBinLowEdge(i)
0072 for i in range(1, ny + 2)]
0073 self.x = [(self.xedges[i+1] + self.xedges[i])/2
0074 for i in range(nx)]
0075 self.y = [(self.yedges[i+1] + self.yedges[i])/2
0076 for i in range(ny)]
0077 self.title = title or hist.GetTitle()
0078 self.xlabel = xlabel or hist.GetXaxis().GetTitle()
0079 self.ylabel = ylabel or hist.GetYaxis().GetTitle()
0080 self.label = label
0081 def _flat_content(self):
0082 flatcontent = []
0083 for row in self.content:
0084 flatcontent += row
0085 return flatcontent
0086 def __getitem__(self, index):
0087 """Return contents of indexth bin: x.__getitem__(y) <==> x[y]"""
0088 return self._flat_content()[index]
0089 def __len__(self):
0090 """Return the number of bins: x.__len__() <==> len(x)"""
0091 return len(self._flat_content())
0092 def __iter__(self):
0093 """Iterate through bins: x.__iter__() <==> iter(x)"""
0094 return iter(self._flat_content())
0095 def TH2F(self, name=""):
0096 """Return a ROOT.TH2F object with contents of this Hist2D."""
0097 th2f = ROOT.TH2F(name, "",
0098 self.nbinsx, array.array('f', self.xedges),
0099 self.nbinsy, array.array('f', self.yedges))
0100 th2f.SetTitle("%s;%s;%s" % (self.title, self.xlabel, self.ylabel))
0101 for ix in range(self.nbinsx):
0102 for iy in range(self.nbinsy):
0103 th2f.SetBinContent(ix + 1, iy + 1, self.content[iy][ix])
0104 return th2f
0105
0106 class Hist(object):
0107 """A container to hold the parameters from a ROOT histogram."""
0108 def __init__(self, hist, label="__nolabel__",
0109 name=None, title=None, xlabel=None, ylabel=None):
0110 try:
0111 hist.GetNbinsX()
0112 self.__init_TH1(hist)
0113 except AttributeError:
0114 try:
0115 hist.GetN()
0116 self.__init_TGraph(hist)
0117 except AttributeError:
0118 raise TypeError("%s is not a 1D histogram or TGraph" % hist)
0119 self.rootclass = hist.ClassName()
0120 self.name = name or hist.GetName()
0121 self.title = title or hist.GetTitle().split(';')[0]
0122 self.xlabel = xlabel or hist.GetXaxis().GetTitle()
0123 self.ylabel = ylabel or hist.GetYaxis().GetTitle()
0124 self.label = label
0125 def __init_TH1(self, hist):
0126 self.nbins = n = hist.GetNbinsX()
0127 self.binlabels = process_bin_labels([hist.GetXaxis().GetBinLabel(i)
0128 for i in range(1, n + 1)])
0129 if self.binlabels and '' in self.binlabels:
0130
0131 self.nbins = n = self.binlabels.index('')
0132 self.binlabels = self.binlabels[:n]
0133 self.entries = hist.GetEntries()
0134 self.xedges = [hist.GetBinLowEdge(i) for i in range(1, n + 2)]
0135 self.x = [(self.xedges[i+1] + self.xedges[i])/2 for i in range(n)]
0136 self.xerr = [(self.xedges[i+1] - self.xedges[i])/2 for i in range(n)]
0137 self.xerr = [self.xerr[:], self.xerr[:]]
0138 self.width = [(self.xedges[i+1] - self.xedges[i]) for i in range(n)]
0139 self.y = [hist.GetBinContent(i) for i in range(1, n + 1)]
0140 self.yerr = [hist.GetBinError( i) for i in range(1, n + 1)]
0141 self.yerr = [self.yerr[:], self.yerr[:]]
0142 self.underflow = hist.GetBinContent(0)
0143 self.overflow = hist.GetBinContent(self.nbins + 1)
0144 def __init_TGraph(self, hist):
0145 self.nbins = n = hist.GetN()
0146 self.x, self.y = [], []
0147 x, y = ROOT.Double(0), ROOT.Double(0)
0148 for i in range(n):
0149 hist.GetPoint(i, x, y)
0150 self.x.append(copy.copy(x))
0151 self.y.append(copy.copy(y))
0152 lower = [max(0, hist.GetErrorXlow(i)) for i in range(n)]
0153 upper = [max(0, hist.GetErrorXhigh(i)) for i in range(n)]
0154 self.xerr = [lower[:], upper[:]]
0155 lower = [max(0, hist.GetErrorYlow(i)) for i in range(n)]
0156 upper = [max(0, hist.GetErrorYhigh(i)) for i in range(n)]
0157 self.yerr = [lower[:], upper[:]]
0158 self.xedges = [self.x[i] - self.xerr[0][i] for i in range(n)]
0159 self.xedges.append(self.x[n - 1] + self.xerr[1][n - 1])
0160 self.width = [self.xedges[i + 1] - self.xedges[i] for i in range(n)]
0161 self.underflow, self.overflow = 0, 0
0162 self.binlabels = None
0163 self.entries = n
0164 def __add__(self, b):
0165 """Return the sum of self and b: x.__add__(y) <==> x + y"""
0166 c = copy.copy(self)
0167 for i in range(len(self)):
0168 c.y[i] += b.y[i]
0169 c.yerr[0][i] += b.yerr[0][i]
0170 c.yerr[1][i] += b.yerr[1][i]
0171 c.overflow += b.overflow
0172 c.underflow += b.underflow
0173 return c
0174 def __sub__(self, b):
0175 """Return the difference of self and b: x.__sub__(y) <==> x - y"""
0176 c = copy.copy(self)
0177 for i in range(len(self)):
0178 c.y[i] -= b.y[i]
0179 c.yerr[0][i] -= b.yerr[0][i]
0180 c.yerr[1][i] -= b.yerr[1][i]
0181 c.overflow -= b.overflow
0182 c.underflow -= b.underflow
0183 return c
0184 def __div__(self, denominator):
0185 return self.divide(denominator)
0186 def __getitem__(self, index):
0187 """Return contents of indexth bin: x.__getitem__(y) <==> x[y]"""
0188 return self.y[index]
0189 def __setitem__(self, index, value):
0190 """Set contents of indexth bin: x.__setitem__(i, y) <==> x[i]=y"""
0191 self.y[index] = value
0192 def __len__(self):
0193 """Return the number of bins: x.__len__() <==> len(x)"""
0194 return self.nbins
0195 def __iter__(self):
0196 """Iterate through bins: x.__iter__() <==> iter(x)"""
0197 return iter(self.y)
0198 def min(self, threshold=None):
0199 """Return the y-value of the bottom tip of the lowest errorbar."""
0200 vals = [(yval - yerr) for yval, yerr in zip(self.y, self.yerr[0])
0201 if (yval - yerr) > threshold]
0202 if vals:
0203 return min(vals)
0204 else:
0205 return threshold
0206 def av_xerr(self):
0207 """Return average between the upper and lower xerr."""
0208 return [(self.xerr[0][i] + self.xerr[1][i]) / 2
0209 for i in range(self.nbins)]
0210 def av_yerr(self):
0211 """Return average between the upper and lower yerr."""
0212 return [(self.yerr[0][i] + self.yerr[1][i]) / 2
0213 for i in range(self.nbins)]
0214 def scale(self, factor):
0215 """
0216 Scale contents, errors, and over/underflow by the given scale factor.
0217 """
0218 self.y = [x * factor for x in self.y]
0219 self.yerr[0] = [x * factor for x in self.yerr[0]]
0220 self.yerr[1] = [x * factor for x in self.yerr[1]]
0221 self.overflow *= factor
0222 self.underflow *= factor
0223 def delete_bin(self, index):
0224 """
0225 Delete a the contents of a bin, sliding all the other data one bin to
0226 the left. This can be useful for histograms with labeled bins.
0227 """
0228 self.nbins -= 1
0229 self.xedges.pop()
0230 self.x.pop()
0231 self.width.pop()
0232 self.y.pop(index)
0233 self.xerr[0].pop(index)
0234 self.xerr[1].pop(index)
0235 self.yerr[0].pop(index)
0236 self.yerr[1].pop(index)
0237 if self.binlabels:
0238 self.binlabels.pop(index)
0239 def TH1F(self, name=None):
0240 """Return a ROOT.TH1F object with contents of this Hist"""
0241 th1f = ROOT.TH1F(name or self.name, "", self.nbins,
0242 array.array('f', self.xedges))
0243 th1f.Sumw2()
0244 th1f.SetTitle("%s;%s;%s" % (self.title, self.xlabel, self.ylabel))
0245 for i in range(self.nbins):
0246 th1f.SetBinContent(i + 1, self.y[i])
0247 try:
0248 th1f.SetBinError(i + 1, (self.yerr[0][i] + self.yerr[1][i]) / 2)
0249 except TypeError:
0250 th1f.SetBinError(i + 1, self.yerr[i])
0251 if self.binlabels:
0252 th1f.GetXaxis().SetBinLabel(i + 1, self.binlabels[i])
0253 th1f.SetBinContent(0, self.underflow)
0254 th1f.SetBinContent(self.nbins + 2, self.overflow)
0255 th1f.SetEntries(self.entries)
0256 return th1f
0257 def TGraph(self, name=None):
0258 """Return a ROOT.TGraphAsymmErrors object with contents of this Hist"""
0259 x = array.array('f', self.x)
0260 y = array.array('f', self.y)
0261 xl = array.array('f', self.xerr[0])
0262 xh = array.array('f', self.xerr[1])
0263 yl = array.array('f', self.yerr[0])
0264 yh = array.array('f', self.yerr[1])
0265 tgraph = ROOT.TGraphAsymmErrors(self.nbins, x, y, xl, xh, yl, yh)
0266 tgraph.SetName(name or self.name)
0267 tgraph.SetTitle('%s;%s;%s' % (self.title, self.xlabel, self.ylabel))
0268 return tgraph
0269 def divide(self, denominator):
0270 """
0271 Return the simple quotient with errors added in quadrature.
0272
0273 This function is called by the division operator:
0274 hist3 = hist1.divide_wilson(hist2) <--> hist3 = hist1 / hist2
0275 """
0276 if len(self) != len(denominator):
0277 raise TypeError("Cannot divide %s with %i bins by "
0278 "%s with %i bins." %
0279 (denominator.name, len(denominator),
0280 self.name, len(self)))
0281 quotient = copy.deepcopy(self)
0282 num_yerr = self.av_yerr()
0283 den_yerr = denominator.av_yerr()
0284 quotient.yerr = [0. for i in range(self.nbins)]
0285 for i in range(self.nbins):
0286 if denominator[i] == 0 or self[i] == 0:
0287 quotient.y[i] = 0.
0288 else:
0289 quotient.y[i] = self[i] / denominator[i]
0290 quotient.yerr[i] = quotient[i]
0291 quotient.yerr[i] *= math.sqrt((num_yerr[i] / self[i]) ** 2 +
0292 (den_yerr[i] / denominator[i]) ** 2)
0293 if quotient.yerr[i] > quotient[i]:
0294 quotient.yerr[i] = quotient[i]
0295 quotient.yerr = [quotient.yerr, quotient.yerr]
0296 return quotient
0297 def divide_wilson(self, denominator):
0298 """Return an efficiency plot with Wilson score interval errors."""
0299 if len(self) != len(denominator):
0300 raise TypeError("Cannot divide %s with %i bins by "
0301 "%s with %i bins." %
0302 (denominator.name, len(denominator),
0303 self.name, len(self)))
0304 eff, upper_err, lower_err = wilson_interval(self.y, denominator.y)
0305 quotient = copy.deepcopy(self)
0306 quotient.y = eff
0307 quotient.yerr = [lower_err, upper_err]
0308 return quotient
0309
0310 class HistStack(object):
0311 """
0312 A container to hold Hist objects for plotting together.
0313
0314 When plotting, the title and the x and y labels of the last Hist added
0315 will be used unless specified otherwise in the constructor.
0316 """
0317 def __init__(self, hists=None, title=None, xlabel=None, ylabel=None):
0318 self.hists = []
0319 self.kwargs = []
0320 self.title = title
0321 self.xlabel = xlabel
0322 self.ylabel = ylabel
0323 if hists:
0324 for hist in hists:
0325 self.add(hist)
0326 def __getitem__(self, index):
0327 """Return indexth hist: x.__getitem__(y) <==> x[y]"""
0328 return self.hists[index]
0329 def __setitem__(self, index, value):
0330 """Replace indexth hist with value: x.__setitem__(i, y) <==> x[i]=y"""
0331 self.hists[index] = value
0332 def __len__(self):
0333 """Return the number of hists in the stack: x.__len__() <==> len(x)"""
0334 return len(self.hists)
0335 def __iter__(self):
0336 """Iterate through hists in the stack: x.__iter__() <==> iter(x)"""
0337 return iter(self.hists)
0338 def max(self):
0339 """Return the value of the highest bin of all hists in the stack."""
0340 maxes = [max(x) for x in self.hists]
0341 try:
0342 return max(maxes)
0343 except ValueError:
0344 return 0
0345 def stackmax(self):
0346 """Return the value of the highest bin in the addition of all hists."""
0347 try:
0348 return max([sum([h[i] for h in self.hists])
0349 for i in range(self.hists[0].nbins)])
0350 except:
0351 print([h.nbins for h in self.hists])
0352 def scale(self, factor):
0353 """Scale all Hists by factor."""
0354 for hist in self.hists:
0355 hist.scale(factor)
0356 def min(self, threshold=None):
0357 """
0358 Return the value of the lowest bin of all hists in the stack.
0359
0360 If threshold is specified, only values above the threshold will be
0361 considered.
0362 """
0363 mins = [x.min(threshold) for x in self.hists]
0364 return min(mins)
0365 def add(self, hist, **kwargs):
0366 """
0367 Add a Hist object to this stack.
0368
0369 Any additional keyword arguments will be added to just this Hist
0370 when the stack is plotted.
0371 """
0372 if "label" in kwargs:
0373 hist.label = kwargs['label']
0374 del kwargs['label']
0375 if len(self) > 0:
0376 if hist.xedges != self.hists[0].xedges:
0377 raise ValueError("Cannot add %s to stack; all Hists must "
0378 "have the same binning." % hist.name)
0379 self.hists.append(hist)
0380 self.kwargs.append(kwargs)
0381
0382
0383
0384
0385 class RootFile(object):
0386 """A wrapper for TFiles, allowing easier access to methods."""
0387 def __init__(self, filename, name=None):
0388 self.filename = filename
0389 self.name = name or filename[:-5]
0390 self.file = ROOT.TFile(filename, 'read')
0391 if self.file.IsZombie():
0392 raise ValueError("Error opening %s" % filename)
0393 def cd(self, directory=''):
0394 """Make directory the current directory."""
0395 self.file.cd(directory)
0396 def get(self, object_name, path=None, type1D=Hist, type2D=Hist2D):
0397 """Return a Hist object from the given path within this file."""
0398 if not path:
0399 path = os.path.dirname(object_name)
0400 object_name = os.path.basename(object_name)
0401 try:
0402 roothist = self.file.GetDirectory(path).Get(object_name)
0403 except ReferenceError as e:
0404 raise ReferenceError(e)
0405 try:
0406 return type2D(roothist)
0407 except TypeError:
0408 return type1D(roothist)
0409
0410 def ls(directory=None):
0411 """Return a python list of ROOT object names from the given directory."""
0412 if directory == None:
0413 keys = ROOT.gDirectory.GetListOfKeys()
0414 else:
0415 keys = ROOT.gDirectory.GetDirectory(directory).GetListOfKeys()
0416 key = keys[0]
0417 names = []
0418 while key:
0419 obj = key.ReadObj()
0420 key = keys.After(key)
0421 names.append(obj.GetName())
0422 return names
0423
0424 def pwd():
0425 """Return ROOT's present working directory."""
0426 return ROOT.gDirectory.GetPath()
0427
0428 def get(object_name):
0429 """Return a Hist object with the given name."""
0430 return Hist(ROOT.gDirectory.Get(object_name))
0431
0432
0433
0434
0435 def loadROOT(batch=True):
0436
0437
0438 saved_argv = sys.argv[:]
0439 argstring = ' '.join(sys.argv)
0440 sys.argv = [sys.argv[0]]
0441 try:
0442 import ROOT
0443 except ImportError:
0444 print("""\
0445 The program was unable to access PyROOT. Usually, this just requires switching
0446 to the same major version of python that used when compiling ROOT. To
0447 determine which version that is, try the following command:
0448 root -config 2>&1 | tr ' ' '\\n' | egrep 'python|PYTHON'
0449 If this is different from the python version you are currently using, try
0450 changing your PATH to point to the new one.""")
0451 sys.exit(1)
0452
0453
0454 if batch:
0455 ROOT.gROOT.SetBatch()
0456 ROOT.gErrorIgnoreLevel = ROOT.kWarning
0457
0458 if os.path.exists('rootlogon.C'):
0459 ROOT.gROOT.Macro('rootlogon.C')
0460 sys.argv = saved_argv[:]
0461 return ROOT
0462
0463 def replace(string, replacements):
0464 """
0465 Modify a string based on a list of patterns and substitutions.
0466
0467 replacements should be a list of two-entry tuples, the first entry giving
0468 a string to search for and the second entry giving the string with which
0469 to replace it. If replacements includes a pattern entry containing
0470 'use_regexp', then all patterns will be treated as regular expressions
0471 using re.sub.
0472 """
0473 if not replacements:
0474 return string
0475 if 'use_regexp' in [x for x,y in replacements]:
0476 for pattern, repl in [x for x in replacements
0477 if x[0] != 'use_regexp']:
0478 string = re.sub(pattern, repl, string)
0479 else:
0480 for pattern, repl in replacements:
0481 string = string.replace(pattern, repl)
0482 if re.match(_all_whitespace_string, string):
0483 return ""
0484 return string
0485
0486 def process_bin_labels(binlabels):
0487 has_labels = False
0488 for binlabel in binlabels:
0489 if binlabel:
0490 has_labels = True
0491 if has_labels:
0492 return binlabels
0493 else:
0494 return None
0495
0496 def wilson_interval(numerator_array, denominator_array):
0497 eff, upper_err, lower_err = [], [], []
0498 for n, d in zip(numerator_array, denominator_array):
0499 try:
0500 p = float(n) / d
0501 s = math.sqrt(p * (1 - p) / d + 1 / (4 * d * d))
0502 t = p + 1 / (2 * d)
0503 eff.append(p)
0504 upper_err.append(-p + 1/(1 + 1/d) * (t + s))
0505 lower_err.append(+p - 1/(1 + 1/d) * (t - s))
0506 except ZeroDivisionError:
0507 eff.append(0)
0508 upper_err.append(0)
0509 lower_err.append(0)
0510 return eff, upper_err, lower_err
0511
0512 def find_num_processors():
0513 import os
0514 try:
0515 num_processors = os.sysconf('SC_NPROCESSORS_ONLN')
0516 except:
0517 try:
0518 num_processors = os.environ['NUMBER_OF_PROCESSORS']
0519 except:
0520 num_processors = 1
0521 return num_processors
0522
0523 def testfile():
0524 outfile = ROOT.TFile("test.root", "recreate")
0525 for i in range(4):
0526 d = outfile.mkdir("dir%i" % (i + 1))
0527 d.cd()
0528 for j in range(4):
0529 hist = ROOT.TH1F("hist%i" % (j + 1), "A Histogram", 10, 0, 10)
0530 hist.Fill(j)
0531 hist.Write()
0532 outfile.Write()
0533 return outfile
0534
0535
0536
0537 glob_magic_check = re.compile('[*?[]')
0538
0539 def has_glob_magic(s):
0540 return glob_magic_check.search(s) is not None
0541
0542
0543
0544
0545
0546
0547 def _rootglob1(tdirectory, dirname, pattern):
0548 if not tdirectory.GetDirectory(dirname):
0549 return []
0550 names = [key.GetName() for key in
0551 tdirectory.GetDirectory(dirname).GetListOfKeys()]
0552 return fnmatch.filter(names, pattern)
0553
0554 def _rootglob0(tdirectory, dirname, basename):
0555 if tdirectory.Get(os.path.join(dirname, basename)):
0556 return [basename]
0557 return []
0558
0559 def rootglob(tdirectory, pathname):
0560 """Return a list of paths matching a pathname pattern.
0561
0562 The pattern may contain simple shell-style wildcards a la fnmatch.
0563
0564 >>> import rootplot.utilities
0565 >>> f = rootplot.utilities.testfile()
0566 >>> rootglob(f, '*')
0567 ['dir1', 'dir2', 'dir3', 'dir4']
0568 >>> rootglob(f, 'dir1/*')
0569 ['dir1/hist1', 'dir1/hist2', 'dir1/hist3', 'dir1/hist4']
0570 >>> rootglob(f, '*/hist1')
0571 ['dir1/hist1', 'dir2/hist1', 'dir3/hist1', 'dir4/hist1']
0572 >>> rootglob(f, 'dir1/hist[1-2]')
0573 ['dir1/hist1', 'dir1/hist2']
0574 """
0575 return list(irootglob(tdirectory, pathname))
0576
0577 def irootglob(tdirectory, pathname):
0578 """Return an iterator which yields the paths matching a pathname pattern.
0579
0580 The pattern may contain simple shell-style wildcards a la fnmatch.
0581
0582 """
0583 if not has_glob_magic(pathname):
0584 if tdirectory.Get(pathname):
0585 yield pathname
0586 return
0587 dirname, basename = os.path.split(pathname)
0588 if has_glob_magic(dirname):
0589 dirs = irootglob(tdirectory, dirname)
0590 else:
0591 dirs = [dirname]
0592 if has_glob_magic(basename):
0593 glob_in_dir = _rootglob1
0594 else:
0595 glob_in_dir = _rootglob0
0596 for dirname in dirs:
0597 for name in glob_in_dir(tdirectory, dirname, basename):
0598 yield os.path.join(dirname, name)
0599
0600 if __name__ == '__main__':
0601 import doctest
0602 doctest.testmod()