关闭和删除使用Matplotlib数字的后续PyQt选项卡不会释放内存

时间:2014-10-28 19:54:38

标签: matplotlib pyqt

我创建了一个简单的PyQt gui,可以打开/关闭包含时间序列数据的文件,并使用matplotlib显示图形。每个新文件都显示在新选项卡上。当选项卡关闭时,应删除对图形等的所有引用。要告诉PyQt销毁Qt项目,我在结束选项卡上调用deleteLater()。

然而,有人不放弃记忆:(

我已尝试覆盖deleteLater()并清除图形/轴,然后再调用父级的deleteLater(),但这只会释放一小部分内存。

任何?

更新:管理以创建一个再现某些行为的调试示例:

#!/usr/bin/env python
# AbfView debug example
# Copyright 2014 Michael Clerx (michael@myokit.org)
import gc
import sys
# PyQt for python 2
import sip
sip.setapi('QString', 2)
sip.setapi('QVariant', 2)
from PyQt4 import QtGui, QtCore
from PyQt4.QtCore import Qt
QtCore.Signal = QtCore.pyqtSignal
QtCore.Slot = QtCore.pyqtSlot
# Matplotlib
import matplotlib
matplotlib.use('Qt4Agg') 
import matplotlib.backends.backend_qt4agg as backend
import matplotlib.figure
class AbfView(QtGui.QMainWindow):
    """
    Main window
    """
    def __init__(self):
        super(AbfView, self).__init__()
        # Set size, center
        self.resize(800,600)
        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())
        self.create_toolbar()
        # Add widget for Abf file tabs
        self._tabs = QtGui.QTabWidget()
        self._tabs.setTabsClosable(True)
        self._tabs.tabCloseRequested.connect(self.action_close)
        self.setCentralWidget(self._tabs)
    def action_open(self, event):
        """
        Mock-up file opening
        """
        for i in xrange(1):
            filename = 'file_' + str(i) + '.txt'
            abf = AbfFile(filename)
            self._tabs.addTab(AbfTab(self, abf), filename)
    def action_close(self, index):
        """
        Called when a tab should be closed
        """
        tab = self._tabs.widget(index)
        self._tabs.removeTab(index)
        if tab is not None:
            tab.deleteLater()
        gc.collect()
        del(tab)
    def create_toolbar(self):
        """
        Creates this widget's toolbar
        """
        self._tool_open = QtGui.QAction('&Open', self)
        self._tool_open.setShortcut('Ctrl+O')
        self._tool_open.setStatusTip('Open a file')
        self._tool_open.setIcon(QtGui.QIcon.fromTheme('document-open'))
        self._tool_open.triggered.connect(self.action_open)
        self._toolbar = self.addToolBar('tools')
        self._toolbar.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
        self._toolbar.addAction(self._tool_open)
