Matplotlib Imshow Doesn't Update With Draw - python

I'm having trouble getting imshow to update with new data. For reference I'm pulling data off a serial port and trying to plot it, updating every second or so. I had been accumulating the data with a thread, so I initially thought that might be the problem as matplotlib isn't thread safe. However, I can't get the following simpler example to work:
import numpy as np
import matplotlib.pyplot as plt
import time
dat = np.random.rand(100,10)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
image = ax.imshow(np.zeros((10,10)))
fig.canvas.draw()
count = 0
while count < 100:
image.set_data(dat[count:count+10])
fig.canvas.draw()
count += 10
time.sleep(1)
Using TkAgg, I just get the plot of all zeros, it never updates then quits.
With Qt5Agg, an empty window pops up before quitting.
I've tried various combinations of draw_idle(), flush_events() and plt.show(block=False), with the same results.
python 3.8.10 , matplotlib 3.2.2

Immediately after posting this I figured out the solution.
I initialized the plot with all zeros - changing this to be a random array of values fixes it fixes it.
I'm not sure why starting with all zeros broke the color scaling, though from the matplotlib documentation the default normalization scales the input data on [0,1], so I suspect that was the issue.

Related

Python plt: close or clear figure does not work

I generate a lots of figures with a script which I do not display but store to harddrive. After a while I get the message
/usr/lib/pymodules/python2.7/matplotlib/pyplot.py:412: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (matplotlib.pyplot.figure) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam figure.max_num_figures).
max_open_warning, RuntimeWarning)
Thus, I tried to close or clear the figures after storing. So far, I tried all of the followings but no one works. I still get the message from above.
plt.figure().clf()
plt.figure().clear()
plt.clf()
plt.close()
plt.close('all')
plt.close(plt.figure())
And furthermore I tried to restrict the number of open figures by
plt.rcParams.update({'figure.max_num_figures':1})
Here follows a piece of sample code that behaves like described above. I added the different options I tried as comments at the places I tried them.
from pandas import DataFrame
from numpy import random
df = DataFrame(random.randint(0,10,40))
import matplotlib.pyplot as plt
plt.ioff()
#plt.rcParams.update({'figure.max_num_figures':1})
for i in range(0,30):
fig, ax = plt.subplots()
ax.hist([df])
plt.savefig("/home/userXYZ/Development/pic_test.png")
#plt.figure().clf()
#plt.figure().clear()
#plt.clf()
#plt.close() # results in an error
#plt.close('all') # also error
#plt.close(plt.figure()) # also error
To be complete, that is the error I get when using plt.close:
can't invoke "event" command: application has been destroyed
while executing "event generate $w <>"
(procedure "ttk::ThemeChanged" line 6)
invoked from within "ttk::ThemeChanged"
The correct way to close your figures would be to use plt.close(fig), as can be seen in the below edit of the code you originally posted.
from pandas import DataFrame
from numpy import random
df = DataFrame(random.randint(0,10,40))
import matplotlib.pyplot as plt
plt.ioff()
for i in range(0,30):
fig, ax = plt.subplots()
ax.hist(df)
name = 'fig'+str(i)+'.png' # Note that the name should change dynamically
plt.savefig(name)
plt.close(fig) # <-- use this line
The error that you describe at the end of your question suggests to me that your problem is not with matplotlib, but rather with another part of your code (such as ttk).
plt.show() is a blocking function, so in the above code, plt.close() will not execute until the fig windows are closed.
You can use plt.ion() at the beginning of your code to make it non-blocking. Even though this has some other implications the fig will be closed.
I was still having the same issue on Python 3.9.7, matplotlib 3.5.1, and VS Code (the issue that no combination of plt.close() closes the figure). I have three loops which the most inner loop plots more than 20 figures. The solution that is working for me is using agg as backend and del someFig after plt.close(someFig). Subsequently, the order of code would be something like:
import matplotlib
matplotlib.use('agg')
import matplotlib.pyplot as plt
someFig = plt.figure()
.
.
.
someFig.savefig('OUTPUT_PATH')
plt.close(someFig) # --> (Note 1)
del someFig
.
.
.
NOTE 1: If this line is removed, the output figures may not be plotted correctly! Especially when the number of elements to be rendered in the figure is high.
NOTE 2: I don't know whether this solution could backfire or not, but at least it is working and not hugging RAM or preventing plotting figures!
import tensorflow as tf
from matplotlib import pyplot as plt
sample_image = tf.io.read_file(str(PATH / 'Path to your file'))
sample_image = tf.io.decode_jpeg(sample_image)
print(sample_image.shape)
plt.figure("1 - Sample Image ")
plt.title(label="Sample Image", fontsize=12, color="red")
plt.imshow(sample_image)
plt.show(block=False)
plt.pause(3)
plt.close()
plt.show(block=False)
plt.pause(interval) do the trick
This does not really solve my problem, but it is a work-around to handle the high memory consumption I faced and I do not get any of the error messages as before:
from pandas import DataFrame
from numpy import random
df = DataFrame(random.randint(0,10,40))
import matplotlib.pyplot as plt
plt.ioff()
for i in range(0,30):
plt.close('all')
fig, ax = plt.subplots()
ax.hist([df])
plt.savefig("/home/userXYZ/Development/pic_test.png")

