Ejemplo n.º 1
0
 def __init__(self, cutvalue, **kwargs):
     MethodProxy.__init__(self)
     self._name = "CutMarker_{}".format(uuid4().hex[:8])
     self._cutvalue = cutvalue
     self._lineymin = None
     self._lineymax = None
     self._rellinelength = 0.8
     self._drawarrow = True
     self._relarrowpos = 1.0
     self._relarrowlength = 0.15
     self._direction = 1
     self._drawoption = ""
     self._propcache = DissectProperties(kwargs, [self, Line, Arrow])
     props = self._propcache.pop("CutMarker", {})
     props.setdefault("template", "common")
     self.DeclareProperties(**props)
Ejemplo n.º 2
0
    def Register(self, object, pad=0, **kwargs):
        r"""Register a :class:`ROOT` object to the plot.

        The associated :class:`.Pad` is defined by **pad**. Properties of the **object**
        and the associated :class:`.Pad` can be changed via keyword arguments.

        :param object: *drawable* :class:`ROOT` object to be registered to the plot,
            e.g. ``Histo1D``, ``TH1D``, ``Histo2D``, ``TH2D``, ``Stack``, ...
        :type object: ``ROOT.TObject``

        :param pad: index of the target pad (default: 0)
        :type pad: ``int``

        :param \**kwargs: **object**, :class:`.Pad` properties
        """
        self.AssertPadIndex(pad)
        properties = DissectProperties(kwargs, [object, Pad])
        objclsname = object.__class__.__name__
        properties["Pad"].update({
            key: properties[objclsname][key]
            for key in ["xmin", "xmax", "ymin", "ymax"]
            if key in properties[objclsname].keys()
        })
        logger.debug("Registering {} object {} ('{}') to Plot '{}'...".format(
            objclsname,
            object,
            object.GetName()
            if not object.InheritsFrom("TText") else object.GetTitle(),
            self.GetName(),
        ))
        self._padproperties[pad].update(properties["Pad"])
        try:
            for key, value in object.BuildFrame(
                    **MergeDicts(self._padproperties[pad])).items():
                if (key.endswith("max") and
                        self._padproperties[pad].get(key, value - 1) < value
                    ) or (key.endswith("min") and self._padproperties[pad].get(
                        key, value + 1) > value):
                    self._padproperties[pad][key] = value
                if key.endswith("title"):
                    tmpltval = self._padproperties[pad].get(key, None)
                    if self._padproperties[pad].get(key, tmpltval) == tmpltval:
                        self._padproperties[pad][key] = value
        except AttributeError:
            logger.debug(
                "Cannot infer frame value ranges from {} object '{}'".format(
                    objclsname, object.GetName()))
        self._store[pad].append((object, properties[objclsname]))
        self._padproperties[pad].update(properties["Pad"])
Ejemplo n.º 3
0
    def Print(self, path, **kwargs):
        r"""Print the histogram to a file.

        Creates a PDF/PNG/... file with the absolute path defined by **path**. If a file
        with the same name already exists it will be overwritten (can be changed  with
        the **overwrite** keyword argument). If **mkdir** is set to ``True`` (default:
        ``False``) directories in **path** with do not yet exist will be created
        automatically. The styling of the histogram, pad and canvas can be configured
        via their respective properties passed as keyword arguments.

        :param path: path of the output file (must end with '.pdf', '.png', ...)
        :type path: ``str``

        :param \**kwargs: :class:`.Histo2D`, :class:`.Plot`, :class:`.Canvas` and
            :class:`.Pad` properties + additional properties (see below)

        Keyword Arguments:

            * **inject** (``list``, ``tuple``, ``ROOT.TObject``) -- inject a (list of)
              *drawable* :class:`ROOT` object(s) to the main pad, object properties can
              be specified by passing instead a ``tuple`` of the format
              :code:`(obj, props)` where :code:`props` is a ``dict`` holding the object
              properties (default: \[\])

            * **overwrite** (``bool``) -- overwrite an existing file located at **path**
              (default: ``True``)

            * **mkdir** (``bool``) -- create non-existing directories in **path**
              (default: ``False``)
        """
        injections = {"inject0": kwargs.pop("inject", [])}
        kwargs.setdefault("logy", False)  # overwriting Pad template's default value!
        properties = DissectProperties(kwargs, [Histo2D, Plot, Canvas, Pad])
        if any(
            map(
                lambda s: "Z" in s.upper(),
                [self.GetDrawOption(), properties["Pad"].get("drawoption", "")],
            )
        ):
            properties["Pad"].setdefault("rightmargin", 0.18)
        plot = Plot(npads=1)
        plot.Register(self, **MergeDicts(properties["Histo2D"], properties["Pad"]))
        plot.Print(
            path, **MergeDicts(properties["Plot"], properties["Canvas"], injections)
        )
