Skip to content

Commit 53d5275

Browse files
authored
Merge pull request #420 from BiAPoL/add-context-menu-to-plotter-for-layer-export
Add option to plotter for copying selected objects to a new layer
2 parents d2c94fa + cf870d8 commit 53d5275

15 files changed

+613
-30
lines changed
25.9 KB
Loading
4.3 MB
Loading
29.4 KB
Loading
5.98 KB
Loading
-272 Bytes
Loading
5.68 KB
Loading

docs/usage/overview.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,9 @@ In combination with the dimensionality reduction and clustering algorithms, this
3838
The drawing of the plotted data is triggered whenever the data in the `layer.features` attribute has been updated. This gives trise to a few cool use cases of the clusters plotter. One of them is the simple generation of feature maps directly on top of an existing layer:
3939

4040
![Feature map generation](./imgs/features_immediacy1.gif)
41+
42+
## Copy selected objects to new layers
43+
44+
In case you ever found yourself in the situation that you wanted to process or inspect an individual object from a layer separately from all the other objects without tedious image processing to pick out *just the right object*, you can now do this in the clusters plotter. Simply draw a selection in the canvas (using the `MANUAL_CLUSTER_ID`) as hue column or select any other categorical feature. Then use the cluster selector on the top of the canvas to select the object class you'd want to export and click the `Add current class as new layer button`!
45+
46+
![Add selected objects to new layer](./imgs/copy_export_objects_function.gif)

docs/usage/plotter_widget.md

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,17 @@ The core functionality of the plugin is available to you directly upon opening i
2222
5. Canvas: Features will be visualized here
2323
6. What feature to plot on the x-axis and y-axis, respectively. The `Hue` dropdown controls the coloring of the data on the canvas, categorical features are highlighted in orange.
2424
7. Reset button: Resets all drawn clusters
25+
8. Copy button: Copies the currently selected objects according to selector **3** to a new layer. All respective features will be copied over to the new layer accordingly.
2526

2627
![Advanced settings](./imgs/plotter_overview3_annotated.png)
2728

2829
Under the `Advanced Options` tab, you have access to some more customization options for the visualization:
2930

30-
8. Change the colormap for the chosen overlay color. If a layer is colored by a non-categorical feature, this determines the colormap for the depiction. Only enabled if a non-categorical feature is selected in the `Hue` dropdown.
31-
9. Apply a log-scale to the chosen feature
32-
10. Switch plot type between `SCATTER` and `HISTOGRAM2D`
33-
11. Colorap for 2D histogram (only enabled if `HISTOGRAM2D` is selected in 10.)
34-
12. Change the size of the bins (only enabled if `HISTOGRAM2D` is selected in 10.)
31+
9. Change the colormap for the chosen overlay color. If a layer is colored by a non-categorical feature, this determines the colormap for the depiction. Only enabled if a non-categorical feature is selected in the `Hue` dropdown.
32+
10. Apply a log-scale to the chosen feature
33+
11. Switch plot type between `SCATTER` and `HISTOGRAM2D`
34+
12. Colorap for 2D histogram (only enabled if `HISTOGRAM2D` is selected in 10.)
35+
13. Change the size of the bins (only enabled if `HISTOGRAM2D` is selected in 10.)
3536

3637
## Visualizing layer features
3738

@@ -69,3 +70,27 @@ There are two things to keep in mind here:
6970
So far, the `Hue` selector was always set to `MANUAL_CLUSTER_ID`, which is by design a *categorical* column. However, the napari-clusters-plotter supports visualizing any feature as the `Hue` parameter. If this is done, the points are colored according to the selected feature and each point's color will be project on the respective object in the napari viewport. In essence, this creates feature maps for each feature you select:
7071

