11

Introduction

As I am coming from , I am used to an interactive interface where a script can update figures while it is running. During the processing each figure can be re-sized or even closed. This probably means that each figure is running in its own thread which is obviously not the case with .

IPython can imitate the Matlab behavior using the magic command %pylab or %matplotlib which does something that I don't understand yet and which is the very point of my question.

My goal is then to allow standalone Python scripts to work as Matlab does (or as IPython with %matplotlib does). In other words, I would like this script to be executed from the command line. I am expecting a new figure that pop-up every 3 seconds. During the execution I would be able to zoom, resize or even close the figure.

#!/usr/bin/python
import matplotlib.pyplot as plt
import time

def do_some_work(): 
    time.sleep(3)

for i in range(10):
    plt.plot([1,2,3,4])
    plt.show() # this is way too boilerplate, I'd like to avoid it too. 
    do_some_work()

What alternative to %matplotlib I can use to manipulate figures while a script is running in Python (not IPython)?

What solutions I've already investigated?

I currently found 3 way to get a plot show.

1. %pylab / %matplotlib

As tom said, the use of %pylab should be avoided to prevent the namespace to be polluted.

>>> %pylab
>>> plot([1,2,3,4])

This solution is sweet, the plot is non-blocking, there is no need for an additionnal show(), I can still add a grid with grid() afterwards and I can close, resize or zoom on my figure with no additional issues.

Unfortunately the %matplotlib command is only available on IPython.

2. from pylab import * or from matplotlib.pyplot import plt

>>> from pylab import *
>>> plot([1,2,3,4])

Things are quite different here. I need to add the command show() to display my figure which is blocking. I cannot do anything but closing the figure to execute the next command such as grid() which will have no effect since the figure is now closed...

** 3. from pylab import * or from matplotlib.pyplot import plt + ion()** Some suggestions recommend to use the ion() command as follow:

>>> from pylab import *
>>> ion()
>>> plot([1,2,3,4])
>>> draw()
>>> pause(0.0001)

Unfortunately, even if the plot shows, I cannot close the figure manually. I will need to execute close() on the terminal which is not very convenient. Moreover the need for two additional commands such as draw(); pause(0.0001) is not what I am expecting.

Summary

With %pylab, everything is wonderful, but I cannot use it outside of IPython

With from pylab import * followed by a plot, I get a blocking behavior and all the power of IPython is wasted.

