Repository: rootpy/rootpy

Issue:
Histogram attributes lost when making a histfactory channel with make_channel
I'm experiencing a weird error with the rootpy `make_channel` function. When I try to retrieve histograms from the channel, it seems that the attributes are lost. I've been able to reproduce the problem with this small example:

``` python
from rootpy.stats import histfactory
from rootpy.plotting import Hist

h_data = Hist(10, -1, 1)
h_data.name = 'h_data'
h_data.color = 'black'

h_bkg1 = Hist(10, -1, 1)
h_bkg1.name = 'h_bkg1'
h_bkg1.color = 'red'

h_bkg2 = Hist(10, -1, 1)
h_bkg2.name = 'h_bkg1'
h_bkg2.color = 'blue'

data = histfactory.Data('data')
data.hist = h_data

bkg1 = histfactory.Sample('bkg1')
bkg1.hist = h_bkg1

bkg2 = histfactory.Sample('bkg2')
bkg2.hist = h_bkg2

print 'From the histograms:'
hists = [h_data, h_bkg1, h_bkg2]
for h in hists:
    print h.name, h.color
print
print 'From the samples:'
samples = [data, bkg1, bkg2]
for s in samples:
    print s.name, s.hist.color
print
print 'From the channel:'
channel = histfactory.make_channel('totot', samples[1:], data=samples[0])
for s in [channel.data] + channel.samples:
    print s.name, s.hist.color
```

This gives me:

``` bash
(atlasVE)[qbuat@lhc14 hhana]$ python test_histfactory.py

RooFit v3.60 -- Developed by Wouter Verkerke and David Kirkby
                Copyright (C) 2000-2013 NIKHEF, University of California & Stanford University
                All rights reserved, please read http://roofit.sourceforge.net/license.txt

WARNING:ROOT.TClassTable.Add] class stack<RooAbsArg*,deque<RooAbsArg*> > already in TClassTable
WARNING:ROOT.TUnixSystem.SetDisplay] DISPLAY not set, setting it to 192.168.1.15:0.0
From the histograms:
h_data ('black', 'black', 'black')
h_bkg1 ('red', 'red', 'red')
h_bkg1 ('blue', 'blue', 'blue')

From the samples:
data ('black', 'black', 'black')
bkg1 ('red', 'red', 'red')
bkg2 ('blue', 'blue', 'blue')

From the channel:
data ('black', 'black', 'white')
bkg1 ('black', 'black', 'white')
bkg2 ('black', 'black', 'white')
(atlasVE)[qbuat@lhc14 hhana]$
```

So it looks like someone the decorations are lost or reset to default values.
It's worth mentionning that I see this using ROOT6 (6.02.12-x86_64-slc6-gcc48-opt) but I don't see that happening in ROOT5 (5.34.14-x86_64-slc6-gcc4.8), so maybe this is a problem on the ROOT side.
I'll try to reproduce it without using rootpy but I thought it was worth mentionning here.

Cheers,
Quentin



Discussion:
Quick update: a minimal ROOT-only script seems to indicate that the problem is on the rootpy side.

``` python
import ROOT

h_data = ROOT.TH1F('data', '', 10, -1, 1)
h_data.SetLineColor(ROOT.kBlack)

h_bkg1 = ROOT.TH1F('bkg1', '', 10, -1, 1)
h_bkg1.SetLineColor(ROOT.kRed)

h_bkg2 = ROOT.TH1F('bkg2', '', 10, -1, 1)
h_bkg2.SetLineColor(ROOT.kBlue)


data = ROOT.RooStats.HistFactory.Data()
data.SetHisto(h_data)
data.SetName('data')
bkg1 = ROOT.RooStats.HistFactory.Sample('bkg1')
bkg1.SetHisto(h_bkg1)

bkg2 = ROOT.RooStats.HistFactory.Sample('bkg2')
bkg2.SetHisto(h_bkg2)

print 'From the histograms:'
hists = [h_data, h_bkg1, h_bkg2]
for h in hists:
    print h.GetName(), h.GetLineColor()
print
print 'From the samples:'
samples = [data, bkg1, bkg2]
for s in samples:
    print s.GetName(), s.GetHisto().GetLineColor()
print
print 'From the channel:'
channel = ROOT.RooStats.HistFactory.Channel('tototot')
channel.SetData(data)
channel.AddSample(bkg1)
channel.AddSample(bkg2)
for s in channel.GetSamples():
    print s.GetName(), s.GetHisto().GetLineColor()
```

gives me:

``` bash
(atlasVE)[qbuat@lhc14 hhana]$ python test_histfactory.py

RooFit v3.60 -- Developed by Wouter Verkerke and David Kirkby
                Copyright (C) 2000-2013 NIKHEF, University of California & Stanford University
                All rights reserved, please read http://roofit.sourceforge.net/license.txt

From the histograms:
data 1
bkg1 632
bkg2 600

From the samples:
data 1
bkg1 632
bkg2 600

From the channel:
bkg1 632
bkg2 600
```