Ejemplo n.º 4
0
 def __init__(self, name="undefined", *args, **kwargs):
     MethodProxy.__init__(self)
     self._frame = None
     self._drawframe = False
     self._xmin = self._ymin = 1e-2
     self._xmax = self._ymax = 1.0
     self._xunits, self._yunits = None, None
     self._ypadding = 0.25  # frame is 25% higher than max. bin content
     self._drawlegend = False
     self._legendxshift = 0.0
     self._legendyshift = 0.0
     self._labeloption = {}
     if len(args) > 0:
         self._xposlow, self._yposlow = args[0:2]
         self._xposup, self._yposup = args[2:4]
         ROOT.TPad.__init__(self, name, "", *args)
     else:
         self._xposlow, self._yposlow, self._xposup, self._yposup = (
             0.0,
             0.0,
             1.0,
             1.0,
         )
         ROOT.TPad.__init__(self)
         self.SetName(name)
     self.SetTitle(";;")
     properties = DissectProperties(kwargs, [{
         "Frame": self._frameproperties
     }, Pad])
     self.Draw()
     self.DeclareProperty("padposition",
                          properties["Pad"].pop("padposition"))
     self.DeclareProperties(**properties["Pad"])
     self.Draw()
     self.cd()
     if self._drawframe:
         self.DrawFrame(**properties["Frame"])
Ejemplo n.º 5
0
 def CreateHistograms(self):
     factories = {}
     for histotype, configs in self._configs.items():
         self._store[histotype] = {}
         for i, (infile, config) in enumerate(configs):
             self._store[histotype][i] = {}
             config = DissectProperties(
                 config, [Histo1D, {
                     "Fill": ["tree", "cuts", "weight"]
                 }])
             tree = config["Fill"]["tree"]
             if not (infile, tree) in factories.keys():
                 factories[infile, tree] = IOManager.Factory(infile, tree)
             for varexp, comparator, cutvalue in self._drawcuts:
                 if not varexp in self._binning:
                     logger.warning("No binning defined for varexp '{}'. "
                                    "Skipping...".format(varexp))
                     continue
                 reducedcuts = list(
                     filter(
                         lambda x: x != "".join(
                             [varexp, comparator, cutvalue]),
                         self._cuts,
                     ))
                 self._store[histotype][i][varexp, cutvalue] = Histo1D(
                     "N1_{}".format(uuid4().hex[:8]), "",
                     self._binning[varexp], **config["Histo1D"])
                 factories[infile, tree].Register(
                     self._store[histotype][i][varexp, cutvalue],
                     varexp=varexp,
                     weight=config["Fill"].get("weight", "1"),
                     cuts=list(config["Fill"].get("cuts", []) +
                               reducedcuts + self._preselection),
                 )
     for factory in factories.values():
         factory.Run()