Showing a few figures without stopping calculations in matplotlib

Hi I would like to show a few figures in matplotlib without stopping calculations. I would like the figure to show up right after the calculations that concern it are finished for example:
import numpy as np
import pylab as py
x=np.linspace(0,50,51)
y=x
fig, axs = plt.subplots(1, 1)
cs = axs.plot(x, y)
now i want to show the plot without blocking the possibility to make some other calculations
plt.show(block=False)
plt.pause(5)
I create the second plot
y1=2*x
fig1, axs1 = plt.subplots(1, 1)
cs1 = axs1.plot(x, y1)
plt.show()
This works however the first freezes (after 5 secound pause which I added) until I call plt.show() at the end. It is crucial that the first figure shows and works, then after calculations another figure is added to it.
The following code should do what you want. I did this in an IPython Notebook.
from IPython import display
import matplotlib.pyplot as plt
def stream_plot(iterable, plotlife=10.):
for I in iterable:
display.clear_output(wait=True)
output = do_calculations_on_i(I)
plt.plot(output)
display.display(plt.gca());
time.sleep(plotlife); #how long to show the plot for
the wait=True will wait to clear the old plot until it has something new to plot, or any other output is printed.
I put the sleep in there so I can observe each plot before it is wiped away. This was useful for having to observe distributions for several entities. You may or may not need it for what you want to do.

Real-time plot in matplotlib - python

I'm trying to get real-time spectrum analyzer type plot in matplotlib. I've got some code working (with help from other posts on StackOverflow) as follows:
import time
import numpy as np
import matplotlib.pyplot as plt
plt.axis([0, 1000, 0, 1])
plt.ion()
plt.show()
i=0
np.zeros([1,500],'float')
lines=plt.plot(y[0])
while 1:
i=i+1
lines.pop(0).remove()
y = np.random.rand(1,100)
lines=plt.plot(y[0])
plt.draw()
The code works and I'm getting what I want, but there is a serious problem. The plot window would freeze after some time. I know the program is still running by inspecting the i variable (I'm running the code in Anaconda/Spyder so I can see the variables). However the plot window would show "Non responding" and if I terminate the python program in Spyder by ctrl+c, the plot window comes back to life and show the latest plot.
I'm out of wits here as how to further debug the issue. Anyone to help?
Thanks
I am not sure that adding plt.pause will entirely solve your issue. It may just take longer before the application crash. The memory used by your application seems to constantly increase over time (even after adding plt.pause). Below are two suggestions that may help you with your current issue:
Instead of removing/recreating the lines artists with each iteration with remove and plot, I would use the same artist throughout the whole animation and simply update its ydata.
I'll use explicit handlers for the axe and figure and call show and draw explicitly on the figure manager and canvas instead of going with implicit calls through pyplot, following the advices given in a post by tcaswell.
Following the above, the code would look something like this:
import numpy as np
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.axis([0, 100, 0, 1])
y = np.random.rand(100)
lines = ax.plot(y)
fig.canvas.manager.show()
i=0
while 1:
i=i+1
y = np.random.rand(100)
lines[0].set_ydata(y)
fig.canvas.draw()
fig.canvas.flush_events()
I've run the above code for a good 10 minutes and the memory used by the application remained stable the whole time, while the memory used by your current code (without plt.pause) increased by about 30MiB over the same period.
To answer myself, I solved the issue by adding
plt.pause(0.01)
after the
plt.draw()
This probably allows the GUI to finish the drawing and clear the buffer somewhere (my guess) before the new data comes in.
I know I'm late to answer this question, but for your issue you could look into the "joystick" package. It is based on the line.set_data() and canvas.draw() methods, with optional axes re-scaling, hence most probably faster than removing a line and adding a new one. It also allows for interactive text logging or image plotting (in addition to graph plotting).
No need to do your own loops in a separate thread, the package takes care of it, just give the update frequency you wish. Plus the terminal remains available for more monitoring commands while live plotting, which is not possible with a "while True" loop.
See http://www.github.com/ceyzeriat/joystick/ or https://pypi.python.org/pypi/joystick (use pip install joystick to install)
try:
import joystick as jk
import numpy as np
import time
class test(jk.Joystick):
# initialize the infinite loop decorator
_infinite_loop = jk.deco_infinite_loop()
def _init(self, *args, **kwargs):
"""
Function called at initialization, see the doc
"""
self._t0 = time.time() # initialize time
self.xdata = np.array([self._t0]) # time x-axis
self.ydata = np.array([0.0]) # fake data y-axis
# create a graph frame
self.mygraph = self.add_frame(jk.Graph(name="test", size=(500, 500), pos=(50, 50), fmt="go-", xnpts=100, xnptsmax=1000, xylim=(None, None, 0, 1)))
#_infinite_loop(wait_time=0.2)
def _generate_data(self): # function looped every 0.2 second to read or produce data
"""
Loop starting with the simulation start, getting data and
pushing it to the graph every 0.2 seconds
"""
# concatenate data on the time x-axis
self.xdata = jk.core.add_datapoint(self.xdata, time.time(), xnptsmax=self.mygraph.xnptsmax)
# concatenate data on the fake data y-axis
self.ydata = jk.core.add_datapoint(self.ydata, np.random.random(), xnptsmax=self.mygraph.xnptsmax)
self.mygraph.set_xydata(t, self.ydata)
t = test()
t.start()
t.stop()

