I created a simple PyQt gui that can open / close files containing time series data and displaying graphs using matplotlib. Each new file is displayed in a new tab. When the tab is closed, all links to the drawing, etc. Must be removed. To tell PyQt about the destruction of Qt elements, I call deleteLater () on the closing tab.
However, someone does not let go of memory :(
I tried to override deleteLater () and clear the shape / axes before calling deleteLater () on the parent element, but it only frees up some of the memory.
Is anyone
Update. It was possible to execute a debugging example that reproduces some of the following:
import gc
import sys
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
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__()
self.resize(800,600)
qr = self.frameGeometry()
cp = QtGui.QDesktopWidget().availableGeometry().center()
qr.moveCenter(cp)
self.move(qr.topLeft())
self.create_toolbar()
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 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)
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)
for i, sweep in enumerate(self._abf):
c = sweep[channel]
axes.plot(c.times(), c.values())
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)
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)
for i, sweep in enumerate(self._abf.protocol()):
c = sweep[channel]
axes.plot(c.times(), c.values())
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)
app.connect(app, QtCore.SIGNAL('lastWindowClosed()'),
app, QtCore.SLOT('quit()'))
def int_signal(signum, frame):
app.closeAllWindows()
signal.signal(signal.SIGINT, int_signal)
window = AbfView()
window.show()
timer = QtCore.QTimer()
timer.start(500)
timer.timeout.connect(lambda: None)
sys.exit(app.exec_())
if __name__ == '__main__':
load()
: , "" ( Ctrl-O), "". . . , - matplotlib. , , ?