Cheers,
Quentin

Tracking down this a bit more, I think the problem comes from the `asrootpy` in [Line 972 of histfactory.py](https://github.com/rootpy/rootpy/blob/0.8.0/rootpy/stats/histfactory/histfactory.py#L972).

I also noticed that `asrootpy` does not keep the color attributes for histograms when doing:

``` python
histo_data_test = asrootpy(super(Channel, self).GetData().GetHisto())
```

However I still can't figure out what the actual issue is and also why is it ROOT6-specific.

Any chance someone has insights on this ?

Thanks!
Quentin

I'm finally here. Sorry.


Resolving patch:
diff --git a/rootpy/__init__.py b/rootpy/__init__.py
--- a/rootpy/__init__.py
+++ b/rootpy/__init__.py
@@ -265,8 +265,9 @@ def __new__(self, *args, **kwargs):
 
     # cast
     thing.__class__ = rootpy_cls
-    # call the _post_init if one exists
     if hasattr(thing, '_post_init'):
+        if hasattr(thing, '_clone_post_init'):
+            kwargs.setdefault('obj', thing)
         thing._post_init(**kwargs)
 
     return thing
diff --git a/rootpy/plotting/base.py b/rootpy/plotting/base.py
--- a/rootpy/plotting/base.py
+++ b/rootpy/plotting/base.py
@@ -93,20 +93,23 @@ def f(self, value):
         return f
 
     def _post_init(self, **kwargs):
-        self._clone_post_init(obj=None, **kwargs)
+        self._clone_post_init(**kwargs)
 
-    def _clone_post_init(self, obj, **kwargs):
+    def _clone_post_init(self, obj=None, **kwargs):
         """
         obj must be another Plottable instance. obj is used by Clone to properly
         transfer all attributes onto this object.
         """
         # Initialize the extra attributes
-        for attr, value in Plottable.EXTRA_ATTRS.items():
-            if obj is not None:
-                setattr(self, attr, getattr(obj, attr))
-            else:
+        if obj is None or obj is self:
+            # We must be asrootpy-ing a ROOT object
+            # or freshly init-ing a rootpy object
+            for attr, value in Plottable.EXTRA_ATTRS.items():
                 # Use the default value
                 setattr(self, attr, value)
+        else:
+            for attr, value in Plottable.EXTRA_ATTRS.items():
+                setattr(self, attr, getattr(obj, attr))
 
         # Create aliases from deprecated to current attributes
         for depattr, newattr in Plottable.EXTRA_ATTRS_DEPRECATED.items():
@@ -115,46 +118,61 @@ def _clone_post_init(self, obj, **kwargs):
                         fget=Plottable._get_attr_depr(depattr, newattr),
                         fset=Plottable._set_attr_depr(depattr, newattr)))
 
-        if obj is not None:
-            # Initialize style attrs to style of the other object
-            if isinstance(self, ROOT.TAttLine):
-                self.SetLineColor(obj.GetLineColor())
-                self.SetLineStyle(obj.GetLineStyle())
-                self.SetLineWidth(obj.GetLineWidth())
-            if isinstance(self, ROOT.TAttFill):
-                self.SetFillColor(obj.GetFillColor())
-                self.SetFillStyle(obj.GetFillStyle())
-            if isinstance(self, ROOT.TAttMarker):
-                self.SetMarkerColor(obj.GetMarkerColor())
-                self.SetMarkerStyle(obj.GetMarkerStyle())
-                self.SetMarkerSize(obj.GetMarkerSize())
-
-        else:
+        if obj is None or obj is self:
+            # We must be asrootpy-ing a ROOT object
+            # or freshly init-ing a rootpy object
             # Initialize style attrs to style of TObject
             if isinstance(self, ROOT.TAttLine):
                 self._linecolor = Color(ROOT.TAttLine.GetLineColor(self))
                 self._linestyle = LineStyle(ROOT.TAttLine.GetLineStyle(self))
                 self._linewidth = ROOT.TAttLine.GetLineWidth(self)
+            else:  # HistStack
+                self._linecolor = Color(Plottable.DEFAULT_DECOR['linecolor'])
+                self._linestyle = LineStyle(Plottable.DEFAULT_DECOR['linestyle'])
+                self._linewidth = Plottable.DEFAULT_DECOR['linewidth']
+
             if isinstance(self, ROOT.TAttFill):
                 self._fillcolor = Color(ROOT.TAttFill.GetFillColor(self))
                 self._fillstyle = FillStyle(ROOT.TAttFill.GetFillStyle(self))