Ejemplo n.º 6
0
    def __init__(self, name=None, *args, **kwargs):
        r"""Initialize a collection of 1-dimensional histograms.

        Create an instance of :class:`.Stack` with the specified **name**. Can also be
        used to copy another histogram (or upgrade from a :class:`ROOT.THStack`).

        :param name: name of the stack (default: random 8-digits HEX hash value)
        :type name: ``str``

        :param \*args: see below

        :param \**kwargs: :class:`.Stack` properties

        :Arguments:
            Depending on the number of arguments (besides **name**) there are two ways
            to initialize a :class:`.Stack` object\:

            * *zero* arguments\: an empty stack is created

            * *one* argument\:

                #. **stack** (``Stack``, ``THStack``) -- existing stack to be copied
        """
        MethodProxy.__init__(self)
        if name is None:
            name = uuid4().hex[:8]
        self._stacksumhisto = None
        self._stacksumproperties = {}
        self._stacksorting = None
        self._drawoption = ""
        self._drawstacksum = False
        self._store = dict(stack=[], nostack=[])
        if len(args) == 0:
            ROOT.THStack.__init__(self, name, "")
        elif len(args) == 1:
            if isinstance(args[0], str):
                ROOT.THStack.__init__(self, name, args[0])
            elif args[0].InheritsFrom("THStack"):
                ROOT.THStack.__init__(self, args[0])
                self.SetName(name)
                if args[0].__class__.__name__ == "Stack":
                    if args[0]._stacksumhisto is not None:
                        self._stacksumhisto = Histo1D(
                            "{}_stacksum", args[0]._stacksumhisto
                        )
                    for key, store in args[0]._store.items():
                        for histo in store:
                            self._store[key].append(
                                Histo1D("{}_{}".format(histo.GetName(), name), histo)
                            )
                    self.DeclareProperties(**args[0].GetProperties())
            else:
                raise TypeError
        else:
            raise TypeError
        for key, value in self.GetTemplate(kwargs.get("template", "common")).items():
            kwargs.setdefault(key, value)
        properties = DissectProperties(
            kwargs,
            [
                {
                    "Stacksum": [
                        "stacksum{}".format(p) for p in Histo1D.GetListOfProperties()
                    ]
                },
                Stack,
            ],
        )
        self.DeclareProperties(**properties["Stack"])
        self._stacksumproperties = properties["Stacksum"]
