Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature request] Start interactive loop #21

Closed
ghaspias opened this issue May 13, 2013 · 13 comments
Closed

[Feature request] Start interactive loop #21

ghaspias opened this issue May 13, 2013 · 13 comments

Comments

@ghaspias
Copy link

Hi,

I am trying to use mayavi from julia. For now, it seems to work just fine, but the interaction with the gui only works after mlab.show(), which blocks julia; so, I can't programatically manipulate the mayavi pipeline from julia and interact with the mayavi gui...
In ipython the same happens, unless it is invoked with --gui=wx... can the same be achieved in PyCall?

Thanks.

Miguel Gaspar

@stevengj
Copy link
Member

@fperez, any tips on how IPython does this?

@ghaspias
Copy link
Author

Don't know much about how it works (don't know much about python either - I am a matlab refugee_) .
But maybe we should look at http://ipython.org/ipython-doc/stable/api/index.html
and especially http://ipython.org/ipython-doc/stable/api/generated/IPython.lib.guisupport.html.
I understand an event loop must be started on a separate thread (?) to allow for the user interaction...
(_Using Julia for coding myalgorithms and mayavi/python/qt/wx/... for UI... it's a wet dream!!)

PS: Have looked through the PyCall code, and didn't find an obvious way to achieve the same effect as calling ipython --gui=... Unless it is possible to pass some kind of 'arguments' in ccall when loading the shared lib...

@fperez
Copy link

fperez commented May 14, 2013

No, it's not threads-based in IPython anymore, we use a Python input hook along with toolkit-specific implementations (in that same directory).

@stevengj
Copy link
Member

We should be able to do something similar to IPython's input hook. Julia should have a way to register an event-loop task with its main event loop (it did at one point, but the functionality seems to have been renamed; see Julia issue #3099).

We don't need any special arguments when the shared library is running; there is nothing special about the command line that cannot be done after the program starts. Since Julia has access to the full Python C API, it can do anything Python can do, and it's just a matter of telling MayaVi that the GUI event loop is already running. Looking at the source code for mayavi.mlab.show, it appears that this is done via pyface.api.

@stevengj
Copy link
Member

It looks like the following should work to set up an event loop. Run wx_eventloop() before starting MayaVi. (I haven't tried it yet with MayaVi, but I tried it with matplotlib and also with Chaco; the latter uses the same Traits GUI toolkit as MayaVi.) @ghaspias, can you try it and verify that it works, assuming you are using the wxWidgets backend?

I also have a GTK version working (but no QT version yet). The next task is to make this a bit more transparent, so that you don't have to call wx_eventloop() manually (at least for pylab, chaco, or mayavi).

# call doevent(status) every ms milliseconds                                    
function install_doevent(doevent::Function, ms::Real)
    timeout = Base.TimeoutAsyncWork(doevent)
    Base.start_timer(timeout,int64(ms),int64(ms))
    return timeout
end

# wx:  (based on IPython/lib/inputhookwx.py, which is 3-clause BSD-licensed)    
function wx_eventloop(ms::Real=50)
    wx = pyimport("wx")
    GetApp = wx["GetApp"]
    EventLoop = wx["EventLoop"]
    EventLoopActivator = wx["EventLoopActivator"]
    function doevent(status)
    app = pycall(GetApp, PyObject)
    if app.o != PyCall.pynothing.o
            evtloop = pycall(EventLoop, PyObject)
            ea = pycall(EventLoopActivator, PyObject, evtloop)
            Pending = evtloop["Pending"]
            Dispatch = evtloop["Dispatch"]
            while pycall(Pending, Bool)
                pycall(Dispatch, PyObject)
            end
            pydecref(ea) # deactivate event loop                                
            pycall(app["ProcessIdle"], PyObject)
       end
    end
    install_doevent(doevent, ms)
end

@ghaspias
Copy link
Author

The code above is working for me, with Mayavi. The only issue is that it
hangs when calling mlab.show(), as is the normal way to start the event
loop; probably some pre-existing mayavi code might have problems with
this...

mlab.show() should be detecting the running event loop...

using PyCall

@pyimport mayavi.mlab as mlab
WARNING: unsafe_ref is deprecated, use unsafe_load instead.
in pyexc_initialize at /home/miguel/.julia/PyCall/src/exception.jl:145

mlab.show(stop=true) // here julia blocks, but resumes after closing the mlab start/stop window

require("wx_eventloop.jl")

wx_eventloop()
TimeoutAsyncWork(doevent,Ptr{Void} @0x000000000a354fe0)

mlab.test_triangular_mesh() // working interactive mlab window, non-blocking
PyObject <mayavi.modules.surface.Surface object at 0xaf2b590>

mlab.show() // as above, but the only way to return to interactive julia is closing the active mlab window(s)...

mlab.show(stop=true) // blocking, no hang. Clicking "stop" has no effect on the mlab window, besides returning to interactive julia

mesh=mlab.test_triangular_mesh() // still working
PyObject <mayavi.modules.surface.Surface object at 0x9855230>

Thanks a lot, Steven!

(Edit: github should recognize julia code...)

On Tue, May 21, 2013 at 9:40 PM, Steven G. Johnson <notifications@github.com

wrote:

It looks like the following should work to set up an event loop. Run
wx_eventloop() before starting MayaVi. (I haven't tried it yet with
MayaVi, but I tried it with matplotlib and also with Chaco; the latter uses
the same Traits GUI toolkit as MayaVi.) @ghaspiashttps://github.com/ghaspias,
can you try it and verify that it works, assuming you are using the
wxWidgets backend?

I also have a GTK version working (but no QT version yet). The next task
is to make this a bit more transparent, so that you don't have to call
wx_eventloop() manually (at least for pylab, chaco, or mayavi).
call doevent(status) every ms milliseconds

function install_doevent(doevent::Function, ms::Real)
timeout = Base.TimeoutAsyncWork(doevent)
Base.start_timer(timeout,int64(ms),int64(ms))
return timeout
end
wx: (based on IPython/lib/inputhookwx.py, which is 3-clause BSD-licensed)

function wx_eventloop(ms::Real=50)
wx = pyimport("wx")
GetApp = wx["GetApp"]
EventLoop = wx["EventLoop"]
EventLoopActivator = wx["EventLoopActivator"]
function doevent(status)
app = pycall(GetApp, PyObject)
if app.o != PyCall.pynothing.o
evtloop = pycall(EventLoop, PyObject)
ea = pycall(EventLoopActivator, PyObject, evtloop)
Pending = evtloop["Pending"]
Dispatch = evtloop["Dispatch"]
while pycall(Pending, Bool)
pycall(Dispatch, PyObject)
end
pydecref(ea) # deactivate event loop

pycall(app["ProcessIdle"], PyObject)
end
end
install_doevent(doevent, ms)
end


Reply to this email directly or view it on GitHubhttps://github.com//issues/21#issuecomment-18239040
.

@stevengj
Copy link
Member

Try inserting a line app["_in_event_loop"] = true before evtloop = ... to see if that fixes show().

@ghaspias
Copy link
Author

@stevengj
yes, it certainly does!
Now I wonder how should this be presented? Should pyinitialize accept a
flag signaling whether we want an event loop to be started?...
(I'm sorry I can't help more, but this has been a crazy week at work; but I
will try to take it from here, if you don't do it before)

On Wed, May 22, 2013 at 4:46 AM, Steven G. Johnson <notifications@github.com

wrote:

Try inserting a line app["_in_event_loop"] = true before evtloop = ... to
see if that fixes show().


Reply to this email directly or view it on GitHubhttps://github.com//issues/21#issuecomment-18255928
.

@stevengj
Copy link
Member

I don't think it should be an argument of pyinitialize, since most users don't call that explicitly.

I was thinking of just adding a macro @pylab, @chaco, @mayavi for each of the most important plotting toolkits, which imports them, starts an event loop, and does any other necessary monkey patching. For other uses I would just document explicit wx_eventloop() functions. Similar to how IPython has explicit options for pylab etcetera.

@fperez, any thoughts?

@simonp0420
Copy link

As you requested, this is a continuation of the discussion begun in #23

Following your suggestion, I switched to WX as the backend to matplotlib. Still no joy in avoiding blocking...

simonp@ubuntu:~$ julia
               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: http://docs.julialang.org
   _ _   _| |_  __ _   |  Type "help()" to list help topics
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.2.0-1796.rbac428f9
 _/ |\__'_|_|_|\__'_|  |  Commit bac428f990 2013-06-03 15:11:03
|__/                   |

julia> using PyCall

julia> include("/home/simonp/julia/PyCall/wx_eventloop.jl"); wx_eventloop()
WARNING: unsafe_ref is deprecated, use unsafe_load instead.
 in pyexc_initialize at /home/simonp/.julia/PyCall/src/exception.jl:145
TimeoutAsyncWork(doevent,Ptr{Void} @0x0000000003d7b8e0)

julia> @pyimport pylab

julia> x = linspace(0,2*pi,1000); y = sin(3*x + 4*cos(2*x));
julia> pylab.plot(x, y; color="red", linewidth=2.0, linestyle="--")
WARNING: has(d,k) is deprecated, use haskey(d,k) instead.
 in npyinitialize at /home/simonp/.julia/PyCall/src/numpy.jl:81
1-element Any Array:
 PyObject <matplotlib.lines.Line2D object at 0x64fbad0>

julia> pylab.show()

julia> # I had to close the Matplotlib window to get the julia prompt back

julia> pylab.get_backend()
"WX"

I tried your code both with and without the line app["_in_event_loop"] = true inserted before the line evtloop = ... . Neither seemed to make any difference from the behavior of pylab without including wx_eventloop.jl .

I appreciate your continuing help on this. I dream of switching from Matlab to Julia with Matplotlib as the plotting engine (for now).

@stevengj
Copy link
Member

stevengj commented Jun 6, 2013

@simonp0420, you need to tell matplotlib to use the WX backend (rather than the GTK default). You can do this by running pyimport("matplotlib")[:use]("WXAgg") before importing pylab. I usually also turn on interactive mode so that it will plot immediately without any need to call show.

Try e.g.

using PyCall
include("eventloop.jl")
wx_eventloop()
pyimport("matplotlib")[:use]("WXAgg")
@pyimport pylab as plt
println("plt.get_backend() = ", plt.get_backend())
plt.interactive(true)
x = linspace(0,2*pi,1000); y = sin(3*x + 4*cos(2*x));
plt.plot(x, y; color="red", linewidth=2.0, linestyle="--")

@simonp0420
Copy link

Success!!
However, please note that I had already changed my default backend to WX by editing my ~/.matplotlib/matplotlibrc file (see final line of the session transcript in my previous comment/post, where I was trying to show that I had made this change: The response to my invocation of pylab.get_backend() is "WX"). I get the same results in terms of blocking using either the WX or WXAgg backend.

By testing subsets of the commands you provided, I found that the minimal set of commands that avoids blocking (for me) seems to be

using PyCall
include("wx_eventloop.jl")
wx_eventloop()
@pyimport pylab as plt
plt.interactive(true)

The key thing that I was missing before is apparently the plt.interactive(true) without which julia is blocked whenever a plt.show() is executed.

Thanks again!

@stevengj
Copy link
Member

You can now just do @pylab as plt to start it interactively.

For MayaVi or Chaco, just do pygui_start(:wx) before importing MayaVi.

amyascwk added a commit to amyascwk/PyCall.jl that referenced this issue Oct 30, 2015
galenlynch added a commit to galenlynch/PyCall.jl that referenced this issue Oct 30, 2018
I have added [weak reference support][1] for `pyjlwrap` types, following the
documentation for Python 3+. Adding weak reference support for Python 2.x seems
much the same as in 3.x, however 3.x does not use Py_TPFLAGS_HAVE_WEAKREFS,
which also does not seem necessary for 2.x. Simple testing suggests that this is
working as expected.

Closes JuliaPy#21, JuliaPy#158

[1]: https://docs.python.org/3/extending/newtypes.html#weak-reference-support
stevengj pushed a commit that referenced this issue Nov 4, 2018
I have added [weak reference support][1] for `pyjlwrap` types, following the
documentation for Python 3+. Adding weak reference support for Python 2.x seems
much the same as in 3.x, however 3.x does not use Py_TPFLAGS_HAVE_WEAKREFS,
which also does not seem necessary for 2.x. Simple testing suggests that this is
working as expected.

Closes #21, #158

[1]: https://docs.python.org/3/extending/newtypes.html#weak-reference-support
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants