Skip to content

Commit f4bab4a

Browse files
authored
Fix selector garbage collection (#319)
* fix longstanding issue where auto_scale didn't work with zero width or height * BaseSelector inherits from Graphic, progress on gc, not yet full there * clear() for Synchronizer
1 parent 6936678 commit f4bab4a

8 files changed

Lines changed: 59 additions & 25 deletions

File tree

fastplotlib/graphics/_base.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,15 @@ def __eq__(self, other):
158158

159159
return False
160160

161+
def _cleanup(self):
162+
"""
163+
Cleans up the graphic in preparation for __del__(), such as removing event handlers from
164+
plot renderer, feature event handlers, etc.
165+
166+
Optionally implemented in subclasses
167+
"""
168+
pass
169+
161170
def __del__(self):
162171
del WORLD_OBJECTS[self.loc]
163172

fastplotlib/graphics/image.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ def _get_linear_selector_init_args(self, padding: float, **kwargs):
149149
if axis == "x":
150150
offset = self.position_x
151151
# x limits, number of columns
152-
limits = (offset, data.shape[1])
152+
limits = (offset, data.shape[1] - 1)
153153

154154
# size is number of rows + padding
155155
# used by LinearRegionSelector but not LinearSelector
@@ -169,7 +169,7 @@ def _get_linear_selector_init_args(self, padding: float, **kwargs):
169169
else:
170170
offset = self.position_y
171171
# y limits
172-
limits = (offset, data.shape[0])
172+
limits = (offset, data.shape[0] - 1)
173173

174174
# width + padding
175175
# used by LinearRegionSelector but not LinearSelector

fastplotlib/graphics/selectors/_base_selector.py

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66

77
from pygfx import WorldObject, Line, Mesh, Points
88

9+
from .._base import Graphic
10+
911

1012
@dataclass
1113
class MoveInfo:
@@ -31,7 +33,7 @@ class MoveInfo:
3133

3234

3335
# Selector base class
34-
class BaseSelector:
36+
class BaseSelector(Graphic):
3537
feature_events = ("selection",)
3638

3739
def __init__(
@@ -42,6 +44,7 @@ def __init__(
4244
hover_responsive: Tuple[WorldObject, ...] = None,
4345
arrow_keys_modifier: str = None,
4446
axis: str = None,
47+
name: str = None
4548
):
4649
if edges is None:
4750
edges = tuple()
@@ -89,6 +92,8 @@ def __init__(
8992

9093
self._pygfx_event = None
9194

95+
Graphic.__init__(self, name=name)
96+
9297
def get_selected_index(self):
9398
"""Not implemented for this selector"""
9499
raise NotImplementedError
@@ -341,12 +346,10 @@ def _key_up(self, ev):
341346

342347
self._move_info = None
343348

344-
def __del__(self):
345-
# clear wo event handlers
346-
for wo in self._world_objects:
347-
wo._event_handlers.clear()
348-
349-
# remove renderer event handlers
349+
def _cleanup(self):
350+
"""
351+
Cleanup plot renderer event handlers etc.
352+
"""
350353
self._plot_area.renderer.remove_event_handler(self._move, "pointer_move")
351354
self._plot_area.renderer.remove_event_handler(self._move_end, "pointer_up")
352355
self._plot_area.renderer.remove_event_handler(self._move_to_pointer, "click")
@@ -357,6 +360,10 @@ def __del__(self):
357360
# remove animation func
358361
self._plot_area.remove_animation(self._key_hold)
359362

363+
# clear wo event handlers
364+
for wo in self._world_objects:
365+
wo._event_handlers.clear()
366+
360367
if hasattr(self, "feature_events"):
361368
feature_names = getattr(self, "feature_events")
362369
for n in feature_names:

fastplotlib/graphics/selectors/_linear.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from ._base_selector import BaseSelector
1919

2020

21-
class LinearSelector(Graphic, BaseSelector):
21+
class LinearSelector(BaseSelector):
2222
@property
2323
def limits(self) -> Tuple[float, float]:
2424
return self._limits
@@ -117,9 +117,6 @@ def __init__(
117117

118118
line_data = line_data.astype(np.float32)
119119

120-
# init Graphic
121-
Graphic.__init__(self, name=name)
122-
123120
if thickness < 1.1:
124121
material = pygfx.LineThinMaterial
125122
else:
@@ -172,6 +169,7 @@ def __init__(
172169
hover_responsive=(line_inner, self.line_outer),
173170
arrow_keys_modifier=arrow_keys_modifier,
174171
axis=axis,
172+
name=name,
175173
)
176174

177175
def _setup_ipywidget_slider(self, widget):
@@ -189,8 +187,6 @@ def _setup_ipywidget_slider(self, widget):
189187
# user changes linear selection -> widget changes
190188
self.selection.add_event_handler(self._update_ipywidgets)
191189

192-
self._plot_area.renderer.add_event_handler(self._set_slider_layout, "resize")
193-
194190
self._handled_widgets.append(widget)
195191

196192
def _update_ipywidgets(self, ev):
@@ -214,6 +210,12 @@ def _ipywidget_callback(self, change):
214210

215211
self.selection = change["new"]
216212

213+
def _add_plot_area_hook(self, plot_area):
214+
super()._add_plot_area_hook(plot_area=plot_area)
215+
216+
# resize the slider widgets when the canvas is resized
217+
self._plot_area.renderer.add_event_handler(self._set_slider_layout, "resize")
218+
217219
def _set_slider_layout(self, *args):
218220
w, h = self._plot_area.renderer.logical_size
219221

@@ -375,3 +377,11 @@ def _move_graphic(self, delta: np.ndarray):
375377
self.selection = self.selection() + delta[0]
376378
else:
377379
self.selection = self.selection() + delta[1]
380+
381+
def _cleanup(self):
382+
super()._cleanup()
383+
384+
for widget in self._handled_widgets:
385+
widget.unobserve(self._ipywidget_callback, "value")
386+
387+
self._plot_area.renderer.remove_event_handler(self._set_slider_layout, "resize")

fastplotlib/graphics/selectors/_linear_region.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from .._features._selection_features import LinearRegionSelectionFeature
1818

1919

20-
class LinearRegionSelector(Graphic, BaseSelector):
20+
class LinearRegionSelector(BaseSelector):
2121
@property
2222
def limits(self) -> Tuple[float, float]:
2323
return self._limits
@@ -127,8 +127,6 @@ def __init__(
127127
# f"{limits[0]} != {origin[1]} != {bounds[0]}"
128128
# )
129129

130-
Graphic.__init__(self, name=name)
131-
132130
self.parent = parent
133131

134132
# world object for this will be a group
@@ -241,6 +239,7 @@ def __init__(
241239
hover_responsive=self.edges,
242240
arrow_keys_modifier=arrow_keys_modifier,
243241
axis=axis,
242+
name=name
244243
)
245244

246245
def get_selected_data(

fastplotlib/graphics/selectors/_polygon.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,14 @@
88
from .._base import Graphic
99

1010

11-
class PolygonSelector(Graphic, BaseSelector):
11+
class PolygonSelector(BaseSelector):
1212
def __init__(
1313
self,
1414
edge_color="magenta",
1515
edge_width: float = 3,
1616
parent: Graphic = None,
1717
name: str = None,
1818
):
19-
Graphic.__init__(self, name=name)
2019

2120
self.parent = parent
2221

@@ -31,6 +30,8 @@ def __init__(
3130

3231
self._current_mode = None
3332

33+
BaseSelector.__init__(self, name=name)
34+
3435
def get_vertices(self) -> np.ndarray:
3536
"""Get the vertices for the polygon"""
3637
vertices = list()

fastplotlib/graphics/selectors/_sync.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,12 @@ def add(self, selector):
3939

4040
def remove(self, selector):
4141
"""remove a selector"""
42-
self._selectors.remove(selector)
4342
selector.selection.remove_event_handler(self._handle_event)
43+
self._selectors.remove(selector)
44+
45+
def clear(self):
46+
for i in range(len(self.selectors)):
47+
self.remove(self.selectors[0])
4448

4549
def _handle_event(self, ev):
4650
if self.block_event:
@@ -81,7 +85,4 @@ def _move_selectors(self, source, delta):
8185
s._move_graphic(delta)
8286

8387
def __del__(self):
84-
for s in self.selectors:
85-
self.remove(s)
86-
87-
self.selectors.clear()
88+
self.clear()

fastplotlib/layouts/_base.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,12 +329,16 @@ def _add_or_insert_graphic(
329329
else:
330330
self._graphics.append(loc)
331331

332+
# now that it's in the dict, just use the weakref
333+
graphic = weakref.proxy(graphic)
334+
332335
# add world object to scene
333336
self.scene.add(graphic.world_object)
334337

335338
if center:
336339
self.center_graphic(graphic)
337340

341+
# if we don't use the weakref above, then the object lingers if a plot hook is used!
338342
if hasattr(graphic, "_add_plot_area_hook"):
339343
graphic._add_plot_area_hook(self)
340344

@@ -483,6 +487,9 @@ def delete_graphic(self, graphic: Graphic):
483487
# remove from list of addresses
484488
glist.remove(loc)
485489

490+
# cleanup
491+
graphic._cleanup()
492+
486493
if kind == "graphic":
487494
del GRAPHICS[loc]
488495
elif kind == "selector":

0 commit comments

Comments
 (0)