class AbfTab(QtGui.QTabWidget):
    """
    A Qt widget displaying an ABF file.
    """
    def __init__(self, parent, abf):
        super(AbfTab, self).__init__(parent)
        self.setTabsClosable(False)
        self.setTabPosition(self.East)
        self._abf = abf
        self._abf.fold_sweeps()
        self._abf.set_time_scale(1000)
        self._figures = []
        self._axes = []
        for i in xrange(self._abf.count_data_channels()):
            self.addTab(self.create_graph_tab(i), 'AD' + str(i))
        for i in xrange(self._abf.count_protocol_channels()):
            self.addTab(self.create_protocol_tab(i), 'DA' + str(i))
        self.addTab(self.create_info_tab(), 'Info')
    def create_graph_tab(self, channel):
        """
        Creates a widget displaying the main data.
        """
        widget = QtGui.QWidget(self)
        # Create figure
        figure = matplotlib.figure.Figure()
        figure.suptitle(self._abf.filename())
        canvas = backend.FigureCanvasQTAgg(figure)
        canvas.setParent(widget)
        axes = figure.add_subplot(1,1,1)        
        toolbar = backend.NavigationToolbar2QTAgg(canvas, widget)
        # Draw lines
        for i, sweep in enumerate(self._abf):
            c = sweep[channel]
            axes.plot(c.times(), c.values())
        # Create a layout
        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(canvas)
        vbox.addWidget(toolbar)
        widget.setLayout(vbox)
        self._figures.append(figure)
        self._axes.append(axes)
        return widget
    def create_protocol_tab(self, channel):
        """
        Creates a widget displaying a stored D/A signal.
        """
        widget = QtGui.QWidget(self)
        # Create figure
        figure = matplotlib.figure.Figure()
        figure.suptitle(self._abf.filename())
        canvas = backend.FigureCanvasQTAgg(figure)
        canvas.setParent(widget)
        axes = figure.add_subplot(1,1,1)        
        toolbar = backend.NavigationToolbar2QTAgg(canvas, widget)
        # Draw lines
        for i, sweep in enumerate(self._abf.protocol()):
            c = sweep[channel]
            axes.plot(c.times(), c.values())
        # Create a layout
        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(canvas)
        vbox.addWidget(toolbar)
        widget.setLayout(vbox)
        self._figures.append(figure)
        self._axes.append(axes)
        return widget
    def create_info_tab(self):
        """
        Creates a tab displaying information about the file.
        """
        widget = QtGui.QTextEdit(self)
        widget.setText(self._abf.info(show_header=True))
        widget.setReadOnly(True)
        return widget
    def deleteLater(self):
        """
        Deletes this tab (later).
        """
        for figure in self._figures:
            figure.clear()
        for axes in self._axes:
            axes.cla()
        del(self._abf, self._figures, self._axes)
        gc.collect()
        super(AbfTab, self).deleteLater()
class AbfFile(object):
    """
    Mock-up for abf file class
    """
    def __init__(self, filename):
        import numpy as np
        self._filename = filename
        n = 500000
        s = 20
        self._time = np.linspace(0,6,n)
        self._data = []
        self._prot = []
        for i in xrange(s):
            self._data.append(AbfFile.Sweep(self._time,
                np.sin(self._time + np.random.random() * 36)))
            self._prot.append(AbfFile.Sweep(self._time,
                np.cos(self._time + np.random.random() * 36)))
    def count_data_channels(self):
        return 1
    def count_protocol_channels(self):
        return 4
    def info(self, show_header=False):
        return 'fake info'
    def fold_sweeps(self):
        pass
    def set_time_scale(self, scale):
        pass
    def __iter__(self):
        return iter(self._data)
    def protocol(self):
        return iter(self._prot)
    def filename(self):
        return self._filename
    class Sweep(object):
        def __init__(self, time, data):
            self._channel = AbfFile.Channel(time, data)
        def __getitem__(self, index):
            return self._channel
    class Channel(object):
        def __init__(self, time, data):
            self._time = time
            self._data = data
        def times(self):
            return self._time
        def values(self):
            return self._data
def load():
    """
    Loads the Gui, and adds signal handling.
    """
    import sys
    import signal
    app = QtGui.QApplication(sys.argv)
    # Close with last window
    app.connect(app, QtCore.SIGNAL('lastWindowClosed()'),
                app, QtCore.SLOT('quit()'))
    # Close on Ctrl-C
    def int_signal(signum, frame):
        app.closeAllWindows()
    signal.signal(signal.SIGINT, int_signal)
    # Create main window and show
    window = AbfView()
    window.show()
    # For some reason, PyQt needs the focus to handle the SIGINT catching...
    timer = QtCore.QTimer()
    timer.start(500) # Flags timeout every 500ms
    timer.timeout.connect(lambda: None)
    # Wait for app to exit
    sys.exit(app.exec_())
if __name__ == '__main__':
    load()

要重现:运行程序,然后单击“打开”(或Ctrl-O),然后再次“打开”。现在关闭所有标签。没有内存被释放。我想知道这是否是matplotlib中的某种性能破解。如果是这样,有没有办法告诉它让内存消失?

0 个答案:

没有答案