Skip to content

Commit

Permalink
Improve memory footprint (pyvista#338)
Browse files Browse the repository at this point in the history
* Improve memory footprint

* Clean renderer list in del finalizer

* Decrease reference counts of internal objects

* deep_clear -> deep_clean

* Use clear in close

* More cleaning

* Do not remove references to the renderers on clean

* Fix broken test

* Remove use of actors on BasePlotter

* Fix broken test

* Fix parent reference cleanup issue

* Add pytest-memprof for memory profiling during pytest

* Decrease memory useage of tests
  • Loading branch information
banesullivan committed Aug 1, 2019
1 parent 53cdcf4 commit f130740
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 12 deletions.
25 changes: 17 additions & 8 deletions pyvista/plotting/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
_ALL_PLOTTERS = {}

def close_all():
"""Close all open/active plotters"""
"""Close all open/active plotters and clean up memory"""
for key, p in _ALL_PLOTTERS.items():
p.close()
p.deep_clean()
_ALL_PLOTTERS.clear()
return True

Expand Down Expand Up @@ -109,7 +110,6 @@ def __init__(self, shape=(1, 1), border=None, border_color='k',
self._scalar_bar_mappers = {}
self._scalar_bar_actors = {}
self._scalar_bar_widgets = {}
self._actors = {}
# track if the camera has been setup
# self.camera_set = False
self._first_time = True
Expand Down Expand Up @@ -2194,12 +2194,7 @@ def close(self):
del self.scalar_widget

# reset scalar bar stuff
self._scalar_bar_slots = set(range(MAX_N_COLOR_BARS))
self._scalar_bar_slot_lookup = {}
self._scalar_bar_ranges = {}
self._scalar_bar_mappers = {}
self._scalar_bar_actors = {}
self._scalar_bar_widgets = {}
self.clear()

if hasattr(self, 'ren_win'):
self.ren_win.Finalize()
Expand All @@ -2210,6 +2205,7 @@ def close(self):

if hasattr(self, 'iren'):
self.iren.RemoveAllObservers()
self.iren.TerminateApp()
del self.iren

if hasattr(self, 'textActor'):
Expand All @@ -2222,6 +2218,13 @@ def close(self):
except BaseException:
pass

def deep_clean(self):
for renderer in self.renderers:
renderer.deep_clean()
# Do not remove the renderers on the clean
self.mesh = None
self.mapper = None

def add_text(self, text, position='upper_left', font_size=18, color=None,
font=None, shadow=False, name=None, loc=None):
"""
Expand Down Expand Up @@ -3239,6 +3242,12 @@ def export_vtkjs(self, filename, compress_arrays=False):
return export_plotter_vtkjs(self, filename, compress_arrays=compress_arrays)


def __del__(self):
self.close()
self.deep_clean()
del self.renderers


class Plotter(BasePlotter):
""" Plotting object to display vtk meshes or numpy arrays.
Expand Down
3 changes: 1 addition & 2 deletions pyvista/plotting/qt_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,8 +332,7 @@ def key_quit(self, obj=None, event=None): # pragma: no cover

def quit(self):
"""Quit application"""
if hasattr(self, 'iren'):
self.iren.TerminateApp()
BasePlotter.close(self)
QVTKRenderWindowInteractor.close(self)


Expand Down
20 changes: 19 additions & 1 deletion pyvista/plotting/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def add_axes_at_origin(self, x_color=None, y_color=None, z_color=None,
x_color=x_color, y_color=y_color, z_color=z_color,
xlabel=xlabel, ylabel=ylabel, zlabel=zlabel, labels_off=labels_off)
self.AddActor(self.marker_actor)
self.parent._actors[str(hex(id(self.marker_actor)))] = self.marker_actor
self._actors[str(hex(id(self.marker_actor)))] = self.marker_actor
return self.marker_actor

def show_bounds(self, mesh=None, bounds=None, show_xaxis=True,
Expand Down Expand Up @@ -801,6 +801,24 @@ def get_pick_position(self):
return x0, y0, x1, y1


def deep_clean(self):
if hasattr(self, 'cube_axes_actor'):
del self.cube_axes_actor
if hasattr(self, 'edl_pass'):
del self.edl_pass
if hasattr(self, '_box_object'):
self.remove_bounding_box()

self.RemoveAllViewProps()
self._actors = None
# remove reference to parent last
self.parent = None
return


def __del__(self):
self.deep_clean()


def _remove_mapper_from_plotter(plotter, actor, reset_camera):
"""removes this actor's mapper from the given plotter's _scalar_bar_mappers"""
Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ matplotlib
appdirs
pytest
pytest-cov
pytest-memprof
codecov
PyQt5==5.11.3
pytest-qt
Expand Down
2 changes: 1 addition & 1 deletion tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -391,7 +391,7 @@ def test_streamlines():

def test_plot_over_line():
"""this requires matplotlib"""
mesh = examples.load_channels()
mesh = examples.load_uniform()
# Make two points to construct the line between
a = [mesh.bounds[0], mesh.bounds[2], mesh.bounds[4]]
b = [mesh.bounds[1], mesh.bounds[3], mesh.bounds[5]]
Expand Down
12 changes: 12 additions & 0 deletions tests/test_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -694,3 +694,15 @@ def test_opacity_transfer_functions():
foo = [3, 5, 6, 10]
mapping = pyvista.opacity_transfer_function(foo, n)
assert len(mapping) == n


@pytest.mark.skipif(NO_PLOTTING, reason="Requires system to support plotting")
def test_closing_and_mem_cleanup():
n = 5
for i in range(n):
for j in range(n):
p = pyvista.Plotter(off_screen=OFF_SCREEN)
for k in range(n):
p.add_mesh(pyvista.Sphere(radius=k))
p.show()
pyvista.close_all()

0 comments on commit f130740

Please sign in to comment.