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
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 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.
I am trying to use matplotlib LassoSelector to select some points from a scatter plot and produce a separate figure for selected points only. When I try to use another matplotlib widget on the second plot it doesn't work but there is no error or warning message. Below is a minimal example with LassoSelector and SpanSelector used.
I tried other widgets too; the Button widget displays the button but the action on the button press is not performed.
import numpy as np
from matplotlib.pyplot import *
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
def onselect(verts):
global xys,data
#get indexes of selected points
path = Path(verts)
xysn = xys.get_offsets()
ind = np.nonzero([path.contains_point(xy) for xy in xysn])[0]
#plot the second figure
fig=figure(2)
ax=fig.add_subplot(111)
ax.hist(data[:,0][ind],10)
#this should be executed when SpanSelector is used
def action(min,max):
print min,max
#try to do SpanSelector (this fails)
span=SpanSelector(ax,action,'horizontal')
show()
#initialize a figure
fig=figure(1)
ax=fig.add_subplot(111)
#create data
data=np.array([[1,6], [4,8],[0,4],[4,2],[9,6],[10,8],[2,2],[5,5],[0,4],[4,5]])
#plot data
xys=ax.scatter(data[:,0],data[:,1])
#select point by drawing a path around them
lasso = LassoSelector(ax, onselect=onselect)
show()
matplotlib widgets are event driven, so wait for user input. The problem with you code is you are trying to create a new figure with a new event handler SpanSelector. I'm not sure if you can add new events as a result of previous ones and with SpanSelector commented out, I get the following error,
QCoreApplication::exec: The event loop is already running
So the new event, LassoSelector is not registered and user input is not picked up (and the new figure doesn't appear). It is better to create all figures and register all possible events at the beginning of the code. The following should be closer to what you want to do,
import numpy as np
from matplotlib.pyplot import *
from matplotlib.widgets import SpanSelector, LassoSelector
from matplotlib.path import Path
#this should be executed when LassoSelector is used
def onselect(verts):
global xys,data
#get indexes of selected points
path = Path(verts)
xysn = xys.get_offsets()
ind = np.nonzero([path.contains_point(xy) for xy in xysn])[0]
#Clear and update bar chart
h, b = np.histogram(data[:,0][ind],10)
for rect, bars in zip(rects, h):
rect.set_height(bars)
ax2.bar(mb, h, align='center')
draw()
#this should be executed when SpanSelector is used
def action(min,max):
print min,max
#initialize figures
fig1=figure(1)
ax1=fig1.add_subplot(111)
fig2=figure(2)
ax2=fig2.add_subplot(111)
#create data
data=np.array([[1,6],[4,8],[0,4],[4,2],[9,6],[10,8],[2,2],[5,5],[0,4],[4,5]])
#plot data
xys=ax1.scatter(data[:,0],data[:,1])
#Plot initial histogram of all data
h, b = np.histogram(data[:,0],10)
mb = [0.5*(b[i]+b[i+1]) for i in range(b.shape[0]-1)]
rects = ax2.bar(mb, h, align='center')
#Register lasso selector
lasso = LassoSelector(ax1, onselect=onselect)
#Register SpanSelector
span=SpanSelector(ax2,action,'horizontal')
show()
Note, in order to update bar charts, it's a little more tricky than plots so I used this answer here Dynamically updating a bar plot in matplotlib
For some reason, the histogram figure 2 only updates when you click on it. I would consider using a single figure with two axes for this which may be easier to work with,
fig, ax = subplots(2,1)
ax1 = ax[0]; ax2 = ax[1]
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)