Ejemplo n.º 7
0
    def Print(self, path, **kwargs):
        r"""Print the histogram to a file.

        Creates a PDF/PNG/... file with the absolute path defined by **path**. If a file
        with the same name already exists it will be overwritten (can be changed  with
        the **overwrite** keyword argument). If **mkdir** is set to ``True`` (default:
        ``False``) directories in **path** with do not yet exist will be created
        automatically. The styling of the stack, pad and canvas can be configured via
        their respective properties passed as keyword arguments.

        Additional plots can be added to :class:`.Pad` s below the main one where the
        :class:`.Stack` object is drawn using the **ratio**, **contribution** and/or
        **sensitivity** flag (see below).

        :param path: path of the output file (must end with '.pdf', '.png', ...)
        :type path: ``str``

        :param \**kwargs: :class:`.Stack`, :class:`.Plot`, :class:`.Canvas` and
            :class:`.Pad` properties + additional properties (see below)

        Keyword Arguments:

            * **sort** (``bool``) -- define whether the stack is sorted before it is
              drawn (see :func:`Stack.SetStackSorting`, default: ``True``)

            * **contribution** (``bool``) -- draw the relative per-bin contribution of
              each stacked histogram in an additional :class:`.Pad` (default: ``False``)

            * **ratio** (``bool``) -- draw a :class:`RatioPlot` of (assumed) data versus
              the (assumed) total background in an additional :class:`.Pad` (default:
              ``False``)
            * **sensitivity** (``bool``) -- draw a sensitivity scan for all (assumed)
              signal histograms and using the (asumed) total background as reference in
              an additional :class:`.Pad` (default: ``False``)

            * **inject<N>** (``list``, ``tuple``, ``ROOT.TObject``) -- inject a (list
              of) *drawable* :class:`ROOT` object(s) to pad **<N>** (default: 0), object
              properties can be specified by passing instead a ``tuple`` of the format
              :code:`(obj, props)` where :code:`props` is a ``dict`` holding the object
              properties (default: \[\])

            * **overwrite** (``bool``) -- overwrite an existing file located at **path**
              (default: ``True``)

            * **mkdir** (``bool``) -- create non-existing directories in **path**
              (default: ``False``)
        """
        from RatioPlot import RatioPlot
        from SensitivityScan import SensitivityScan
        from ContributionPlot import ContributionPlot

        sort = kwargs.pop("sort", True)
        contribution = kwargs.pop("contribution", False)
        ratio = kwargs.pop("ratio", False)
        sensitivity = kwargs.pop("sensitivity", False)
        if kwargs.get("drawstacksum"):
            self.SetDrawStackSum(kwargs.pop("drawstacksum"))
        injections = {
            k: kwargs.pop(k) for k in dict(kwargs).keys() if k.startswith("inject")
        }
        npads = sum([contribution, bool(ratio), bool(sensitivity)]) + 1
        addcls = [RatioPlot] if ratio else []
        addcls += [ContributionPlot] if contribution else []
        addcls += [SensitivityScan] if sensitivity else []
        properties = DissectProperties(kwargs, [Stack, Plot, Canvas, Pad] + addcls)
        self.BuildStack(sort=sort)
        if self.GetNhists() == 0:
            injections = {"inject0": injections.get("inject0", [])}
            if contribution:
                logger.warning("Cannot create ContributionPlot: Missing stack!")
                contribution = False
            if ratio:
                logger.warning("Cannot create RatioPlot: Missing stack!")
                ratio = False
            if sensitivity:
                logger.warning("Cannot create SensitivityScan: Missing stack!")
                sensitivity = False
        if ratio is True:
            try:  # it's just a guess...
                datahisto = filter(
                    lambda h: not h.GetDrawOption().upper().startswith("HIST"),
                    self._store["nostack"],
                )[0]
                ratio = [datahisto, self._stacksumhisto]  # overwrite boolean
            except IndexError:
                if self._store["nostack"]:
                    ratio = [self._store["nostack"][0], self._stacksumhisto]
                else:
                    logger.error(
                        "Failed to identify appropriate numerator histogram for "
                        "RatioPlot pad!"
                    )
                    ratio = False
        if sensitivity:
            try:  # again just making assumptions here...
                sensitivity = filter(
                    lambda h: "HIST" in h.GetDrawOption().upper(),
                    self._store["nostack"],  # overwrite boolean with list of sig histos
                )
            except IndexError:
                logger.error(
                    "Failed to identify appropriate numerator histogram for "
                    "SensitivityScan pad!"
                )
                sensitivity = False
        plot = Plot(npads=npads, **properties["Plot"])
        # Register the Stack to the upper Pad (pad=0):
        plot.Register(self, **MergeDicts(properties["Stack"], properties["Pad"]))
        if self._drawstacksum and self.GetNhists() > 1:
            # Dummy histo with the correct legend entry styling:
            htmp = Histo1D(
                "{}_legendentry".format(self._stacksumhisto.GetName()),
                self._stacksumhisto.GetTitle(),
                [
                    self._stacksumhisto._lowbinedges[0],
                    self._stacksumhisto._lowbinedges[self._stacksumhisto._nbins],
                ],
                **{
                    k[8:]: v
                    for k, v in DissectProperties(
                        properties["Stack"],
                        [
                            {
                                "Stacksum": [
                                    "stacksum{}".format(p)
                                    for p in Histo1D.GetListOfProperties()
                                ]
                            }
                        ],
                    )["Stacksum"].items()
                }
            )
            plot.Register(
                htmp,
                fillcolor=self._stacksumhisto._errorband.GetFillColor(),
                fillstyle=self._stacksumhisto._errorband.GetFillStyle(),
                legenddrawoption=self._stacksumhisto.GetLegendDrawOption(),
                **properties["Pad"]
            )
        idx = 1
        xaxisprops = {
            k: v for k, v in properties["Pad"].items() if k in ["xtitle", "xunits"]
        }
        if contribution:
            contribplot = ContributionPlot(self)
            properties["ContributionPlot"].update(
                xaxisprops, **properties["ContributionPlot"]
            )
            plot.Register(
                contribplot,
                pad=idx,
                ytitle="Contrib.",
                logy=False,
                ymin=0,
                ymax=1,
                **{k: v for k, v in properties["Pad"].items() if k.startswith("x")}
            )
            idx += 1
        if ratio:
            ratioplot = RatioPlot(*ratio, **properties["RatioPlot"])
            properties["RatioPlot"].update(xaxisprops)
            plot.Register(
                ratioplot,
                pad=idx,
                ytitle="Data / SM",
                logy=False,
                ymin=0.2,
                ymax=1.8,
                **{k: v for k, v in properties["Pad"].items() if k.startswith("x")}
            )
            idx += 1
        if sensitivity:
            sensitivityscan = SensitivityScan(
                sensitivity, self._stacksumhisto, **properties["SensitivityScan"]
            )
            properties["SensitivityScan"].update(xaxisprops)
            plot.Register(
                sensitivityscan,
                pad=idx,
                ytitle="Z_{A}-value",
                logy=False,
                **{k: v for k, v in properties["Pad"].items() if k.startswith("x")}
            )
        plot.Print(path, **MergeDicts(properties["Canvas"], injections))
