I have a python / matplotlib application that frequently updates a plot with new data coming in from a measurement instrument. The plot window should not change from background to foreground (or vice versa) with respect to other windows on my desktop when the plot is updated with new data.
This worked as desired with Python 3 on a machine running Ubuntu 16.10 with matplotlib 1.5.2rc. However, on a different machine with Ubuntu 17.04 and matplotlib 2.0.0, the figure window pops to the front every time the plot is updated with new data.
How can I control the window foreground/background behavior and keep the window focus when updating the plot with new data?
Here's a code example illustrating my plotting routine:
import matplotlib
import matplotlib.pyplot as plt
from time import time
from random import random
print ( matplotlib.__version__ )
# set up the figure
fig = plt.figure()
plt.xlabel('Time')
plt.ylabel('Value')
plt.ion()
# plot things while new data is generated:
t0 = time()
t = []
y = []
while True:
t.append( time()-t0 )
y.append( random() )
fig.clear()
plt.plot( t , y )
plt.pause(1)
matplotlib was changed somewhere from version 1.5.2rc to 2.0.0 such that pyplot.show() brings the window to the foreground (see here). The key is therefore to avoid calling pyplot.show() in the loop. The same goes for pyplot.pause().
Below is a working example. This will still bring the window to the foreground at the beginning. But the user may move the window to the background, and the window will stay there when the figure is updated with new data.
Note that the matplotlib animation module might be a good choice to produce the plot shown in this example. However, I couldn't make the animation work with interactive plot, so it blocks further execution of other code. That's why I could not use the animation module in my real-life application.
import matplotlib
matplotlib.use('TkAgg')
import matplotlib.pyplot as plt
import time
from random import random
print ( matplotlib.__version__ )
# set up the figure
plt.ion()
fig = plt.figure()
ax = plt.subplot(1,1,1)
ax.set_xlabel('Time')
ax.set_ylabel('Value')
t = []
y = []
ax.plot( t , y , 'ko-' , markersize = 10 ) # add an empty line to the plot
fig.show() # show the window (figure will be in foreground, but the user may move it to background)
# plot things while new data is generated:
# (avoid calling plt.show() and plt.pause() to prevent window popping to foreground)
t0 = time.time()
while True:
t.append( time.time()-t0 ) # add new x data value
y.append( random() ) # add new y data value
ax.lines[0].set_data( t,y ) # set plot data
ax.relim() # recompute the data limits
ax.autoscale_view() # automatic axis scaling
fig.canvas.flush_events() # update the plot and take care of window events (like resizing etc.)
time.sleep(1) # wait for next loop iteration
For the tkinter backend (matplotlib.use("TkAgg")), using flush_events is not sufficient: you also need to call fig.canvas.draw_idle() before each fig.canvas.flush_events(). As #samlaf wrote, the same holds for the Qt5Agg backend.
Related
If I run the following code:
import matplotlib.pyplot as plt
import numpy as np
#plt.ion()
while True:
print('loop')
x = range(10)
y = np.random.rand(10)
plt.scatter(x, y)
plt.show()
Then I see a scatter plot displayed on my screen. Then each time I close the window for the plot, it displays a new plot with new data.
However, if I uncomment the line plt.ion(), nothing is displayed at all. There is no window created, and the program just continues through the loop, printing out 'loop'.
I want to be able to display a graph, and then return to the code automatically, with the graph still displayed. How can I do this?
If you want to plot on top of the same figure window, rather than generating a new window at every iteration the following will work:
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots(1, 1)
while True:
# If wanting to see an "animation" of points added, add a pause to allow the plotting to take place
plt.pause(1)
x = range(10)
y = np.random.rand(10)
ax.scatter(x, y)
The result you see will depend on the which matplotlib backend you are using. If you're wanting to see the new points being added you should use Qt4 or Qt5
I am plotting multiple arrows using matplotlib pyplot arrow function. It's plottting just fine. After a set of arrows have been plotted, I need to recompute the arrow locations and plot them again. I am using draw for that. So essentially I draw and plot arrows in a loop and clear the plot using cla() after a time interval. Here's what I don't understand: when I substitute time.sleep(1) function with raw_input() that waits for a key input, it works just fine but when I use the time.sleep(1) there is no plot shown at all.
Here's my code(I hv removed some presumably irrelevant stuff):
import matplotlib.pyplot as PP
import time
fig = PP.figure()
fig.show()
ax = fig.add_subplot(111)
for i in range(30):
for i in range(100):
pass
# loc = some code to recalculate arrow locations
for x,y in loc.iteritems():
ax.arrow( x, y,.05,.05, fc="k", ec="k",head_width=0.04, head_length=0.04 )
PP.draw()
#raw_input()
time.sleep(1)
PP.cla()
From here I found this code:
import random
from matplotlib import pyplot as plt
import numpy as np
plt.ion() # interactive mode
ydata = [0] * 50
# make plot
ax1 = plt.axes()
line, = plt.plot(ydata)
plt.ylim([0, 100]) # set the y-range
while True:
randint = int(random.random() * 100)
ymin = float(min(ydata)) - 10
ymax = float(max(ydata)) + 10
plt.ylim([ymin,ymax])
ydata.append(randint)
del ydata[0]
line.set_xdata(np.arange(len(ydata)))
line.set_ydata(ydata) # update data
plt.draw() # update plot
I get a plot window that pops up, but no data appears and nothing gets redrawn...any idea what I'm doing wrong?
The issue you are having is due to the way that gui mainloops work. When ever you plot call draw events get added to the queue of events for the main loop to process. If you add them as fast as possible the loop can never clear it's queue and actually draw to the screen.
Adding a plt.pause(.1) will pause the loop and allow the main loop (at the risk of being anthropomorphic) 'catch it's breath' and update the widgets on the screen
Related:
Python- 1 second plots continous presentation
matplotlib qt imshow animate
Figure GUI freezing
make matplotlib draw() only show new point
I have a python script that reads in a data file and displays one figure with four plots using the matplotlib library. The data file is being updated every few seconds since it is an output file for a different piece of software that is running concurrently. I would like the four plots in my matplotlib figure to refresh themselves using the updated data file every 20 seconds. The way I've implemented this is as follows:
import pylab as pl
import time
pl.ion()
fig = pl.figure()
while True:
f = open('data.out', 'rb')
#code to parse data and plot four charts
ax = fig.add_subplot(2,2,1)
#...
ax = fig.add_subplot(2,2,4)
#...
pl.draw()
time.sleep(20)
This works, but I lose functionality of the zoom and pan buttons which normally work if pl.show() is called. This is not optimal. However, if pl.show() is substituted for pl.draw(), the script no longer updates the plots. Is there a way to dynamically update a plot without completely losing the zoom/pan functionality?
Your code is a little too vague to know what is going on.
I can offer this:
You should retain normal functionality if you create your subplots once, saving all the axes objects and then calling show().
Subsequent changes to those subplots could be done like this:
#inside while loop
for i in #subplotlist
ax[i].clear() #ax[i] is the axis object of the i'th subplot
ax[i].plot(#plotstuff)
ax[i].draw()
The toolbar for zooming and panning can be added by hand if you so desire.
As you are developping a sofware, I supposed you may have a multi-threaded approach.
So in this case using an infinite while loop is a bad idea, like you are holding up your main thread.
In addition when it comes to GUI it’s also a bad idea to interfere abruptly with GUI internal threads (wxPython for instance) and you should have an event driven design approach in order to not abruptly interrupt other threads (and that will cause the crash of your application).
The use of a timer will do the job.
A timer would do these actions in the following script :
1/ call a function to clear previous artist
2 / replot the data
3/ apply changes to canvas
4/ create another identical timer in the following design way : a timer who calls another identical timer after doing its job
Like I do not have access to your datas, I created a random data provider for the illustration.
The defined variable delay_repeat allows you to program in seconds the refresh.
import pylab as pl
import random
from threading import Timer
def dataprovider():
return [random.randint(0, 8) for i in range(8)]
def random_col():
return ['blue', 'red', 'green', 'orange'][random.randint(0,3)]
# .... #
fig = pl.figure()
axes = [fig.add_subplot(2,2,i) for i in range(1,5)]
paths = [ax.scatter(x=dataprovider(), y=dataprovider(), marker = '+', c=random_col()) for ax in axes]
# .... #
def clear_arts(paths, all_arts=-1):
if all_arts < 0:
all_arts = len(paths)
for path in paths[:all_arts]:
path.remove()
def refresh_arts(paths, delay_repeat):
# 1 - clear previous artists
clear_arts(paths,all_arts=-1)
# 2 - Get artists paths for cleaning
paths = [ax.scatter(x=dataprovider(), y=dataprovider(), marker = '+', c=random_col()) for ax in axes]
# 3 - Apply changes
fig.canvas.draw_idle()
# 4 - Create another timer
Timer(delay_repeat, refresh_arts, (paths, delay_repeat)).start()
# 4- Create a timer that will run function with arguments args and keyword arguments kwargs,
# after interval seconds have passed.
delay_repeat = 2
Timer(delay_repeat, refresh_arts, (paths, delay_repeat)).start()
# print("process continues here")
pl.show()
You can do it like this. It accept x,y as list and output a scatter plot plus a linear trend on the same plot.
from IPython.display import clear_output
from matplotlib import pyplot as plt
%matplotlib inline
def live_plot(x, y, figsize=(7,5), title=''):
clear_output(wait=True)
plt.figure(figsize=figsize)
plt.xlim(0, training_steps)
plt.ylim(0, 100)
x= [float(i) for i in x]
y= [float(i) for i in y]
if len(x) > 1:
plt.scatter(x,y, label='axis y', color='k')
m, b = np.polyfit(x, y, 1)
plt.plot(x, [x * m for x in x] + b)
plt.title(title)
plt.grid(True)
plt.xlabel('axis x')
plt.ylabel('axis y')
plt.show();
you just need to call live_plot(x, y) inside a loop. here's how it looks:
I want to plot a sequence of .png images in matplotlib. The goal is to plot them rapidly to simulate the effect of a movie, but I have additional reasons for wanting to avoid actually creating an .avi file or saving matplotlib figures and then viewing them in sequence outside of Python.
I'm specifically trying to view the image files in sequence inside a for-loop in Python. Assuming I have imported matplotlib correctly, and I have my own functions 'new_image()' and 'new_rect()', here's some example code that fails to work because of the blocking effect of the show() function's call to the GUI mainloop:
for index in index_list:
img = new_image(index)
rect = new_rect(index)
plt.imshow(img)
plt.gca().add_patch(rect)
plt.show()
#I also tried pausing briefly and then closing, but this doesn't
#get executed due to the GUI mainloop from show()
time.sleep(0.25)
plt.close()
The above code works to show only the first image, but then the program just hangs and waits for me to manually close the resultant figure window. Once I do close it, the program then just hangs and doesn't re-plot with the new image data. What should I be doing? Also note that I have tried replacing the plt.show() command with a plt.draw() command, and then adding the plt.show() outside of the for-loop. This doesn't display anything and just hangs.
Based on http://matplotlib.sourceforge.net/examples/animation/simple_anim_tkagg.html:
import time
import numpy as np
import matplotlib
matplotlib.use('TkAgg') # do this before importing pylab
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
def animate():
tstart = time.time() # for profiling
data=np.random.randn(10,10)
im=plt.imshow(data)
for i in np.arange(1,200):
data=np.random.randn(10,10)
im.set_data(data)
fig.canvas.draw() # redraw the canvas
print 'FPS:' , 200/(time.time()-tstart)
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate)
plt.show()
plt.imshow can accept a float array, uint8 array, or a PIL image.
So if you have a directory of PNG files, you could open them as PIL images and animate them like this:
import matplotlib
matplotlib.use('TkAgg') # do this before importing pylab
import matplotlib.pyplot as plt
import Image
import glob
fig = plt.figure()
ax = fig.add_subplot(111)
def animate():
filenames=sorted(glob.glob('*.png'))
im=plt.imshow(Image.open(filenames[0]))
for filename in filenames[1:]:
image=Image.open(filename)
im.set_data(image)
fig.canvas.draw()
win = fig.canvas.manager.window
fig.canvas.manager.window.after(100, animate)
plt.show()
The best way I have found for this was with the command pylab.ion() after you import pylab.
Here is a script that does use show(), but which displays the different plots each time pylab.draw() is called, and which leaves the plot windows showing indefinitely. It uses simple input logic to decide when to close the figures (because using show() means pylab won't process clicks on the windows x button), but that should be simple to add to your gui as another button or as a text field.
import numpy as np
import pylab
pylab.ion()
def get_fig(fig_num, some_data, some_labels):
fig = pylab.figure(fig_num,figsize=(8,8),frameon=False)
ax = fig.add_subplot(111)
ax.set_ylim([0.1,0.8]); ax.set_xlim([0.1, 0.8]);
ax.set_title("Quarterly Stapler Thefts")
ax.pie(some_data, labels=some_labels, autopct='%1.1f%%', shadow=True);
return fig
my_labels = ("You", "Me", "Some guy", "Bob")
# To ensure first plot is always made.
do_plot = 1; num_plots = 0;
while do_plot:
num_plots = num_plots + 1;
data = np.random.rand(1,4).tolist()[0]
fig = get_fig(num_plots,data,my_labels)
fig.canvas.draw()
pylab.draw()
print "Close any of the previous plots? If yes, enter its number, otherwise enter 0..."
close_plot = raw_input()
if int(close_plot) > 0:
pylab.close(int(close_plot))
print "Create another random plot? 1 for yes; 0 for no."
do_plot = raw_input();
# Don't allow plots to go over 10.
if num_plots > 10:
do_plot = 0
pylab.show()
By modifying the basic logic here, I can have it close windows and plot images consecutively to simulate playing a movie, or I can maintain keyboard control over how it steps through the movie.
Note: This has worked for me across platforms and seems strictly superior to the window canvas manager approach above, and doesn't require the 'TkAgg' option.
I have implemented a handy script that just suits your need. Try it out here
Below is a example that show images together with its bounding box:
import os
import glob
from scipy.misc import imread
from matplotlib.pyplot import Rectangle
video_dir = 'YOUR-VIDEO-DIRECTORY'
img_files = glob.glob(os.path.join(video_dir, '*.jpg'))
box_files = glob.glob(os.path.join(video_dir, '*.txt'))
def redraw_fn(f, axes):
img = imread(img_files[f])
box = bbread(box_files[f]) # Define your own bounding box reading utility
x, y, w, h = box
if not redraw_fn.initialized:
im = axes.imshow(img, animated=True)
bb = Rectangle((x, y), w, h,
fill=False, # remove background
edgecolor="red")
axes.add_patch(bb)
redraw_fn.im = im
redraw_fn.bb = bb
redraw_fn.initialized = True
else:
redraw_fn.im.set_array(img)
redraw_fn.bb.set_xy((x, y))
redraw_fn.bb.set_width(w)
redraw_fn.bb.set_height(h)
redraw_fn.initialized = False
videofig(len(img_files), redraw_fn, play_fps=30)