Skip to content

Commit 9284758

Browse files
committed
Defer updates for hidden expensive views
1 parent 0c7bac7 commit 9284758

7 files changed

Lines changed: 116 additions & 0 deletions

File tree

docs/changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ behavior they verify rather than listed separately.
2929
with `n_spikes_amplitudes_background` (10,000 by default).
3030
- Waveform, Amplitude, and Correlogram views use fixed total spike budgets
3131
across multi-cluster selections, while retaining their per-cluster ceilings.
32+
- Hidden or inactive-tab Waveform, Amplitude, and Correlogram views defer
33+
selection plotting until they become visible, retaining only the latest
34+
selection.
3235

3336
### Changed
3437

docs/performance.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ Other common cluster display limits are eight clusters for the Waveform,
2828
Feature, Amplitude, and scatter views, and 20 for histogram and Probe views.
2929
These limits control what a view plots; they do not change the table selection.
3030

31+
Waveform, Amplitude, and Correlogram views also defer selection plotting while
32+
their dock is hidden or in an inactive tab. Their public cluster selection is
33+
updated immediately, but the canvas is redrawn only when it becomes visible.
34+
Only the latest pending selection is retained.
35+
3136
## How spikes are chosen
3237

3338
The standard selector samples up to the configured number independently from

phy/cluster/views/amplitude.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class AmplitudeView(MarkerSizeMixin, LassoMixin, ManualClusteringView):
4444

4545
# Do not show too many clusters.
4646
max_n_clusters = 8
47+
defer_hidden_updates = True
4748

4849
_default_position = 'right'
4950

phy/cluster/views/base.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,13 @@ class ManualClusteringView:
6565
plot_canvas_class = PlotCanvas
6666
ex_status = '' # the GUI can update this to
6767
max_n_clusters = 0 # By default, show all clusters.
68+
defer_hidden_updates = False
6869

6970
def __init__(self, shortcuts=None, **kwargs):
7071
self._lock = None
7172
self._closed = False
73+
self._dock_visible = True
74+
self._pending_selection = None
7275
self.cluster_ids = ()
7376

7477
# Load default shortcuts, and override with any user shortcuts.
@@ -156,6 +159,14 @@ def on_select_threaded(self, sender, cluster_ids, gui=None, **kwargs):
156159
# than leaving its previous contents on screen.
157160
if self.max_n_clusters and len(cluster_ids) > self.max_n_clusters:
158161
cluster_ids = cluster_ids[:self.max_n_clusters]
162+
if self.defer_hidden_updates and not self._dock_visible:
163+
# Keep the public selection state current while retaining only the
164+
# latest small payload needed to redraw the view when it is shown.
165+
cluster_ids = list(cluster_ids)
166+
self.cluster_ids = cluster_ids
167+
self._pending_selection = (cluster_ids, dict(kwargs))
168+
return
169+
self._pending_selection = None
159170

160171
# The lock is used so that two different background threads do not access the same
161172
# view simultaneously, which can lead to conflicts, errors in the plotting code,
@@ -206,6 +217,7 @@ def finished():
206217
emit('is_busy', self, False)
207218
self._lock = None
208219
self.update_status()
220+
self._flush_pending_selection()
209221

210222
# Start the task on the thread pool, and let the OpenGL canvas know that we're
211223
# starting to record all OpenGL calls instead of executing them immediately.
@@ -223,6 +235,24 @@ def finished():
223235
worker.run()
224236
self._lock = None
225237