Ejemplo n.º 8
0
    def Print(self, path, **kwargs):
        r"""Print the plot to a file.

        Creates a :class:`.Canvas` and draws all registered objects into their
        associated :class:`Pad`. The canvas is saved as a PDF/PNG/... file with the
        absolute path defined by **path**. If a file with the same name already exists
        it will be overwritten (can be changed  with the **overwrite** keyword
        argument). If **mkdir** is set to ``True`` (default: ``False``) directories in
        **path** with do not yet exist will be created automatically.

        The properties of the of the plot and canvas can be configured via their
        respective properties passed as keyword arguments.

        :param path: path of the output file (must end with '.pdf', '.png', ...)
        :type path: ``str``

        :param \**kwargs: :class:`.Plot` and :class:`.Canvas` properties + additional
            properties (see below)

        Keyword Arguments:

            * **inject<N>** (``list``, ``tuple``, ``ROOT.TObject``) -- inject a (list
              of) *drawable* :class:`ROOT` object(s) to pad **<N>** (default: 0), object
              properties can be specified by passing instead a ``tuple`` of the format
              :code:`(obj, props)` where :code:`props` is a ``dict`` holding the object
              properties (default: \[\])

            * **overwrite** (``bool``) -- overwrite an existing file located at **path**
              (default: ``True``)

            * **mkdir** (``bool``) -- create non-existing directories in **path**
              (default: ``False``)

        """
        for idx, injections in {
                int(k[6:]) if len(k) > 6 else 0: kwargs.pop(k)
                for k in dict(kwargs.items()) if k.startswith("inject")
        }.items():
            if not isinstance(injections, list):
                injections = [injections]
            self.Inject(idx, *injections)
        properties = DissectProperties(kwargs, [Plot, Canvas])
        ROOT.gStyle.SetOptStat(0)
        ROOT.gStyle.SetPaintTextFormat("4.2f")
        canvas = Canvas("{}_Canvas".format(self._name),
                        template=str(self._npads),
                        **properties["Canvas"])
        legend = {}
        self.DeclareProperties(**properties["Plot"])
        self.AddPlotDecorations()
        for i, store in self._store.items():
            pad = Pad("{}_Pad-{}".format(canvas.GetName(), i),
                      **self._padproperties[i])
            pad.Draw()
            pad.cd()
            legend[i] = Legend(
                "{}_Legend".format(pad.GetName()),
                xshift=pad.GetLegendXShift(),
                yshift=pad.GetLegendYShift(),
            )
            canvas.SetSelectedPad(pad)
            for obj, objprops in store:
                with UsingProperties(obj, **objprops):
                    if any([
                            obj.InheritsFrom(tcls)
                            for tcls in ["TH1", "THStack"]
                    ]):
                        legend[i].Register(obj)
                    suffix = "SAME" if pad.GetDrawFrame() else ""
                    obj.Draw(obj.GetDrawOption() + suffix)
            if pad.GetDrawFrame():
                pad.RedrawAxis()
            if pad.GetDrawLegend():
                legend[i].Draw("SAME")
            canvas.cd()
        canvas.Print(path)
        if os.path.isfile(path):
            logger.info("Created plot: '{}'".format(path))
        canvas.Delete()