7172
![Selecting features as hue](./imgs/selecting_features1.gif)
73+
74+
75+
## Adding objects to new layer
76+
(widget:plotter:add_objects_to_layer)=
77+
78+
If you want to copy selected objects to a new layer, you can use the `Add current class to new layer` button in the Plotter Widget. To use it, make sure you have a *categorical feature* selected in the `Hue` dropdown selector (i.e., `MANUAL_CLUSTER_ID`, which is generated upon drawing a selection). However, resulting cluster ids from a clustering operation (see [Clustering Widget](widget:clustering)) can also be used here. Generally, categorical features are highlighted in orange in the `Hue` dropdown selector:
79+
80+
![Categorical features in hue selector](./imgs//categorical_features.png)
81+
82+
Then, select the cluster id you want to copy to a new layer in the `Selected cluster index` selector (see above):
83+
84+
![Selecting cluster id](./imgs/selecting_cluster_id.png)
85+
86+
Finally, click the `Add current class as new layer` button.
87+
88+
![Adding objects to a new layer](./imgs/copy_export_objects_function.gif)
89+
90+
```{note}
91+
It is possible to export all *unselected objects* to a new layer, too. To do so, simply select zero as the `Selected cluster index`. This will copy all objects that are not part of any selection to a new layer.
92+
```
93+
94+
```{note}
95+
Selecting a cluster id that is not present in the `Hue` column will result in nothing happening.
96+
```
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "code",
5+
"execution_count": 12,
6+
"id": "e28ea1eb",
7+
"metadata": {},
8+
"outputs": [],
9+
"source": [
10+
"import napari\n",
11+
"from napari.layers import Points\n",
12+
"import numpy as np\n",
13+
"from napari_clusters_plotter._new_plotter_widget import _export_cluster_to_layer, PlotterWidget"
14+
]
15+
},
16+
{
17+
"cell_type": "code",
18+
"execution_count": 9,
19+
"id": "c8eef9bc",
20+
"metadata": {},
21+
"outputs": [],
22+
"source": [
23+
"viewer = napari.Viewer(ndisplay=3)"
24+
]
25+
},
26+
{
27+
"cell_type": "code",
28+
"execution_count": 13,
29+
"id": "57b5ae7a",
30+
"metadata": {},
31+
"outputs": [],
32+
"source": [
33+
"def create_multi_point_layer(n_samples: int = 100):\n",
34+
" import pandas as pd\n",
35+
"\n",
36+
" loc = 5\n",
37+
" n_timeframes = 5\n",
38+
" frame = np.arange(n_timeframes).repeat(n_samples // n_timeframes)\n",
39+
" # make some random points with random features\n",
40+
" points = np.random.random((n_samples, 4))\n",
41+
" points2 = np.random.random((n_samples - 1, 4))\n",
42+
"\n",
43+
" points[:, 0] = frame\n",
44+
" points2[:, 0] = frame[:-1]\n",
45+
"\n",
46+
" features = pd.DataFrame(\n",
47+
" {\n",
48+
" \"frame\": frame,\n",
49+
" \"feature1\": np.random.normal(size=n_samples, loc=loc),\n",
50+
" \"feature2\": np.random.normal(size=n_samples, loc=loc),\n",
51+
" \"feature3\": np.random.normal(size=n_samples, loc=loc),\n",
52+
" \"feature4\": np.random.normal(size=n_samples, loc=loc),\n",
53+
" }\n",
54+
" )\n",
55+
"\n",
56+
" features2 = pd.DataFrame(\n",
57+
" {\n",
58+
" \"frame\": frame[:-1],\n",
59+
" \"feature2\": np.random.normal(size=n_samples - 1, loc=-loc),\n",
60+
" \"feature3\": np.random.normal(size=n_samples - 1, loc=-loc),\n",
61+
" \"feature4\": np.random.normal(size=n_samples - 1, loc=-loc),\n",
62+
" }\n",
63+
" )\n",
64+
"\n",
65+
" layer = Points(\n",
66+
" points, features=features, size=0.1, blending=\"translucent_no_depth\"\n",
67+
" )\n",
68+
" layer2 = Points(\n",
69+
" points2,\n",
70+
" features=features2,\n",
71+
" size=0.1,\n",
72+
" translate=(0, 0, 2),\n",
73+
" blending=\"translucent_no_depth\",\n",
74+
" )\n",
75+
"\n",
76+
" return layer, layer2\n",
77+
"\n",
78+
"\n",
79+
"def create_multi_tracks_layer(n_samples: int = 100):\n",
80+
" from napari.layers import Tracks\n",
81+
"\n",
82+
" points1, points2 = create_multi_point_layer(n_samples=n_samples)\n",
83+
"\n",
84+
" tracks1 = points1.data.copy()\n",
85+
" tracks2 = points2.data.copy()\n",
86+
"\n",
87+
" # insert empty track id column\n",
88+
" tracks1 = np.insert(tracks1, 0, 0, axis=1)\n",
89+
" tracks2 = np.insert(tracks2, 0, 0, axis=1)\n",
90+
"\n",
91+
" for t in range(int(points1.data[:, 0].max() + 1)):\n",
92+
" # set the track id for each point\n",
93+
" tracks1[tracks1[:, 1] == t, 0] = np.arange(\n",
94+
" len(tracks1[tracks1[:, 1] == t]), dtype=int\n",
95+
" )\n",
96+
"\n",
97+
" for t in range(int(points2.data[:, 0].max() + 1)):\n",
98+
" # set the track id for each point\n",
99+
" tracks2[tracks2[:, 1] == t, 0] = np.arange(\n",
100+
" len(tracks2[tracks2[:, 1] == t]), dtype=int\n",
101+
" )\n",
102+
"\n",
103+
" tracks1 = Tracks(tracks1, features=points1.features, name=\"tracks1\")\n",
104+
" tracks2 = Tracks(\n",
105+
" tracks2, features=points2.features, name=\"tracks2\", translate=(0, 0, 2)\n",
106+
" )\n",
107+
"\n",
108+
" return tracks1, tracks2"
109+
]
110+
},
111+
{
112+
"cell_type": "code",
113+
"execution_count": 14,
114+
"id": "73f9b34c",
115+
"metadata": {},
116+
"outputs": [],
117+
"source": [
118+
"layer1, layer2 = create_multi_tracks_layer()\n",
119+
"viewer.add_layer(layer1)\n",
120+
"viewer.add_layer(layer2)\n",
121+
"viewer.layers.select_all()"
122+
]
123+
},
124+
{
125+
"cell_type": "code",
126+
"execution_count": 15,
127+
"id": "1909214b",
128+
"metadata": {},
129+
"outputs": [
130+
{
131+
"name": "stderr",
132+
"output_type": "stream",
133+
"text": [
134+
"c:\\Users\\Johan\\mambaforge\\envs\\clusters-plotter\\Lib\\site-packages\\biaplotter\\colormap.py:34: UserWarning: Categorical colormap detected. Setting categorical=True. If the colormap is continuous, set categorical=False explicitly.\n",
135+
" warnings.warn(\n"
136+
]
137+
},
138+
{
139+
"data": {
140+
"text/plain": [
141+
"<napari._qt.widgets.qt_viewer_dock_widget.QtViewerDockWidget at 0x1b685722dd0>"
142+
]
143+
},
144+
"execution_count": 15,
145+
"metadata": {},
146+
"output_type": "execute_result"
147+
}
148+
],
149+
"source": [
150+
"n_layers = len(viewer.layers)\n",
151+
"\n",
152+
"widget = PlotterWidget(viewer)\n",
153+
"viewer.window.add_dock_widget(widget, area=\"right\")"
154+
]
155+
},
156+
{
157+
"cell_type": "code",
158+
"execution_count": null,
159+
"id": "c74f2128",
160+
"metadata": {},
161+
"outputs": [],
162+
"source": [
163+
"for layer in viewer.layers:\n",
164+
" if type(layer) in widget.input_layer_types:\n",
165+
" features = layer.features\n",
166+
" features['MANUAL_CLUSTER_ID'] = np.random.randint(low=0, high=2, size=len(features))\n",
167+
" layer.features = features\n",
168+
"\n",
169+
"widget.plot_needs_update.emit()"
170+
]
171+
},
172+
{
173+
"cell_type": "code",
174+
"execution_count": null,
175+
"id": "0d2d5fd4",
176+
"metadata": {},
177+
"outputs": [],
178+
"source": [
179+
"widget._on_export_clusters()"
180+
]
181+
},
182+
{
183+
"cell_type": "code",
184+
"execution_count": 20,
185+
"id": "7dda611d",
186+
"metadata": {},
187+
"outputs": [
188+
{
189+
"data": {
190+
"text/plain": [
191+
"(100, 5)"
192+
]
193+
},
194+
"execution_count": 20,
195+
"metadata": {},
196+
"output_type": "execute_result"
197+
}
198+
],
199+
"source": [
200+
"viewer.layers[0].data.shape"
201+
]
202+
},
203+
{
204+
"cell_type": "code",
205+
"execution_count": null,
206+
"id": "8751cb4a",
207+
"metadata": {},
208+
"outputs": [],
209+
"source": []
210+
}
211+
],
212+
"metadata": {
213+
"kernelspec": {
214+
"display_name": "clusters-plotter",
215+
"language": "python",
216+
"name": "python3"
217+
},
218+
"language_info": {
219+
"codemirror_mode": {
220+
"name": "ipython",
221+
"version": 3
222+
},
223+
"file_extension": ".py",
224+
"mimetype": "text/x-python",
225+
"name": "python",
226+
"nbconvert_exporter": "python",
227+
"pygments_lexer": "ipython3",
228+
"version": "3.11.12"
229+
}
230+
},
231+
"nbformat": 4,
232+
"nbformat_minor": 5
233+
}

src/napari_clusters_plotter/_algorithm_widget.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ def _get_features(self):
3939
_features = layer.features[self.common_columns].copy()
4040

4141
# Add layer name as a categorical column
42-
_features["layer"] = layer.name
42+
_features["layer"] = layer.unique_id
4343
_features["layer"] = _features["layer"].astype("category")
4444
features = pd.concat([features, _features], axis=0)
4545

0 commit comments

Comments
 (0)