238+
def _flush_pending_selection(self):
239+
"""Render the latest selection deferred while this view was hidden."""
240+
if (
241+
not self._pending_selection
242+
or not self._dock_visible
243+
or self._lock
244+
or self._closed
245+
):
246+
return
247+
cluster_ids, kwargs = self._pending_selection
248+
self._pending_selection = None
249+
self.on_select_threaded(
250+
self,
251+
cluster_ids=cluster_ids,
252+
gui=self.gui,
253+
**kwargs,
254+
)
255+
226256
def on_cluster(self, up):
227257
"""Callback function when a clustering action occurs. May be overridden.
228258
@@ -251,6 +281,14 @@ def attach(self, gui):
251281

252282
gui.add_view(self, position=self._default_position)
253283
self.gui = gui
284+
self._dock_visible = self.dock.isVisible()
285+
286+
def on_visibility_changed(visible):
287+
self._dock_visible = visible
288+
if visible:
289+
self._flush_pending_selection()
290+
291+
self.dock.visibilityChanged.connect(on_visibility_changed)
254292

255293
# Set the view state.
256294
self.set_state(gui.state.get_view_state(self))
@@ -281,6 +319,8 @@ def on_close_view(view_, gui):
281319
return
282320
logger.debug('Close view %s.', self.name)
283321
self._closed = True
322+
self._pending_selection = None
323+
self.dock.visibilityChanged.disconnect(on_visibility_changed)
284324
gui.remove_menu(self.name)
285325
unconnect(on_select)
286326
gui.state.update_view_state(self, self.state)

phy/cluster/views/correlogram.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class CorrelogramView(ScalingMixin, ManualClusteringView):
4242

4343
# Do not show too many clusters.
4444
max_n_clusters = 20
45+
defer_hidden_updates = True
4546

4647
_default_position = 'left'
4748
cluster_ids = ()

phy/cluster/views/tests/test_base.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,18 @@ def status(self):
2929
return 'hello'
3030

3131

32+
class DeferredView(ManualClusteringView):
33+
defer_hidden_updates = True
34+
max_n_clusters = 2
35+
36+
def __init__(self):
37+
super().__init__()
38+
self.updates = []
39+
40+
def plot(self, **kwargs):
41+
self.updates.append((list(self.cluster_ids), kwargs))
42+
43+
3244
def test_manual_clustering_view_1(qtbot, tempdir):
3345
v = MyView()
3446
v.canvas.show()
@@ -88,3 +100,56 @@ class Supervisor:
88100
assert v.cluster_ids == [3, 2]
89101

90102
_stop_and_close(qtbot, v)
103+
104+
105+
def test_manual_clustering_view_defers_latest_selection_while_hidden(qtbot, gui):
106+
v = DeferredView()
107+
v.attach(gui)
108+
109+
class Supervisor:
110+
pass
111+
112+
emit('select', Supervisor(), cluster_ids=[1], marker='visible')
113+
assert v.updates == [([1], {'marker': 'visible'})]
114+
115+
v.dock.hide()
116+
qtbot.waitUntil(lambda: not v._dock_visible)
117+
emit('select', Supervisor(), cluster_ids=[2], marker='superseded')
118+
emit('select', Supervisor(), cluster_ids=[3, 4, 5], marker='latest')
119+
120+
# Public state follows the limited selection, but no hidden plot occurs and
121+
# only the latest payload remains retained.
122+
assert v.cluster_ids == [3, 4]
123+
assert v.updates == [([1], {'marker': 'visible'})]
124+
assert v._pending_selection == ([3, 4], {'marker': 'latest'})
125+
126+
v.dock.show()
127+
qtbot.waitUntil(lambda: len(v.updates) == 2)
128+
assert v.updates[-1] == ([3, 4], {'marker': 'latest'})
129+
assert v._pending_selection is None
130+
131+
_stop_and_close(qtbot, v)
132+
133+
134+
def test_manual_clustering_view_defers_inactive_tab(qtbot, gui):
135+
hidden = DeferredView()
136+
visible = DeferredView()
137+
hidden.attach(gui)
138+
visible.attach(gui)
139+
gui.tabifyDockWidget(hidden.dock, visible.dock)
140+
visible.dock.raise_()
141+
qtbot.waitUntil(lambda: not hidden._dock_visible and visible._dock_visible)
142+
143+
class Supervisor:
144+
pass
145+
146+
emit('select', Supervisor(), cluster_ids=[7])
147+
assert hidden.updates == []
148+
assert visible.updates == [([7], {})]
149+
150+
hidden.dock.raise_()
151+
qtbot.waitUntil(lambda: len(hidden.updates) == 1)
152+
assert hidden.updates == [([7], {})]
153+
154+
_stop_and_close(qtbot, hidden)
155+
_stop_and_close(qtbot, visible)

phy/cluster/views/waveform.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ class WaveformView(ScalingMixin, ManualClusteringView):
9696

9797
# Do not show too many clusters.
9898
max_n_clusters = 8
99+
defer_hidden_updates = True
99100

100101
_default_position = 'right'
101102
ax_color = (0.75, 0.75, 0.75, 1.0)

0 commit comments

Comments
 (0)