Ejemplo n.º 9
0
class CutMarker(MethodProxy):
    def __init__(self, cutvalue, **kwargs):
        MethodProxy.__init__(self)
        self._name = "CutMarker_{}".format(uuid4().hex[:8])
        self._cutvalue = cutvalue
        self._lineymin = None
        self._lineymax = None
        self._rellinelength = 0.8
        self._drawarrow = True
        self._relarrowpos = 1.0
        self._relarrowlength = 0.15
        self._direction = 1
        self._drawoption = ""
        self._propcache = DissectProperties(kwargs, [self, Line, Arrow])
        props = self._propcache.pop("CutMarker", {})
        props.setdefault("template", "common")
        self.DeclareProperties(**props)

    def DeclareProperty(self, property, args):
        isvalid = False
        property = property.lower()
        if property in self._properties:
            super(CutMarker, self).DeclareProperty(property, args)
            isvalid = True
        else:
            if property in Line._properties:
                self._propcache["Line"][property] = args
                isvalid = True
            if property in Arrow._properties:
                self._propcache["Arrow"][property] = args
                isvalid = True
        if not isvalid:
            raise KeyError("'{}' object has no property named '{}'!".format(
                self.__class__.__name__, property))

    @classmethod
    def InheritsFrom(cls, classname):
        return False

    def SetName(self, name):
        self._name = name

    def GetName(self):
        return self._name

    def SetDrawOption(self, option):
        self._drawoption = option

    def GetDrawOption(self):
        return self._drawoption

    def SetRelLineLength(self, scale):
        self._rellinelength = scale

    def GetRelLineLength(self):
        return self._rellinelength

    def SetLineYMin(self, ymin):
        self._lineymin = ymin

    def GetLineYMin(self):
        return self._lineymin

    def SetLineYMax(self, ymax):
        self._lineymax = ymax

    def GetLineYMax(self):
        return self._lineymax

    def SetRelArrowLength(self, scale):
        self._relarrowlength = scale

    def GetRelArrowLength(self):
        return self._relarrowlength

    def SetDrawArrow(self, boolean):
        self._drawarrow = boolean

    def GetDrawArrow(self):
        return self._drawarrow

    def SetDirection(self, sign):
        assert sign in ["+", "-"]
        self._direction = {"+": 1, "-": -1}.get(sign)

    def GetDirection(self):
        return {1: "+", -1: "-"}.get(self._direction)

    def Draw(self, option):
        # TODO: Add support for horizontal cut markers, i.e. cuts in y-axis values.
        if option is not None:
            self.SetDrawOption(option)
        currentpad = ROOT.gPad
        frame = currentpad.GetFrame()
        logx = currentpad.GetLogx()
        logy = currentpad.GetLogy()
        padxmin = currentpad.PadtoX(frame.GetX1())
        padxmax = currentpad.PadtoX(frame.GetX2())
        padymin = currentpad.PadtoY(frame.GetY1())
        padymax = currentpad.PadtoY(frame.GetY2())
        lineymin = padymin if self._lineymin is None else self._lineymin
        if logy:
            lineymax = self._rellinelength * padymax
            lineymax = 10**(
                self._rellinelength * ROOT.TMath.Log10(padymax / padymin) +
                ROOT.TMath.Log10(padymin))
            arrowpos = 10**(
                self._relarrowpos * ROOT.TMath.Log10(lineymax / lineymin) +
                ROOT.TMath.Log10(lineymin))
        else:
            lineymax = ((padymax - padymin) * self._rellinelength
                        if self._lineymax is None else self._lineymax)
            arrowpos = (lineymax - lineymin) * self._relarrowpos
        self._line = Line(self._cutvalue, lineymin, self._cutvalue, lineymax,
                          **self._propcache.get("Line", {}))
        self._line.Draw(self.GetDrawOption())
        if self._drawarrow:
            if logx:
                arrowlength = 10**(self._relarrowlength *
                                   ROOT.TMath.Log10(padymax / padymin) +
                                   ROOT.TMath.Log10(padymin))
            else:
                arrowlength = (padxmax - padxmin) * self._relarrowlength
            self._arrow = Arrow(self._cutvalue, arrowpos,
                                self._cutvalue + self._direction * arrowlength,
                                arrowpos, **self._propcache.get("Arrow", {}))
            self._arrow.Draw()