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,864 @@
|
|
|
1
|
+
"""Widget to manage and process the data"""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from os.path import dirname, splitext
|
|
5
|
+
|
|
6
|
+
sys.path.append(dirname(dirname(__file__)))
|
|
7
|
+
|
|
8
|
+
import h5py
|
|
9
|
+
import napari
|
|
10
|
+
import numpy as np
|
|
11
|
+
from magicgui.widgets import (
|
|
12
|
+
CheckBox,
|
|
13
|
+
ComboBox,
|
|
14
|
+
Container,
|
|
15
|
+
FloatSpinBox,
|
|
16
|
+
PushButton,
|
|
17
|
+
SpinBox,
|
|
18
|
+
)
|
|
19
|
+
from napari.utils.notifications import show_info, show_warning
|
|
20
|
+
from PIL import Image
|
|
21
|
+
from qtpy.QtCore import QTimer, Signal
|
|
22
|
+
from qtpy.QtWidgets import (
|
|
23
|
+
QFileDialog,
|
|
24
|
+
QGroupBox,
|
|
25
|
+
QHBoxLayout,
|
|
26
|
+
QInputDialog,
|
|
27
|
+
QScrollArea,
|
|
28
|
+
QVBoxLayout,
|
|
29
|
+
QWidget,
|
|
30
|
+
)
|
|
31
|
+
from scipy.io import savemat
|
|
32
|
+
|
|
33
|
+
from napari_musa.modules.functions import (
|
|
34
|
+
HSI2RGB,
|
|
35
|
+
SVD_denoise,
|
|
36
|
+
create_mask,
|
|
37
|
+
crop_xy,
|
|
38
|
+
derivative,
|
|
39
|
+
despike,
|
|
40
|
+
dimensionality_reduction,
|
|
41
|
+
open_file,
|
|
42
|
+
preprocessing,
|
|
43
|
+
reduce_spatial_dimension_dwt,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class DataManager(QWidget): # From QWidget
|
|
48
|
+
""" """
|
|
49
|
+
|
|
50
|
+
mode_added = Signal()
|
|
51
|
+
|
|
52
|
+
def __init__(self, viewer: napari.Viewer, data):
|
|
53
|
+
""" """
|
|
54
|
+
super().__init__() # Initialize the QWidget
|
|
55
|
+
self.viewer = viewer
|
|
56
|
+
self.data = data
|
|
57
|
+
# Configure the scroll area
|
|
58
|
+
scroll = QScrollArea()
|
|
59
|
+
scroll.setWidgetResizable(True)
|
|
60
|
+
content_widget = QWidget() # Container widget
|
|
61
|
+
content_layout = QVBoxLayout(
|
|
62
|
+
content_widget
|
|
63
|
+
) # Vertical layout: organize widgets from top to bottom
|
|
64
|
+
# Configure UI
|
|
65
|
+
self.createUI(
|
|
66
|
+
content_layout
|
|
67
|
+
) # The function to create the UI that fills the content_layout
|
|
68
|
+
# Configure principal layout
|
|
69
|
+
scroll.setWidget(
|
|
70
|
+
content_widget
|
|
71
|
+
) # content_widget is a content of scroll area
|
|
72
|
+
main_layout = QVBoxLayout(self)
|
|
73
|
+
main_layout.addWidget(scroll)
|
|
74
|
+
self.setLayout(main_layout)
|
|
75
|
+
|
|
76
|
+
def createUI(self, layout):
|
|
77
|
+
"""Create the components for the UI"""
|
|
78
|
+
layout.addWidget(self.create_open_box())
|
|
79
|
+
layout.addWidget(self.create_save_layer_box())
|
|
80
|
+
layout.addWidget(self.create_processing_box())
|
|
81
|
+
layout.addWidget(self.create_manipulation_box())
|
|
82
|
+
layout.addWidget(self.create_dimred_box())
|
|
83
|
+
|
|
84
|
+
# %% Creation of UI boxes
|
|
85
|
+
# OPEN BOX
|
|
86
|
+
def create_open_box(self):
|
|
87
|
+
"""Create box and elements for file opening"""
|
|
88
|
+
open_box = QGroupBox("Open file")
|
|
89
|
+
open_layout = QVBoxLayout()
|
|
90
|
+
open_layout.addSpacing(10)
|
|
91
|
+
# Elements
|
|
92
|
+
self.modes_combobox = ComboBox(
|
|
93
|
+
choices=self.data.modes, label="Select the imaging mode"
|
|
94
|
+
)
|
|
95
|
+
open_btn = PushButton(text="Open File")
|
|
96
|
+
open_btn.clicked.connect(self.open_btn_f)
|
|
97
|
+
|
|
98
|
+
# Add widgets to the layout
|
|
99
|
+
open_layout.addWidget(
|
|
100
|
+
Container(
|
|
101
|
+
widgets=[self.modes_combobox], layout="horizontal"
|
|
102
|
+
).native
|
|
103
|
+
)
|
|
104
|
+
open_layout.addWidget(
|
|
105
|
+
Container(
|
|
106
|
+
widgets=[
|
|
107
|
+
open_btn,
|
|
108
|
+
],
|
|
109
|
+
layout="horizontal",
|
|
110
|
+
).native
|
|
111
|
+
)
|
|
112
|
+
open_box.setLayout(open_layout)
|
|
113
|
+
return open_box
|
|
114
|
+
|
|
115
|
+
def create_save_layer_box(self):
|
|
116
|
+
"""Create box and elements to save a selected layer"""
|
|
117
|
+
save_box = QGroupBox("Save")
|
|
118
|
+
save_layout = QVBoxLayout()
|
|
119
|
+
save_layout.addSpacing(10)
|
|
120
|
+
savedata_btn = PushButton(text="Save selected layer")
|
|
121
|
+
savedata_btn.clicked.connect(self.savedata_btn_f)
|
|
122
|
+
|
|
123
|
+
save_layout.addWidget(
|
|
124
|
+
Container(
|
|
125
|
+
widgets=[
|
|
126
|
+
savedata_btn,
|
|
127
|
+
],
|
|
128
|
+
layout="horizontal",
|
|
129
|
+
).native
|
|
130
|
+
)
|
|
131
|
+
save_box.setLayout(save_layout)
|
|
132
|
+
return save_box
|
|
133
|
+
|
|
134
|
+
# PREPROCESSING BOX
|
|
135
|
+
def create_processing_box(self):
|
|
136
|
+
"""Preprocessing of the data"""
|
|
137
|
+
processing_box = QGroupBox("Processing")
|
|
138
|
+
processing_layout = QVBoxLayout()
|
|
139
|
+
processing_layout.addSpacing(20)
|
|
140
|
+
#
|
|
141
|
+
# Crop
|
|
142
|
+
crop_box = QGroupBox("Crop")
|
|
143
|
+
crop_layout = self.create_crop_section()
|
|
144
|
+
crop_box.setLayout(crop_layout)
|
|
145
|
+
processing_layout.addSpacing(20)
|
|
146
|
+
processing_layout.addWidget(crop_box)
|
|
147
|
+
|
|
148
|
+
# Mask
|
|
149
|
+
mask_box = QGroupBox("Mask")
|
|
150
|
+
mask_layout = self.create_mask_section()
|
|
151
|
+
mask_box.setLayout(mask_layout)
|
|
152
|
+
processing_layout.addSpacing(20)
|
|
153
|
+
processing_layout.addWidget(mask_box)
|
|
154
|
+
|
|
155
|
+
#
|
|
156
|
+
# Data cleaning
|
|
157
|
+
cleaning_box = QGroupBox("Data cleaning")
|
|
158
|
+
cleaning_layout = QVBoxLayout() # one layout for each box box
|
|
159
|
+
cleaning_layout.addSpacing(10)
|
|
160
|
+
# Despike
|
|
161
|
+
despike_layout = self.create_despike_section()
|
|
162
|
+
cleaning_layout.addLayout(despike_layout)
|
|
163
|
+
# SVD preprocessing
|
|
164
|
+
SVD_denoise_layout = self.create_SVD_denoise_section()
|
|
165
|
+
cleaning_layout.addLayout(SVD_denoise_layout)
|
|
166
|
+
# Median filter
|
|
167
|
+
medfilt_layout = self.create_medfilt_section()
|
|
168
|
+
cleaning_layout.addLayout(medfilt_layout)
|
|
169
|
+
# Gaussian filter
|
|
170
|
+
gaussian_layout = self.create_gaussian_section()
|
|
171
|
+
cleaning_layout.addLayout(gaussian_layout)
|
|
172
|
+
# Savitzky-Golay
|
|
173
|
+
savgol_layout = self.create_savgol_section()
|
|
174
|
+
cleaning_layout.addLayout(savgol_layout)
|
|
175
|
+
# Background
|
|
176
|
+
bkg_layout = self.create_bkg_section()
|
|
177
|
+
cleaning_layout.addLayout(bkg_layout)
|
|
178
|
+
# Preprocessing button
|
|
179
|
+
processing_btn_layout = QVBoxLayout()
|
|
180
|
+
preprocessing_btn = PushButton(text="Process data")
|
|
181
|
+
preprocessing_btn.clicked.connect(self.preprocessing_btn_f)
|
|
182
|
+
processing_btn_layout.addWidget(
|
|
183
|
+
Container(widgets=[preprocessing_btn]).native
|
|
184
|
+
)
|
|
185
|
+
cleaning_layout.addLayout(processing_btn_layout)
|
|
186
|
+
cleaning_box.setLayout(cleaning_layout)
|
|
187
|
+
# Add cleaning box to processing layout
|
|
188
|
+
processing_layout.addWidget(cleaning_box)
|
|
189
|
+
processing_box.setLayout(processing_layout)
|
|
190
|
+
return processing_box
|
|
191
|
+
|
|
192
|
+
# MANIPULATION BOX
|
|
193
|
+
def create_manipulation_box(self):
|
|
194
|
+
manipulation_box = QGroupBox("Data manipulation")
|
|
195
|
+
manipulation_layout = QVBoxLayout()
|
|
196
|
+
manipulation_layout.addSpacing(20)
|
|
197
|
+
derivative_btn = PushButton(text="Create first derivative")
|
|
198
|
+
derivative_btn.clicked.connect(self.derivative_btn_f)
|
|
199
|
+
manipulation_layout.addWidget(
|
|
200
|
+
Container(
|
|
201
|
+
widgets=[
|
|
202
|
+
derivative_btn,
|
|
203
|
+
],
|
|
204
|
+
layout="horizontal",
|
|
205
|
+
).native
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
manipulation_box.setLayout(manipulation_layout)
|
|
209
|
+
return manipulation_box
|
|
210
|
+
|
|
211
|
+
# DIMENSIONAL REDUCTION BOX
|
|
212
|
+
def create_dimred_box(self):
|
|
213
|
+
dimred_box = QGroupBox("Dimensionality reduction")
|
|
214
|
+
dimred_layout = QVBoxLayout()
|
|
215
|
+
dimred_layout.addSpacing(20)
|
|
216
|
+
self.spectral_dimred_checkbox = CheckBox(text="Spectral Reduction")
|
|
217
|
+
self.spatial_dimred_checkbox = CheckBox(text="Spatial Reduction")
|
|
218
|
+
dimred_btn = PushButton(text="Reduce data")
|
|
219
|
+
dimred_btn.clicked.connect(self.dimred_btn_f)
|
|
220
|
+
dimred_layout.addWidget(
|
|
221
|
+
Container(
|
|
222
|
+
widgets=[
|
|
223
|
+
self.spectral_dimred_checkbox,
|
|
224
|
+
self.spatial_dimred_checkbox,
|
|
225
|
+
dimred_btn,
|
|
226
|
+
]
|
|
227
|
+
).native
|
|
228
|
+
)
|
|
229
|
+
dimred_box.setLayout(dimred_layout)
|
|
230
|
+
return dimred_box
|
|
231
|
+
|
|
232
|
+
# UI SUB-BOXES
|
|
233
|
+
def create_crop_section(self):
|
|
234
|
+
"""Crop and create mask"""
|
|
235
|
+
crop_xy_layout = QHBoxLayout()
|
|
236
|
+
crop_xy_btn = PushButton(text="Crop \n (rectangle shape)")
|
|
237
|
+
crop_xy_btn.clicked.connect(self.crop_xy_btn_f)
|
|
238
|
+
crop_xy_layout.addWidget(Container(widgets=[crop_xy_btn]).native)
|
|
239
|
+
|
|
240
|
+
crop_wl_layout = QHBoxLayout()
|
|
241
|
+
self.min_wl_spinbox = SpinBox(
|
|
242
|
+
min=0, max=10000, step=1, value=0, label="Min. WL channel"
|
|
243
|
+
)
|
|
244
|
+
self.max_wl_spinbox = SpinBox(
|
|
245
|
+
min=0, max=10000, step=1, value=100, label="Max. WL channel"
|
|
246
|
+
)
|
|
247
|
+
crop_wl_btn = PushButton(text="Crop wavelengths")
|
|
248
|
+
crop_wl_btn.clicked.connect(self.crop_wl_btn_f)
|
|
249
|
+
crop_wl_layout.addWidget(
|
|
250
|
+
Container(
|
|
251
|
+
widgets=[
|
|
252
|
+
self.min_wl_spinbox,
|
|
253
|
+
self.max_wl_spinbox,
|
|
254
|
+
crop_wl_btn,
|
|
255
|
+
]
|
|
256
|
+
).native
|
|
257
|
+
)
|
|
258
|
+
crop_xy_layout.addLayout(crop_wl_layout)
|
|
259
|
+
return crop_xy_layout
|
|
260
|
+
|
|
261
|
+
def create_mask_section(self):
|
|
262
|
+
mask_layout = QHBoxLayout()
|
|
263
|
+
self.mask_reduced_checkbox = CheckBox(text="From reduced dataset")
|
|
264
|
+
mask_btn = PushButton(text="Create Mask \n(from Label layer)")
|
|
265
|
+
mask_btn.clicked.connect(self.mask_btn_f)
|
|
266
|
+
mask_layout.addWidget(
|
|
267
|
+
Container(
|
|
268
|
+
widgets=[
|
|
269
|
+
self.mask_reduced_checkbox,
|
|
270
|
+
mask_btn,
|
|
271
|
+
],
|
|
272
|
+
layout="horizontal",
|
|
273
|
+
).native
|
|
274
|
+
)
|
|
275
|
+
return mask_layout
|
|
276
|
+
|
|
277
|
+
def create_despike_section(self):
|
|
278
|
+
"""UI of despike section"""
|
|
279
|
+
despike_layout = QHBoxLayout()
|
|
280
|
+
despike_btn = PushButton(text="Despike")
|
|
281
|
+
despike_btn.clicked.connect(self.despike_btn_f)
|
|
282
|
+
despike_layout.addWidget(Container(widgets=[despike_btn]).native)
|
|
283
|
+
return despike_layout
|
|
284
|
+
|
|
285
|
+
def create_SVD_denoise_section(self):
|
|
286
|
+
"""UI of SVD denoising section"""
|
|
287
|
+
SVD_layout = QHBoxLayout()
|
|
288
|
+
SVD_btn = PushButton(text="SVD Calculation")
|
|
289
|
+
SVD_btn.clicked.connect(self.SVD_btn_f)
|
|
290
|
+
self.SVD_spinbox = SpinBox(
|
|
291
|
+
min=1, max=1000, value=5, step=1, name="N. of components"
|
|
292
|
+
)
|
|
293
|
+
SVD_denoise_btn = PushButton(text="SVD Denoise")
|
|
294
|
+
SVD_denoise_btn.clicked.connect(self.SVD_denoise_btn_f)
|
|
295
|
+
|
|
296
|
+
SVD_layout.addWidget(Container(widgets=[SVD_btn]).native)
|
|
297
|
+
SVD_layout.addWidget(
|
|
298
|
+
Container(widgets=[self.SVD_spinbox, SVD_denoise_btn]).native
|
|
299
|
+
)
|
|
300
|
+
return SVD_layout
|
|
301
|
+
|
|
302
|
+
def create_medfilt_section(self):
|
|
303
|
+
"""Median filter"""
|
|
304
|
+
medfilt_layout = QHBoxLayout()
|
|
305
|
+
# self.medfilt_checkbox = CheckBox(text="2D Gaussian filter")
|
|
306
|
+
# self.medfilt_spinbox = FloatSpinBox(
|
|
307
|
+
# min=0.3, max=5.0, value=1.0, step=0.1, name="Sigma"
|
|
308
|
+
self.medfilt_checkbox = CheckBox(text="2D medfilt")
|
|
309
|
+
self.medfilt_spinbox = SpinBox(
|
|
310
|
+
min=1, max=101, value=5, step=2, name="Window"
|
|
311
|
+
)
|
|
312
|
+
medfilt_layout.addWidget(
|
|
313
|
+
Container(widgets=[self.medfilt_checkbox]).native
|
|
314
|
+
)
|
|
315
|
+
medfilt_layout.addWidget(
|
|
316
|
+
Container(widgets=[self.medfilt_spinbox]).native
|
|
317
|
+
)
|
|
318
|
+
return medfilt_layout
|
|
319
|
+
|
|
320
|
+
def create_gaussian_section(self):
|
|
321
|
+
"""gaussian filter"""
|
|
322
|
+
gaussian_layout = QHBoxLayout()
|
|
323
|
+
self.gaussian_checkbox = CheckBox(text="2D Gaussian filter")
|
|
324
|
+
self.gaussian_spinbox = FloatSpinBox(
|
|
325
|
+
min=0.3, max=5.0, value=1.0, step=0.1, name="Sigma"
|
|
326
|
+
)
|
|
327
|
+
gaussian_layout.addWidget(
|
|
328
|
+
Container(widgets=[self.gaussian_checkbox]).native
|
|
329
|
+
)
|
|
330
|
+
gaussian_layout.addWidget(
|
|
331
|
+
Container(widgets=[self.gaussian_spinbox]).native
|
|
332
|
+
)
|
|
333
|
+
return gaussian_layout
|
|
334
|
+
|
|
335
|
+
def create_savgol_section(self):
|
|
336
|
+
"""Savitzky-Golay"""
|
|
337
|
+
savgol_layout = QHBoxLayout()
|
|
338
|
+
savgol_variables_layout = QHBoxLayout()
|
|
339
|
+
self.savgol_checkbox = CheckBox(text="Savitzky-Golay filter")
|
|
340
|
+
self.savgolw_spinbox = SpinBox(
|
|
341
|
+
min=1, max=100, value=11, step=2, name="Window"
|
|
342
|
+
)
|
|
343
|
+
self.savgolp_spinbox = SpinBox(
|
|
344
|
+
min=1, max=100, value=3, step=2, name="Polynom"
|
|
345
|
+
)
|
|
346
|
+
savgol_layout.addWidget(
|
|
347
|
+
Container(widgets=[self.savgol_checkbox]).native
|
|
348
|
+
)
|
|
349
|
+
savgol_variables_layout.addWidget(
|
|
350
|
+
Container(
|
|
351
|
+
widgets=[self.savgolw_spinbox, self.savgolp_spinbox]
|
|
352
|
+
).native
|
|
353
|
+
)
|
|
354
|
+
savgol_layout.addLayout(savgol_variables_layout)
|
|
355
|
+
return savgol_layout
|
|
356
|
+
|
|
357
|
+
def create_bkg_section(self):
|
|
358
|
+
"""Background correction"""
|
|
359
|
+
bkg_layout = QHBoxLayout()
|
|
360
|
+
bkg_variables_layout = QHBoxLayout()
|
|
361
|
+
|
|
362
|
+
self.bkg_checkbox = CheckBox(text="Background correction (SNIP)")
|
|
363
|
+
self.bkgw_spinbox = SpinBox(
|
|
364
|
+
min=1, max=1000, value=30, step=2, name="Window"
|
|
365
|
+
)
|
|
366
|
+
bkg_layout.addWidget(Container(widgets=[self.bkg_checkbox]).native)
|
|
367
|
+
bkg_variables_layout.addWidget(
|
|
368
|
+
Container(widgets=[self.bkgw_spinbox]).native
|
|
369
|
+
)
|
|
370
|
+
bkg_layout.addLayout(bkg_variables_layout)
|
|
371
|
+
return bkg_layout
|
|
372
|
+
|
|
373
|
+
# %% Button functions
|
|
374
|
+
def open_btn_f(self):
|
|
375
|
+
""" """
|
|
376
|
+
self.data.filepath, _ = QFileDialog.getOpenFileName()
|
|
377
|
+
print(f"The data with path {self.data.filepath} will now be opened")
|
|
378
|
+
data_mode = self.modes_combobox.value
|
|
379
|
+
|
|
380
|
+
self.data.hypercubes[data_mode], self.data.wls[data_mode] = open_file(
|
|
381
|
+
self.data.filepath
|
|
382
|
+
)
|
|
383
|
+
if isinstance(self.data.hypercubes[data_mode], int) and isinstance(
|
|
384
|
+
self.data.wls[data_mode], int
|
|
385
|
+
):
|
|
386
|
+
hsi_cube_name, ok1 = QInputDialog.getText(
|
|
387
|
+
None,
|
|
388
|
+
"Name of the spectral hypercube",
|
|
389
|
+
"Insert the variable name of the spectral hypercube:",
|
|
390
|
+
)
|
|
391
|
+
wl_name, ok2 = QInputDialog.getText(
|
|
392
|
+
None,
|
|
393
|
+
"Name of the wavelengths vector",
|
|
394
|
+
"Insert the variable name of the wavelengths vector:",
|
|
395
|
+
)
|
|
396
|
+
|
|
397
|
+
if ok1 and ok2:
|
|
398
|
+
self.data.hypercubes[data_mode], self.data.wls[data_mode] = (
|
|
399
|
+
open_file(
|
|
400
|
+
self.data.filepath,
|
|
401
|
+
hsi_cube_var=hsi_cube_name,
|
|
402
|
+
wl_var=wl_name,
|
|
403
|
+
)
|
|
404
|
+
)
|
|
405
|
+
|
|
406
|
+
self.viewer.add_image(
|
|
407
|
+
self.data.hypercubes[data_mode].transpose(
|
|
408
|
+
2, 0, 1
|
|
409
|
+
), # napari wants (channels, height, width)
|
|
410
|
+
name=str(data_mode),
|
|
411
|
+
metadata={"type": "hyperspectral_cube"},
|
|
412
|
+
)
|
|
413
|
+
# Array for spatial cropping
|
|
414
|
+
# self.crop_array = [
|
|
415
|
+
# 0,
|
|
416
|
+
# 0,
|
|
417
|
+
# self.data.hypercubes[data_mode].shape[0],
|
|
418
|
+
# self.data.hypercubes[data_mode].shape[1],
|
|
419
|
+
# ]
|
|
420
|
+
|
|
421
|
+
def savedata_btn_f(self):
|
|
422
|
+
data_mode = self.modes_combobox.value
|
|
423
|
+
selected_layer = self.viewer.layers.selection.active
|
|
424
|
+
cube_types = [
|
|
425
|
+
"hyperspectral_cube",
|
|
426
|
+
"reduced_hsi_cube",
|
|
427
|
+
"masked_hsi_cube",
|
|
428
|
+
]
|
|
429
|
+
rgb_types = ["rgb", "reduced_rgb", "masked_rgb", "false_rgb"]
|
|
430
|
+
if (
|
|
431
|
+
selected_layer
|
|
432
|
+
and selected_layer.metadata.get("type") in cube_types
|
|
433
|
+
):
|
|
434
|
+
save_dict = {
|
|
435
|
+
"data": selected_layer.data.transpose(1, 2, 0),
|
|
436
|
+
"WL": self.data.wls[data_mode],
|
|
437
|
+
}
|
|
438
|
+
filename, _ = QFileDialog.getSaveFileName(
|
|
439
|
+
self,
|
|
440
|
+
"Save selected dataset",
|
|
441
|
+
"",
|
|
442
|
+
"MATLAB file (*.mat);;HDF5 file (*.h5)",
|
|
443
|
+
)
|
|
444
|
+
savemat(filename, save_dict)
|
|
445
|
+
|
|
446
|
+
if not filename:
|
|
447
|
+
return # the user has canceled the save dialog
|
|
448
|
+
|
|
449
|
+
ext = splitext(filename)[1].lower()
|
|
450
|
+
if ext == "":
|
|
451
|
+
if _.startswith("MATLAB"):
|
|
452
|
+
filename += ".mat"
|
|
453
|
+
ext = ".mat"
|
|
454
|
+
elif _.startswith("HDF5"):
|
|
455
|
+
filename += ".h5"
|
|
456
|
+
ext = ".h5"
|
|
457
|
+
|
|
458
|
+
if ext == ".mat":
|
|
459
|
+
savemat(filename, save_dict)
|
|
460
|
+
elif ext == ".h5":
|
|
461
|
+
with h5py.File(filename, "w") as f:
|
|
462
|
+
f.create_dataset(
|
|
463
|
+
"data", data=self.data.hypercubes[data_mode]
|
|
464
|
+
)
|
|
465
|
+
f.create_dataset("WL", data=self.data.wls[data_mode])
|
|
466
|
+
|
|
467
|
+
# RGB
|
|
468
|
+
elif (
|
|
469
|
+
selected_layer and selected_layer.metadata.get("type") in rgb_types
|
|
470
|
+
):
|
|
471
|
+
filename, _ = QFileDialog.getSaveFileName(
|
|
472
|
+
self,
|
|
473
|
+
"Save selected rgb",
|
|
474
|
+
"",
|
|
475
|
+
"PNG image (*.png);;JPEG image (*.jpg *.jpeg)",
|
|
476
|
+
)
|
|
477
|
+
if not filename:
|
|
478
|
+
return
|
|
479
|
+
if not (
|
|
480
|
+
filename.lower().endswith(".png")
|
|
481
|
+
or filename.lower().endswith(".jpg")
|
|
482
|
+
or filename.lower().endswith(".jpeg")
|
|
483
|
+
):
|
|
484
|
+
if _.startswith("PNG"):
|
|
485
|
+
filename += ".png"
|
|
486
|
+
else:
|
|
487
|
+
filename += ".jpg"
|
|
488
|
+
rgb_data = selected_layer.data
|
|
489
|
+
if rgb_data.dtype != "uint8":
|
|
490
|
+
rgb_data = (
|
|
491
|
+
255
|
|
492
|
+
* (rgb_data - rgb_data.min())
|
|
493
|
+
/ (rgb_data.max() - rgb_data.min())
|
|
494
|
+
).astype("uint8")
|
|
495
|
+
rgb_image = Image.fromarray(rgb_data[..., :3])
|
|
496
|
+
rgb_image.save(filename)
|
|
497
|
+
|
|
498
|
+
# LABEL LAYER
|
|
499
|
+
elif isinstance(selected_layer, napari.layers.Labels):
|
|
500
|
+
filename, _ = QFileDialog.getSaveFileName(
|
|
501
|
+
self,
|
|
502
|
+
"Save selected label layer",
|
|
503
|
+
"",
|
|
504
|
+
"PNG image (*.png);;JPEG image (*.jpg *.jpeg)",
|
|
505
|
+
)
|
|
506
|
+
if not filename:
|
|
507
|
+
return
|
|
508
|
+
if not (
|
|
509
|
+
filename.lower().endswith(".png")
|
|
510
|
+
or filename.lower().endswith(".jpg")
|
|
511
|
+
or filename.lower().endswith(".jpeg")
|
|
512
|
+
):
|
|
513
|
+
if _.startswith("PNG"):
|
|
514
|
+
filename += ".png"
|
|
515
|
+
else:
|
|
516
|
+
filename += ".jpg"
|
|
517
|
+
labels = selected_layer.data.astype(np.uint8)
|
|
518
|
+
colormap = selected_layer.colormap
|
|
519
|
+
lut = (
|
|
520
|
+
colormap.map(np.arange(labels.max() + 1))[:, :3] * 255
|
|
521
|
+
).astype(np.uint8)
|
|
522
|
+
colored_labels = lut[labels]
|
|
523
|
+
alpha = np.where(labels == 0, 0, 255).astype(
|
|
524
|
+
np.uint8
|
|
525
|
+
) # 255 = opaque
|
|
526
|
+
rgba = np.dstack((colored_labels, alpha)) # (H, W, 4)
|
|
527
|
+
img = Image.fromarray(rgba, mode="RGBA")
|
|
528
|
+
img.save(filename)
|
|
529
|
+
|
|
530
|
+
def crop_xy_btn_f(self):
|
|
531
|
+
""" """
|
|
532
|
+
data_mode = self.modes_combobox.value
|
|
533
|
+
selected_layers = list(self.viewer.layers.selection)
|
|
534
|
+
print(selected_layers)
|
|
535
|
+
shape_layer = [
|
|
536
|
+
layer
|
|
537
|
+
for layer in selected_layers
|
|
538
|
+
if isinstance(layer, napari.layers.Shapes)
|
|
539
|
+
]
|
|
540
|
+
# If there are not selected shape layers
|
|
541
|
+
if not shape_layer or len(shape_layer[0].data) == 0:
|
|
542
|
+
show_warning("No shape layer or rect found")
|
|
543
|
+
print("No shape layer or rect found")
|
|
544
|
+
return None
|
|
545
|
+
|
|
546
|
+
shape = shape_layer[0].data[0]
|
|
547
|
+
if self.data.rgb.get(data_mode) is not None:
|
|
548
|
+
print(self.data.rgb[data_mode].shape)
|
|
549
|
+
self.data.hypercubes[data_mode], self.data.rgb[data_mode] = (
|
|
550
|
+
crop_xy(
|
|
551
|
+
self.data.hypercubes[data_mode],
|
|
552
|
+
shape,
|
|
553
|
+
rgb=self.data.rgb[data_mode],
|
|
554
|
+
)
|
|
555
|
+
)
|
|
556
|
+
print(self.data.rgb[data_mode].shape)
|
|
557
|
+
if any(
|
|
558
|
+
layer.name == str(data_mode) + " RGB"
|
|
559
|
+
for layer in self.viewer.layers
|
|
560
|
+
):
|
|
561
|
+
self.viewer.layers.remove(str(data_mode) + " RGB")
|
|
562
|
+
self.viewer.add_image(
|
|
563
|
+
self.data.rgb[data_mode],
|
|
564
|
+
name=str(data_mode) + " RGB",
|
|
565
|
+
rgb=True,
|
|
566
|
+
metadata={"type": "rgb"},
|
|
567
|
+
)
|
|
568
|
+
else:
|
|
569
|
+
self.data.hypercubes[data_mode] = crop_xy(
|
|
570
|
+
self.data.hypercubes[data_mode], shape
|
|
571
|
+
)
|
|
572
|
+
if any(layer.name == str(data_mode) for layer in self.viewer.layers):
|
|
573
|
+
self.viewer.layers.remove(str(data_mode))
|
|
574
|
+
self.viewer.add_image(
|
|
575
|
+
self.data.hypercubes[data_mode].transpose(2, 0, 1),
|
|
576
|
+
name=str(data_mode),
|
|
577
|
+
metadata={"type": "hyperspectral_cube"},
|
|
578
|
+
)
|
|
579
|
+
return
|
|
580
|
+
|
|
581
|
+
def crop_wl_btn_f(self):
|
|
582
|
+
""" """
|
|
583
|
+
data_mode = self.modes_combobox.value
|
|
584
|
+
min_wl = self.min_wl_spinbox.value
|
|
585
|
+
max_wl = self.max_wl_spinbox.value
|
|
586
|
+
self.data.hypercubes[data_mode] = self.data.hypercubes[data_mode][
|
|
587
|
+
:, :, min_wl:max_wl
|
|
588
|
+
]
|
|
589
|
+
self.data.wls[data_mode] = self.data.wls[data_mode][min_wl:max_wl]
|
|
590
|
+
print(f"Cropped shape: {self.data.hypercubes[data_mode].shape}")
|
|
591
|
+
if any(layer.name == str(data_mode) for layer in self.viewer.layers):
|
|
592
|
+
self.viewer.layers.remove(str(data_mode))
|
|
593
|
+
self.viewer.add_image(
|
|
594
|
+
self.data.hypercubes[data_mode].transpose(2, 0, 1),
|
|
595
|
+
name=str(data_mode),
|
|
596
|
+
metadata={"type": "hyperspectral_cube"},
|
|
597
|
+
)
|
|
598
|
+
return
|
|
599
|
+
|
|
600
|
+
def mask_btn_f(self):
|
|
601
|
+
""" """
|
|
602
|
+
data_mode = self.modes_combobox.value
|
|
603
|
+
# SELECT LABEL LAYER
|
|
604
|
+
# takes all the layers but the seleciton is only in the image (WL) in which i've done it
|
|
605
|
+
active_layer = self.viewer.layers.selection.active
|
|
606
|
+
print(isinstance(active_layer, napari.layers.Labels))
|
|
607
|
+
if isinstance(active_layer, napari.layers.Labels):
|
|
608
|
+
labels_layer = active_layer.data
|
|
609
|
+
else:
|
|
610
|
+
show_warning("The selected layer is not a label layer")
|
|
611
|
+
return
|
|
612
|
+
|
|
613
|
+
# If coming from UMAP, we don't need to do np.sum
|
|
614
|
+
print(labels_layer.shape)
|
|
615
|
+
binary_mask = np.where(labels_layer == 0, np.nan, 1).astype(float)
|
|
616
|
+
|
|
617
|
+
if self.mask_reduced_checkbox.value:
|
|
618
|
+
data = self.data.hypercubes_red[data_mode]
|
|
619
|
+
rgb = self.data.rgb_red[data_mode]
|
|
620
|
+
else:
|
|
621
|
+
data = self.data.hypercubes[data_mode]
|
|
622
|
+
if self.data.rgb.get(data_mode) is None:
|
|
623
|
+
show_warning("RGB image not found \nRGB created")
|
|
624
|
+
self.data.rgb[data_mode] = HSI2RGB(
|
|
625
|
+
self.data.wls[data_mode],
|
|
626
|
+
self.data.hypercubes[data_mode],
|
|
627
|
+
self.data.hypercubes[data_mode].shape[0],
|
|
628
|
+
self.data.hypercubes[data_mode].shape[1],
|
|
629
|
+
65,
|
|
630
|
+
False,
|
|
631
|
+
)
|
|
632
|
+
print(self.data.rgb[data_mode].shape)
|
|
633
|
+
self.viewer.add_image(
|
|
634
|
+
self.data.rgb[data_mode],
|
|
635
|
+
name=str(data_mode) + " RGB",
|
|
636
|
+
rgb=True,
|
|
637
|
+
metadata={"type": "rgb"},
|
|
638
|
+
)
|
|
639
|
+
rgb = self.data.rgb[data_mode]
|
|
640
|
+
|
|
641
|
+
(
|
|
642
|
+
self.data.hypercubes_masked[data_mode],
|
|
643
|
+
self.data.rgb_masked[data_mode],
|
|
644
|
+
) = create_mask(data, rgb, binary_mask)
|
|
645
|
+
|
|
646
|
+
self.viewer.add_image(
|
|
647
|
+
self.data.hypercubes_masked[data_mode].transpose(2, 0, 1),
|
|
648
|
+
name=str(data_mode) + " masked",
|
|
649
|
+
metadata={"type": "masked_hsi_cube"},
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
self.viewer.add_image(
|
|
653
|
+
self.data.rgb_masked[data_mode],
|
|
654
|
+
name=str(data_mode) + " masked - RGB",
|
|
655
|
+
metadata={"type": "masked_rgb"},
|
|
656
|
+
)
|
|
657
|
+
return
|
|
658
|
+
|
|
659
|
+
def despike_btn_f(self):
|
|
660
|
+
data_mode = self.modes_combobox.value
|
|
661
|
+
self.data.hypercubes[data_mode] = despike(
|
|
662
|
+
self.data.hypercubes[data_mode]
|
|
663
|
+
)
|
|
664
|
+
print(f"Despiked dataset of {data_mode} created")
|
|
665
|
+
QTimer.singleShot(
|
|
666
|
+
0, lambda: show_info("Despiked dataset of {data_mode} created")
|
|
667
|
+
)
|
|
668
|
+
if any(layer.name == str(data_mode) for layer in self.viewer.layers):
|
|
669
|
+
self.viewer.layers.remove(str(data_mode))
|
|
670
|
+
self.viewer.add_image(
|
|
671
|
+
self.data.hypercubes[data_mode].transpose(2, 0, 1),
|
|
672
|
+
name=str(data_mode),
|
|
673
|
+
metadata={"type": "hyperspectral_cube"},
|
|
674
|
+
)
|
|
675
|
+
return
|
|
676
|
+
|
|
677
|
+
def SVD_btn_f(self):
|
|
678
|
+
data_mode = self.modes_combobox.value
|
|
679
|
+
|
|
680
|
+
self.data.hypercubes[data_mode], self.data.svd_maps[data_mode] = (
|
|
681
|
+
SVD_denoise(
|
|
682
|
+
self.data.hypercubes[data_mode],
|
|
683
|
+
self.data.hypercubes[data_mode].shape[2], # all components
|
|
684
|
+
)
|
|
685
|
+
)
|
|
686
|
+
U_3D = self.data.svd_maps[data_mode][0].reshape(
|
|
687
|
+
self.data.hypercubes[data_mode].shape
|
|
688
|
+
)
|
|
689
|
+
if any(layer.name == str(data_mode) for layer in self.viewer.layers):
|
|
690
|
+
self.viewer.layers.remove(str(data_mode))
|
|
691
|
+
self.viewer.add_image(
|
|
692
|
+
U_3D.transpose(2, 0, 1),
|
|
693
|
+
name=str(data_mode) + " - SVD",
|
|
694
|
+
metadata={"type": "SVD_hsi"},
|
|
695
|
+
)
|
|
696
|
+
return
|
|
697
|
+
|
|
698
|
+
def SVD_denoise_btn_f(self):
|
|
699
|
+
data_mode = self.modes_combobox.value
|
|
700
|
+
components = self.SVD_spinbox.value
|
|
701
|
+
self.data.hypercubes[data_mode], maps = SVD_denoise(
|
|
702
|
+
self.data.hypercubes[data_mode],
|
|
703
|
+
components,
|
|
704
|
+
matrices=self.data.svd_maps[data_mode],
|
|
705
|
+
)
|
|
706
|
+
print(f"SVD denoise of {data_mode} created")
|
|
707
|
+
QTimer.singleShot(
|
|
708
|
+
0, lambda: show_info("SVD denoise of {data_mode} created")
|
|
709
|
+
)
|
|
710
|
+
self.viewer.add_image(
|
|
711
|
+
self.data.hypercubes[data_mode].transpose(2, 0, 1),
|
|
712
|
+
name=str(data_mode),
|
|
713
|
+
metadata={"type": "hyperspectral_cube"},
|
|
714
|
+
)
|
|
715
|
+
return
|
|
716
|
+
|
|
717
|
+
def preprocessing_btn_f(self):
|
|
718
|
+
data_mode = self.modes_combobox.value
|
|
719
|
+
medfilt_checkbox = self.medfilt_checkbox.value
|
|
720
|
+
gaussian_checkbox = self.gaussian_checkbox.value
|
|
721
|
+
savgol_checkbox = self.savgol_checkbox.value
|
|
722
|
+
bkg_checkbox = self.bkg_checkbox.value
|
|
723
|
+
medfilt_w = self.medfilt_spinbox.value
|
|
724
|
+
gaussian_s = self.gaussian_spinbox.value
|
|
725
|
+
savgol_w = self.savgolw_spinbox.value
|
|
726
|
+
savgol_p = self.savgolp_spinbox.value
|
|
727
|
+
bkg_w = self.bkgw_spinbox.value
|
|
728
|
+
|
|
729
|
+
self.data.hypercubes[data_mode] = preprocessing(
|
|
730
|
+
self.data.hypercubes[data_mode],
|
|
731
|
+
medfilt_w,
|
|
732
|
+
gaussian_s,
|
|
733
|
+
savgol_w,
|
|
734
|
+
savgol_p,
|
|
735
|
+
bkg_w,
|
|
736
|
+
medfilt_checkbox=medfilt_checkbox,
|
|
737
|
+
gaussian_checkbox=gaussian_checkbox,
|
|
738
|
+
savgol_checkbox=savgol_checkbox,
|
|
739
|
+
bkg_checkbox=bkg_checkbox,
|
|
740
|
+
)
|
|
741
|
+
QTimer.singleShot(0, lambda: show_info("Preprocessing completed!"))
|
|
742
|
+
if any(layer.name == str(data_mode) for layer in self.viewer.layers):
|
|
743
|
+
self.viewer.layers.remove(str(data_mode))
|
|
744
|
+
self.viewer.add_image(
|
|
745
|
+
self.data.hypercubes[data_mode].transpose(2, 0, 1),
|
|
746
|
+
name=str(data_mode),
|
|
747
|
+
metadata={"type": "hyperspectral_cube"},
|
|
748
|
+
)
|
|
749
|
+
return
|
|
750
|
+
|
|
751
|
+
def derivative_btn_f(self):
|
|
752
|
+
data_mode = self.modes_combobox.value
|
|
753
|
+
name = str(data_mode) + " - derivative"
|
|
754
|
+
# Add the name of the derivative in the list of modes and in the combobox
|
|
755
|
+
if name not in self.data.modes:
|
|
756
|
+
self.data.modes.append(name)
|
|
757
|
+
self.modes_combobox.choices = self.data.modes
|
|
758
|
+
self.mode_added.emit()
|
|
759
|
+
|
|
760
|
+
self.data.hypercubes[data_mode + " - derivative"] = derivative(
|
|
761
|
+
self.data.hypercubes[data_mode],
|
|
762
|
+
savgol_w=9,
|
|
763
|
+
savgol_pol=3,
|
|
764
|
+
deriv=1,
|
|
765
|
+
)
|
|
766
|
+
self.viewer.add_image(
|
|
767
|
+
self.data.hypercubes[data_mode + " - derivative"].transpose(
|
|
768
|
+
2, 0, 1
|
|
769
|
+
),
|
|
770
|
+
name=str(data_mode + " - derivative"),
|
|
771
|
+
metadata={"type": "hyperspectral_cube"},
|
|
772
|
+
)
|
|
773
|
+
self.data.wls[data_mode + " - derivative"] = self.data.wls[data_mode]
|
|
774
|
+
|
|
775
|
+
return
|
|
776
|
+
|
|
777
|
+
def dimred_btn_f(self):
|
|
778
|
+
data_mode = self.modes_combobox.value
|
|
779
|
+
dataset = self.data.hypercubes[data_mode]
|
|
780
|
+
spectral_dimred_checkbox = self.spectral_dimred_checkbox.value
|
|
781
|
+
spatial_dimred_checkbox = self.spatial_dimred_checkbox.value
|
|
782
|
+
(
|
|
783
|
+
self.data.hypercubes_red[data_mode],
|
|
784
|
+
self.data.wls_red[data_mode],
|
|
785
|
+
self.data.rgb_red[data_mode],
|
|
786
|
+
) = dimensionality_reduction(
|
|
787
|
+
dataset,
|
|
788
|
+
spectral_dimred_checkbox,
|
|
789
|
+
spatial_dimred_checkbox,
|
|
790
|
+
self.data.wls[data_mode],
|
|
791
|
+
)
|
|
792
|
+
print(
|
|
793
|
+
f"Dimensionality of dataset (Mode: {data_mode}) has been reduced"
|
|
794
|
+
)
|
|
795
|
+
print(
|
|
796
|
+
f"New channel array of dimension {self.data.wls_red[data_mode].shape}"
|
|
797
|
+
)
|
|
798
|
+
print(
|
|
799
|
+
f"New rgb matrix of reduced dataset. Dimensions: {self.data.rgb_red[data_mode].shape}"
|
|
800
|
+
)
|
|
801
|
+
if spatial_dimred_checkbox:
|
|
802
|
+
(
|
|
803
|
+
self.data.hypercubes_spatial_red[data_mode],
|
|
804
|
+
self.data.hypercubes_spatial_red_params[data_mode],
|
|
805
|
+
) = reduce_spatial_dimension_dwt(dataset)
|
|
806
|
+
|
|
807
|
+
# print(self.data.hypercubes_red[data_mode].shape)
|
|
808
|
+
self.viewer.add_image(
|
|
809
|
+
self.data.hypercubes_red[data_mode].transpose(2, 0, 1),
|
|
810
|
+
name=str(data_mode) + " - REDUCED",
|
|
811
|
+
metadata={"type": "reduced_hsi_cube"},
|
|
812
|
+
)
|
|
813
|
+
self.viewer.add_image(
|
|
814
|
+
self.data.rgb_red[data_mode],
|
|
815
|
+
name=str(data_mode) + " - REDUCED RGB",
|
|
816
|
+
metadata={"type": "reduced_rgb"},
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
# %% Other functions
|
|
820
|
+
def update_wl(self):
|
|
821
|
+
""" """
|
|
822
|
+
data_mode = self.modes_combobox.value
|
|
823
|
+
wl_index = self.viewer.dims.current_step[0]
|
|
824
|
+
max_index = len(self.data.wls[data_mode]) - 1
|
|
825
|
+
wl_index = min(wl_index, max_index)
|
|
826
|
+
wl_value = wl_index
|
|
827
|
+
wl = round(self.data.wls[data_mode][wl_index], 2)
|
|
828
|
+
self.viewer.text_overlay.text = (
|
|
829
|
+
f"Wavelength: {wl} nm \nChannel: {wl_value}"
|
|
830
|
+
)
|
|
831
|
+
|
|
832
|
+
def layer_auto_selection(self):
|
|
833
|
+
""" """
|
|
834
|
+
selected_layer = self.viewer.layers.selection.active
|
|
835
|
+
if selected_layer is None:
|
|
836
|
+
return
|
|
837
|
+
elif selected_layer.metadata.get("type") == "hyperspectral_cube":
|
|
838
|
+
print(selected_layer.name)
|
|
839
|
+
self.modes_combobox.value = selected_layer.name
|
|
840
|
+
|
|
841
|
+
"""
|
|
842
|
+
# cambiare nomi in modo che tolgo la grandezza della stringa del nome
|
|
843
|
+
elif selected_layer.metadata.get("type") == "rgb":
|
|
844
|
+
print(selected_layer.name[:-4])
|
|
845
|
+
self.modes_combobox.value = selected_layer.name[:-4]
|
|
846
|
+
elif selected_layer.metadata.get("type") == "reduced_hsi_cube":
|
|
847
|
+
print(selected_layer.name[:-10])
|
|
848
|
+
self.modes_combobox.value = selected_layer.name[:-10]
|
|
849
|
+
elif selected_layer.metadata.get("type") == "reduced_rgb":
|
|
850
|
+
print(selected_layer.name[:-14])
|
|
851
|
+
self.modes_combobox.value = selected_layer.name[:-14]
|
|
852
|
+
elif selected_layer.metadata.get("type") == "masked_hsi_cube":
|
|
853
|
+
print(selected_layer.name[:-7])
|
|
854
|
+
self.modes_combobox.value = selected_layer.name[:-7]
|
|
855
|
+
elif selected_layer.metadata.get("type") == "masked_rgb":
|
|
856
|
+
print(selected_layer.name[:-13])
|
|
857
|
+
self.modes_combobox.value = selected_layer.name[:-13]
|
|
858
|
+
elif selected_layer.metadata.get("type") == "denoised_hsi":
|
|
859
|
+
print(selected_layer.name[:-11])
|
|
860
|
+
self.modes_combobox.value = selected_layer.name[:-11]
|
|
861
|
+
elif selected_layer.metadata.get("type") == "SVD_hsi":
|
|
862
|
+
print(selected_layer.name[:-6])
|
|
863
|
+
self.modes_combobox.value = selected_layer.name[:-6]
|
|
864
|
+
"""
|