-
Notifications
You must be signed in to change notification settings - Fork 27
clustering feature in napari deeplabcut #38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f645696
f7e66ae
5a57d40
1d1deaa
8758bf7
56e10a4
7beaa0a
4b733eb
27141f9
fdcd392
1d06e2c
9dc8583
2588bba
a700577
0c7b383
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,3 +17,4 @@ | |
| get_config_reader, | ||
| ) | ||
| from ._writer import write_hdf, write_masks | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -193,11 +193,12 @@ def read_hdf(filename: str) -> List[LayerData]: | |||||||||||||||||||||||
| if isinstance(temp.index, pd.MultiIndex): | ||||||||||||||||||||||||
| temp.index = [os.path.join(*row) for row in temp.index] | ||||||||||||||||||||||||
| df = ( | ||||||||||||||||||||||||
| temp.stack(["individuals", "bodyparts"]) | ||||||||||||||||||||||||
| temp.stack(["individuals", "bodyparts"])#, dropna=False) | ||||||||||||||||||||||||
| .reindex(header.individuals, level="individuals") | ||||||||||||||||||||||||
| .reindex(header.bodyparts, level="bodyparts") | ||||||||||||||||||||||||
| .reset_index() | ||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||
| #df.fillna(0, inplace=True) | ||||||||||||||||||||||||
|
Comment on lines
+196
to
+201
|
||||||||||||||||||||||||
| temp.stack(["individuals", "bodyparts"])#, dropna=False) | |
| .reindex(header.individuals, level="individuals") | |
| .reindex(header.bodyparts, level="bodyparts") | |
| .reset_index() | |
| ) | |
| #df.fillna(0, inplace=True) | |
| temp.stack(["individuals", "bodyparts"]) | |
| .reindex(header.individuals, level="individuals") | |
| .reindex(header.bodyparts, level="bodyparts") | |
| .reset_index() | |
| ) |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,4 +1,13 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from collections import defaultdict | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from functools import partial | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import pandas as pd | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from matplotlib.figure import Figure | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from types import MethodType | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from typing import Optional, Sequence, Union | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari.layers import Image, Points | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from collections import defaultdict, namedtuple | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from copy import deepcopy | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from datetime import datetime | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -45,9 +54,38 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QStyleOption, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QVBoxLayout, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QWidget, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QPushButton, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari_deeplabcut.kmeans import cluster_data | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari_deeplabcut import keypoints | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari_deeplabcut.misc import to_os_dir_sep, find_project_name | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| class Worker(QtCore.QObject): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| started = QtCore.Signal() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| finished = QtCore.Signal() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value = QtCore.Signal(object) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+68
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def __init__(self, func): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| super().__init__() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.func = func | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def run(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| out = self.func() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.value.emit(out) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.finished.emit() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def move_to_separate_thread(func): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| thread = QtCore.QThread() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| worker = Worker(func) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| worker.moveToThread(thread) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| thread.started.connect(worker.run) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| worker.finished.connect(thread.quit) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| worker.finished.connect(worker.deleteLater) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| worker.finished.connect(thread.deleteLater) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return worker, thread | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari_deeplabcut._reader import _load_config | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari_deeplabcut._writer import _write_config, _write_image, _form_df | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari_deeplabcut.misc import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -538,7 +576,6 @@ def __init__(self, napari_viewer): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer = napari_viewer | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer.layers.events.inserted.connect(self.on_insert) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer.layers.events.removed.connect(self.on_remove) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer.window.qt_viewer._get_and_try_preferred_reader = MethodType( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _get_and_try_preferred_reader, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer.window.qt_viewer, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -779,6 +816,108 @@ def _store_crop_coordinates(self, *args): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| _write_config(config_path, cfg) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.add_clustering_buttons() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+819
to
+820
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Initialize an empty canvas onto which to draw the images | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.fig = Figure(tight_layout=True, dpi=100) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.fig.patch.set_facecolor("None") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.ax = self.fig.add_subplot(111) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.ax.invert_yaxis() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.ax.set_axis_off() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._im = None | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._scatter = self.ax.scatter([], []) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.canvas = FigureCanvas(self.fig) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.show() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def add_clustering_buttons(self): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| layout = QHBoxLayout() | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| btn_cluster = QPushButton('cluster pose', self) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| btn_cluster.clicked.connect(self.on_click) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| btn_show = QPushButton('show img', self) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| btn_show.clicked.connect(self.on_click_show_img) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| btn_close = QPushButton('close img', self) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| btn_close.clicked.connect(self.on_click_close_img) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| layout.addWidget(btn_cluster) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| layout.addWidget(btn_show) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| layout.addWidget(btn_close) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._layout.addLayout(layout) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _show_clusters(self, input_): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| points, names = input_ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| points[:, [0, 1]] = points[:, [1, 0]] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| colors = points[:, 2] + 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dict_prop_points = {'colorn': colors, 'frame': names} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clust_layer = self.viewer.add_points( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| points[:, :2], | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| size=8, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name='cluster', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| features=dict_prop_points, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| face_color='colorn', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| face_colormap='plasma', | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| clust_layer.mode = 'select' | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer.window.add_dock_widget(self.canvas, name='frames') | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self.viewer.layers[0].visible = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._df = pd.read_hdf(self.viewer.layers[0].source.path) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._df.index = ['/'.join(row) for row in list(self._df.index)] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._df.index = ['/'.join(row) for row in list(self._df.index)] | |
| # Normalize frame index to OS-appropriate path strings. | |
| self._df.index = [ | |
| to_os_dir_sep(os.path.join(*idx)) if isinstance(idx, tuple) else to_os_dir_sep(idx) | |
| for idx in self._df.index | |
| ] |
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Image_ is used to open images, but it’s not imported anywhere in this module. This will raise NameError the first time a cluster point is selected. Please import PIL.Image (or whichever image loader is intended) and ensure it’s included in dependencies if not already present.
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These handlers assume specific layer ordering (layers[0] is the source Points layer and layers[1] is the cluster layer) and use layers.remove('image refine label'), which likely won’t work because LayersList.remove typically expects a Layer instance (not a name). This is brittle if users have other layers open, or if the new image layer isn’t present. Please store explicit references to the created layers (cluster layer + refine image layer) and show/hide/remove them via those references (or by name indexing like del viewer.layers[name]).
| self.viewer.layers[0].visible = True | |
| self.viewer.layers[1].visible = False | |
| self.viewer.dims.set_current_step(0, self.step) | |
| self.viewer.add_image(self._im.get_array(), name='image refine label') | |
| self.viewer.layers.move_selected(0, 2) | |
| def on_click_close_img(self): | |
| self.viewer.layers.remove('image refine label') | |
| self.viewer.layers.move_selected(0, 1) | |
| self.viewer.layers[0].visible = False | |
| self.viewer.layers[1].visible = True | |
| # Lazily determine and cache source and cluster layers to avoid relying | |
| # on fixed layer positions in the viewer. | |
| source_layer = getattr(self, "_source_points_layer", None) | |
| cluster_layer = getattr(self, "_cluster_points_layer", None) | |
| if source_layer is None or cluster_layer is None: | |
| layers_list = list(self.viewer.layers) | |
| if len(layers_list) < 2: | |
| # Not enough layers to determine source/cluster; abort safely. | |
| return | |
| source_layer = layers_list[0] | |
| cluster_layer = layers_list[1] | |
| self._source_points_layer = source_layer | |
| self._cluster_points_layer = cluster_layer | |
| source_layer.visible = True | |
| cluster_layer.visible = False | |
| self.viewer.dims.set_current_step(0, self.step) | |
| # Store a reference to the refine image layer so it can be removed safely. | |
| refine_layer = self.viewer.add_image( | |
| self._im.get_array(), name='image refine label' | |
| ) | |
| self._refine_image_layer = refine_layer | |
| self.viewer.layers.move_selected(0, 2) | |
| def on_click_close_img(self): | |
| # Safely remove the refine image layer if it exists. | |
| refine_layer = getattr(self, "_refine_image_layer", None) | |
| if refine_layer is not None and refine_layer in self.viewer.layers: | |
| self.viewer.layers.remove(refine_layer) | |
| self._refine_image_layer = None | |
| self.viewer.layers.move_selected(0, 1) | |
| source_layer = getattr(self, "_source_points_layer", None) | |
| cluster_layer = getattr(self, "_cluster_points_layer", None) | |
| if source_layer is not None: | |
| source_layer.visible = False | |
| if cluster_layer is not None: | |
| cluster_layer.visible = True |
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filtering inserted layers via any(s in str(layer) ...) is unreliable: str(layer) isn’t a stable API and may match unrelated layers, causing metadata propagation and store setup to be skipped unexpectedly. If this guard is needed, it should check explicit layer attributes (e.g., layer.name against exact names) or use a dedicated flag on layers created by this widget.
| # FIXME Is the following necessary? | |
| if any(s in str(layer) for s in ('cluster', 'refine')): | |
| # Skip auxiliary layers created by this widget (e.g. clustering/refinement results) | |
| layer_name = getattr(layer, "name", "") | |
| if isinstance(layer, Points) and layer_name in ("cluster", "refine"): |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,45 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import pandas as pd | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from scipy.spatial.distance import pdist | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from sklearn.cluster import DBSCAN | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from sklearn.decomposition import PCA | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+5
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari_deeplabcut._writer import _conv_layer_to_df | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from napari_deeplabcut.misc import DLCHeader | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+6
to
+7
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def _cluster(data): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pca = PCA(n_components=2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| principalComponents = pca.fit_transform(data) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # putting components in a dataframe for later | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| PCA_components = pd.DataFrame(principalComponents) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dbscan=DBSCAN(eps=9.7, min_samples=20, algorithm='ball_tree', metric='minkowski', leaf_size=90, p=2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # fit - perform DBSCAN clustering from features, or distance matrix. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dbscan = dbscan.fit(PCA_components) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| cluster1 = dbscan.labels_ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+4
to
+21
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from sklearn.cluster import DBSCAN | |
| from sklearn.decomposition import PCA | |
| from napari_deeplabcut._writer import _conv_layer_to_df | |
| from napari_deeplabcut.misc import DLCHeader | |
| def _cluster(data): | |
| pca = PCA(n_components=2) | |
| principalComponents = pca.fit_transform(data) | |
| # putting components in a dataframe for later | |
| PCA_components = pd.DataFrame(principalComponents) | |
| dbscan=DBSCAN(eps=9.7, min_samples=20, algorithm='ball_tree', metric='minkowski', leaf_size=90, p=2) | |
| # fit - perform DBSCAN clustering from features, or distance matrix. | |
| dbscan = dbscan.fit(PCA_components) | |
| cluster1 = dbscan.labels_ | |
| from sklearn.cluster import KMeans | |
| from sklearn.decomposition import PCA | |
| from napari_deeplabcut._writer import _conv_layer_to_df | |
| from napari_deeplabcut.misc import DLCHeader | |
| def _cluster(data, n_clusters: int = 8): | |
| pca = PCA(n_components=2) | |
| principalComponents = pca.fit_transform(data) | |
| # putting components in a dataframe for later | |
| PCA_components = pd.DataFrame(principalComponents) | |
| kmeans = KMeans(n_clusters=n_clusters, random_state=0) | |
| # fit - perform k-means clustering from features. | |
| kmeans = kmeans.fit(PCA_components) | |
| cluster1 = kmeans.labels_ |
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
df.index = ['/'.join(row) for row in df.index] assumes each index entry is an iterable of path parts and forces POSIX separators. If the index entries are already strings (common after read_hdf) this will join characters, and on Windows it won’t match the os.path.join paths used elsewhere. Use to_os_dir_sep() / Path normalization and only join when dealing with tuples/MultiIndex entries.
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
cluster_data introduces non-trivial data reshaping and clustering logic but currently has no tests. Please add unit tests for expected shapes/labels (including noise label -1 from DBSCAN) using a small synthetic Points-layer-like input, similar to existing pytest coverage in src/napari_deeplabcut/_tests.
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,15 +1,24 @@ | ||||||
| from __future__ import annotations | ||||||
|
|
||||||
| import os | ||||||
| import re | ||||||
| from enum import Enum, EnumMeta | ||||||
| from itertools import cycle | ||||||
| from pathlib import Path | ||||||
| from typing import Dict, List, Optional, Sequence, Tuple, Union | ||||||
|
|
||||||
| import numpy as np | ||||||
| import pandas as pd | ||||||
| from napari.utils import colormaps | ||||||
|
|
||||||
|
|
||||||
| def find_project_name(s): | ||||||
| pat = re.compile('.+-.+-\d{4}-\d{1,2}-\d{1,2}') | ||||||
|
||||||
| pat = re.compile('.+-.+-\d{4}-\d{1,2}-\d{1,2}') | |
| pat = re.compile(r'.+-.+-\d{4}-\d{1,2}-\d{1,2}') |
Copilot
AI
Mar 27, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
New helper find_project_name is used by the clustering workflow but has no test coverage. Given the path parsing/regex sensitivity across OSes, please add unit tests (e.g., POSIX + Windows style paths, and a case where no match is found) alongside existing test_misc.py coverage.
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -26,6 +26,7 @@ contributions: | |||||||||||||||||||
| - id: napari-deeplabcut.make_keypoint_controls | ||||||||||||||||||||
| python_name: napari_deeplabcut._widgets:KeypointControls | ||||||||||||||||||||
| title: Make keypoint controls | ||||||||||||||||||||
|
|
||||||||||||||||||||
| readers: | ||||||||||||||||||||
| - command: napari-deeplabcut.get_hdf_reader | ||||||||||||||||||||
| accepts_directories: false | ||||||||||||||||||||
|
|
@@ -42,6 +43,7 @@ contributions: | |||||||||||||||||||
| - command: napari-deeplabcut.get_folder_parser | ||||||||||||||||||||
| accepts_directories: true | ||||||||||||||||||||
| filename_patterns: ['*'] | ||||||||||||||||||||
|
|
||||||||||||||||||||
| writers: | ||||||||||||||||||||
| - command: napari-deeplabcut.write_hdf | ||||||||||||||||||||
| layer_types: ["points{1}"] | ||||||||||||||||||||
|
|
@@ -52,3 +54,10 @@ contributions: | |||||||||||||||||||
| widgets: | ||||||||||||||||||||
| - command: napari-deeplabcut.make_keypoint_controls | ||||||||||||||||||||
| display_name: Keypoint controls | ||||||||||||||||||||
| kmeans: | ||||||||||||||||||||
| - command: napari-deeplabcut.get_hdf_reader1 | ||||||||||||||||||||
| accepts_directories: false | ||||||||||||||||||||
| filename_patterns: ['*.h5'] | ||||||||||||||||||||
| - command: napari-deeplabcut.get_folder_parser1 | ||||||||||||||||||||
| accepts_directories: true | ||||||||||||||||||||
| filename_patterns: ['*'] | ||||||||||||||||||||
|
Comment on lines
56
to
+63
|
||||||||||||||||||||
| display_name: Keypoint controls | |
| kmeans: | |
| - command: napari-deeplabcut.get_hdf_reader1 | |
| accepts_directories: false | |
| filename_patterns: ['*.h5'] | |
| - command: napari-deeplabcut.get_folder_parser1 | |
| accepts_directories: true | |
| filename_patterns: ['*'] | |
| display_name: Keypoint controls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This new workflow step is a single long run-on paragraph with several unclear instructions (e.g., “ctl s”) and inconsistent button naming vs the UI labels (“cluster pose”, “show img”, “close img”). Please rewrite for clarity (short steps, consistent button names, and correct shortcut notation like “Ctrl+S” / “Cmd+S”).