Xiaoguang Liu
Xiaoguang Liu

Reputation: 11

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

Upvotes: 1

Views: 6390

Answers (3)

Guillaume S
Guillaume S

Reputation: 190

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()

Upvotes: 0

Jean-Sébastien
Jean-Sébastien

Reputation: 2697

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:

  1. 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.

  2. 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.

Upvotes: 1

Xiaoguang Liu
Xiaoguang Liu

Reputation: 11

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.

Upvotes: 0

Related Questions