From c42b057d4278e77d8289d6138d3df13077e4a8b2 Mon Sep 17 00:00:00 2001 From: thorstenwagner Date: Mon, 18 Dec 2023 16:09:06 +0100 Subject: [PATCH 1/9] implemented delete selected cluster --- napari_clusters_plotter/_Qt_code.py | 43 +++++++++++++++++-- napari_clusters_plotter/_plotter.py | 14 ++++-- napari_clusters_plotter/_plotter_utilities.py | 4 +- 3 files changed, 53 insertions(+), 8 deletions(-) diff --git a/napari_clusters_plotter/_Qt_code.py b/napari_clusters_plotter/_Qt_code.py index 92f0f5ad..de32bf27 100644 --- a/napari_clusters_plotter/_Qt_code.py +++ b/napari_clusters_plotter/_Qt_code.py @@ -13,6 +13,8 @@ from matplotlib.widgets import LassoSelector, RectangleSelector, SpanSelector from napari.layers import Image, Layer from qtpy.QtCore import QRect +from qtpy.QtCore import Qt +from qtpy.QtGui import QGuiApplication from qtpy.QtGui import QIcon from qtpy.QtWidgets import ( QAbstractItemView, @@ -390,24 +392,49 @@ def create_options_dropdown(name: str, value, options: dict, label: str): class SelectFrom2DHistogram: - def __init__(self, parent, ax, full_data): + def __init__(self, parent, ax, full_data, histogram): self.parent = parent self.ax = ax self.canvas = ax.figure.canvas self.xys = full_data + self.cluster_id_histo_overlay = None + self.histogram = histogram self.lasso = LassoSelector(ax, onselect=self.onselect) self.ind = [] self.ind_mask = [] + def vert_to_coord(self, vert): + ''' + Converts verticis to histogram coordinates in pixels + ''' + xrange = self.histogram[1][-1] - self.histogram[1][0] + yrange = self.histogram[2][-1] - self.histogram[2][0] + v = ( + (vert[0] - self.histogram[1][0]) / (xrange) * self.histogram[0].shape[0], + (vert[1] - self.histogram[2][0]) / (yrange) * self.histogram[0].shape[1], + ) + + coord = tuple([int(c) for c in v]) + return coord + def onselect(self, verts): path = Path(verts) self.ind_mask = path.contains_points(self.xys) - self.ind = np.nonzero(self.ind_mask)[0] + modifiers = QGuiApplication.keyboardModifiers() if self.parent.manual_clustering_method is not None: - self.parent.manual_clustering_method(self.ind_mask) + if modifiers == Qt.ControlModifier: + # I tried to solve it with self.ax.transData.transform... but it did not work... + coord_click = self.vert_to_coord(verts[0]) + cluster_id_to_delete = self.cluster_id_histo_overlay[coord_click[1],coord_click[0]][0] + if cluster_id_to_delete>0: + self.parent.manual_clustering_method(self.ind_mask, delete_cluster=cluster_id_to_delete) + else: + self.parent.manual_clustering_method(self.ind_mask) + else: + self.parent.manual_clustering_method(self.ind_mask) def disconnect(self): self.lasso.disconnect_events() @@ -521,6 +548,7 @@ class MplCanvas(FigureCanvas): def __init__(self, parent=None, width=7, height=4, manual_clustering_method=None): self.fig = Figure(figsize=(width, height), constrained_layout=True) self.manual_clustering_method = manual_clustering_method + self.parent = parent self.axes = self.fig.add_subplot(111) self.histogram = None @@ -551,6 +579,13 @@ def __init__(self, parent=None, width=7, height=4, manual_clustering_method=None self.reset() + def set_selector_cluster_id_overlay(self, overlay: np.array): + try: + self.selector.cluster_id_histo_overlay = overlay + except: + # might not exist... + pass + def reset_zoom(self): if self.xylim: self.axes.set_xlim(self.xylim[0]) @@ -617,7 +652,7 @@ def make_2d_histogram( full_data = pd.concat([pd.DataFrame(data_x), pd.DataFrame(data_y)], axis=1) self.selector.disconnect() - self.selector = SelectFrom2DHistogram(self, self.axes, full_data) + self.selector = SelectFrom2DHistogram(self, self.axes, full_data, self.histogram) self.axes.figure.canvas.draw_idle() def make_1d_histogram( diff --git a/napari_clusters_plotter/_plotter.py b/napari_clusters_plotter/_plotter.py index 386407ee..b1cb2595 100644 --- a/napari_clusters_plotter/_plotter.py +++ b/napari_clusters_plotter/_plotter.py @@ -90,8 +90,9 @@ def __init__(self, napari_viewer): self.analysed_layer = None self.visualized_layer = None + self.cluster_id_histo_overlay = None - def manual_clustering_method(inside): + def manual_clustering_method(inside, **kwargs): inside = np.array(inside) # leads to errors sometimes otherwise if self.analysed_layer is None or len(inside) == 0: @@ -101,7 +102,12 @@ def manual_clustering_method(inside): features = get_layer_tabular_data(self.analysed_layer) modifiers = QGuiApplication.keyboardModifiers() - if modifiers == Qt.ShiftModifier and clustering_ID in features.keys(): + if 'delete_cluster' in kwargs: + features[clustering_ID].mask( + features[clustering_ID]==kwargs['delete_cluster'], other=-1, inplace=True + ) + + elif modifiers == Qt.ShiftModifier and clustering_ID in features.keys(): features[clustering_ID].mask( inside, other=features[clustering_ID].max() + 1, inplace=True ) @@ -677,7 +683,7 @@ def run( log_scale=self.log_scale.isChecked(), ) - rgb_img = make_cluster_overlay_img( + rgb_img, self.cluster_id_histo_overlay = make_cluster_overlay_img( cluster_id=plot_cluster_name, features=features, feature_x=self.plot_x_axis_name, @@ -686,6 +692,8 @@ def run( histogram_data=self.graphics_widget.histogram, hide_first_cluster=self.plot_hide_non_selected.isChecked(), ) + print("SET OV") + self.graphics_widget.set_selector_cluster_id_overlay(self.cluster_id_histo_overlay) xedges = self.graphics_widget.histogram[1] yedges = self.graphics_widget.histogram[2] diff --git a/napari_clusters_plotter/_plotter_utilities.py b/napari_clusters_plotter/_plotter_utilities.py index b77ee5fb..c88f3c64 100644 --- a/napari_clusters_plotter/_plotter_utilities.py +++ b/napari_clusters_plotter/_plotter_utilities.py @@ -549,6 +549,7 @@ def make_cluster_overlay_img( ] cluster_overlay_rgba = np.zeros((*h.shape, 4), dtype=float) + cluster_overlay_cluster_id = np.zeros((*h.shape, 1), dtype=int) output_max = np.zeros(h.shape, dtype=float) for cluster, entries in relevant_entries.groupby(cluster_id): @@ -565,5 +566,6 @@ def make_cluster_overlay_img( ] rgba.append(0.9) cluster_overlay_rgba[mask] = rgba + cluster_overlay_cluster_id[mask] = cluster - return cluster_overlay_rgba.swapaxes(0, 1) + return cluster_overlay_rgba.swapaxes(0, 1), cluster_overlay_cluster_id.swapaxes(0, 1) From 6b8de74740b20a38a0c2a64d1999ca9005d073a0 Mon Sep 17 00:00:00 2001 From: thorstenwagner Date: Mon, 18 Dec 2023 16:12:14 +0100 Subject: [PATCH 2/9] remove print --- napari_clusters_plotter/_plotter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/napari_clusters_plotter/_plotter.py b/napari_clusters_plotter/_plotter.py index b1cb2595..64dc985c 100644 --- a/napari_clusters_plotter/_plotter.py +++ b/napari_clusters_plotter/_plotter.py @@ -692,7 +692,6 @@ def run( histogram_data=self.graphics_widget.histogram, hide_first_cluster=self.plot_hide_non_selected.isChecked(), ) - print("SET OV") self.graphics_widget.set_selector_cluster_id_overlay(self.cluster_id_histo_overlay) xedges = self.graphics_widget.histogram[1] yedges = self.graphics_widget.histogram[2] From 98c63c5112a92df88acd45411102393d630e9f24 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:16:49 +0000 Subject: [PATCH 3/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- napari_clusters_plotter/_Qt_code.py | 24 +++++++++++-------- napari_clusters_plotter/_plotter.py | 10 +++++--- napari_clusters_plotter/_plotter_utilities.py | 4 +++- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/napari_clusters_plotter/_Qt_code.py b/napari_clusters_plotter/_Qt_code.py index de32bf27..454805b5 100644 --- a/napari_clusters_plotter/_Qt_code.py +++ b/napari_clusters_plotter/_Qt_code.py @@ -12,10 +12,8 @@ from matplotlib.path import Path from matplotlib.widgets import LassoSelector, RectangleSelector, SpanSelector from napari.layers import Image, Layer -from qtpy.QtCore import QRect -from qtpy.QtCore import Qt -from qtpy.QtGui import QGuiApplication -from qtpy.QtGui import QIcon +from qtpy.QtCore import QRect, Qt +from qtpy.QtGui import QGuiApplication, QIcon from qtpy.QtWidgets import ( QAbstractItemView, QHBoxLayout, @@ -405,9 +403,9 @@ def __init__(self, parent, ax, full_data, histogram): self.ind_mask = [] def vert_to_coord(self, vert): - ''' + """ Converts verticis to histogram coordinates in pixels - ''' + """ xrange = self.histogram[1][-1] - self.histogram[1][0] yrange = self.histogram[2][-1] - self.histogram[2][0] v = ( @@ -428,9 +426,13 @@ def onselect(self, verts): if modifiers == Qt.ControlModifier: # I tried to solve it with self.ax.transData.transform... but it did not work... coord_click = self.vert_to_coord(verts[0]) - cluster_id_to_delete = self.cluster_id_histo_overlay[coord_click[1],coord_click[0]][0] - if cluster_id_to_delete>0: - self.parent.manual_clustering_method(self.ind_mask, delete_cluster=cluster_id_to_delete) + cluster_id_to_delete = self.cluster_id_histo_overlay[ + coord_click[1], coord_click[0] + ][0] + if cluster_id_to_delete > 0: + self.parent.manual_clustering_method( + self.ind_mask, delete_cluster=cluster_id_to_delete + ) else: self.parent.manual_clustering_method(self.ind_mask) else: @@ -652,7 +654,9 @@ def make_2d_histogram( full_data = pd.concat([pd.DataFrame(data_x), pd.DataFrame(data_y)], axis=1) self.selector.disconnect() - self.selector = SelectFrom2DHistogram(self, self.axes, full_data, self.histogram) + self.selector = SelectFrom2DHistogram( + self, self.axes, full_data, self.histogram + ) self.axes.figure.canvas.draw_idle() def make_1d_histogram( diff --git a/napari_clusters_plotter/_plotter.py b/napari_clusters_plotter/_plotter.py index 64dc985c..78592b96 100644 --- a/napari_clusters_plotter/_plotter.py +++ b/napari_clusters_plotter/_plotter.py @@ -102,9 +102,11 @@ def manual_clustering_method(inside, **kwargs): features = get_layer_tabular_data(self.analysed_layer) modifiers = QGuiApplication.keyboardModifiers() - if 'delete_cluster' in kwargs: + if "delete_cluster" in kwargs: features[clustering_ID].mask( - features[clustering_ID]==kwargs['delete_cluster'], other=-1, inplace=True + features[clustering_ID] == kwargs["delete_cluster"], + other=-1, + inplace=True, ) elif modifiers == Qt.ShiftModifier and clustering_ID in features.keys(): @@ -692,7 +694,9 @@ def run( histogram_data=self.graphics_widget.histogram, hide_first_cluster=self.plot_hide_non_selected.isChecked(), ) - self.graphics_widget.set_selector_cluster_id_overlay(self.cluster_id_histo_overlay) + self.graphics_widget.set_selector_cluster_id_overlay( + self.cluster_id_histo_overlay + ) xedges = self.graphics_widget.histogram[1] yedges = self.graphics_widget.histogram[2] diff --git a/napari_clusters_plotter/_plotter_utilities.py b/napari_clusters_plotter/_plotter_utilities.py index c88f3c64..b99bde64 100644 --- a/napari_clusters_plotter/_plotter_utilities.py +++ b/napari_clusters_plotter/_plotter_utilities.py @@ -568,4 +568,6 @@ def make_cluster_overlay_img( cluster_overlay_rgba[mask] = rgba cluster_overlay_cluster_id[mask] = cluster - return cluster_overlay_rgba.swapaxes(0, 1), cluster_overlay_cluster_id.swapaxes(0, 1) + return cluster_overlay_rgba.swapaxes(0, 1), cluster_overlay_cluster_id.swapaxes( + 0, 1 + ) From 75c22134dbd789383ab23db7885091f2c16a37e0 Mon Sep 17 00:00:00 2001 From: thorstenwagner Date: Mon, 18 Dec 2023 16:30:34 +0100 Subject: [PATCH 4/9] remove try except... it is not necessary --- napari_clusters_plotter/_Qt_code.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/napari_clusters_plotter/_Qt_code.py b/napari_clusters_plotter/_Qt_code.py index de32bf27..105a0da1 100644 --- a/napari_clusters_plotter/_Qt_code.py +++ b/napari_clusters_plotter/_Qt_code.py @@ -580,11 +580,7 @@ def __init__(self, parent=None, width=7, height=4, manual_clustering_method=None self.reset() def set_selector_cluster_id_overlay(self, overlay: np.array): - try: self.selector.cluster_id_histo_overlay = overlay - except: - # might not exist... - pass def reset_zoom(self): if self.xylim: From 856e79db5c55663607070bfec42e6d4a5f43eccb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 18 Dec 2023 15:31:35 +0000 Subject: [PATCH 5/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- napari_clusters_plotter/_Qt_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napari_clusters_plotter/_Qt_code.py b/napari_clusters_plotter/_Qt_code.py index e14246af..ffd9d231 100644 --- a/napari_clusters_plotter/_Qt_code.py +++ b/napari_clusters_plotter/_Qt_code.py @@ -582,7 +582,7 @@ def __init__(self, parent=None, width=7, height=4, manual_clustering_method=None self.reset() def set_selector_cluster_id_overlay(self, overlay: np.array): - self.selector.cluster_id_histo_overlay = overlay + self.selector.cluster_id_histo_overlay = overlay def reset_zoom(self): if self.xylim: From 715d02b7453876d29f67dba968f79f71adb7e2bf Mon Sep 17 00:00:00 2001 From: thorstenwagner Date: Tue, 19 Dec 2023 09:15:35 +0100 Subject: [PATCH 6/9] minor refactoring --- napari_clusters_plotter/_Qt_code.py | 35 +++++++++++++++-------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/napari_clusters_plotter/_Qt_code.py b/napari_clusters_plotter/_Qt_code.py index ffd9d231..b8a1a79c 100644 --- a/napari_clusters_plotter/_Qt_code.py +++ b/napari_clusters_plotter/_Qt_code.py @@ -406,6 +406,8 @@ def vert_to_coord(self, vert): """ Converts verticis to histogram coordinates in pixels """ + + # I tried to solve it with self.ax.transData.transform... but it did not work... xrange = self.histogram[1][-1] - self.histogram[1][0] yrange = self.histogram[2][-1] - self.histogram[2][0] v = ( @@ -416,27 +418,26 @@ def vert_to_coord(self, vert): coord = tuple([int(c) for c in v]) return coord + def onselect(self, verts): - path = Path(verts) - self.ind_mask = path.contains_points(self.xys) + if self.parent.manual_clustering_method is None: + return modifiers = QGuiApplication.keyboardModifiers() - if self.parent.manual_clustering_method is not None: - if modifiers == Qt.ControlModifier: - # I tried to solve it with self.ax.transData.transform... but it did not work... - coord_click = self.vert_to_coord(verts[0]) - cluster_id_to_delete = self.cluster_id_histo_overlay[ - coord_click[1], coord_click[0] - ][0] - if cluster_id_to_delete > 0: - self.parent.manual_clustering_method( - self.ind_mask, delete_cluster=cluster_id_to_delete - ) - else: - self.parent.manual_clustering_method(self.ind_mask) - else: - self.parent.manual_clustering_method(self.ind_mask) + + if modifiers == Qt.ControlModifier and len(verts)==2: # has len of 2 when single click was done + coord_click = self.vert_to_coord(verts[0]) + cluster_id_to_delete = self.cluster_id_histo_overlay[coord_click[::-1]][0] + if cluster_id_to_delete > 0: + self.parent.manual_clustering_method( + np.zeros(shape=self.xys.shape), delete_cluster=cluster_id_to_delete + ) + return + + path = Path(verts) + self.ind_mask = path.contains_points(self.xys) + self.parent.manual_clustering_method(self.ind_mask) def disconnect(self): self.lasso.disconnect_events() From ff4fdbc3031845a510c047571a4d4323a7d3ded6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 19 Dec 2023 08:15:51 +0000 Subject: [PATCH 7/9] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- napari_clusters_plotter/_Qt_code.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/napari_clusters_plotter/_Qt_code.py b/napari_clusters_plotter/_Qt_code.py index b8a1a79c..a0146ffb 100644 --- a/napari_clusters_plotter/_Qt_code.py +++ b/napari_clusters_plotter/_Qt_code.py @@ -418,15 +418,15 @@ def vert_to_coord(self, vert): coord = tuple([int(c) for c in v]) return coord - def onselect(self, verts): - if self.parent.manual_clustering_method is None: return modifiers = QGuiApplication.keyboardModifiers() - if modifiers == Qt.ControlModifier and len(verts)==2: # has len of 2 when single click was done + if ( + modifiers == Qt.ControlModifier and len(verts) == 2 + ): # has len of 2 when single click was done coord_click = self.vert_to_coord(verts[0]) cluster_id_to_delete = self.cluster_id_histo_overlay[coord_click[::-1]][0] if cluster_id_to_delete > 0: From 9d46bb8b6cc34a602929980e2f80a686db50c0b6 Mon Sep 17 00:00:00 2001 From: thorstenwagner Date: Thu, 21 Dec 2023 16:28:52 +0100 Subject: [PATCH 8/9] do nothing when no clsuter was selected and control is pressed --- napari_clusters_plotter/_Qt_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napari_clusters_plotter/_Qt_code.py b/napari_clusters_plotter/_Qt_code.py index b8a1a79c..4c97ba51 100644 --- a/napari_clusters_plotter/_Qt_code.py +++ b/napari_clusters_plotter/_Qt_code.py @@ -433,7 +433,7 @@ def onselect(self, verts): self.parent.manual_clustering_method( np.zeros(shape=self.xys.shape), delete_cluster=cluster_id_to_delete ) - return + return path = Path(verts) self.ind_mask = path.contains_points(self.xys) From 0217a5113ccfef1e6f3652a76276f4d104d1f8bb Mon Sep 17 00:00:00 2001 From: thorstenwagner Date: Thu, 21 Dec 2023 16:34:30 +0100 Subject: [PATCH 9/9] use self.full_data --- napari_clusters_plotter/_Qt_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/napari_clusters_plotter/_Qt_code.py b/napari_clusters_plotter/_Qt_code.py index 5673300f..a54cc210 100644 --- a/napari_clusters_plotter/_Qt_code.py +++ b/napari_clusters_plotter/_Qt_code.py @@ -654,7 +654,7 @@ def make_2d_histogram( self.histogram = (h, xedges, yedges) self.selector.disconnect() self.selector = SelectFrom2DHistogram( - self, self.axes, full_data, self.histogram + self, self.axes, self.full_data, self.histogram ) self.axes.figure.canvas.draw_idle()