Update/Refresh matplotlib plots on second monitor

At the moment I am working with Spyder and doing my plotting with matplotlib. I have two monitors, one for development and another for (data) browsing and other stuff. Since I am doing some calculations and my code often changes, I often (re)execute the code and have a look at the plots to check if the results are valid.
Is there any way to place my matplotlib plots on a second monitor and refresh them from the main monitor?
I have already searched for a solution but could not find anything. It would be really helpful for me!
Here's some additional information:
OS: Ubuntu 14.04 (64 Bit)
Spyder-Version: 2.3.2
Matplotlib-Version: 1.3.1.-1.4.2.
I know it's an old question but I came across a similar problem and found this question. I managed to move my plots to a second display using the QT4Agg backend.
import matplotlib.pyplot as plt
plt.switch_backend('QT4Agg')
# a little hack to get screen size; from here [1]
mgr = plt.get_current_fig_manager()
mgr.full_screen_toggle()
py = mgr.canvas.height()
px = mgr.canvas.width()
mgr.window.close()
# hack end
x = [i for i in range(0,10)]
plt.figure()
plt.plot(x)
figManager = plt.get_current_fig_manager()
# if px=0, plot will display on 1st screen
figManager.window.move(px, 0)
figManager.window.showMaximized()
figManager.window.setFocus()
plt.show()
[1] answer from #divenex: How do you set the absolute position of figure windows with matplotlib?
This has to do with matplotlib, not Spyder. Placing the location of a figure explicitly appears to be one of those things for which there's really just workarounds ... see the answers to the question here. That's an old question, but I'm not sure there's been change since then (any matplotlib devs, feel free to correct me!).
The second monitor shouldn't make any difference, it sounds like the issue is just that the figure is being replaced with a new one.
Fortunately you can update figures you've moved to where you want them pretty easily, by using the object interface specifically, and updating the Axes object without creating a new figure. An example is below:
import matplotlib.pyplot as plt
import numpy as np
# Create the figure and axes, keeping the object references
fig = plt.figure()
ax = fig.add_subplot(111)
p, = ax.plot(np.linspace(0,1))
# First display
plt.show()
# Some time to let you look at the result and move/resize the figure
plt.pause(3)
# Replace the contents of the Axes without making a new window
ax.cla()
p, = ax.plot(2*np.linspace(0,1)**2)
# Since the figure is shown already, use draw() to update the display
plt.draw()
plt.pause(3)
# Or you can get really fancy and simply replace the data in the plot
p.set_data(np.linspace(-1,1), 10*np.linspace(-1,1)**3)
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
plt.draw()

Combining mayavi and matplotlib in the same figure

I will be making animations. In each frame I want to contain both a mayavi plot obtained with
mlab.pipeline.iso_surface(source, some other superfluous args)
and a matplotlib plot obtained using simply
pylab.plot(args)
I have scripts to do both separately, but have no idea how to go about combining them into one figure. I want the end product to be one script which contains the code from both the scripts that I currently have.
AFAIK, there is no direct way because the backends used are so different. It does not seem possible to add matplotlib axes to mayavi.figure or vice versa.
However, there is a "kind of a way" by using the the mlab.screenshot.
import mayavi.mlab as mlab
import matplotlib.pyplot as plt
# create and capture a mlab object
mlab.test_plot3d()
img = mlab.screenshot()
mlab.close()
# create a pyplot
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax1.plot([0,1], [1,0], 'r')
# add the screen capture
ax2 = fig.add_subplot(122)
ax2.imshow(img)
ax2.set_axis_off()
This is not necessarily the nicest possible way of doing things, and you may bump into resolution problems, as well (check the size of the mayavi window). However, it gets the job done in most cases.
Adding to the answer by DrV which helped me a great deal, you can work with the mlab figure to set resolution before screenshot such as with batch plotting:
mfig = mlab.figure(size=(1024, 1024))
src = mlab.pipeline.scalar_field(field_3d_numpy_array)
mlab.pipeline.iso_surface(src)
iso_surface_plot = mlab.screenshot(figure=mfig, mode='rgba', antialiased=True)
mlab.clf(mfig)
mlab.close()
# Then later in a matplotlib fig:
plt.imshow(iso_surface_plot)

Categories

Resources