How to remove matplotlib figure embedded in child window in PySide correctly to free memory

Problem:

I have an application from which I open a modeless child window by clicking on a button. This child window contains an inline matplotlib shape. I would like this child window to be destroyed along with the matplotlib figure after it has been closed.

The problem is that while the child window seems to be correctly removed by Qt, it seems that the memory is not freed from the process. The problem seems cumulative, i.e. The memory occupied by my application increases if I open several child windows and then close them manually with "X".

My system: Ubuntu 15.04 + Matplotlib 1.4.2 + python 2.7.9 or 3.4.3 + PySide 1.2.2

Note. Currently, I will bypass the "memory leak" problem without destroying the child window and reuse the same artists to update the figure with new data. However, I would like to completely free the memory occupied by this child window when it is not needed.

What I have tried so far:

I tried all the combination I could think of using gc.collect(), setParent(None), deleteLater(), del, set_attributes(QtCore.Qt.WA_DeleteOnClose). I tried to be careful with the namespace and use only weak references to matplotlib artists, I also tried to use without using pyplot, but without real success ... I was able to free some of the memory received by the child windows of matplotlib by overriding closeEventthe class method of the child window and cleaning the material manually, but still it doesn’t work perfectly and it’s not very.

MCVE, "", . , FigureCanvasQTAgg "" qt- (a QLabel, QPixmap), , , .

import sys
import numpy as np
from PySide import QtGui, QtCore
import matplotlib as mpl
mpl.rcParams['backend.qt4']='PySide'
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
import gc

class MyApp(QtGui.QWidget):
    def __init__(self):
        super(MyApp, self).__init__()

        btn_open = QtGui.QPushButton('Show Figure')
        btn_open.clicked.connect(self.show_a_figure)

        layout = QtGui.QGridLayout()
        layout.addWidget(btn_open, 0, 0)
        self.setLayout(layout)

    def show_a_figure(self):        
        MyFigureManager(self).show()


class MyFigureManager(QtGui.QWidget):    
    def __init__(self, parent=None):
        super(MyFigureManager, self).__init__(parent)

        self.setWindowFlags(QtCore.Qt.Window)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        layout = QtGui.QGridLayout()
        layout.addWidget(MyFigureCanvas(), 0, 0)
        self.setLayout(layout)

    def closeEvent(self, event):
        fig_canvas = self.findChild(MyFigureCanvas)        

        fig_canvas.figure.clear()
        del fig_canvas.figure

        fig_canvas.renderer.clear()
        del fig_canvas.renderer

        fig_canvas.mpl_disconnect(fig_canvas.scroll_pick_id)
        fig_canvas.mpl_disconnect(fig_canvas.button_pick_id)        
        fig_canvas.close()
        del fig_canvas

        gc.collect()

        super(MyFigureManager, self).closeEvent(event) 

class MyFigureCanvas(FigureCanvasQTAgg):               
    def __init__(self, parent=None):        
        super(MyFigureCanvas, self).__init__(figure=mpl.figure.Figure())
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        #-- plot some data --

        ax = self.figure.add_axes([0.1, 0.1, 0.85, 0.85])
        ax.axis([0, 1, 0, 1])

        N = 100000
        x = np.random.rand(N)
        y = np.random.rand(N)
        colors = np.random.rand(N)
        area = np.pi * (5 * np.random.rand(N)) ** 2

        ax.scatter(x, y, s=area, c=colors, alpha=0.5)

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)

    w = MyApp()
    w.show()

    sys.exit(app.exec_()) 

2015-09-01

QLabel FigureCanvasAgg:

, , . " ":

  • QSpinBox 1, QLabel, QPixmap, .
  • QSpinBox 2, QLabel, QPixmap, matplotlib, GUI FigureCanvasAgg.

enter image description here

gc.collect(). (mode = 1) , (), . "matplotlib" ( = 2) ​​ gc.collect(), . , , -, , mpl, , .

. , , Ubuntu. , .

import sys
import numpy as np
from PySide import QtGui, QtCore
import matplotlib as mpl
import gc
from matplotlib.backends.backend_agg import FigureCanvasAgg

class MyApp(QtGui.QWidget):
    def __init__(self):
        super(MyApp, self).__init__()

        btn_open = QtGui.QPushButton('Show Figure')
        btn_open.clicked.connect(self.show_a_figure)

        btn_call4gc = QtGui.QPushButton('Garbage Collect')
        btn_call4gc.clicked.connect(self.collect)

        self.mode = QtGui.QSpinBox()
        self.mode.setRange (1, 2)

        layout = QtGui.QGridLayout()
        layout.addWidget(btn_open, 1, 1)
        layout.addWidget(self.mode, 1, 2)
        layout.addWidget(btn_call4gc, 2, 1)
        self.setLayout(layout)

    def show_a_figure(self):        
        MyFigureManager(mode=self.mode.value(), parent=self).show()

    def collect(self):
        num = gc.collect()
        print(num)

class MyFigureManager(QtGui.QWidget):    
    def __init__(self, mode, parent=None):
        super(MyFigureManager, self).__init__(parent)

        self.setWindowFlags(QtCore.Qt.Window)
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)

        layout = QtGui.QGridLayout()
        layout.addWidget(MyFigureCanvas(mode=mode, parent=self), 0, 0)
        self.setLayout(layout)

class MyFigureCanvas(QtGui.QLabel):               
    def __init__(self, mode, parent=None):        
        super(MyFigureCanvas, self).__init__()
        self.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        self.setMaximumSize(1000, 650)

        if mode == 1:

            #-- import a large image from hardisk --

            qpix = QtGui.QPixmap('aLargeImg.jpg') 

        elif mode == 2:

            #-- plot some data with mpl --

            canvas = FigureCanvasAgg(mpl.figure.Figure())
            renderer = canvas.get_renderer()

            ax = canvas.figure.add_axes([0.1, 0.1, 0.85, 0.85])
            ax.axis([0, 1, 0, 1])

            N = 50000
            x = np.random.rand(N)
            y = np.random.rand(N)
            colors = np.random.rand(N)
            area = np.pi * (5 * np.random.rand(N)) ** 2

            ax.scatter(x, y, s=area, c=colors, alpha=0.5)

            #-- convert mpl imag to pixmap --

            canvas.draw()
            imgbuf = canvas.figure.canvas.buffer_rgba()
            imgwidth = int(renderer.width)
            imgheight =int(renderer.height)
            qimg = QtGui.QImage(imgbuf, imgwidth, imgheight,
                                QtGui.QImage.Format_ARGB32)
            qimg = QtGui.QImage.rgbSwapped(qimg)
            qpix = QtGui.QPixmap(qimg)

        self.setPixmap(qpix)

if __name__ == '__main__':

    app = QtGui.QApplication(sys.argv)

    w = MyApp()
    w.show()

    sys.exit(app.exec_())

:

SO:

Matplotlib:

+4

All Articles