rootpy/rootpy

No interactive ROOT Canvas with qtconsole & ROOT 6

Opened this issue · 6 comments

I like to work with rootpy (great piece of work btw) within a jupyter qtconsole. As JSROOT still misses some features and I prefer drawables to be displayed in popup-windows rather than inline, I still rely on the classical ROOT canvases.

With ROOT 5.36/36 the following script brings up such a canvas -- thanks to rootpy it can be used interactively:

import rootpy as rpy
import rootpy.ROOT as R
import rootpy.plotting

print R.gROOT.GetVersion() #call R once
R.gROOT.SetBatch(0)
get_ipython().display_formatter.formatters['image/png'].enabled = False

h = R.TH1F("test", "test", 100, -5, 5)
h.FillRandom("gaus")

can = R.TCanvas() #empty ROOT Canvas pops up
h.Draw() #interactive histogram is drawn into ROOT Canvas

However this is not possible with newer ROOT Versions, e.g. ROOT 6.10/06

In [1]: %run ~/Desktop/ext_canvas.py
Welcome to JupyROOT 6.10/06
ERROR:ROOT.TGClient.TGClient] only one instance of TGClient allowed
6.10/06
---------------------------------------------------------------------------
ROOTError                                 Traceback (most recent call last)
/home/philipp/Desktop/ext_canvas.py in <module>()
     10 h.FillRandom("gaus")
     11 
---> 12 can = R.TCanvas() #empty ROOT Canvas pops up
     13 h.Draw() #interactive histogram is drawn into ROOT Canvas

/home/philipp/.virtualenvs/python2-CB/lib/python2.7/site-packages/rootpy-1.0.0.dev0-py2.7.egg/rootpy/__init__.pyc in __new__(self, *args, **kwargs)
    247             class asrootpy_cls(result):
    248                 def __new__(self, *args, **kwargs):
--> 249                     return asrootpy(thing(*args, **kwargs), warn=warn)
    250             asrootpy_cls.__name__ = '{0}_asrootpy'.format(thing.__name__)
    251             return asrootpy_cls

/home/philipp/.virtualenvs/python2-CB/lib/python2.7/site-packages/rootpy-1.0.0.dev0-py2.7.egg/rootpy/__init__.pyc in __new__(self, *args, **kwargs)
    247             class asrootpy_cls(result):
    248                 def __new__(self, *args, **kwargs):
--> 249                     return asrootpy(thing(*args, **kwargs), warn=warn)
    250             asrootpy_cls.__name__ = '{0}_asrootpy'.format(thing.__name__)
    251             return asrootpy_cls

/home/philipp/.virtualenvs/python2-CB/lib/python2.7/site-packages/rootpy-1.0.0.dev0-py2.7.egg/rootpy/logger/magic.pyc in intercept_next_line(f, why, *args)
    243         if sys.version_info[0] < 3:
    244             #raise exception.__class__, exception, traceback
--> 245             raise exception
    246         raise exception.with_traceback(traceback)
    247 

ROOTError: level=3000, loc='TGClient::TGClient', msg='only one instance of TGClient allowed'

I suppose this to be related to the notebook features added to core ROOT 6. Now, not only rootpy but also JupyROOT both are trying to "catch the canvas" to be drawn, which results in above exception.

But it would be so nice to get the workflow outlined above back to rootpy also when run in conjunction with ROOT 6.

Btw, how does rootpy deals with features that are making it into core ROOT in general? E.g. the drawing of histograms/canvases as png images within an interactive jupyter session. I met it as first in rootpy, but nowadays it seems to be adapted by JupyROOT?

Once again, I really like rootpy -- many thanks for providing it to the community!!

Hi I ran into a similar problem, which is not related to JupyROOT. The example from phi-man reduced to the essential:

import rootpy.ROOT as R
import rootpy.plotting

print(R.gROOT.GetVersion()) #call R once
R.gROOT.SetBatch(0)

h = R.TH1F("test", "test", 100, -5, 5)

h.Draw() #interactive histogram is drawn into ROOT Canvas

will give the output:

6.10/08
ERROR:ROOT.TGClient.TGClient] only one instance of TGClient allowed

If you remove the import rootpy.plotting, it works!

So there is something happening in the include, which is not supposed to happen.

Might be a related issue:

import rootpy.ROOT as ROOT
import rootpy.plotting
ROOT.Canvas()

doesn't create a pop-up canvas

as

import rootpy.ROOT as ROOT
ROOT.Canvas()

does create a pop-up canvas.

Similar issues happens whenever I try to import some modules inside rootpy (rootpy.plotting, rootpy.tree for example) before ROOT.Canvas().
Test with root 6.10/04 and 6.10/08 on both Ubuntu16 and Mac Os

fedxa commented

Just a workaround, based on the previous post:

import rootpy.ROOT as ROOT
ROOT.Canvas().Close()
import rootpy.plotting

