Real-time plot in matplotlib - python - 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()

Related

Matplotlib Imshow Doesn't Update With Draw

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.

Matplotlib figure does not update in every loop

I would like to update a figure of matplotlib for every iteration in a loop. It works for about the first 30 iterations, but then the updates stop although there are more iterations.
Following you can find my code for the figure:
import numpy as np
import matplotlib.pyplot as plt
class SimpOutput:
fig = None
ax_l = None
ax_r = None
it_container = []
obj_container = []
def __init__(self):
self.fig, (self.ax_l, self.ax_r) = plt.subplots(nrows=1, ncols=2, figsize=(8, 4))
self.ax_l.set_title("Flexibility $c$")
self.ax_l.set_ylabel("obj. value $c$")
self.ax_l.set_xlabel("Iteration")
self.ax_r.set_title("Shape")
self.fig.show()
def update(self, iteration, obj, x):
self.it_container.append(iteration)
self.obj_container.append(obj)
self.ax_l.plot(self.it_container, self.obj_container, c="r")
x = x.reshape((4, 4))
x = x.T
x = np.flip(x, 0)
self.ax_r.imshow(x, cmap="binary")
plt.pause(0.1)
self.fig.show()
plt.pause(0.1)
if __name__ == "__main__":
out = SimpOutput()
for i in range(50):
out.update(i, 1000 * np.random.rand(), np.random.rand(16))
update is called in every loop.
Using fig.canvas.show() and different values for pause does not affect on the update. Furthermore, the methods set_array() and set_data() do not fix the problem either. While debugging, the figure is updated for every iteration. I write the code with PyCharm.
Does anyone had the same issue or rather does anyone has an idea how to solve this issue?
Thanks in advance!
Bests,
Sebastian
I tried to run the following simple code:
for i in range(50):
plt.clf()
pd.DataFrame([3,4,i]).plot.line(title=f'iteration {i}')
plt.show()
Not updating any figure, just clearing the old one and plotting a new figure.
It stops creating new figures after 30 iterations as well. Same problem as you.
I suspected the problem is in pycharm, so I tried one more thing.
I replaced plt.show() with plt.savefig(f'example_{i}.jpg'). It indeed saved all 50 figures in the folder (means it worked okay), and in addition, it gave me a warning:
envs\my_main_env\lib\site-packages\pandas\plotting\_matplotlib\core.py:337: 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_open_warning`).
fig = self.plt.figure(figsize=self.figsize)
indicating the problem is in the number of figures open at parallel. You can google the warning find some answers to it (e.g. warning about too many open figures).
I added the parameter at the beginning of the code: plt.rcParams.update({'figure.max_open_warning': 60})
now, running again. In pycharm in scientific mode, it didn't work. So I turned off scientific mode (opening a new figure for each plot) and now it works well! so I think that the limitation is a pycharm limitation for scientific mode - how many figures can be open at the same time.
turn off scientific mode for plotting by going to Settings->Tools->Python Scientific-> uncheck "Show plots in tool window" box.
That worked for me. I don't know how to change the limit in scientific mode (if possible at all). I believe it gives enough insights for you to solve your issue - whether by saving the figures or by plotting them without scientific mode.

Ending Python execution whilst keeping Matplotlib Pyplot open?

Making a script that grabs the latest COVID-19 England figures and then displays it as a matplotlib line graph. Early stages, but just thought I'd give a bit of background.
def plot(cases,deaths):
plt.show(block=False)
cases.plot(x='Specimen date',y='Cumulative lab-confirmed cases',color='green')
deaths.plot(x='Reporting date',y='Cumulative deaths',color='red')
plt.show()
This is one of my .py scripts (make_graph.py) that is meant to draw the graph after the .csv files are downloaded and parsed.
# Then, parse the data: we only need the rows with 'England', and the date and cumulative values
imported = csv_parser()
cases_filtered_import = imported[0]
deaths_filtered_import = imported[1]
# Finally, make the graph
plot(cases_filtered_import,deaths_filtered_import)
# !! This is the end of the main.py file
My only issue is that I cannot get the plot windows to be separated from main.py. I want it so that the graphs are displayed and then the code finishes after, instead of having to close the windows before the code execution finishes.
Any help would be appreciated. I am aware that this is probably a duplicated question, but I cannot seem to find a solution that works.
Is it perhaps the way that I executing the program (making separate .py files for different functions, and then importing and executing them from the main.py script)?
This is indeed an old question. The thing is that matplotlib plots are interactive, not static (you can change the scale), so they do require a thread to run. Hence the only way to detach a plot is to arrange a separate thread for it. Then you can call plots there, and proceed with your code. Here is an example:
import numpy as np
import matplotlib.pyplot as plt
from multiprocessing import Process
from time import sleep
x = np.random.rand(10)
y = np.random.rand(10)
def my_plot(x, y):
plt.scatter(y, x)
plt.show()
p = Process(target=my_plot, args=(x, y))
p.start()
sleep(1)
print('keep going')
# At the end
p.join()

plt.pause leading to improper subplot placement

I am following this tutorial, and I stumbled upon something I don't understand.
The idea is to have a function, that plots an image. This function is then called in a loop where subplots are defined:
minimal example
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
def show_image(image):
"""Show image"""
plt.imshow(image)
def show_image_wait(image):
"""show image, and wait a little bit. similar implementation than in the tutorial"""
plt.imshow(image)
plt.pause(0.001)
Now, calling both functions in a loop:
no waiting:
for i in range(4):
image = np.random.randint(0,3, (10,10))
plt.subplot(1, 4, i+1)
show_image(image)
# expected output: 1 row, with 4 images side by side
# actual output: 1 row, with 4 images, side by side
with waiting, however:
for i in range(4):
image = np.random.randint(0,3, (10,10))
plt.subplot(1, 4, i+1)
show_image_wait(image)
# expected output: 1 row, with 4 images side by side
# actual output: 4 rows, with 1 images each
A function similar to show_image_wait is used in the tutorial linked above, where all images appear correctly positioned.
I don't understand why waiting for a tiny bit overrides the subplot positioning in my case, and not in the linked example.
All of this happens in a jupyter notebook
Help is greatly appreciated!
The reason you see several rows is that for each loop run a new figure is produced. Independent figures are placed below each other in a jupyter output cell.
This in turn is caused by the one from the last loop iteration being drawn on screen and hence when plt.subplot is called another time, no active figure is present - therefore a new one is created.
The underlying cause of all of this is that plt.pause(..) does a bit more than only pausing. Instead it handles possible events on the figure and eventually draws and shows the figure in interactive mode.
The source of plt.pause is
manager = _pylab_helpers.Gcf.get_active()
if manager is not None:
canvas = manager.canvas
if canvas.figure.stale:
canvas.draw_idle()
show(block=False) # <---- here the figure is shown.
canvas.start_event_loop(interval)
else:
time.sleep(interval)
where I marked the crucial line with a comment.
So in total, if you want a true pause as in "Do not do anything for x seconds", plt.pause is not well suited. In general it is also a bit questionable how useful it is in jupyter notebooks with inline backend, because that backend does not provide any interactivity.
I think it would work if you use plt.show() after the for loop but in JuPyTer notebook, the plotting is inline. A work around solution could be to use time.sleep(0.001). You may try and see if it serves your purpose.
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np
import time
def show_image_wait(image):
"""show image, and wait a little bit. similar implementation than in the tutorial"""
plt.imshow(image)
time.sleep(0.001)
for i in range(4):
image = np.random.randint(0,3, (10,10))
plt.subplot(1, 4, i+1)
show_image_wait(image)

Make several figures from a dictionary without overwriting the previous figure [duplicate]

I have a Python program that generates graphs using matplotlib. I am trying to get the program to generate a bunch of plots in one program run (the user is asked if they want to generate another graph) all in separate windows. Any way I can do this?
To generate a new figure, you can add plt.figure() before any plotting that your program does.
import matplotlib.pyplot as plt
import numpy as np
def make_plot(slope):
x = np.arange(1,10)
y = slope*x+3
plt.figure()
plt.plot(x,y)
make_plot(2)
make_plot(3)
Using the latest matlibplot, I found the following to work for my purposes:
# create figure (will only create new window if needed)
plt.figure()
# Generate plot1
plt.plot(range(10, 20))
# Show the plot in non-blocking mode
plt.show(block=False)
# create figure (will only create new window if needed)
plt.figure()
# Generate plot2
plt.plot(range(10, 20))
# Show the plot in non-blocking mode
plt.show(block=False)
...
# Finally block main thread until all plots are closed
plt.show()
The easiest way to ensure all of your lines go to the correct figure window is something like:
from six.moves import input
import matplotlib.pyplot as plt
another = True
while another:
fig, ax = plt.subplots()
ax.plot(range(5))
fig.canvas.manager.show()
# this makes sure that the gui window gets shown
# if this is needed depends on rcparams, this is just to be safe
fig.canvas.flush_events()
# this make sure that if the event loop integration is not
# set up by the gui framework the plot will update
another = bool(input("would you like another? "))
If you want to run this with a non-gui backend you will need to drop the flush_events call or wrap it in a try: ... except NotImplementedError. Much of this complication is defensive programming because GUIs can be difficult and the behavior of this code may be dependent on many factors which are not obvious from the code shown.
Using the implicit axes of pyplot can cause problems as the 'current axes' is set by the last axes the user clicked on. You should really only use pyplot when interactively typing at the rpel and almost never (other than plt.subplots) in scripts/programs.
Use the .figure() function to create a new window, the following code makes two windows:
import matplotlib.pyplot as plt
plt.plot(range(10)) # Creates the plot. No need to save the current figure.
plt.draw() # Draws, but does not block
plt.figure() # New window, if needed. No need to save it, as pyplot uses the concept of current figure
plt.plot(range(10, 20))
plt.draw()
You can repeat this as many times as you want

Categories

Resources