+            else:  # HistStack
+                self._fillcolor = Color(Plottable.DEFAULT_DECOR['fillcolor'])
+                self._fillstyle = FillStyle(Plottable.DEFAULT_DECOR['fillstyle'])
+
             if isinstance(self, ROOT.TAttMarker):
                 self._markercolor = Color(ROOT.TAttMarker.GetMarkerColor(self))
                 self._markerstyle = MarkerStyle(ROOT.TAttMarker.GetMarkerStyle(self))
                 self._markersize = ROOT.TAttMarker.GetMarkerSize(self)
+            else:  # HistStack
+                self._markercolor = Color(Plottable.DEFAULT_DECOR['markercolor'])
+                self._markerstyle = MarkerStyle(Plottable.DEFAULT_DECOR['markerstyle'])
+                self._markersize = Plottable.DEFAULT_DECOR['markersize']
+
+            if obj is None:
+                # Populate defaults if we are not asrootpy-ing existing object
+                decor = dict(**Plottable.DEFAULT_DECOR)
+                decor.update(Plottable.EXTRA_ATTRS)
+                if 'color' in kwargs:
+                    decor.pop('linecolor', None)
+                    decor.pop('fillcolor', None)
+                    decor.pop('markercolor', None)
+                decor.update(kwargs)
+                self.decorate(**decor)
 
-        if obj is None:
-            # Populate defaults
-            decor = dict(**Plottable.DEFAULT_DECOR)
-            decor.update(Plottable.EXTRA_ATTRS)
-            if 'color' in kwargs:
-                decor.pop('linecolor', None)
-                decor.pop('fillcolor', None)
-                decor.pop('markercolor', None)
-            decor.update(kwargs)
-            self.decorate(**decor)
         else:
-            self.decorate(**kwargs)
+            # Initialize style attrs to style of the other object
+            if isinstance(obj, ROOT.TAttLine):
+                self.SetLineColor(obj.GetLineColor())
+                self.SetLineStyle(obj.GetLineStyle())
+                self.SetLineWidth(obj.GetLineWidth())
+            if isinstance(obj, ROOT.TAttFill):
+                self.SetFillColor(obj.GetFillColor())
+                self.SetFillStyle(obj.GetFillStyle())
+            if isinstance(obj, ROOT.TAttMarker):
+                self.SetMarkerColor(obj.GetMarkerColor())
+                self.SetMarkerStyle(obj.GetMarkerStyle())
+                self.SetMarkerSize(obj.GetMarkerSize())
+            if kwargs:
+                self.decorate(**kwargs)
 
     #TODO: @chainable
     def decorate(self, other=None, **kwargs):
diff --git a/rootpy/plotting/hist.py b/rootpy/plotting/hist.py
--- a/rootpy/plotting/hist.py
+++ b/rootpy/plotting/hist.py
@@ -2506,8 +2506,8 @@ def min(self, include_error=False):
         return min([hist.min(include_error=include_error)
                     for hist in self.hists])
 
-    def Clone(self, newName=None):
-        clone = HistStack(name=newName,
+    def Clone(self, name=None):
+        clone = HistStack(name=name,
                           title=self.GetTitle(),
                           stacked=self.stacked,
                           **self.decorators)


Test patch:
diff --git a/rootpy/plotting/tests/test_plottable.py b/rootpy/plotting/tests/test_plottable.py
--- a/rootpy/plotting/tests/test_plottable.py
+++ b/rootpy/plotting/tests/test_plottable.py
@@ -1,6 +1,8 @@
 # Copyright 2012 the rootpy developers
 # distributed under the terms of the GNU General Public License
 from rootpy.plotting import Hist
+from rootpy import asrootpy
+import ROOT
 from nose.tools import assert_equals
 
 
@@ -19,6 +21,13 @@ def test_plottable_clone():
     assert_equals(c.markercolor, 'red')
 
 
+def test_plottable_asrootpy():
+    hist = ROOT.TH1D("hist", "", 10, 0, 1)
+    hist.SetLineColor(3)
+    hist = asrootpy(hist)
+    assert_equals(hist.linecolor, 3)
+
+
 if __name__ == "__main__":
     import nose
     nose.runmodule()
diff --git a/rootpy/tests/test_base.py b/rootpy/tests/test_base.py
--- a/rootpy/tests/test_base.py
+++ b/rootpy/tests/test_base.py
@@ -9,7 +9,6 @@
 
 
 def test_object():
-
     with MemFile('test', 'recreate'):
         for cls in iter_rootpy_classes():
             # avoid RooStats bugs for now
@@ -22,8 +21,8 @@ def test_object():
                     cls.__name__)
             if issubclass(cls, ROOT.TDirectory):
                 continue
-            obj = asrootpy(cls._ROOT())
 
+            obj = asrootpy(cls._ROOT())
             if isinstance(obj, Object):
                 clone = obj.Clone()