napari-musa 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- napari_musa/Widget_DataManager.py +864 -0
- napari_musa/Widgets_DataVisualization.py +257 -0
- napari_musa/Widgets_EndmembersExtraction.py +382 -0
- napari_musa/Widgets_Fusion.py +458 -0
- napari_musa/Widgets_NMF.py +265 -0
- napari_musa/Widgets_PCA.py +212 -0
- napari_musa/Widgets_UMAP.py +463 -0
- napari_musa/_version.py +34 -0
- napari_musa/main.py +150 -0
- napari_musa/modules/D_illuminants.mat +0 -0
- napari_musa/modules/data.py +48 -0
- napari_musa/modules/functions.py +1331 -0
- napari_musa/modules/plot.py +581 -0
- napari_musa/napari.yaml +15 -0
- napari_musa-1.0.0.dist-info/METADATA +156 -0
- napari_musa-1.0.0.dist-info/RECORD +20 -0
- napari_musa-1.0.0.dist-info/WHEEL +5 -0
- napari_musa-1.0.0.dist-info/entry_points.txt +2 -0
- napari_musa-1.0.0.dist-info/licenses/LICENSE +28 -0
- napari_musa-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
""" """
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from os.path import dirname
|
|
5
|
+
|
|
6
|
+
sys.path.append(dirname(dirname(__file__)))
|
|
7
|
+
import napari
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pyqtgraph as pg
|
|
10
|
+
from magicgui.widgets import (
|
|
11
|
+
CheckBox,
|
|
12
|
+
ComboBox,
|
|
13
|
+
Container,
|
|
14
|
+
FloatSpinBox,
|
|
15
|
+
PushButton,
|
|
16
|
+
SpinBox,
|
|
17
|
+
)
|
|
18
|
+
from matplotlib.backends.backend_qt5agg import (
|
|
19
|
+
FigureCanvasQTAgg as FigureCanvas,
|
|
20
|
+
)
|
|
21
|
+
from matplotlib.backends.backend_qt5agg import (
|
|
22
|
+
NavigationToolbar2QT as NavigationToolbar,
|
|
23
|
+
)
|
|
24
|
+
from matplotlib.figure import Figure
|
|
25
|
+
from napari.utils.notifications import show_info, show_warning
|
|
26
|
+
from qtpy.QtWidgets import (
|
|
27
|
+
QGroupBox,
|
|
28
|
+
QHBoxLayout,
|
|
29
|
+
QScrollArea,
|
|
30
|
+
QVBoxLayout,
|
|
31
|
+
QWidget,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from napari_musa.modules.functions import (
|
|
35
|
+
RGB_to_hex,
|
|
36
|
+
UMAP_analysis,
|
|
37
|
+
reduce_spatial_dimension_dwt_inverse,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class UMAP(QWidget):
|
|
42
|
+
""" """
|
|
43
|
+
|
|
44
|
+
def __init__(self, viewer: napari.Viewer, data, plot):
|
|
45
|
+
""" """
|
|
46
|
+
super().__init__()
|
|
47
|
+
self.viewer = viewer
|
|
48
|
+
self.data = data
|
|
49
|
+
self.plot = plot
|
|
50
|
+
# Configure the scroll area
|
|
51
|
+
scroll = QScrollArea()
|
|
52
|
+
scroll.setWidgetResizable(True)
|
|
53
|
+
content_widget = QWidget() # Container widget
|
|
54
|
+
content_layout = QVBoxLayout(
|
|
55
|
+
content_widget
|
|
56
|
+
) # Vertical layout: organize widgets from top to bottom
|
|
57
|
+
# Configure UI
|
|
58
|
+
self.createUI(
|
|
59
|
+
content_layout
|
|
60
|
+
) # The function to create the UI that fills the content_layout
|
|
61
|
+
# Configure principal layout
|
|
62
|
+
scroll.setWidget(
|
|
63
|
+
content_widget
|
|
64
|
+
) # content_widget is a content of scroll area
|
|
65
|
+
main_layout = QVBoxLayout(self)
|
|
66
|
+
main_layout.addWidget(scroll)
|
|
67
|
+
self.setLayout(main_layout)
|
|
68
|
+
|
|
69
|
+
def createUI(self, layout):
|
|
70
|
+
"""Create the components for the UI"""
|
|
71
|
+
layout.addWidget(self.create_umap_controls_box())
|
|
72
|
+
layout.addWidget(self.creare_scatterplot_box())
|
|
73
|
+
layout.addWidget(self.creare_plot_box())
|
|
74
|
+
layout.addWidget(self.create_inverse_reduction_box())
|
|
75
|
+
|
|
76
|
+
# %% Creation of UI boxes
|
|
77
|
+
|
|
78
|
+
def create_umap_controls_box(self):
|
|
79
|
+
""" """
|
|
80
|
+
controls_box = QGroupBox("UMAP Parameters")
|
|
81
|
+
# rgb_box.setFixedHeight(200)
|
|
82
|
+
controls_layout = QVBoxLayout()
|
|
83
|
+
controls_layout.addSpacing(10)
|
|
84
|
+
# Elements
|
|
85
|
+
row1 = QHBoxLayout()
|
|
86
|
+
self.reduced_dataset = CheckBox(text="Reduced dataset")
|
|
87
|
+
self.masked_dataset = CheckBox(text="Masked dataset")
|
|
88
|
+
self.modes_combobox = ComboBox(
|
|
89
|
+
choices=self.data.modes, label="Select the imaging mode"
|
|
90
|
+
)
|
|
91
|
+
row1.addWidget(self.reduced_dataset.native)
|
|
92
|
+
row1.addWidget(self.modes_combobox.native)
|
|
93
|
+
controls_layout.addLayout(row1)
|
|
94
|
+
|
|
95
|
+
row2 = QHBoxLayout()
|
|
96
|
+
row2.addWidget(self.masked_dataset.native)
|
|
97
|
+
controls_layout.addLayout(row2)
|
|
98
|
+
|
|
99
|
+
self.downsampling_spinbox = SpinBox(
|
|
100
|
+
min=1, max=6, value=1, step=1, name="Downsampling"
|
|
101
|
+
)
|
|
102
|
+
self.metric_dropdown = ComboBox(
|
|
103
|
+
choices=[
|
|
104
|
+
"cosine",
|
|
105
|
+
"euclidean",
|
|
106
|
+
"correlation",
|
|
107
|
+
"mahalanobis",
|
|
108
|
+
"seuclidean ",
|
|
109
|
+
"braycurtis",
|
|
110
|
+
],
|
|
111
|
+
label="Select the metric",
|
|
112
|
+
)
|
|
113
|
+
self.n_neighbors_spinbox = SpinBox(
|
|
114
|
+
min=5, max=500, value=20, step=5, name="N Neighbours"
|
|
115
|
+
)
|
|
116
|
+
self.min_dist_spinbox = FloatSpinBox(
|
|
117
|
+
min=0.0, max=1.0, value=0.0, step=0.1, name="Min dist"
|
|
118
|
+
)
|
|
119
|
+
self.spread_spinbox = FloatSpinBox(
|
|
120
|
+
min=1.0, max=3.0, value=1.0, step=0.1, name="Spread"
|
|
121
|
+
)
|
|
122
|
+
self.init_dropdown = ComboBox(
|
|
123
|
+
choices=["spectral", "pca", "tswspectral"], label="Init"
|
|
124
|
+
)
|
|
125
|
+
self.densmap = CheckBox(text="Densmap")
|
|
126
|
+
controls_layout.addWidget(
|
|
127
|
+
Container(
|
|
128
|
+
widgets=[
|
|
129
|
+
self.downsampling_spinbox,
|
|
130
|
+
self.metric_dropdown,
|
|
131
|
+
self.n_neighbors_spinbox,
|
|
132
|
+
self.min_dist_spinbox,
|
|
133
|
+
self.spread_spinbox,
|
|
134
|
+
self.init_dropdown,
|
|
135
|
+
self.densmap,
|
|
136
|
+
]
|
|
137
|
+
).native
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
run_btn = PushButton(text="Run UMAP")
|
|
141
|
+
run_btn.clicked.connect(self.run_umap)
|
|
142
|
+
controls_layout.addWidget(run_btn.native)
|
|
143
|
+
|
|
144
|
+
UMAP_loyout_perform = QHBoxLayout()
|
|
145
|
+
self.UMAP_colorRGB = CheckBox(text="Scatterplot with True RGB")
|
|
146
|
+
show_btn = PushButton(text="Show UMAP scatterplot")
|
|
147
|
+
show_btn.clicked.connect(self.show_umap_scatter)
|
|
148
|
+
UMAP_loyout_perform.addWidget(
|
|
149
|
+
Container(
|
|
150
|
+
widgets=[self.UMAP_colorRGB, show_btn], layout="horizontal"
|
|
151
|
+
).native
|
|
152
|
+
)
|
|
153
|
+
controls_layout.addLayout(UMAP_loyout_perform)
|
|
154
|
+
controls_box.setLayout(controls_layout)
|
|
155
|
+
return controls_box
|
|
156
|
+
|
|
157
|
+
def creare_scatterplot_box(self):
|
|
158
|
+
""" """
|
|
159
|
+
scatterplot_box = QGroupBox("UMAP Scatterplot")
|
|
160
|
+
layout = QVBoxLayout()
|
|
161
|
+
layout.addSpacing(10)
|
|
162
|
+
self.umap_plot = pg.PlotWidget()
|
|
163
|
+
self.plot.setup_scatterplot(self.umap_plot)
|
|
164
|
+
# Add control buttons for scatter plot interaction
|
|
165
|
+
btn_layout = QHBoxLayout()
|
|
166
|
+
for icon, func in [
|
|
167
|
+
("fa5s.home", lambda: self.umap_plot.getViewBox().autoRange()),
|
|
168
|
+
(
|
|
169
|
+
"fa5s.draw-polygon",
|
|
170
|
+
lambda: self.plot.polygon_selection(self.umap_plot),
|
|
171
|
+
),
|
|
172
|
+
("ri.add-box-fill", self.handle_selection),
|
|
173
|
+
(
|
|
174
|
+
"mdi6.image-edit",
|
|
175
|
+
lambda: self.plot.save_image_button(self.umap_plot),
|
|
176
|
+
),
|
|
177
|
+
]:
|
|
178
|
+
btn = self.plot.create_button(icon)
|
|
179
|
+
btn.clicked.connect(func)
|
|
180
|
+
btn_layout.addWidget(btn)
|
|
181
|
+
self.point_size = SpinBox(
|
|
182
|
+
min=1, max=100, value=1, step=1, name="Point size"
|
|
183
|
+
)
|
|
184
|
+
show_areas_on_scatterplot_btn = PushButton(
|
|
185
|
+
text="Show areas of Label Layer on scatteplot"
|
|
186
|
+
)
|
|
187
|
+
show_areas_on_scatterplot_btn.clicked.connect(
|
|
188
|
+
self.show_areas_on_scatterplot_btn_f
|
|
189
|
+
)
|
|
190
|
+
btn_layout.addSpacing(30)
|
|
191
|
+
btn_layout.addWidget(Container(widgets=[self.point_size]).native)
|
|
192
|
+
layout.addLayout(btn_layout)
|
|
193
|
+
layout.addWidget(self.umap_plot)
|
|
194
|
+
layout.addWidget(show_areas_on_scatterplot_btn.native)
|
|
195
|
+
scatterplot_box.setLayout(layout)
|
|
196
|
+
return scatterplot_box
|
|
197
|
+
|
|
198
|
+
def creare_plot_box(self):
|
|
199
|
+
""" """
|
|
200
|
+
meanplot_box = QGroupBox("Mean plot")
|
|
201
|
+
layout = QVBoxLayout()
|
|
202
|
+
layout.addSpacing(20)
|
|
203
|
+
layout = QVBoxLayout()
|
|
204
|
+
self.mean_plot = FigureCanvas(Figure(figsize=(5, 3)))
|
|
205
|
+
self.mean_plot.setMinimumSize(300, 450)
|
|
206
|
+
self.mean_plot_toolbar = NavigationToolbar(self.mean_plot, self)
|
|
207
|
+
self.plot.customize_toolbar(self.mean_plot_toolbar)
|
|
208
|
+
self.plot.setup_plot(self.mean_plot)
|
|
209
|
+
|
|
210
|
+
mean_spec_btn = PushButton(text="Mean Spectrum")
|
|
211
|
+
self.std_checkbox = CheckBox(text="Plot Std Dev")
|
|
212
|
+
self.norm_checkbox = CheckBox(text="Normalize")
|
|
213
|
+
self.derivative_checkbox = CheckBox(text="Derivative")
|
|
214
|
+
|
|
215
|
+
mean_spec_btn.clicked.connect(
|
|
216
|
+
lambda: self.plot.show_plot(
|
|
217
|
+
self.mean_plot,
|
|
218
|
+
mode=self.modes_combobox.value,
|
|
219
|
+
std_flag=self.std_checkbox.value,
|
|
220
|
+
norm_flag=self.norm_checkbox.value,
|
|
221
|
+
reduced_dataset_flag=self.reduced_dataset.value,
|
|
222
|
+
derivative_flag=self.derivative_checkbox.value,
|
|
223
|
+
)
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
controls = [
|
|
227
|
+
self.std_checkbox,
|
|
228
|
+
self.norm_checkbox,
|
|
229
|
+
self.derivative_checkbox,
|
|
230
|
+
mean_spec_btn,
|
|
231
|
+
]
|
|
232
|
+
layout.addWidget(Container(widgets=controls).native)
|
|
233
|
+
layout.addWidget(self.mean_plot)
|
|
234
|
+
layout.addWidget(self.mean_plot_toolbar)
|
|
235
|
+
|
|
236
|
+
# Export button
|
|
237
|
+
export_btn = PushButton(text="Export spectra")
|
|
238
|
+
export_btn.clicked.connect(
|
|
239
|
+
lambda: (
|
|
240
|
+
self.plot.show_plot(
|
|
241
|
+
self.mean_plot,
|
|
242
|
+
mode=self.modes_combobox.value,
|
|
243
|
+
std_flag=self.std_checkbox.value,
|
|
244
|
+
norm_flag=self.norm_checkbox.value,
|
|
245
|
+
reduced_dataset_flag=self.reduced_dataset.value,
|
|
246
|
+
export_txt_flag=True,
|
|
247
|
+
)
|
|
248
|
+
)
|
|
249
|
+
)
|
|
250
|
+
layout.addWidget(Container(widgets=[export_btn]).native)
|
|
251
|
+
meanplot_box.setLayout(layout)
|
|
252
|
+
return meanplot_box
|
|
253
|
+
|
|
254
|
+
def create_inverse_reduction_box(self):
|
|
255
|
+
""" """
|
|
256
|
+
inverse_reduction_box = QGroupBox("Inverse Dimensionality Reduction")
|
|
257
|
+
# rgb_box.setFixedHeight(200)
|
|
258
|
+
inverse_reduction_layout = QVBoxLayout()
|
|
259
|
+
inverse_reduction_layout.addSpacing(10)
|
|
260
|
+
# Elements
|
|
261
|
+
inverse_reduction_btn = PushButton(text="Perform inverse reduction")
|
|
262
|
+
inverse_reduction_btn.clicked.connect(self.inverse_reduction_btn_f)
|
|
263
|
+
inverse_reduction_layout.addWidget(inverse_reduction_btn.native)
|
|
264
|
+
|
|
265
|
+
inverse_reduction_box.setLayout(inverse_reduction_layout)
|
|
266
|
+
return inverse_reduction_box
|
|
267
|
+
|
|
268
|
+
def run_umap(self):
|
|
269
|
+
"""Perform UMAP"""
|
|
270
|
+
mode = self.modes_combobox.value
|
|
271
|
+
if self.masked_dataset.value:
|
|
272
|
+
dataset = self.data.hypercubes_masked[mode]
|
|
273
|
+
data_reshaped = dataset.reshape(
|
|
274
|
+
dataset.shape[0] * dataset.shape[1], -1
|
|
275
|
+
)
|
|
276
|
+
self.points = np.array(
|
|
277
|
+
np.where(~np.isnan(np.mean(data_reshaped, axis=1)))
|
|
278
|
+
).flatten()
|
|
279
|
+
elif self.reduced_dataset.value:
|
|
280
|
+
dataset = self.data.hypercubes_red[mode]
|
|
281
|
+
self.points = []
|
|
282
|
+
else:
|
|
283
|
+
dataset = self.data.hypercubes[mode]
|
|
284
|
+
self.points = []
|
|
285
|
+
|
|
286
|
+
self.data.umap_maps[mode] = UMAP_analysis(
|
|
287
|
+
dataset,
|
|
288
|
+
downsampling=self.downsampling_spinbox.value,
|
|
289
|
+
points=self.points,
|
|
290
|
+
metric=self.metric_dropdown.value,
|
|
291
|
+
n_neighbors=self.n_neighbors_spinbox.value,
|
|
292
|
+
min_dist=self.min_dist_spinbox.value,
|
|
293
|
+
spread=self.spread_spinbox.value,
|
|
294
|
+
init=self.init_dropdown.value,
|
|
295
|
+
densmap=self.densmap.value,
|
|
296
|
+
random_state=42,
|
|
297
|
+
)
|
|
298
|
+
show_info("UMAP analysis completed!")
|
|
299
|
+
|
|
300
|
+
def show_umap_scatter(self):
|
|
301
|
+
"""Plot UMAP scatter plot"""
|
|
302
|
+
mode = self.modes_combobox.value
|
|
303
|
+
self.umap_data = self.data.umap_maps[mode]
|
|
304
|
+
if self.UMAP_colorRGB.value:
|
|
305
|
+
if self.reduced_dataset.value:
|
|
306
|
+
colors = np.array(RGB_to_hex(self.data.rgb_red[mode])).reshape(
|
|
307
|
+
-1
|
|
308
|
+
)
|
|
309
|
+
elif self.masked_dataset.value:
|
|
310
|
+
colors = np.array(
|
|
311
|
+
RGB_to_hex(self.data.rgb_masked[mode])
|
|
312
|
+
).reshape(-1)
|
|
313
|
+
else:
|
|
314
|
+
colors = np.array(RGB_to_hex(self.data.rgb[mode])).reshape(-1)
|
|
315
|
+
else:
|
|
316
|
+
colors = pg.mkBrush("#262930")
|
|
317
|
+
|
|
318
|
+
print("Colors: \n", colors)
|
|
319
|
+
self.plot.show_scatterplot(
|
|
320
|
+
self.umap_plot,
|
|
321
|
+
self.umap_data,
|
|
322
|
+
colors,
|
|
323
|
+
self.points,
|
|
324
|
+
self.point_size.value,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
def handle_selection(self):
|
|
328
|
+
"""Handle polygon selection and create label layer"""
|
|
329
|
+
mode = self.modes_combobox.value
|
|
330
|
+
if self.reduced_dataset.value and not self.masked_dataset.value:
|
|
331
|
+
dataset = self.data.hypercubes_red[mode]
|
|
332
|
+
self.points = []
|
|
333
|
+
elif self.masked_dataset.value or (
|
|
334
|
+
self.reduced_dataset.value and self.masked_dataset.value
|
|
335
|
+
):
|
|
336
|
+
dataset = self.data.hypercubes_masked[mode]
|
|
337
|
+
data_reshaped = dataset.reshape(
|
|
338
|
+
dataset.shape[0] * dataset.shape[1], -1
|
|
339
|
+
)
|
|
340
|
+
self.points = np.array(
|
|
341
|
+
np.where(~np.isnan(np.mean(data_reshaped, axis=1)))
|
|
342
|
+
).flatten()
|
|
343
|
+
else:
|
|
344
|
+
dataset = self.data.hypercubes[mode]
|
|
345
|
+
self.points = []
|
|
346
|
+
self.plot.show_selected_points(
|
|
347
|
+
self.umap_data,
|
|
348
|
+
dataset,
|
|
349
|
+
mode,
|
|
350
|
+
self.points,
|
|
351
|
+
)
|
|
352
|
+
|
|
353
|
+
def show_areas_on_scatterplot_btn_f(self):
|
|
354
|
+
mode = self.modes_combobox.value
|
|
355
|
+
selected_layer = self.viewer.layers.selection.active
|
|
356
|
+
# Check if the selected layer is a label layer
|
|
357
|
+
if not isinstance(selected_layer, napari.layers.Labels):
|
|
358
|
+
show_warning(
|
|
359
|
+
"⚠️ The selected layer is not a label layer. Please, select a label layer."
|
|
360
|
+
)
|
|
361
|
+
return
|
|
362
|
+
labels_data = selected_layer.data
|
|
363
|
+
if labels_data is None or np.all(
|
|
364
|
+
labels_data == 0
|
|
365
|
+
): # check if all elements are 0
|
|
366
|
+
show_warning("⚠️ The selected label layer is empty")
|
|
367
|
+
return
|
|
368
|
+
# num_classes = int(labels_data.max())
|
|
369
|
+
colormap = np.array(selected_layer.colormap.colors)
|
|
370
|
+
print(colormap)
|
|
371
|
+
print(colormap.shape)
|
|
372
|
+
# colormap[0] = [0, 0, 0, 0.5]
|
|
373
|
+
rgb_image = colormap[labels_data] # fancy indexing
|
|
374
|
+
if self.reduced_dataset.value:
|
|
375
|
+
dataset = self.data.hypercubes_red[mode]
|
|
376
|
+
rgb_image = rgb_image[: dataset.shape[0], : dataset.shape[1], :]
|
|
377
|
+
print(rgb_image)
|
|
378
|
+
print(rgb_image.shape)
|
|
379
|
+
colors = RGB_to_hex(rgb_image)
|
|
380
|
+
|
|
381
|
+
self.plot.show_scatterplot(
|
|
382
|
+
self.umap_plot,
|
|
383
|
+
self.umap_data,
|
|
384
|
+
colors.reshape(-1),
|
|
385
|
+
self.points,
|
|
386
|
+
self.point_size.value,
|
|
387
|
+
)
|
|
388
|
+
|
|
389
|
+
def inverse_reduction_btn_f(self):
|
|
390
|
+
mode = self.modes_combobox.value
|
|
391
|
+
selected_layer = self.viewer.layers.selection.active
|
|
392
|
+
|
|
393
|
+
if isinstance(selected_layer, napari.layers.Labels):
|
|
394
|
+
label_data = selected_layer.data
|
|
395
|
+
reduced_data = self.data.hypercubes_spatial_red[mode]
|
|
396
|
+
print(label_data.shape)
|
|
397
|
+
label_data = label_data[
|
|
398
|
+
: reduced_data.shape[0], : reduced_data.shape[1]
|
|
399
|
+
]
|
|
400
|
+
num_classes = int(label_data.max())
|
|
401
|
+
# colormap = np.array(selected_layer.colormap.colors)
|
|
402
|
+
reduced_data_masked = np.zeros(
|
|
403
|
+
(label_data.shape[0], label_data.shape[1], num_classes)
|
|
404
|
+
)
|
|
405
|
+
print(self.data.hypercubes_spatial_red_params[mode])
|
|
406
|
+
print(self.data.hypercubes_spatial_red_params[mode][0])
|
|
407
|
+
LH = reduced_data_masked
|
|
408
|
+
HL = reduced_data_masked
|
|
409
|
+
HH = reduced_data_masked
|
|
410
|
+
for idx in range(num_classes):
|
|
411
|
+
points = np.where(label_data == idx + 1, 1, 0).astype(float)
|
|
412
|
+
print("Points shape:", points.shape)
|
|
413
|
+
reduced_data_masked[:, :, idx] = (
|
|
414
|
+
np.mean(reduced_data, axis=2) * points
|
|
415
|
+
)
|
|
416
|
+
LH[:, :, idx] = (
|
|
417
|
+
np.mean(
|
|
418
|
+
self.data.hypercubes_spatial_red_params[mode][0],
|
|
419
|
+
axis=2,
|
|
420
|
+
)
|
|
421
|
+
* points
|
|
422
|
+
)
|
|
423
|
+
HL[:, :, idx] = (
|
|
424
|
+
np.mean(
|
|
425
|
+
self.data.hypercubes_spatial_red_params[mode][1],
|
|
426
|
+
axis=2,
|
|
427
|
+
)
|
|
428
|
+
* points
|
|
429
|
+
)
|
|
430
|
+
HH[:, :, idx] = (
|
|
431
|
+
np.mean(
|
|
432
|
+
self.data.hypercubes_spatial_red_params[mode][2],
|
|
433
|
+
axis=2,
|
|
434
|
+
)
|
|
435
|
+
* points
|
|
436
|
+
)
|
|
437
|
+
reconstructed_data_masked = reduce_spatial_dimension_dwt_inverse(
|
|
438
|
+
reduced_data_masked,
|
|
439
|
+
(LH, HL, HH, self.data.hypercubes_spatial_red_params[mode][3]),
|
|
440
|
+
)
|
|
441
|
+
masks = reconstructed_data_masked != 0 # boolean
|
|
442
|
+
H, W, K = masks.shape
|
|
443
|
+
labels_reconstructed = np.zeros((H, W), dtype=np.int32)
|
|
444
|
+
for k in range(
|
|
445
|
+
K
|
|
446
|
+
): # first mask wins on the others, but with UMAP i should not have overlapping areas
|
|
447
|
+
sel = masks[:, :, k] & (labels_reconstructed == 0)
|
|
448
|
+
labels_reconstructed[sel] = k + 1
|
|
449
|
+
lenx, leny, lenwl = self.data.hypercubes[mode].shape
|
|
450
|
+
labels_reconstructed = labels_reconstructed[:lenx, :leny]
|
|
451
|
+
# K = colormap.shape[0] - 1
|
|
452
|
+
# color_dict = {i: colormap[i, :3] for i in range(1, K+1)}
|
|
453
|
+
self.viewer.add_labels(
|
|
454
|
+
labels_reconstructed,
|
|
455
|
+
name=str(mode) + " - Inverse Reduced Selection",
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
else:
|
|
459
|
+
show_warning(
|
|
460
|
+
"⚠️ The selected layer is not a label layer. Please, select an image layer."
|
|
461
|
+
)
|
|
462
|
+
return
|
|
463
|
+
return
|
napari_musa/_version.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"__version__",
|
|
6
|
+
"__version_tuple__",
|
|
7
|
+
"version",
|
|
8
|
+
"version_tuple",
|
|
9
|
+
"__commit_id__",
|
|
10
|
+
"commit_id",
|
|
11
|
+
]
|
|
12
|
+
|
|
13
|
+
TYPE_CHECKING = False
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from typing import Tuple
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
19
|
+
COMMIT_ID = Union[str, None]
|
|
20
|
+
else:
|
|
21
|
+
VERSION_TUPLE = object
|
|
22
|
+
COMMIT_ID = object
|
|
23
|
+
|
|
24
|
+
version: str
|
|
25
|
+
__version__: str
|
|
26
|
+
__version_tuple__: VERSION_TUPLE
|
|
27
|
+
version_tuple: VERSION_TUPLE
|
|
28
|
+
commit_id: COMMIT_ID
|
|
29
|
+
__commit_id__: COMMIT_ID
|
|
30
|
+
|
|
31
|
+
__version__ = version = '1.0.0'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 0, 0)
|
|
33
|
+
|
|
34
|
+
__commit_id__ = commit_id = None
|
napari_musa/main.py
ADDED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
""" """
|
|
2
|
+
|
|
3
|
+
import napari
|
|
4
|
+
import napari as np
|
|
5
|
+
from qtpy.QtCore import QTimer
|
|
6
|
+
|
|
7
|
+
from napari_musa.modules.data import Data
|
|
8
|
+
from napari_musa.modules.plot import Plot
|
|
9
|
+
from napari_musa.Widget_DataManager import DataManager
|
|
10
|
+
from napari_musa.Widgets_DataVisualization import DataVisualization
|
|
11
|
+
from napari_musa.Widgets_EndmembersExtraction import EndmembersExtraction
|
|
12
|
+
from napari_musa.Widgets_Fusion import Fusion
|
|
13
|
+
from napari_musa.Widgets_NMF import NMF
|
|
14
|
+
from napari_musa.Widgets_PCA import PCA
|
|
15
|
+
from napari_musa.Widgets_UMAP import UMAP
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def setup_connections(self):
|
|
19
|
+
""" """
|
|
20
|
+
self.viewer.text_overlay.visible = True
|
|
21
|
+
|
|
22
|
+
def on_step_change(event=None):
|
|
23
|
+
layer = self.viewer.layers.selection.active
|
|
24
|
+
if layer and isinstance(layer, napari.layers.Image):
|
|
25
|
+
name = layer.name
|
|
26
|
+
if "NNLS" in name or "SAM" in name:
|
|
27
|
+
self.endmextr_widget.update_number_H()
|
|
28
|
+
elif "NMF" in name:
|
|
29
|
+
self.nmf_widget.update_number_H()
|
|
30
|
+
elif "PCA" in name:
|
|
31
|
+
self.pca_widget.update_number_H()
|
|
32
|
+
else:
|
|
33
|
+
self.datamanager_widget.update_wl()
|
|
34
|
+
|
|
35
|
+
# collega la funzione wrapper
|
|
36
|
+
self.viewer.dims.events.current_step.connect(on_step_change)
|
|
37
|
+
|
|
38
|
+
# resto invariato
|
|
39
|
+
self.viewer.layers.selection.events.active.connect(
|
|
40
|
+
self.datamanager_widget.layer_auto_selection
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def on_new_layer(self, event):
|
|
45
|
+
layer = event.value
|
|
46
|
+
if (
|
|
47
|
+
isinstance(layer, napari.layers.Labels) and layer.data.ndim == 3
|
|
48
|
+
): # (C, Y, X)
|
|
49
|
+
|
|
50
|
+
def replace():
|
|
51
|
+
if layer in self.viewer.layers: # solo se esiste ancora
|
|
52
|
+
new_labels = np.zeros(layer.data.shape[1:], dtype=np.int32)
|
|
53
|
+
name = layer.name
|
|
54
|
+
self.viewer.layers.remove(layer)
|
|
55
|
+
self.viewer.add_labels(new_labels, name=name)
|
|
56
|
+
|
|
57
|
+
QTimer.singleShot(0, replace)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def update_modes_comboboxes(self):
|
|
61
|
+
for widget in [
|
|
62
|
+
self.datamanager_widget,
|
|
63
|
+
self.umap_widget,
|
|
64
|
+
self.fusion_widget,
|
|
65
|
+
self.endmextr_widget,
|
|
66
|
+
self.pca_widget,
|
|
67
|
+
self.nmf_widget,
|
|
68
|
+
]:
|
|
69
|
+
for attr_name in dir(widget):
|
|
70
|
+
if attr_name.startswith("modes_combobox"):
|
|
71
|
+
if widget == self.fusion_widget:
|
|
72
|
+
widget.modes_combobox_1.choices = self.data.modes
|
|
73
|
+
widget.modes_combobox_2.choices = self.data.modes
|
|
74
|
+
widget.modes_combobox_3.choices = self.data.modes
|
|
75
|
+
else:
|
|
76
|
+
current_value = widget.modes_combobox.value
|
|
77
|
+
widget.modes_combobox.choices = self.data.modes
|
|
78
|
+
if current_value not in self.data.modes:
|
|
79
|
+
widget.modes_combobox.value = current_value
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def run_napari_app():
|
|
83
|
+
"""Add widgets to the viewer"""
|
|
84
|
+
try:
|
|
85
|
+
viewer = napari.current_viewer()
|
|
86
|
+
except AttributeError:
|
|
87
|
+
viewer = napari.Viewer()
|
|
88
|
+
|
|
89
|
+
# WIDGETS
|
|
90
|
+
data = Data()
|
|
91
|
+
plot_datavisualization = Plot(viewer=viewer, data=data)
|
|
92
|
+
datamanager_widget = DataManager(viewer, data)
|
|
93
|
+
datavisualization_widget = DataVisualization(
|
|
94
|
+
viewer, data, plot_datavisualization, datamanager_widget
|
|
95
|
+
)
|
|
96
|
+
fusion_widget = Fusion(viewer, data)
|
|
97
|
+
|
|
98
|
+
plot_umap = Plot(viewer=viewer, data=data)
|
|
99
|
+
umap_widget = UMAP(viewer, data, plot_umap)
|
|
100
|
+
nmf_widget = NMF(viewer, data, plot_umap)
|
|
101
|
+
endmextr_widget = EndmembersExtraction(viewer, data, plot_umap)
|
|
102
|
+
plot_pca = Plot(viewer=viewer, data=data)
|
|
103
|
+
pca_widget = PCA(viewer, data, plot_pca)
|
|
104
|
+
|
|
105
|
+
datamanager_widget.mode_added.connect(update_modes_comboboxes)
|
|
106
|
+
|
|
107
|
+
# Add widget as dock
|
|
108
|
+
datamanager_dock = viewer.window.add_dock_widget(
|
|
109
|
+
datamanager_widget, name="Data Manager", area="right"
|
|
110
|
+
)
|
|
111
|
+
datavisualization_dock = viewer.window.add_dock_widget(
|
|
112
|
+
datavisualization_widget, name="Data Visualization", area="right"
|
|
113
|
+
)
|
|
114
|
+
fusion_dock = viewer.window.add_dock_widget(
|
|
115
|
+
fusion_widget, name="Fusion", area="right"
|
|
116
|
+
)
|
|
117
|
+
umap_dock = viewer.window.add_dock_widget(
|
|
118
|
+
umap_widget, name="UMAP", area="right"
|
|
119
|
+
)
|
|
120
|
+
endmextr_dock = viewer.window.add_dock_widget(
|
|
121
|
+
endmextr_widget, name="Endmembers", area="right"
|
|
122
|
+
)
|
|
123
|
+
pca_dock = viewer.window.add_dock_widget(
|
|
124
|
+
pca_widget, name="PCA", area="right"
|
|
125
|
+
)
|
|
126
|
+
nmf_dock = viewer.window.add_dock_widget(
|
|
127
|
+
nmf_widget, name="NMF", area="right"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
# Tabify the widgets
|
|
131
|
+
viewer.window._qt_window.tabifyDockWidget(
|
|
132
|
+
datamanager_dock, datavisualization_dock
|
|
133
|
+
)
|
|
134
|
+
viewer.window._qt_window.tabifyDockWidget(
|
|
135
|
+
datavisualization_dock, fusion_dock
|
|
136
|
+
)
|
|
137
|
+
viewer.window._qt_window.tabifyDockWidget(fusion_dock, umap_dock)
|
|
138
|
+
viewer.window._qt_window.tabifyDockWidget(umap_dock, pca_dock)
|
|
139
|
+
viewer.window._qt_window.tabifyDockWidget(pca_dock, endmextr_dock)
|
|
140
|
+
viewer.window._qt_window.tabifyDockWidget(endmextr_dock, nmf_dock)
|
|
141
|
+
# Text overlay in the viewer
|
|
142
|
+
viewer.text_overlay.visible = True
|
|
143
|
+
|
|
144
|
+
setup_connections()
|
|
145
|
+
viewer.layers.events.inserted.connect(on_new_layer)
|
|
146
|
+
viewer.layers.selection.events.active.connect(
|
|
147
|
+
datamanager_widget.on_layer_selected
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
return None # Non serve restituire nulla
|
|
Binary file
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
""" """
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from os.path import dirname
|
|
5
|
+
|
|
6
|
+
sys.path.append(dirname(dirname(__file__)))
|
|
7
|
+
|
|
8
|
+
# print("here: ", dirname(dirname(__file__))) #print for the directory folder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Data:
|
|
12
|
+
""" """
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
""" """
|
|
16
|
+
self.filepath = ""
|
|
17
|
+
self.hypercubes = {}
|
|
18
|
+
self.hypercubes_red = {}
|
|
19
|
+
self.hypercubes_spatial_red = {} # For the spectra
|
|
20
|
+
self.hypercubes_spatial_red_params = {}
|
|
21
|
+
self.hypercubes_masked = {}
|
|
22
|
+
self.wls = {}
|
|
23
|
+
self.rgb = {} # Dictionary needed for the fusion process
|
|
24
|
+
self.rgb_red = {} # Dictionary needed for the fusion process
|
|
25
|
+
self.rgb_masked = {}
|
|
26
|
+
self.wls_red = {}
|
|
27
|
+
self.pca_maps = {}
|
|
28
|
+
self.nmf_maps = {}
|
|
29
|
+
self.nmf_basis = {}
|
|
30
|
+
self.svd_maps = {}
|
|
31
|
+
self.umap_maps = {} # valutare se da togliere.
|
|
32
|
+
self.vertex_basis = {}
|
|
33
|
+
self.nnls_maps = {}
|
|
34
|
+
self.sam_maps = {}
|
|
35
|
+
self.modes = [
|
|
36
|
+
"Reflectance",
|
|
37
|
+
"PL",
|
|
38
|
+
"PL - 2",
|
|
39
|
+
"Raman",
|
|
40
|
+
"Fused",
|
|
41
|
+
"-",
|
|
42
|
+
] # fused: self.modes.append
|
|
43
|
+
self.mode = None # valutare se da togliere con nuovo widget
|
|
44
|
+
self.wl_value = 0
|
|
45
|
+
self.fusion_modes = []
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# %% Other functions
|