Seems if the GUI is properly initialised (after the ROOT import and creation of one canvas), then afterwards everything works.

rakab commented

I've started investigating of this issue. This has indeed something to do with ROOT version, both python2 and python3 show similar behaviour with ROOT 6.
A workaround written by @fedxa works because in that case PyROOT's version of finalSetup() gets fired. It's not even necessary to draw a dummy canvas, one can call for example ROOT.gPad or ROOT.gStyle and finalSetup() will be triggered. This does work for sure, but that way we always avoid rootpy's finalSetup(), which is probably not very desirable.
I've stated digging a code little bit and it seems there is something going on with plotting/func.py.
Try for example commenting following line in plotting/init.py:

from .func import F1, F2, F3

This won't make dear TF[1-3] functions very happy 😄 but after this rootpy's finalSetup() won't activate a batch mode anymore. After further investigation I've found that problem lies in decorators.py file, namely line
members = inspect.getmembers(root_base)

When inspector.getmembers() gets called for F1, F2, F3 something happans and batch mode gets activated (which can't be reverted after this). I kinda found a solution for this problem, which involves dirty modification of inspector.getmembers() function and fixes the problem without hurting anything. This is better than having an mandatory import from PyROOT just for drawing, but still not the best one. Besides that, as much as I know in python3 using inspector.getmembers() method to get all the class members can cause problems too. Anyway I can try to make a PR with my "dirty" solution or probably someone can propose a better idea?

BTW #782 should be related with this issue too.

As this topic is still discussed, I would like to show you my solution:

If I remember correctly, the error is originally caused by the TCanvas::Init() function, which is called in every canvas constructor. Both, pyroot as well as rootpy activate the batch mode if they detected an interactive jupyter session. In addition, some of the rootpy modules (e.g. rootpy.plotting) led to a Canvas being constructed (and consequently Init() being called).

The problem with the TCanvas::Init routine arises if it get firstly called in batch mode (doing its init stuff within this state) and later on "real" canvases are requested to be drawn.

So you have to ensure by all means that the init part is also done in interactive mode. In my specific case even my rootlogon.C led to such a call, which is why I ended up with this rather complicated startup routine:

During jupyter kernel startup/header of your python script

print "executing import script..."

import numpy as npy
import rootpy
import rootpy.ROOT as R

R.gROOT.GetVersion()
R.gROOT.SetBatch(0)
##dummy statement to trigger rootlogon execution

import rootpy.plotting
R.gROOT.SetBatch(0)

and my rootlogon.py (which, in turn, is calling my rootlogon.C):

import ROOT
import sys
import os

# we have to call that here before rootlogon.C gets loaded

preserve_batch = ROOT.gROOT.IsBatch()

try:
        print "...loading rootlogon.C..."
        ROOT.gROOT.SetBatch(0)
        ret = ROOT.gROOT.Macro(os.path.expanduser("rootlogon.C"))

except Exception as e:
        print "ERROR while loading rootlogon.C\n", e
        sys.exit(-1)
finally:
        ROOT.gROOT.SetBatch(preserve_batch)

Btw, it took me some time to get ROOT's interactive mode back to work when used with newer root versions. To be more precise, I had to combine the event-loop of the ROOT framework with the one from the jupyter qtconsole/notebook. Did you encounter a similar problem and how did you solve it?

rakab commented

The problem with the TCanvas::Init routine arises if it get firstly called in batch mode (doing its init stuff within this state) and later on "real" canvases are requested to be drawn.
So you have to ensure by all means that the init part is also done in interactive mode. In my specific case even my rootlogon.C led to such a call, which is why I ended up with this rather complicated startup routine:

@phi-mah You are absolutely correct and this is also more general and not related with jupyter only. The question is why does batch mode get activated without asking for it?
I have already partially discussed this in my previous post: importing anything from rootpy.plotting activates plotting/func.py file too, then something gets broken and after rootpy's finalSetup() will get activated there is no way to get interactive mode back.

Temporary solution could be what you did, i.e triggering PyROOT's version of finalSetup() with calls like ROOT.gROOT.SetBatch(0), ROOT.gPad, ROOT.gStyle, etc. If this is done before importing anything from rootpy.plotting, than since finalSetup() is being done only once, rootpy's finalSetup() becomes obsolete and we have interactive mode. So you could in principle place simple ROOT.gPad before this line

from .func import F1, F2, F3

and without complicated setups and worrryng about the import order in your code, everything should work fine.

But again, as I said this makes rootpy's finalSetup() obsolete, which by design isn't very desirable. Better idea would be to investigate what's wrong with TF[1-3] functions and fix it. I have already pointed problematic spot in the code (in my previous post) and have some idea how to fix it, but not very happy with my current solution, plus I still don't understand what exactly forces rootpy to activate the batch mode. Anyway I will attempt to make a PR in this week and maybe somebody more experienced can get better explanation and solution then.