Closing and deleteLater-ing PyQt tabs with Matplotlib figures on them doesn't free up memory -
i've created simple pyqt gui can open/close files containing time-series info , display graphs using matplotlib. each new file displayed on new tab. when tab closed, references figure etc. should deleted. tell pyqt destroy qt items, i'm calling deletelater() on closing tab.
however, someone's not letting go of memory :(
i've tried overriding deletelater() , clearing figure/axes before calling deletelater() on parent frees fraction of memory.
anyone?
update: managed create debug illustration reproduces of behaviour:
#!/usr/bin/env python # abfview debug illustration # copyright 2014 michael clerx (michael@myokit.org) import gc import sys # pyqt python 2 import sip sip.setapi('qstring', 2) sip.setapi('qvariant', 2) pyqt4 import qtgui, qtcore pyqt4.qtcore import qt qtcore.signal = qtcore.pyqtsignal qtcore.slot = qtcore.pyqtslot # matplotlib import matplotlib matplotlib.use('qt4agg') import matplotlib.backends.backend_qt4agg 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 together widget 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 """ 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 tab should closed """ tab = self._tabs.widget(index) self._tabs.removetab(index) if tab not none: tab.deletelater() gc.collect() del(tab) def create_toolbar(self): """ creates widget's toolbar """ self._tool_open = qtgui.qaction('&open', self) self._tool_open.setshortcut('ctrl+o') self._tool_open.setstatustip('open 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): """ qt widget displaying 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 = [] in xrange(self._abf.count_data_channels()): self.addtab(self.create_graph_tab(i), 'ad' + str(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 widget displaying 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 i, sweep in enumerate(self._abf): c = sweep[channel] axes.plot(c.times(), c.values()) # create layout vbox = qtgui.qvboxlayout() vbox.addwidget(canvas) vbox.addwidget(toolbar) widget.setlayout(vbox) self._figures.append(figure) self._axes.append(axes) homecoming widget def create_protocol_tab(self, channel): """ creates widget displaying 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 i, sweep in enumerate(self._abf.protocol()): c = sweep[channel] axes.plot(c.times(), c.values()) # create layout vbox = qtgui.qvboxlayout() vbox.addwidget(canvas) vbox.addwidget(toolbar) widget.setlayout(vbox) self._figures.append(figure) self._axes.append(axes) homecoming widget def create_info_tab(self): """ creates tab displaying info file. """ widget = qtgui.qtextedit(self) widget.settext(self._abf.info(show_header=true)) widget.setreadonly(true) homecoming widget def deletelater(self): """ deletes tab (later). """ figure in self._figures: figure.clear() axes in self._axes: axes.cla() del(self._abf, self._figures, self._axes) gc.collect() super(abftab, self).deletelater() class abffile(object): """ mock-up abf file class """ def __init__(self, filename): import numpy np self._filename = filename n = 500000 s = 20 self._time = np.linspace(0,6,n) self._data = [] self._prot = [] 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): homecoming 1 def count_protocol_channels(self): homecoming 4 def info(self, show_header=false): homecoming 'fake info' def fold_sweeps(self): pass def set_time_scale(self, scale): pass def __iter__(self): homecoming iter(self._data) def protocol(self): homecoming iter(self._prot) def filename(self): homecoming self._filename class sweep(object): def __init__(self, time, data): self._channel = abffile.channel(time, data) def __getitem__(self, index): homecoming self._channel class channel(object): def __init__(self, time, data): self._time = time self._data = info def times(self): homecoming self._time def values(self): homecoming self._data def load(): """ loads gui, , adds signal handling. """ import sys import signal app = qtgui.qapplication(sys.argv) # close lastly 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 , show window = abfview() window.show() # reason, pyqt needs focus handle sigint catching... timer = qtcore.qtimer() timer.start(500) # flags timeout every 500ms timer.timeout.connect(lambda: none) # wait app exit sys.exit(app.exec_()) if __name__ == '__main__': load() to reproduce: run program, click "open" (or ctrl-o), , "open" again. close tabs. no memory freed. i'm wondering if kind of performance hack in matplotlib. if so, there way tell allow memory go?
matplotlib pyqt
No comments:
Post a Comment