Skip to content

Commit 79fe4bd

Browse files
authored
Merge pull request #379 from BiAPoL/escape-empty-selection
Handles the case if no layer is selected in the layer tab.
2 parents 3fbdd43 + dc23f3d commit 79fe4bd

File tree

4 files changed

+96
-0
lines changed

4 files changed

+96
-0
lines changed

src/napari_clusters_plotter/_algorithm_widget.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import warnings
2+
13
import pandas as pd
24
from magicgui import magicgui
35
from napari.layers import (
@@ -50,6 +52,13 @@ def _get_features(self):
5052
].astype("category")
5153
return features.reset_index(drop=True)
5254

55+
def _clean_up(self):
56+
"""Determines what happens in case of no layer selected"""
57+
58+
raise NotImplementedError(
59+
"This function should be implemented in the subclass."
60+
)
61+
5362
@property
5463
def common_columns(self):
5564
if len(self.layers) == 0:
@@ -137,6 +146,13 @@ def _update_features(self):
137146
return features
138147

139148
def _wait_for_finish(self, worker):
149+
# escape empty input data
150+
if self.selected_algorithm_widget.data.value.empty:
151+
warnings.warn(
152+
"No features selected. Please select features before running the algorithm.",
153+
stacklevel=1,
154+
)
155+
return
140156
self.worker = worker
141157
self.worker.start()
142158
self.worker.returned.connect(self._process_result)
@@ -164,6 +180,7 @@ def _on_update_layer_selection(self, layer):
164180

165181
# don't do anything if no layer is selected
166182
if self.n_selected_layers == 0:
183+
self._clean_up()
167184
return
168185

169186
# check if the selected layers are of the correct type
@@ -189,6 +206,16 @@ def _on_update_layer_selection(self, layer):
189206
self.feature_selection_widget.addItems(sorted(features_to_add.columns))
190207
self._update_features()
191208

209+
def _clean_up(self):
210+
"""
211+
Clean up the widget when it is closed.
212+
"""
213+
214+
# block signals for feature selection
215+
self.feature_selection_widget.blockSignals(True)
216+
self.feature_selection_widget.clear()
217+
self.feature_selection_widget.blockSignals(False)
218+
192219
@property
193220
def selected_algorithm(self):
194221
return self.algorithm_selection.currentText()

src/napari_clusters_plotter/_new_plotter_widget.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ def _on_finish_draw(self, color_indices: np.ndarray):
149149
# if the hue axis is not set to MANUAL_CLUSTER_ID, set it to that
150150
# otherwise replot the data
151151

152+
if self.n_selected_layers == 0:
153+
return
154+
152155
features = self._get_features()
153156
for layer in self.viewer.layers.selection:
154157
layer_indices = features[features["layer"] == layer.name].index
@@ -335,6 +338,7 @@ def _on_update_layer_selection(
335338
"""
336339
# don't do anything if no layer is selected
337340
if self.n_selected_layers == 0:
341+
self._clean_up()
338342
return
339343

340344
# check if the selected layers are of the correct type
@@ -362,6 +366,23 @@ def _on_update_layer_selection(
362366
for layer in self.layers:
363367
layer.events.features.connect(self._update_feature_selection)
364368

369+
def _clean_up(self):
370+
"""In case of empty layer selection"""
371+
372+
# disconnect the events from the layers
373+
for layer in self.viewer.layers.selection:
374+
layer.events.features.disconnect(self._update_feature_selection)
375+
376+
# reset the selected layers
377+
self.layers = []
378+
379+
# reset the selectors
380+
for dim in ["x", "y", "hue"]:
381+
selector = self._selectors[dim]
382+
selector.blockSignals(True)
383+
selector.clear()
384+
selector.blockSignals(False)
385+
365386
def _update_feature_selection(
366387
self, event: napari.utils.events.Event
367388
) -> None:
@@ -450,6 +471,10 @@ def _reset(self):
450471
"""
451472
Reset the selection in the current plotting widget.
452473
"""
474+
475+
if self.n_selected_layers == 0:
476+
return
477+
453478
self.plotting_widget.active_artist.color_indices = np.zeros(
454479
len(self._get_features())
455480
)

src/napari_clusters_plotter/_tests/test_dimensionality_reduction.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ def test_initialization(make_napari_viewer, widget_config):
7777
# check that all features are in reduce_wdiget.feature_selection_widget
7878
assert len(feature_selection_items) == len(layer.features.columns)
7979

80+
# clear layers to make sure cleanup works
81+
viewer.layers.clear()
82+
83+
assert widget.feature_selection_widget.count() == 0
84+
8085

8186
def test_layer_update(make_napari_viewer, widget_config):
8287
viewer = make_napari_viewer()

src/napari_clusters_plotter/_tests/test_plotter.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,6 +344,45 @@ def test_categorical_handling(make_napari_viewer, create_sample_layers):
344344
assert categorical_columns[1] == "layer"
345345

346346

347+
def test_empty_layer_clean_up(make_napari_viewer, n_samples: int = 100):
348+
"""
349+
This test checks what happenns when you add some layers,
350+
do a manual clustering , then delete the layers and add some others
351+
"""
352+
from napari_clusters_plotter import PlotterWidget
353+
354+
viewer = make_napari_viewer()
355+
356+
points1, points2 = create_multi_point_layer(n_samples=n_samples)
357+
vectors1, _ = create_multi_vectors_layer(n_samples=n_samples)
358+
359+
# add points to viewer
360+
viewer.add_layer(points1)
361+
viewer.add_layer(points2)
362+
363+
widget = PlotterWidget(viewer)
364+
viewer.window.add_dock_widget(widget, area="right")
365+
viewer.layers.selection.active = points1
366+
367+
# do a random drawing
368+
assert "MANUAL_CLUSTER_ID" in points1.features.columns
369+
random_cluster_indeces = np.random.randint(0, 2, len(points1.data))
370+
widget._on_finish_draw(random_cluster_indeces)
371+
372+
# delete the layers
373+
viewer.layers.clear()
374+
375+
# check that all widget._selectros ('x', 'y', 'hue') are empty
376+
assert widget._selectors["x"].currentText() == ""
377+
assert widget._selectors["y"].currentText() == ""
378+
assert widget._selectors["hue"].currentText() == ""
379+
380+
widget._reset()
381+
382+
# add vectors to viewer
383+
viewer.add_layer(vectors1)
384+
385+
347386
@pytest.mark.parametrize(
348387
"create_sample_layers",
349388
[

0 commit comments

Comments
 (0)