1

I want to keep all my pyplot windows alive until the code itself is terminated, but without blocking. Right now, I either have to use

plt.show()

and wait until I close it before moving on.

Or use

plt.draw()
plt.pause(0.05)

where the pause is needed, as without it, for some strange reason the window is never drawn. In that case the window freezes after the pause time (checked by setting it to much longer)

What I want is matlab-like behaviour where the code will continue executing the script until the end of the script (where it will wait for user to hit enter). After hitting enter, everything should close, as with plt.close('all'), but I want the window to stay active, that is - resize, save if requested and have data cursor - just like a matlab plot would without having to close all the windows.

A complete example to illustrate my problem:

from matplotlib import pyplot as plt
from matplotlib import image as mpimg

img = mpimg.imread("/path/to/image.jpg")
plt.imshow(img)
plt.draw()
plt.pause(05.05) #window works like it should, but execution of rest of the code is frozen

input("hit enter") #the window is dead at this point
plt.close('all')

I've seen suggestion to do this (source):

import matplotlib.pyplot as plt
import matplotlib.mlab as mlab
import numpy as np
import os

def detach_display():
    mu, sigma = 0, 0.5
    x = np.linspace(-3, 3, 100)
    plt.plot(x, mlab.normpdf(x, mu, sigma))
    plt.show()    

if os.fork():
    # Parent
    pass
else:
    # Child
    detach_display()

It is mentioned to be linux specific, but since I'm running linux machine is not a problem, however I don't understand what exactly happens here and don't know what will happen when I use it multiple times across the code execution.

Waiting with plots until the end is not really an option, as the time between the first and last gets available is rather long and I prefer to see early on that the data selected is giving bad results, rather than running all processing to find that out.

user3002166
  • 689
  • 9
  • 24
  • There are numerous questions like this on SO and the answer is always the same: `plt.show` is meant to be called exactly once at the end of a script to show some output. There are some special cases where workarounds exist, e.g. you may want to live with a freezed window (which still lets you see the data at an early stage). – ImportanceOfBeingErnest Oct 30 '17 at 15:48
  • 1
    And I asked if there is a working workaround or something new happened (new function or whatnot), since the top search result question is currently 4 years and 7 months old. – user3002166 Oct 31 '17 at 09:11

1 Answers1

2

In principle you can use plt.show(block=False) to continue the execution of the code after showing the plot. You would want to update the plot regularly such that it stays active though - this is done via plt.pause(t), where t can be very short (0.000001) or relatively long (1).

import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0,1,1000)
y = np.zeros_like(x)

fig, ax = plt.subplots()
line, = ax.plot(x,y)

plt.show(block=False)

for i  in range(15):
    y += np.random.randn(1000)
    y -= y.min()
    line.set_data(x,y)
    ax.set_ylim(y.min(), y.max())
    plt.pause(1)

line.set_data(x,y)
print("Simulation finished")
plt.show()

Since the idea may be to just let some simulation run in the background and only show some intermediate result once in a while in the plot; and since you wouldn't want to let the drawing of the plot interfere with the simulation, you may use threading. In the main thread you have the plot with its event loop and a loop that plots the result once in a while. In another thread you have the simulation.

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

class Worker(object):
    def __init__(self):
        super(Worker, self).__init__()
        self.x = np.linspace(0,1,1000)
        self.y = np.zeros_like(self.x)

    def simulate(self):
        for i in range(15):
            self.y += np.random.randn(1000)
            self.y -= self.y.min()
            time.sleep(1)  # simulate some much longer simulation time
        print("Simulation finished")

w = Worker()
thread = threading.Thread(target=w.simulate)
thread.start()

fig, ax = plt.subplots()
line, = ax.plot(w.x,w.y)

plt.show(block=False)

while thread.is_alive():
    line.set_data(w.x, w.y)
    ax.set_ylim(w.y.min(), w.y.max())
    plt.pause(0.05)

plt.show()
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
  • The second option looks promising, I'll try it out and write how it went, but thanks for suggestions! – user3002166 Oct 31 '17 at 09:32
  • Will need a bit of fiddling to get a hang of number of plots, but I think it is the right way, thank you. Too bad there's no built in way, but can't do much about it – user3002166 Oct 31 '17 at 09:55