from pylab import * followed by ion offers a nice alternative to the previous one, but I have to use the weird pause(0.0001) command that leads to a window that I cannot close manually (I know that the pause is not needed with some backends. I am using WxAgg which is the only one that works well on Cygwin x64.

This question advices to use matplotlib.interactive(True). Unfortunately it does not work and gives the same behavior as ion() does.

Community
  • 1
  • 1
nowox
  • 25,978
  • 39
  • 143
  • 293
  • 2
    As a side note: its not recommended to use `pylab` at all anymore; we should import `matplotlib.pyplot` and `numpy` separately instead to prevent namespaces getting polluted. See here: http://matplotlib.org/faq/usage_faq.html#matplotlib-pyplot-and-pylab-how-are-they-related – tmdavison Nov 13 '15 at 16:04

2 Answers2

7

Change your do_some_work function to the following and it should work.

def do_some_work(): 
    plt.pause(3)

For interactive backends plt.pause(3) starts the event loop for 3 seconds so that it can process your resize events. Note that the documentation says that it is an experimental function and that for complex animations you should use the animation module.

The, %pylab and %matplotlib magic commands also start an event loop, which is why user interaction with the plots is possible. Alternatively, you can start the event loop with %gui wx, and turn it off with %gui. You can use the IPython.lib.guisupport.is_event_loop_running_wx() function to test if it is running.

The reason for using ion() or ioff() is very well explained in the 'What is interactive mode' page. In principle, user interaction is possible without IPython. However, I could not get the interactive-example from that page to work with the Qt4Agg backend, only with the MacOSX backend (on my Mac). I didn't try with the WX backend.

Edit

I did manage to get the interactive-example to work with the Qt4Agg backend by using PyQt4 instead of PySide (so by setting backend.qt4 : PyQt4 in my ~/.config/matplotlibrc file). I think the example doesn't work with all backends. I submitted an issue here.

Edit 2

I'm afraid I can't think of a way of manipulating the figure while a long calculation is running, without using threads. As you mentioned: Matplotlib doesn't start a thread, and neither does IPython. The %pylab and %matplotlib commands alternate between processing commands from the read-eval-print loop and letting the GUI processing events for a short time. They do this sequentially.

In fact, I'm unable to reproduce your behavior, even with the %matplotlib or %pylab magic. (Just to be clear: in ipython I first call %matplotlib and then %run yourscript.py). The %matplotlib magic puts Matplotlib in interactive-mode, which makes the plt.show() call non-blocking so that the do_some_work function is executed immediately. However, during the time.sleep(3) call, the figure is unresponsive (this becomes even more apparent if I increase the sleeping period). I don't understand how this can work at your end.

Unless I'm wrong you'll have to break up your calculation in smaller parts and use plt.pause (or even better, the animation module) to update the figures.

titusjan
  • 5,376
  • 2
  • 24
  • 43
  • Well, this is exactly where I want to go. The magic is obviously inside `%gui wx`. Unfortunately `IPython.lib.guisupport.get_app_wx` is not available in my IPython. How can I access to this manually? – nowox Nov 17 '15 at 10:58
  • Ok, now I'm confused about what your actual goal is. I thought you wanted user interaction without depending on IPython. The `get_app_wx` function and the `%gui` [magic-command](http://ipython.readthedocs.org/en/stable/interactive/magics.html) are both IPython functionality. I only included them to explain why the `%pylab` option works, sorry if that wasn't clear. The actual solution is to replace `time.sleep` with `plt.pause` in the `do_some_work` function. Did that work for you? And the [Interactive example](http://matplotlib.org/faq/usage_faq.html#what-is-interactive-mode), does that work? – titusjan Nov 17 '15 at 11:58
  • I mean we still not have a solution, but at least you explained some magic. I may still do `import IPython` from my standalone script to enable the `IPython.lib.guisupport.get_app_wx`. Unfortunately this does not work yet. The `ion()` does not work as explained on my question. – nowox Nov 17 '15 at 12:01
  • What version of IPython do you use? `get_app_wx` has been there since 2010 so it should be available unless you use an ancient version. Also can you please explain why you think using `get_app_wx` is a viable solution? You still are depending on IPython in that case. IMHO the real solution is to use `plt.pause` in the `do_some_work` function but you haven't given any feedback on that yet. – titusjan Nov 19 '15 at 10:24
  • Using `plt.pause` and `do_some_work` does not work because during all the processing of `do_some_work`, the figure is not refreshed. The user cannot *manipulate the figure while `do_some_work` is running*. It seems the only solution that meet my needs is `%matplotlib`. I discovered that this magic function is in fact hiding a call to `get_app_wx`. If I can just use it inside my script by using it I would be happy. The main problem comes that I cannot use a magic function inside a standalone script even if I do `import IPython` – nowox Nov 19 '15 at 10:39
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/95548/discussion-between-titusjan-and-nowox). – titusjan Nov 19 '15 at 10:57
  • IPython `4.0.0` with Python `2.7.10` – nowox Nov 19 '15 at 10:57
  • If you run a long a long calculation in `do_some_work` it might help to call `plt.flush_events()` once in a while. For instance at the end of a for loop. – titusjan Nov 19 '15 at 11:34
  • Could you provide an example based on on my *example* where you do not touch at `do_some_work` function ? You may use anything such as `guisupport`, but the magic commands `%pylab/%matplotlib` and I give you the bounty. The figures must remain responsive (ability to resize, zoom or close them). The app should not close before all the figures are closed. – nowox Nov 20 '15 at 16:08
3

My advice would be to keep using IPython, since it manages the GUI event loop for you (that's what pylab/pylot does). I tried interactive plotting in a normal interpreter and it worked the way it is expected, even without calling ion() (Debian unstable, Python 3.4.3+, Matplotlib 1.4.2-3.1). If I recall it right, it's a fairly new feature in Matplotlib.

Alternatively, you can also use Matplotlib's animation capabilities to update a plot periodically:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import time

plt.ion()

tt = np.linspace(0, 1, 200)
freq = 1  # parameter for sine
t0 = time.time()  # for measuring ellapsed time

fig, ax = plt.subplots()


def draw_func(i):
    """ This function gets called repeated times """ 
    global freq  # needed because freq is changed in this function
    xx = np.sin(2*np.pi*freq*tt)/freq
    ax.set_title("Passed Time: %.1f s, " % (time.time()-t0) +
                 "Parameter i=%d" % i)
    ax.plot(tt, xx, label="$f=%d$ Hz" % freq)
    ax.legend()
    freq += 1


# call draw_func every 3 seconds 1 + 4 times (first time is initialization):
ani = animation.FuncAnimation(fig, draw_func,  np.arange(4), interval=3000,
                              repeat=False)
# plt.show()

Checkout matplotlib.animation.FuncAnimation for details. You'll find further examples in the examples section.

Dietrich
  • 5,241
  • 3
  • 24
  • 36
  • With your example, you showed me that `matplotlib.animation` will not solve my problem. In parallel, I asked [another question](http://stackoverflow.com/questions/33763328/multiple-figures-with-python) where I would like to plot several figures using threads. Unfortunately threads do not play well with IPython or interactive matplotlib. – nowox Nov 23 '15 at 07:04
  • Yes threads and the GUI loop do not work well together. If you want true concurrency, put each matplotlib window into a own process - IPython.parallel (https://ipython.org/ipython-doc/dev/parallel/) can be used for that and it plays nice on multicore machines (it can be used without the IPython interpreter). Unfortunately, I never really used IPython.parallel that way - so I cannot give you an example for that. – Dietrich Nov 23 '15 at 17:36