Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Memory leak when embedding and updating a matplotlib graph in a PyQt GUI

I am trying to embed a matplotlib graph that updates every second into a PyQt GUI main window.

In my program I call an update function every second using threading.Timer via the timer function shown below. I have a problem: my program grows bigger every second - at a rate of about 1k every 4 seconds. My initial thoughts are that the append function (that returns a new array in update_figure) does not delete the old array? Is it possible this is the cause of my problem?

def update_figure(self):
    self.yAxis = np.append(self.yAxis, (getCO22()))
    self.xAxis = np.append(self.xAxis, self.i)
    # print(self.xAxis)
    if len(self.yAxis) > 10:
        self.yAxis = np.delete(self.yAxis, 0)

    if len(self.xAxis) > 10:
        self.xAxis = np.delete(self.xAxis, 0)

    self.axes.plot(self.xAxis, self.yAxis, scaley=False)
    self.axes.grid(True)

    self.i = self.i + 1

    self.draw()

This is my timer function - this is triggered by the click of a button in my PyQt GUI and then calls itself as you can see:

def timer(self):
    getCH4()
    getCO2()
    getConnectedDevices()
    self.dc.update_figure()
    t = threading.Timer(1.0, self.timer)
    t.start()

EDIT: I cant post my entire code because it requires a lot of .dll includes. So i'll try to explain what this program does.

In my GUI I want to show the my CO2 value over time. My get_co22 function just returns a float value and I'm 100% sure this works fine. With my timer, shown above, I want to keep append a value to a matplotlib graph - the Axes object is available to me as self.axes. I try to plot the last 10 values of the data.

EDIT 2: After some discussion in chat, I tried putting the call to update_figure() in a while loop and using just one thread to call it and was able to make this minimal example http://pastebin.com/RXya6Zah. This changed the structure of the code to call update_figure() to the following:

def task(self):
    while True:
        ui.dc.update_figure()
        time.sleep(1.0)

def timer(self):
    t = Timer(1.0, self.task())
    t.start()

but now the program crashes after 5 iterations or so.

like image 287
Rowan Klein Gunnewiek Avatar asked Oct 31 '22 06:10

Rowan Klein Gunnewiek


1 Answers

The problem is definitely not with how you are appending to your numpy array, or truncating it.

The problem here is with your threading model. Integrating calculation loops with a GUI control loop is difficult.

Fundamentally, you need your GUI threading to have control of when your update code is called (spawning a new thread to handle it if necessary) - so that

  1. your code does not block the GUI updating,
  2. the GUI updating does not block your code executing and
  3. you don't spawn loads of threads holding multiple copies of objects (which might be where your memory leak comes from).

In this case, as your main window is controlled by PyQt4, you want to use a QTimer (see a simple example here)

So - alter your timer code to

def task(self):
    getCH4()
    getCO2()
    getConnectedDevices()
    self.dc.update_figure()

def timer(self):
    self.t = QtCore.QTimer()
    self.t.timeout.connect(self.task)
    self.t.start(1000)

and this should work. Keeping the reference to the QTimer is essential - hence self.t = QtCore.QTimer() rather than t = QtCore.QTimer(), otherwise the QTimer object will be garbage collected.


Note:

This is a summary of a long thread in chat clarifying the issue and working through several possible solutions. In particular - the OP managed to mock up a simpler runnable example here: http://pastebin.com/RXya6Zah

and the fixed version of the full runnable example is here: http://pastebin.com/gv7Cmapr

The relevant code and explanation is above, but the links might help anyone who wants to replicate / solve the issue. Note that they require PyQt4 to be installed

like image 74
J Richard Snape Avatar answered Nov 15 '22 03:11

J Richard Snape