celldetective 1.0.2__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.
Files changed (66) hide show
  1. celldetective/__init__.py +2 -0
  2. celldetective/__main__.py +432 -0
  3. celldetective/datasets/segmentation_annotations/blank +0 -0
  4. celldetective/datasets/signal_annotations/blank +0 -0
  5. celldetective/events.py +149 -0
  6. celldetective/extra_properties.py +100 -0
  7. celldetective/filters.py +89 -0
  8. celldetective/gui/__init__.py +20 -0
  9. celldetective/gui/about.py +44 -0
  10. celldetective/gui/analyze_block.py +563 -0
  11. celldetective/gui/btrack_options.py +898 -0
  12. celldetective/gui/classifier_widget.py +386 -0
  13. celldetective/gui/configure_new_exp.py +532 -0
  14. celldetective/gui/control_panel.py +438 -0
  15. celldetective/gui/gui_utils.py +495 -0
  16. celldetective/gui/json_readers.py +113 -0
  17. celldetective/gui/measurement_options.py +1425 -0
  18. celldetective/gui/neighborhood_options.py +452 -0
  19. celldetective/gui/plot_signals_ui.py +1042 -0
  20. celldetective/gui/process_block.py +1055 -0
  21. celldetective/gui/retrain_segmentation_model_options.py +706 -0
  22. celldetective/gui/retrain_signal_model_options.py +643 -0
  23. celldetective/gui/seg_model_loader.py +460 -0
  24. celldetective/gui/signal_annotator.py +2388 -0
  25. celldetective/gui/signal_annotator_options.py +340 -0
  26. celldetective/gui/styles.py +217 -0
  27. celldetective/gui/survival_ui.py +903 -0
  28. celldetective/gui/tableUI.py +608 -0
  29. celldetective/gui/thresholds_gui.py +1300 -0
  30. celldetective/icons/logo-large.png +0 -0
  31. celldetective/icons/logo.png +0 -0
  32. celldetective/icons/signals_icon.png +0 -0
  33. celldetective/icons/splash-test.png +0 -0
  34. celldetective/icons/splash.png +0 -0
  35. celldetective/icons/splash0.png +0 -0
  36. celldetective/icons/survival2.png +0 -0
  37. celldetective/icons/vignette_signals2.png +0 -0
  38. celldetective/icons/vignette_signals2.svg +114 -0
  39. celldetective/io.py +2050 -0
  40. celldetective/links/zenodo.json +561 -0
  41. celldetective/measure.py +1258 -0
  42. celldetective/models/segmentation_effectors/blank +0 -0
  43. celldetective/models/segmentation_generic/blank +0 -0
  44. celldetective/models/segmentation_targets/blank +0 -0
  45. celldetective/models/signal_detection/blank +0 -0
  46. celldetective/models/tracking_configs/mcf7.json +68 -0
  47. celldetective/models/tracking_configs/ricm.json +203 -0
  48. celldetective/models/tracking_configs/ricm2.json +203 -0
  49. celldetective/neighborhood.py +717 -0
  50. celldetective/scripts/analyze_signals.py +51 -0
  51. celldetective/scripts/measure_cells.py +275 -0
  52. celldetective/scripts/segment_cells.py +212 -0
  53. celldetective/scripts/segment_cells_thresholds.py +140 -0
  54. celldetective/scripts/track_cells.py +206 -0
  55. celldetective/scripts/train_segmentation_model.py +246 -0
  56. celldetective/scripts/train_signal_model.py +49 -0
  57. celldetective/segmentation.py +712 -0
  58. celldetective/signals.py +2826 -0
  59. celldetective/tracking.py +974 -0
  60. celldetective/utils.py +1681 -0
  61. celldetective-1.0.2.dist-info/LICENSE +674 -0
  62. celldetective-1.0.2.dist-info/METADATA +192 -0
  63. celldetective-1.0.2.dist-info/RECORD +66 -0
  64. celldetective-1.0.2.dist-info/WHEEL +5 -0
  65. celldetective-1.0.2.dist-info/entry_points.txt +2 -0
  66. celldetective-1.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1425 @@
1
+ import math
2
+
3
+ import skimage
4
+ from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QScrollArea, QComboBox, QFrame, QCheckBox, \
5
+ QFileDialog, QGridLayout, QTextEdit, QLineEdit, QVBoxLayout, QWidget, QLabel, QHBoxLayout, QPushButton, QTabWidget, \
6
+ QRadioButton, QButtonGroup, QSizePolicy, QListWidget, QDialog
7
+ from PyQt5.QtCore import Qt, QSize
8
+ from PyQt5.QtGui import QIcon, QDoubleValidator, QIntValidator
9
+ from matplotlib.patches import Circle
10
+ from scipy import ndimage
11
+ from skimage.draw import disk
12
+ from skimage.morphology import disk
13
+
14
+ from celldetective.filters import std_filter, gauss_filter
15
+ from celldetective.gui.gui_utils import center_window, FeatureChoice, ListWidget, QHSeperationLine, FigureCanvas, \
16
+ GeometryChoice, OperationChoice, ChannelChoice
17
+ from superqt import QLabeledDoubleRangeSlider, QLabeledDoubleSlider, QLabeledSlider
18
+ from superqt.fonticon import icon
19
+ from fonticon_mdi6 import MDI6
20
+
21
+ from celldetective.gui.thresholds_gui import ThresholdNormalisation, ThresholdSpot
22
+ from celldetective.utils import extract_experiment_channels, get_software_location
23
+ from celldetective.io import interpret_tracking_configuration, load_frames, auto_load_number_of_frames
24
+ from celldetective.measure import compute_haralick_features, contour_of_instance_segmentation, correct_image, \
25
+ field_normalisation, normalise_by_cell
26
+ import numpy as np
27
+ from tifffile import imread
28
+ import json
29
+ from shutil import copyfile
30
+ import os
31
+ import matplotlib.pyplot as plt
32
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
33
+ from glob import glob
34
+ from natsort import natsorted
35
+ from tifffile import imread
36
+ from pathlib import Path, PurePath
37
+ import gc
38
+ from stardist import fill_label_holes
39
+
40
+
41
+ class ConfigMeasurements(QMainWindow):
42
+ """
43
+ UI to set measurement instructions.
44
+
45
+ """
46
+
47
+ def __init__(self, parent=None):
48
+
49
+ super().__init__()
50
+ self.parent = parent
51
+ self.setWindowTitle("Configure measurements")
52
+ self.setWindowIcon(QIcon(os.sep.join(['celldetective', 'icons', 'mexican-hat.png'])))
53
+ self.mode = self.parent.mode
54
+ self.exp_dir = self.parent.exp_dir
55
+ self.background_correction = []
56
+ if self.mode == "targets":
57
+ self.config_name = "btrack_config_targets.json"
58
+ self.measure_instructions_path = self.parent.exp_dir + "configs/measurement_instructions_targets.json"
59
+ elif self.mode == "effectors":
60
+ self.config_name = "btrack_config_effectors.json"
61
+ self.measure_instructions_path = self.parent.exp_dir + "configs/measurement_instructions_effectors.json"
62
+ self.soft_path = get_software_location()
63
+ self.clear_previous = False
64
+
65
+ exp_config = self.exp_dir + "config.ini"
66
+ self.config_path = self.exp_dir + self.config_name
67
+ self.channel_names, self.channels = extract_experiment_channels(exp_config)
68
+ self.channel_names = np.array(self.channel_names)
69
+ self.channels = np.array(self.channels)
70
+
71
+ self.screen_height = self.parent.parent.parent.screen_height
72
+ center_window(self)
73
+
74
+ self.onlyFloat = QDoubleValidator()
75
+ self.onlyInt = QIntValidator()
76
+
77
+ self.setMinimumWidth(500)
78
+ self.setMinimumHeight(int(0.3 * self.screen_height))
79
+ self.setMaximumHeight(int(0.8 * self.screen_height))
80
+ self.populate_widget()
81
+ self.load_previous_measurement_instructions()
82
+
83
+ def populate_widget(self):
84
+
85
+ """
86
+ Create the multibox design.
87
+
88
+ """
89
+
90
+ # Create button widget and layout
91
+ self.scroll_area = QScrollArea(self)
92
+ self.button_widget = QWidget()
93
+ main_layout = QVBoxLayout()
94
+ self.button_widget.setLayout(main_layout)
95
+ main_layout.setContentsMargins(30, 30, 30, 30)
96
+
97
+ self.normalisation_frame = QFrame()
98
+ self.normalisation_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
99
+ self.populate_normalisation_tabs()
100
+ main_layout.addWidget(self.normalisation_frame)
101
+
102
+ # first frame for FEATURES
103
+ self.features_frame = QFrame()
104
+ self.features_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
105
+ self.populate_features_frame()
106
+ main_layout.addWidget(self.features_frame)
107
+
108
+ # second frame for ISOTROPIC MEASUREMENTS
109
+ self.iso_frame = QFrame()
110
+ self.iso_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
111
+ self.populate_iso_frame()
112
+ main_layout.addWidget(self.iso_frame)
113
+
114
+ self.spot_detection_frame = QFrame()
115
+ self.spot_detection_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
116
+ self.populate_spot_detection()
117
+ main_layout.addWidget(self.spot_detection_frame)
118
+
119
+ self.clear_previous_btn = QCheckBox('clear previous measurements')
120
+ main_layout.addWidget(self.clear_previous_btn)
121
+
122
+ self.submit_btn = QPushButton('Save')
123
+ self.submit_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet)
124
+ self.submit_btn.clicked.connect(self.write_instructions)
125
+ main_layout.addWidget(self.submit_btn)
126
+
127
+ # self.populate_left_panel()
128
+ # grid.addLayout(self.left_side, 0, 0, 1, 1)
129
+ self.button_widget.adjustSize()
130
+
131
+ self.scroll_area.setAlignment(Qt.AlignCenter)
132
+ self.scroll_area.setWidget(self.button_widget)
133
+ self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
134
+ self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
135
+ self.scroll_area.setWidgetResizable(True)
136
+ self.setCentralWidget(self.scroll_area)
137
+ self.show()
138
+
139
+ QApplication.processEvents()
140
+ self.adjustScrollArea()
141
+
142
+ def populate_iso_frame(self):
143
+
144
+ """
145
+ Add widgets and layout in the POST-PROCESSING frame.
146
+ """
147
+
148
+ grid = QGridLayout(self.iso_frame)
149
+
150
+ self.iso_lbl = QLabel("ISOTROPIC MEASUREMENTS")
151
+ self.iso_lbl.setStyleSheet("""
152
+ font-weight: bold;
153
+ padding: 0px;
154
+ """)
155
+ grid.addWidget(self.iso_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
156
+ self.generate_iso_contents()
157
+ grid.addWidget(self.ContentsIso, 1, 0, 1, 4, alignment=Qt.AlignTop)
158
+
159
+ def populate_features_frame(self):
160
+
161
+ """
162
+ Add widgets and layout in the FEATURES frame.
163
+ """
164
+
165
+ grid = QGridLayout(self.features_frame)
166
+
167
+ self.feature_lbl = QLabel("FEATURES")
168
+ self.feature_lbl.setStyleSheet("""
169
+ font-weight: bold;
170
+ padding: 0px;
171
+ """)
172
+ grid.addWidget(self.feature_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
173
+
174
+ self.generate_feature_panel_contents()
175
+ grid.addWidget(self.ContentsFeatures, 1, 0, 1, 4, alignment=Qt.AlignTop)
176
+
177
+ def generate_iso_contents(self):
178
+
179
+ self.ContentsIso = QFrame()
180
+ layout = QVBoxLayout(self.ContentsIso)
181
+ layout.setContentsMargins(0, 0, 0, 0)
182
+
183
+ radii_layout = QHBoxLayout()
184
+ self.radii_lbl = QLabel('Measurement radii (from center):')
185
+ self.radii_lbl.setToolTip('Define radii or donughts for intensity measurements.')
186
+ radii_layout.addWidget(self.radii_lbl, 90)
187
+
188
+ self.del_radius_btn = QPushButton("")
189
+ self.del_radius_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
190
+ self.del_radius_btn.setIcon(icon(MDI6.trash_can, color="black"))
191
+ self.del_radius_btn.setToolTip("Remove radius")
192
+ self.del_radius_btn.setIconSize(QSize(20, 20))
193
+ radii_layout.addWidget(self.del_radius_btn, 5)
194
+
195
+ self.add_radius_btn = QPushButton("")
196
+ self.add_radius_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
197
+ self.add_radius_btn.setIcon(icon(MDI6.plus, color="black"))
198
+ self.add_radius_btn.setToolTip("Add radius")
199
+ self.add_radius_btn.setIconSize(QSize(20, 20))
200
+ radii_layout.addWidget(self.add_radius_btn, 5)
201
+ layout.addLayout(radii_layout)
202
+
203
+ self.radii_list = ListWidget(self, GeometryChoice, initial_features=["10"], dtype=int)
204
+ layout.addWidget(self.radii_list)
205
+
206
+ self.del_radius_btn.clicked.connect(self.radii_list.removeSel)
207
+ self.add_radius_btn.clicked.connect(self.radii_list.addItem)
208
+
209
+ # Operation
210
+ operation_layout = QHBoxLayout()
211
+ self.op_lbl = QLabel('Operation to perform:')
212
+ self.op_lbl.setToolTip('Set the operations to perform inside the ROI.')
213
+ operation_layout.addWidget(self.op_lbl, 90)
214
+
215
+ self.del_op_btn = QPushButton("")
216
+ self.del_op_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
217
+ self.del_op_btn.setIcon(icon(MDI6.trash_can, color="black"))
218
+ self.del_op_btn.setToolTip("Remove operation")
219
+ self.del_op_btn.setIconSize(QSize(20, 20))
220
+ operation_layout.addWidget(self.del_op_btn, 5)
221
+
222
+ self.add_op_btn = QPushButton("")
223
+ self.add_op_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
224
+ self.add_op_btn.setIcon(icon(MDI6.plus, color="black"))
225
+ self.add_op_btn.setToolTip("Add operation")
226
+ self.add_op_btn.setIconSize(QSize(20, 20))
227
+ operation_layout.addWidget(self.add_op_btn, 5)
228
+ layout.addLayout(operation_layout)
229
+
230
+ self.operations_list = ListWidget(self, OperationChoice, initial_features=["mean"])
231
+ layout.addWidget(self.operations_list)
232
+
233
+ self.del_op_btn.clicked.connect(self.operations_list.removeSel)
234
+ self.add_op_btn.clicked.connect(self.operations_list.addItem)
235
+
236
+ def generate_feature_panel_contents(self):
237
+
238
+ self.ContentsFeatures = QFrame()
239
+ layout = QVBoxLayout(self.ContentsFeatures)
240
+ layout.setContentsMargins(0, 0, 0, 0)
241
+
242
+ feature_layout = QHBoxLayout()
243
+ feature_layout.setContentsMargins(0, 0, 0, 0)
244
+
245
+ self.feature_lbl = QLabel("Add features:")
246
+ self.del_feature_btn = QPushButton("")
247
+ self.del_feature_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
248
+ self.del_feature_btn.setIcon(icon(MDI6.trash_can, color="black"))
249
+ self.del_feature_btn.setToolTip("Remove feature")
250
+ self.del_feature_btn.setIconSize(QSize(20, 20))
251
+
252
+ self.add_feature_btn = QPushButton("")
253
+ self.add_feature_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
254
+ self.add_feature_btn.setIcon(icon(MDI6.filter_plus, color="black"))
255
+ self.add_feature_btn.setToolTip("Add feature")
256
+ self.add_feature_btn.setIconSize(QSize(20, 20))
257
+
258
+ self.features_list = ListWidget(self, FeatureChoice, initial_features=['area', 'intensity_mean', ])
259
+
260
+ self.del_feature_btn.clicked.connect(self.features_list.removeSel)
261
+ self.add_feature_btn.clicked.connect(self.features_list.addItem)
262
+
263
+ feature_layout.addWidget(self.feature_lbl, 90)
264
+ feature_layout.addWidget(self.del_feature_btn, 5)
265
+ feature_layout.addWidget(self.add_feature_btn, 5)
266
+ layout.addLayout(feature_layout)
267
+ layout.addWidget(self.features_list)
268
+
269
+ self.feat_sep2 = QHSeperationLine()
270
+ layout.addWidget(self.feat_sep2)
271
+
272
+ contour_layout = QHBoxLayout()
273
+ self.border_dist_lbl = QLabel('Contour measurements (from edge of mask):')
274
+ self.border_dist_lbl.setToolTip(
275
+ 'Apply the intensity measurements defined above\nto a slice of each cell mask, defined as distance\nfrom the edge.')
276
+ contour_layout.addWidget(self.border_dist_lbl, 90)
277
+
278
+ self.del_contour_btn = QPushButton("")
279
+ self.del_contour_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
280
+ self.del_contour_btn.setIcon(icon(MDI6.trash_can, color="black"))
281
+ self.del_contour_btn.setToolTip("Remove distance")
282
+ self.del_contour_btn.setIconSize(QSize(20, 20))
283
+ contour_layout.addWidget(self.del_contour_btn, 5)
284
+
285
+ self.add_contour_btn = QPushButton("")
286
+ self.add_contour_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
287
+ self.add_contour_btn.setIcon(icon(MDI6.plus, color="black"))
288
+ self.add_contour_btn.setToolTip("Add distance")
289
+ self.add_contour_btn.setIconSize(QSize(20, 20))
290
+ contour_layout.addWidget(self.add_contour_btn, 5)
291
+
292
+ self.view_contour_btn = QPushButton("")
293
+ self.view_contour_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
294
+ self.view_contour_btn.setIcon(icon(MDI6.eye_outline, color="black"))
295
+ self.view_contour_btn.setToolTip("View contour")
296
+ self.view_contour_btn.setIconSize(QSize(20, 20))
297
+ contour_layout.addWidget(self.view_contour_btn, 5)
298
+
299
+ layout.addLayout(contour_layout)
300
+
301
+ self.contours_list = ListWidget(self, GeometryChoice, initial_features=[], dtype=int)
302
+ layout.addWidget(self.contours_list)
303
+
304
+ self.del_contour_btn.clicked.connect(self.contours_list.removeSel)
305
+ self.add_contour_btn.clicked.connect(self.contours_list.addItem)
306
+ self.view_contour_btn.clicked.connect(self.view_selected_contour)
307
+
308
+ self.feat_sep3 = QHSeperationLine()
309
+ layout.addWidget(self.feat_sep3)
310
+ # self.radial_intensity_btn = QCheckBox('Measure radial intensity distribution')
311
+ # layout.addWidget(self.radial_intensity_btn)
312
+ # self.radial_intensity_btn.clicked.connect(self.enable_step_size)
313
+ # self.channel_chechkboxes=[]
314
+ # for channel in self.channel_names:
315
+ # channel_checkbox=QCheckBox(channel)
316
+ # self.channel_chechkboxes.append(channel_checkbox)
317
+ # layout.addWidget(channel_checkbox)
318
+ # channel_checkbox.setEnabled(False)
319
+ # step_box=QHBoxLayout()
320
+ # self.step_lbl=QLabel("Step size (in px)")
321
+ # self.step_size=QLineEdit()
322
+ # self.step_lbl.setEnabled(False)
323
+ # self.step_size.setEnabled(False)
324
+ # step_box.addWidget(self.step_lbl)
325
+ # step_box.addWidget(self.step_size)
326
+ # layout.addLayout(step_box)
327
+ # self.feat_sep4 = QHSeperationLine()
328
+ # layout.addWidget(self.feat_sep4)
329
+
330
+ # Haralick features parameters
331
+ self.activate_haralick_btn = QCheckBox('Measure Haralick texture features')
332
+ self.activate_haralick_btn.toggled.connect(self.show_haralick_options)
333
+
334
+ self.haralick_channel_choice = QComboBox()
335
+ self.haralick_channel_choice.addItems(self.channel_names)
336
+ self.haralick_channel_lbl = QLabel('Target channel: ')
337
+
338
+ self.haralick_distance_le = QLineEdit("1")
339
+ self.haralick_distance_lbl = QLabel('Distance: ')
340
+
341
+ self.haralick_n_gray_levels_le = QLineEdit("256")
342
+ self.haralick_n_gray_levels_lbl = QLabel('# gray levels: ')
343
+
344
+ # Slider to set vmin & vmax
345
+ self.haralick_scale_slider = QLabeledDoubleSlider()
346
+ self.haralick_scale_slider.setSingleStep(0.05)
347
+ self.haralick_scale_slider.setTickInterval(0.05)
348
+ self.haralick_scale_slider.setSingleStep(1)
349
+ self.haralick_scale_slider.setOrientation(1)
350
+ self.haralick_scale_slider.setRange(0, 1)
351
+ self.haralick_scale_slider.setValue(0.5)
352
+ self.haralick_scale_lbl = QLabel('Scale: ')
353
+
354
+ self.haralick_percentile_min_le = QLineEdit('0.01')
355
+ self.haralick_percentile_max_le = QLineEdit('99.9')
356
+ self.haralick_normalization_mode_btn = QPushButton()
357
+ self.haralick_normalization_mode_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
358
+ self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
359
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
360
+ self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
361
+ self.percentile_mode = True
362
+
363
+ min_percentile_hbox = QHBoxLayout()
364
+ min_percentile_hbox.addWidget(self.haralick_percentile_min_le, 90)
365
+ min_percentile_hbox.addWidget(self.haralick_normalization_mode_btn, 10)
366
+ min_percentile_hbox.setContentsMargins(0, 0, 0, 0)
367
+
368
+ self.haralick_percentile_min_lbl = QLabel('Min percentile: ')
369
+ self.haralick_percentile_max_lbl = QLabel('Max percentile: ')
370
+
371
+ self.haralick_hist_btn = QPushButton()
372
+ self.haralick_hist_btn.clicked.connect(self.control_haralick_intensity_histogram)
373
+ self.haralick_hist_btn.setIcon(icon(MDI6.poll, color="k"))
374
+ self.haralick_hist_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
375
+
376
+ self.haralick_digit_btn = QPushButton()
377
+ self.haralick_digit_btn.clicked.connect(self.control_haralick_digitalization)
378
+ self.haralick_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
379
+ self.haralick_digit_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
380
+
381
+ self.haralick_layout = QVBoxLayout()
382
+ self.haralick_layout.setContentsMargins(20, 20, 20, 20)
383
+
384
+ activate_layout = QHBoxLayout()
385
+ activate_layout.addWidget(self.activate_haralick_btn, 80)
386
+ activate_layout.addWidget(self.haralick_hist_btn, 10)
387
+ activate_layout.addWidget(self.haralick_digit_btn, 10)
388
+ self.haralick_layout.addLayout(activate_layout)
389
+
390
+ channel_layout = QHBoxLayout()
391
+ channel_layout.addWidget(self.haralick_channel_lbl, 40)
392
+ channel_layout.addWidget(self.haralick_channel_choice, 60)
393
+ self.haralick_layout.addLayout(channel_layout)
394
+
395
+ distance_layout = QHBoxLayout()
396
+ distance_layout.addWidget(self.haralick_distance_lbl, 40)
397
+ distance_layout.addWidget(self.haralick_distance_le, 60)
398
+ self.haralick_layout.addLayout(distance_layout)
399
+
400
+ gl_layout = QHBoxLayout()
401
+ gl_layout.addWidget(self.haralick_n_gray_levels_lbl, 40)
402
+ gl_layout.addWidget(self.haralick_n_gray_levels_le, 60)
403
+ self.haralick_layout.addLayout(gl_layout)
404
+
405
+ slider_layout = QHBoxLayout()
406
+ slider_layout.addWidget(self.haralick_scale_lbl, 40)
407
+ slider_layout.addWidget(self.haralick_scale_slider, 60)
408
+ self.haralick_layout.addLayout(slider_layout)
409
+
410
+ slider_min_percentile_layout = QHBoxLayout()
411
+ slider_min_percentile_layout.addWidget(self.haralick_percentile_min_lbl, 40)
412
+ # slider_min_percentile_layout.addWidget(self.haralick_percentile_min_le,55)
413
+ slider_min_percentile_layout.addLayout(min_percentile_hbox, 60)
414
+ # slider_min_percentile_layout.addWidget(self.haralick_normalization_mode_btn, 5)
415
+ self.haralick_layout.addLayout(slider_min_percentile_layout)
416
+
417
+ slider_max_percentile_layout = QHBoxLayout()
418
+ slider_max_percentile_layout.addWidget(self.haralick_percentile_max_lbl, 40)
419
+ slider_max_percentile_layout.addWidget(self.haralick_percentile_max_le, 60)
420
+ self.haralick_layout.addLayout(slider_max_percentile_layout)
421
+
422
+ self.haralick_to_hide = [self.haralick_hist_btn, self.haralick_digit_btn, self.haralick_channel_lbl,
423
+ self.haralick_channel_choice,
424
+ self.haralick_distance_le, self.haralick_distance_lbl, self.haralick_n_gray_levels_le,
425
+ self.haralick_n_gray_levels_lbl,
426
+ self.haralick_scale_lbl, self.haralick_scale_slider, self.haralick_percentile_min_lbl,
427
+ self.haralick_percentile_min_le,
428
+ self.haralick_percentile_max_lbl, self.haralick_percentile_max_le,
429
+ self.haralick_normalization_mode_btn]
430
+
431
+ self.features_to_disable = [self.feature_lbl, self.del_feature_btn, self.add_feature_btn, self.features_list,
432
+ self.activate_haralick_btn]
433
+
434
+ self.activate_haralick_btn.setChecked(False)
435
+ for f in self.haralick_to_hide:
436
+ f.setEnabled(False)
437
+
438
+ self.haralick_normalization_mode_btn.clicked.connect(self.switch_to_absolute_normalization_mode)
439
+ layout.addLayout(self.haralick_layout)
440
+
441
+ def switch_to_absolute_normalization_mode(self):
442
+
443
+ if self.percentile_mode:
444
+ self.percentile_mode = False
445
+ self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle_outline, color="black"))
446
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
447
+ self.haralick_normalization_mode_btn.setToolTip("Switch to percentile normalization values.")
448
+ self.haralick_percentile_min_lbl.setText('Min value: ')
449
+ self.haralick_percentile_max_lbl.setText('Max value: ')
450
+ self.haralick_percentile_min_le.setText('0')
451
+ self.haralick_percentile_max_le.setText('10000')
452
+
453
+ else:
454
+ self.percentile_mode = True
455
+ self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
456
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
457
+ self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
458
+ self.haralick_percentile_min_lbl.setText('Min percentile: ')
459
+ self.haralick_percentile_max_lbl.setText('Max percentile: ')
460
+ self.haralick_percentile_min_le.setText('0.01')
461
+ self.haralick_percentile_max_le.setText('99.99')
462
+
463
+ def show_haralick_options(self):
464
+
465
+ """
466
+ Show the Haralick texture options.
467
+ """
468
+
469
+ if self.activate_haralick_btn.isChecked():
470
+ for element in self.haralick_to_hide:
471
+ element.setEnabled(True)
472
+ else:
473
+ for element in self.haralick_to_hide:
474
+ element.setEnabled(False)
475
+
476
+ def adjustScrollArea(self):
477
+
478
+ """
479
+ Auto-adjust scroll area to fill space
480
+ (from https://stackoverflow.com/questions/66417576/make-qscrollarea-use-all-available-space-of-qmainwindow-height-axis)
481
+ """
482
+
483
+ step = 5
484
+ while self.scroll_area.verticalScrollBar().isVisible() and self.height() < self.maximumHeight():
485
+ self.resize(self.width(), self.height() + step)
486
+
487
+ def write_instructions(self):
488
+
489
+ """
490
+ Write the selected options in a json file for later reading by the software.
491
+ """
492
+
493
+ print('Writing instructions...')
494
+ measurement_options = {}
495
+ background_correction = self.background_correction
496
+ if not background_correction:
497
+ background_correction = None
498
+ measurement_options.update({'background_correction': background_correction})
499
+ features = self.features_list.getItems()
500
+ if not features:
501
+ features = None
502
+ measurement_options.update({'features': features})
503
+
504
+ border_distances = self.contours_list.getItems()
505
+ if not border_distances:
506
+ border_distances = None
507
+ measurement_options.update({'border_distances': border_distances})
508
+ # radial_intensity = {}
509
+ # radial_step = int(self.step_size.text())
510
+ # radial_channels = []
511
+ # for checkbox in self.channel_chechkboxes:
512
+ # if checkbox.isChecked():
513
+ # radial_channels.append(checkbox.text())
514
+ # radial_intensity={'radial_step': radial_step, 'radial_channels': radial_channels}
515
+ # if not self.radial_intensity_btn.isChecked():
516
+ # radial_intensity = None
517
+ # measurement_options.update({'radial_intensity' : radial_intensity})
518
+
519
+ self.extract_haralick_options()
520
+ measurement_options.update({'haralick_options': self.haralick_options})
521
+
522
+ intensity_measurement_radii = self.radii_list.getItems()
523
+ if not intensity_measurement_radii:
524
+ intensity_measurement_radii = None
525
+
526
+ isotropic_operations = self.operations_list.getItems()
527
+ if not isotropic_operations:
528
+ isotropic_operations = None
529
+ intensity_measurement_radii = None
530
+ measurement_options.update({'intensity_measurement_radii': intensity_measurement_radii,
531
+ 'isotropic_operations': isotropic_operations})
532
+ spot_detection = None
533
+ if self.spot_check.isChecked():
534
+ spot_detection = {'channel': self.spot_channel.currentText(), 'diameter': float(self.diameter_value.text().replace(',','.')),
535
+ 'threshold': float(self.threshold_value.text().replace(',','.'))}
536
+ measurement_options.update({'spot_detection': spot_detection})
537
+ if self.clear_previous_btn.isChecked():
538
+ self.clear_previous = True
539
+ else:
540
+ self.clear_previous = False
541
+ measurement_options.update({'clear_previous': self.clear_previous})
542
+
543
+
544
+
545
+ print('Measurement instructions: ', measurement_options)
546
+ file_name = self.measure_instructions_path
547
+ with open(file_name, 'w') as f:
548
+ json.dump(measurement_options, f, indent=4)
549
+
550
+ print('Done.')
551
+ self.close()
552
+
553
+ def extract_haralick_options(self):
554
+
555
+ if self.activate_haralick_btn.isChecked():
556
+ self.haralick_options = {"target_channel": self.haralick_channel_choice.currentIndex(),
557
+ "scale_factor": float(self.haralick_scale_slider.value()),
558
+ "n_intensity_bins": int(self.haralick_n_gray_levels_le.text()),
559
+ "distance": int(self.haralick_distance_le.text()),
560
+ }
561
+ if self.percentile_mode:
562
+ self.haralick_options.update({"percentiles": (
563
+ float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text())),
564
+ "clip_values": None})
565
+ else:
566
+ self.haralick_options.update({"percentiles": None, "clip_values": (
567
+ float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text()))})
568
+
569
+ else:
570
+ self.haralick_options = None
571
+
572
+ def load_previous_measurement_instructions(self):
573
+
574
+ """
575
+ Read the measurmeent options from a previously written json file and format properly for the UI.
576
+ """
577
+
578
+ print('Reading instructions..')
579
+ if os.path.exists(self.measure_instructions_path):
580
+ with open(self.measure_instructions_path, 'r') as f:
581
+ measurement_instructions = json.load(f)
582
+ print(measurement_instructions)
583
+ if 'background_correction' in measurement_instructions:
584
+ self.background_correction = measurement_instructions['background_correction']
585
+ if (self.background_correction is not None) and len(self.background_correction) > 0:
586
+ self.normalisation_list.clear()
587
+ for norm_params in self.background_correction:
588
+ normalisation_description = ""
589
+ for index, (key, value) in enumerate(norm_params.items()):
590
+ if index > 0:
591
+ normalisation_description += ", "
592
+ normalisation_description += str(key) + " : " + str(value)
593
+ self.normalisation_list.addItem(normalisation_description)
594
+ else:
595
+ self.normalisation_list.clear()
596
+ if 'features' in measurement_instructions:
597
+ features = measurement_instructions['features']
598
+ if (features is not None) and len(features) > 0:
599
+ self.features_list.list_widget.clear()
600
+ self.features_list.list_widget.addItems(features)
601
+ else:
602
+ self.features_list.list_widget.clear()
603
+
604
+ if 'spot_detection' in measurement_instructions:
605
+ spot_detection = measurement_instructions['spot_detection']
606
+ if spot_detection is not None:
607
+ self.spot_check.setChecked(True)
608
+ if 'channel' in spot_detection:
609
+ idx = spot_detection['channel']
610
+ self.spot_channel.setCurrentText(idx)
611
+ self.diameter_value.setText(str(spot_detection['diameter']))
612
+ self.threshold_value.setText(str(spot_detection['threshold']))
613
+
614
+
615
+ if 'border_distances' in measurement_instructions:
616
+ border_distances = measurement_instructions['border_distances']
617
+ if border_distances is not None:
618
+ if isinstance(border_distances, int):
619
+ distances = [border_distances]
620
+ elif isinstance(border_distances, list):
621
+ distances = []
622
+ for d in border_distances:
623
+ if isinstance(d, int) | isinstance(d, float):
624
+ distances.append(str(int(d)))
625
+ elif isinstance(d, list):
626
+ distances.append(str(int(d[0])) + '-' + str(int(d[1])))
627
+ self.contours_list.list_widget.clear()
628
+ self.contours_list.list_widget.addItems(distances)
629
+
630
+ if 'haralick_options' in measurement_instructions:
631
+ haralick_options = measurement_instructions['haralick_options']
632
+ if haralick_options is None:
633
+ self.activate_haralick_btn.setChecked(False)
634
+ self.show_haralick_options()
635
+ else:
636
+ self.activate_haralick_btn.setChecked(True)
637
+ self.show_haralick_options()
638
+ if 'target_channel' in haralick_options:
639
+ idx = haralick_options['target_channel']
640
+ self.haralick_channel_choice.setCurrentIndex(idx)
641
+ if 'scale_factor' in haralick_options:
642
+ self.haralick_scale_slider.setValue(float(haralick_options['scale_factor']))
643
+ if ('percentiles' in haralick_options) and (haralick_options['percentiles'] is not None):
644
+ perc = list(haralick_options['percentiles'])
645
+ self.haralick_percentile_min_le.setText(str(perc[0]))
646
+ self.haralick_percentile_max_le.setText(str(perc[1]))
647
+ if ('clip_values' in haralick_options) and (haralick_options['clip_values'] is not None):
648
+ values = list(haralick_options['clip_values'])
649
+ self.haralick_percentile_min_le.setText(str(values[0]))
650
+ self.haralick_percentile_max_le.setText(str(values[1]))
651
+ self.percentile_mode = True
652
+ self.switch_to_absolute_normalization_mode()
653
+ if 'n_intensity_bins' in haralick_options:
654
+ self.haralick_n_gray_levels_le.setText(str(haralick_options['n_intensity_bins']))
655
+ if 'distance' in haralick_options:
656
+ self.haralick_distance_le.setText(str(haralick_options['distance']))
657
+
658
+ if 'intensity_measurement_radii' in measurement_instructions:
659
+ intensity_measurement_radii = measurement_instructions['intensity_measurement_radii']
660
+ if intensity_measurement_radii is not None:
661
+ if isinstance(intensity_measurement_radii, int):
662
+ radii = [intensity_measurement_radii]
663
+ elif isinstance(intensity_measurement_radii, list):
664
+ radii = []
665
+ for r in intensity_measurement_radii:
666
+ if isinstance(r, int) | isinstance(r, float):
667
+ radii.append(str(int(r)))
668
+ elif isinstance(r, list):
669
+ radii.append(str(int(r[0])) + '-' + str(int(r[1])))
670
+ self.radii_list.list_widget.clear()
671
+ self.radii_list.list_widget.addItems(radii)
672
+ else:
673
+ self.radii_list.list_widget.clear()
674
+
675
+ if 'isotropic_operations' in measurement_instructions:
676
+ isotropic_operations = measurement_instructions['isotropic_operations']
677
+ if (isotropic_operations is not None) and len(isotropic_operations) > 0:
678
+ self.operations_list.list_widget.clear()
679
+ self.operations_list.list_widget.addItems(isotropic_operations)
680
+ else:
681
+ self.operations_list.list_widget.clear()
682
+
683
+ # if 'radial_intensity' in measurement_instructions:
684
+ # radial_intensity = measurement_instructions['radial_intensity']
685
+ # if radial_intensity is not None:
686
+ # self.radial_intensity_btn.setChecked(True)
687
+ # self.step_size.setText(str(radial_intensity['radial_step']))
688
+ # self.step_size.setEnabled(True)
689
+ # self.step_lbl.setEnabled(True)
690
+ # for checkbox in self.channel_chechkboxes:
691
+ # checkbox.setEnabled(True)
692
+ # if checkbox.text() in radial_intensity['radial_channels']:
693
+ # checkbox.setChecked(True)
694
+
695
+ if 'clear_previous' in measurement_instructions:
696
+ self.clear_previous = measurement_instructions['clear_previous']
697
+ self.clear_previous_btn.setChecked(self.clear_previous)
698
+
699
+ def locate_image(self):
700
+
701
+ """
702
+ Load the first frame of the first movie found in the experiment folder as a sample.
703
+ """
704
+
705
+ movies = glob(self.parent.parent.pos + f"movie/{self.parent.parent.movie_prefix}*.tif")
706
+ print(movies)
707
+ if len(movies) == 0:
708
+ msgBox = QMessageBox()
709
+ msgBox.setIcon(QMessageBox.Warning)
710
+ msgBox.setText("No movies are detected in the experiment folder. Cannot load an image...")
711
+ msgBox.setWindowTitle("Warning")
712
+ msgBox.setStandardButtons(QMessageBox.Ok)
713
+ returnValue = msgBox.exec()
714
+ if returnValue == QMessageBox.Ok:
715
+ self.test_frame = None
716
+ return None
717
+ else:
718
+ self.stack0 = movies[0]
719
+ n_channels = len(self.channels)
720
+ len_movie_auto = auto_load_number_of_frames(self.stack0)
721
+ if len_movie_auto is None:
722
+ stack = imread(self.stack0)
723
+ len_movie_auto = len(stack)
724
+ del stack
725
+ gc.collect()
726
+ self.mid_time = len_movie_auto // 2
727
+ self.test_frame = load_frames(n_channels * self.mid_time + np.arange(n_channels), self.stack0, scale=None,
728
+ normalize_input=False)
729
+
730
+ def control_haralick_digitalization(self):
731
+
732
+ """
733
+ Load an image for the first experiment movie found.
734
+ Apply the Haralick parameters and check the result of the digitization (normalization + binning of intensities).
735
+
736
+ """
737
+
738
+ self.locate_image()
739
+ self.extract_haralick_options()
740
+ if self.test_frame is not None:
741
+ digitized_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
742
+ channels=self.channel_names, return_digit_image_only=True,
743
+ **self.haralick_options
744
+ )
745
+
746
+ self.fig, self.ax = plt.subplots()
747
+ divider = make_axes_locatable(self.ax)
748
+ cax = divider.append_axes('right', size='5%', pad=0.05)
749
+
750
+ self.imshow_digit_window = FigureCanvas(self.fig, title="Haralick: control digitization")
751
+ self.ax.clear()
752
+ im = self.ax.imshow(digitized_img, cmap='gray')
753
+ self.fig.colorbar(im, cax=cax, orientation='vertical')
754
+ self.ax.set_xticks([])
755
+ self.ax.set_yticks([])
756
+ self.fig.set_facecolor('none') # or 'None'
757
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
758
+ self.imshow_digit_window.canvas.draw()
759
+ self.imshow_digit_window.show()
760
+
761
+ def control_haralick_intensity_histogram(self):
762
+
763
+ """
764
+ Load an image for the first experiment movie found.
765
+ Apply the Haralick normalization parameters and check the normalized intensity histogram.
766
+
767
+ """
768
+
769
+ self.locate_image()
770
+ self.extract_haralick_options()
771
+ if self.test_frame is not None:
772
+ norm_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
773
+ channels=self.channel_names, return_norm_image_only=True,
774
+ **self.haralick_options
775
+ )
776
+ self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
777
+ self.hist_window = FigureCanvas(self.fig, title="Haralick: control digitized histogram")
778
+ self.ax.clear()
779
+ self.ax.hist(norm_img.flatten(), bins=self.haralick_options['n_intensity_bins'])
780
+ self.ax.set_xlabel('gray level value')
781
+ self.ax.set_ylabel('#')
782
+ plt.tight_layout()
783
+ self.fig.set_facecolor('none') # or 'None'
784
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
785
+ self.hist_window.canvas.draw()
786
+ self.hist_window.show()
787
+
788
+ def view_selected_contour(self):
789
+
790
+ """
791
+ Show the ROI for the selected contour measurement on experimental data.
792
+
793
+ """
794
+
795
+ if self.parent.parent.position_list.currentText() == '*':
796
+ msgBox = QMessageBox()
797
+ msgBox.setIcon(QMessageBox.Warning)
798
+ msgBox.setText("Please select a single position to visualize the border selection.")
799
+ msgBox.setWindowTitle("Warning")
800
+ msgBox.setStandardButtons(QMessageBox.Ok)
801
+ returnValue = msgBox.exec()
802
+ if returnValue == QMessageBox.Ok:
803
+ return None
804
+ else:
805
+ return None
806
+
807
+ self.locate_image()
808
+
809
+ self.locate_mask()
810
+ if self.test_mask is None:
811
+ msgBox = QMessageBox()
812
+ msgBox.setIcon(QMessageBox.Warning)
813
+ msgBox.setText("The segmentation results could not be found for this position.")
814
+ msgBox.setWindowTitle("Warning")
815
+ msgBox.setStandardButtons(QMessageBox.Ok)
816
+ returnValue = msgBox.exec()
817
+ if returnValue == QMessageBox.Yes:
818
+ return None
819
+ else:
820
+ return None
821
+ # plt.imshow(self.test_frame[:,:,0])
822
+ # plt.pause(2)
823
+ # plt.close()
824
+
825
+ # plt.imshow(self.test_mask)
826
+ # plt.pause(2)
827
+ # plt.close()
828
+
829
+ if (self.test_frame is not None) and (self.test_mask is not None):
830
+
831
+ values = self.contours_list.list_widget.selectedItems()
832
+ if len(values) > 0:
833
+ distance = values[0].text()
834
+ if '-' in distance:
835
+ if distance[0] != '-':
836
+ border_dist = distance.split('-')
837
+ border_dist = [float(d) for d in border_dist]
838
+ else:
839
+ border_dist = float(distance)
840
+ elif distance.isnumeric():
841
+ border_dist = float(distance)
842
+
843
+ print(border_dist)
844
+ border_label = contour_of_instance_segmentation(self.test_mask, border_dist)
845
+
846
+ self.fig_contour, self.ax_contour = plt.subplots(figsize=(5, 5))
847
+ self.imshow_contour = FigureCanvas(self.fig_contour, title="Contour measurement", interactive=True)
848
+ self.ax_contour.clear()
849
+ self.im_contour = self.ax_contour.imshow(self.test_frame[:, :, 0], cmap='gray')
850
+ self.im_mask = self.ax_contour.imshow(np.ma.masked_where(border_label == 0, border_label),
851
+ cmap='viridis', interpolation='none')
852
+ self.ax_contour.set_xticks([])
853
+ self.ax_contour.set_yticks([])
854
+ self.ax_contour.set_title(border_dist)
855
+ self.fig_contour.set_facecolor('none') # or 'None'
856
+ self.fig_contour.canvas.setStyleSheet("background-color: transparent;")
857
+ self.imshow_contour.canvas.draw()
858
+
859
+ self.imshow_contour.layout.setContentsMargins(30, 30, 30, 30)
860
+ self.channel_hbox_contour = QHBoxLayout()
861
+ self.channel_hbox_contour.addWidget(QLabel('channel: '), 10)
862
+ self.channel_cb_contour = QComboBox()
863
+ self.channel_cb_contour.addItems(self.channel_names)
864
+ self.channel_cb_contour.currentIndexChanged.connect(self.switch_channel_contour)
865
+ self.channel_hbox_contour.addWidget(self.channel_cb_contour, 90)
866
+ self.imshow_contour.layout.addLayout(self.channel_hbox_contour)
867
+
868
+ self.contrast_hbox_contour = QHBoxLayout()
869
+ self.contrast_hbox_contour.addWidget(QLabel('contrast: '), 10)
870
+ self.contrast_slider_contour = QLabeledDoubleRangeSlider()
871
+ self.contrast_slider_contour.setSingleStep(0.00001)
872
+ self.contrast_slider_contour.setTickInterval(0.00001)
873
+ self.contrast_slider_contour.setOrientation(1)
874
+ self.contrast_slider_contour.setRange(np.amin(self.test_frame[:, :, 0]),
875
+ np.amax(self.test_frame[:, :, 0]))
876
+ self.contrast_slider_contour.setValue([np.percentile(self.test_frame[:, :, 0].flatten(), 1),
877
+ np.percentile(self.test_frame[:, :, 0].flatten(), 99.99)])
878
+ self.im_contour.set_clim(vmin=np.percentile(self.test_frame[:, :, 0].flatten(), 1),
879
+ vmax=np.percentile(self.test_frame[:, :, 0].flatten(), 99.99))
880
+ self.contrast_slider_contour.valueChanged.connect(self.contrast_im_contour)
881
+ self.contrast_hbox_contour.addWidget(self.contrast_slider_contour, 90)
882
+ self.imshow_contour.layout.addLayout(self.contrast_hbox_contour)
883
+
884
+ self.alpha_mask_hbox_contour = QHBoxLayout()
885
+ self.alpha_mask_hbox_contour.addWidget(QLabel('mask transparency: '), 10)
886
+ self.transparency_slider = QLabeledDoubleSlider()
887
+ self.transparency_slider.setSingleStep(0.001)
888
+ self.transparency_slider.setTickInterval(0.001)
889
+ self.transparency_slider.setOrientation(1)
890
+ self.transparency_slider.setRange(0, 1)
891
+ self.transparency_slider.setValue(0.5)
892
+ self.transparency_slider.valueChanged.connect(self.make_contour_transparent)
893
+ self.alpha_mask_hbox_contour.addWidget(self.transparency_slider, 90)
894
+ self.imshow_contour.layout.addLayout(self.alpha_mask_hbox_contour)
895
+
896
+ self.imshow_contour.show()
897
+
898
+ else:
899
+ msgBox = QMessageBox()
900
+ msgBox.setIcon(QMessageBox.Warning)
901
+ msgBox.setText("No contour was selected. Please first add a contour to the list.")
902
+ msgBox.setWindowTitle("Warning")
903
+ msgBox.setStandardButtons(QMessageBox.Ok)
904
+ returnValue = msgBox.exec()
905
+ if returnValue == QMessageBox.Yes:
906
+ return None
907
+
908
+ def locate_mask(self):
909
+
910
+ """
911
+ Load the first mask of the detected movie.
912
+ """
913
+
914
+ labels_path = str(Path(self.stack0).parent.parent) + f'/labels_{self.mode}/'
915
+ masks = natsorted(glob(labels_path + '*.tif'))
916
+ if len(masks) == 0:
917
+ print('no mask found')
918
+ self.test_mask = None
919
+ else:
920
+ self.test_mask = imread(masks[self.mid_time])
921
+
922
+ def switch_channel_contour(self, value):
923
+
924
+ """
925
+ Adjust intensity values when changing channels in the contour visualizer.
926
+
927
+ """
928
+
929
+ self.im_contour.set_array(self.test_frame[:, :, value])
930
+ self.im_contour.set_clim(vmin=np.percentile(self.test_frame[:, :, value].flatten(), 1),
931
+ vmax=np.percentile(self.test_frame[:, :, value].flatten(), 99.99))
932
+ self.contrast_slider_contour.setRange(np.amin(self.test_frame[:, :, value]),
933
+ np.amax(self.test_frame[:, :, value]))
934
+ self.contrast_slider_contour.setValue([np.percentile(self.test_frame[:, :, value].flatten(), 1),
935
+ np.percentile(self.test_frame[:, :, value].flatten(), 99.99)])
936
+ self.fig_contour.canvas.draw_idle()
937
+
938
+ def contrast_im_contour(self, value):
939
+ vmin = value[0]
940
+ vmax = value[1]
941
+ self.im_contour.set_clim(vmin=vmin, vmax=vmax)
942
+ self.fig_contour.canvas.draw_idle()
943
+
944
+ def make_contour_transparent(self, value):
945
+
946
+ self.im_mask.set_alpha(value)
947
+ self.fig_contour.canvas.draw_idle()
948
+
949
+ def populate_normalisation_tabs(self):
950
+ layout = QVBoxLayout(self.normalisation_frame)
951
+ self.normalisation_lbl = QLabel("BACKGROUND CORRECTION")
952
+ self.normalisation_lbl.setStyleSheet("""
953
+ font-weight: bold;
954
+ padding: 0px;
955
+ """)
956
+ layout.addWidget(self.normalisation_lbl, alignment=Qt.AlignCenter)
957
+ self.tabs = QTabWidget()
958
+ self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
959
+ self.tab1 = QWidget()
960
+ self.tab2 = QWidget()
961
+ self.normalisation_list = QListWidget()
962
+ self.tabs.addTab(self.tab1, 'Local')
963
+ self.tabs.addTab(self.tab2, 'Field')
964
+ self.tab1.setLayout(self.populate_local_norm_tab())
965
+ self.tab2.setLayout(self.populate_field_norm_tab())
966
+ layout.addWidget(self.tabs)
967
+ self.norm_list_lbl = QLabel('Background correction to perform:')
968
+ hbox = QHBoxLayout()
969
+ hbox.addWidget(self.norm_list_lbl)
970
+ self.del_norm_btn = QPushButton("")
971
+ self.del_norm_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
972
+ self.del_norm_btn.setIcon(icon(MDI6.trash_can, color="black"))
973
+ self.del_norm_btn.setToolTip("Remove background correction")
974
+ self.del_norm_btn.setIconSize(QSize(20, 20))
975
+ hbox.addWidget(self.del_norm_btn, alignment=Qt.AlignRight)
976
+ layout.addLayout(hbox)
977
+ self.del_norm_btn.clicked.connect(self.remove_item_from_list)
978
+ layout.addWidget(self.normalisation_list)
979
+
980
+ def populate_local_norm_tab(self):
981
+ tab1_layout = QGridLayout(self.tab1)
982
+
983
+ channel_lbl = QLabel()
984
+ channel_lbl.setText("Channel: ")
985
+ tab1_layout.addWidget(channel_lbl, 0, 0)
986
+
987
+ self.tab1_channel_dropdown = QComboBox()
988
+ self.tab1_channel_dropdown.addItems(self.channel_names)
989
+ tab1_layout.addWidget(self.tab1_channel_dropdown, 0, 1)
990
+
991
+ tab1_lbl = QLabel()
992
+ tab1_lbl.setText("Outer distance (in px): ")
993
+ tab1_layout.addWidget(tab1_lbl, 1, 0)
994
+
995
+ self.tab1_txt_distance = QLineEdit()
996
+ tab1_layout.addWidget(self.tab1_txt_distance, 1, 1)
997
+
998
+ self.tab1_vis_brdr = QPushButton()
999
+ self.tab1_vis_brdr.setStyleSheet(self.parent.parent.parent.button_select_all)
1000
+ self.tab1_vis_brdr.setIcon(icon(MDI6.eye_outline, color="black"))
1001
+ self.tab1_vis_brdr.setToolTip("View contour")
1002
+ self.tab1_vis_brdr.setIconSize(QSize(20, 20))
1003
+ self.tab1_vis_brdr.clicked.connect(self.view_normalisation_contour)
1004
+ tab1_layout.addWidget(self.tab1_vis_brdr, 1, 2)
1005
+
1006
+ tab1_lbl_type = QLabel()
1007
+ tab1_lbl_type.setText("Type: ")
1008
+ tab1_layout.addWidget(tab1_lbl_type, 2, 0)
1009
+
1010
+ self.tab1_dropdown = QComboBox()
1011
+ self.tab1_dropdown.addItem("Mean")
1012
+ self.tab1_dropdown.addItem("Median")
1013
+ tab1_layout.addWidget(self.tab1_dropdown, 2, 1)
1014
+
1015
+ self.tab1_subtract = QRadioButton('Subtract')
1016
+ self.tab1_divide = QRadioButton('Divide')
1017
+ tab1_layout.addWidget(self.tab1_subtract, 3, 0, alignment=Qt.AlignRight)
1018
+ tab1_layout.addWidget(self.tab1_divide, 3, 1, alignment=Qt.AlignRight)
1019
+
1020
+ tab1_submit = QPushButton()
1021
+ tab1_submit.setText('Add channel')
1022
+ tab1_submit.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
1023
+ tab1_layout.addWidget(tab1_submit, 4, 0, 1, 3)
1024
+ tab1_submit.clicked.connect(self.add_item_to_list)
1025
+
1026
+ return tab1_layout
1027
+
1028
+ def populate_field_norm_tab(self):
1029
+
1030
+ tab2_layout = QGridLayout(self.tab2)
1031
+
1032
+ channel_lbl = QLabel()
1033
+ channel_lbl.setText("Channel: ")
1034
+ tab2_layout.addWidget(channel_lbl, 0, 0)
1035
+
1036
+ self.tab2_channel_dropdown = QComboBox()
1037
+ self.tab2_channel_dropdown.addItems(self.channel_names)
1038
+ tab2_layout.addWidget(self.tab2_channel_dropdown, 0, 1)
1039
+
1040
+ tab2_lbl = QLabel()
1041
+ tab2_lbl.setText("std threshold: ")
1042
+ tab2_layout.addWidget(tab2_lbl, 1, 0)
1043
+
1044
+ self.tab2_txt_threshold = QLineEdit()
1045
+ tab2_layout.addWidget(self.tab2_txt_threshold, 1, 1)
1046
+
1047
+ self.norm_digit_btn = QPushButton()
1048
+ self.norm_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
1049
+ self.norm_digit_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1050
+
1051
+ self.norm_digit_btn.clicked.connect(self.show_threshold_visual)
1052
+ tab2_layout.addWidget(self.norm_digit_btn, 1, 2)
1053
+
1054
+ tab2_lbl_type = QLabel()
1055
+ tab2_lbl_type.setText("Type: ")
1056
+ tab2_layout.addWidget(tab2_lbl_type, 2, 0)
1057
+
1058
+ self.tab2_dropdown = QComboBox()
1059
+ self.tab2_dropdown.addItems(["Paraboloid", "Plane"])
1060
+ tab2_layout.addWidget(self.tab2_dropdown, 2, 1)
1061
+
1062
+ self.tab2_subtract = QRadioButton('Subtract')
1063
+ self.tab2_divide = QRadioButton('Divide')
1064
+ self.tab2_sd_btn_group = QButtonGroup(self)
1065
+ self.tab2_sd_btn_group.addButton(self.tab2_subtract)
1066
+ self.tab2_sd_btn_group.addButton(self.tab2_divide)
1067
+ tab2_layout.addWidget(self.tab2_subtract, 3, 0, alignment=Qt.AlignRight)
1068
+ tab2_layout.addWidget(self.tab2_divide, 3, 1, alignment=Qt.AlignRight)
1069
+
1070
+ self.tab2_clip = QRadioButton('Clip')
1071
+ self.tab2_no_clip = QRadioButton("Don't clip")
1072
+ self.tab2_clip_group = QButtonGroup(self)
1073
+ self.tab2_clip_group.addButton(self.tab2_clip)
1074
+ self.tab2_clip_group.addButton(self.tab2_no_clip)
1075
+ self.tab2_clip.setEnabled(False)
1076
+ self.tab2_no_clip.setEnabled(False)
1077
+ tab2_layout.addWidget(self.tab2_clip, 4, 0, alignment=Qt.AlignLeft)
1078
+ tab2_layout.addWidget(self.tab2_no_clip, 4, 1, alignment=Qt.AlignLeft)
1079
+ self.tab2_subtract.toggled.connect(self.show_clipping_options)
1080
+ self.tab2_divide.toggled.connect(self.show_clipping_options)
1081
+
1082
+ self.view_norm_btn = QPushButton("")
1083
+ self.view_norm_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1084
+ self.view_norm_btn.setIcon(icon(MDI6.eye_outline, color="black"))
1085
+ self.view_norm_btn.setToolTip("View corrected image")
1086
+ self.view_norm_btn.setIconSize(QSize(20, 20))
1087
+ self.view_norm_btn.clicked.connect(self.preview_normalisation)
1088
+ tab2_layout.addWidget(self.view_norm_btn, 4, 2)
1089
+
1090
+ tab2_submit = QPushButton()
1091
+ tab2_submit.setText('Add channel')
1092
+ tab2_submit.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
1093
+ tab2_layout.addWidget(tab2_submit, 5, 0, 1, 3)
1094
+ tab2_submit.clicked.connect(self.add_item_to_list)
1095
+
1096
+ return tab2_layout
1097
+
1098
+ def show_threshold_visual(self):
1099
+ min_threshold = self.tab2_txt_threshold.text()
1100
+ if min_threshold == "":
1101
+ min_threshold = 0
1102
+ current_channel = self.tab2_channel_dropdown.currentIndex()
1103
+ self.threshold_visual = ThresholdNormalisation(min_threshold=float(min_threshold),
1104
+ current_channel=current_channel, parent=self)
1105
+
1106
+ def show_clipping_options(self):
1107
+ if self.tab2_subtract.isChecked():
1108
+ for button in self.tab2_clip_group.buttons():
1109
+ button.setEnabled(True)
1110
+ if self.tab2_divide.isChecked():
1111
+ self.tab2_clip_group.setExclusive(False)
1112
+ for button in self.tab2_clip_group.buttons():
1113
+ button.setChecked(False)
1114
+ button.setEnabled(False)
1115
+ self.tab2_clip_group.setExclusive(True)
1116
+
1117
+ def add_item_to_list(self):
1118
+ check = self.check_the_information()
1119
+ if check is True:
1120
+
1121
+ if self.tabs.currentIndex() == 0:
1122
+
1123
+ norm_params = {"target channel": self.tab1_channel_dropdown.currentText(), "mode": "local",
1124
+ "distance": self.tab1_txt_distance.text(),
1125
+ "type": self.tab1_dropdown.currentText()}
1126
+ if self.tab1_subtract.isChecked():
1127
+ norm_params["operation"] = "Subtract"
1128
+ else:
1129
+ norm_params["operation"] = "Divide"
1130
+ elif self.tabs.currentIndex() == 1:
1131
+ norm_params = {"target channel": self.tab2_channel_dropdown.currentText(), "mode": "field",
1132
+ "threshold": self.tab2_txt_threshold.text(),
1133
+ "type": self.tab2_dropdown.currentText()}
1134
+ if self.tab2_subtract.isChecked():
1135
+ norm_params["operation"] = "Subtract"
1136
+ if self.tab2_clip.isChecked():
1137
+ norm_params["clip"] = True
1138
+ else:
1139
+ norm_params["clip"] = False
1140
+ elif self.tab2_divide.isChecked():
1141
+ norm_params["operation"] = "Divide"
1142
+
1143
+ self.background_correction.append(norm_params)
1144
+ normalisation_description = ""
1145
+ for index, (key, value) in enumerate(norm_params.items()):
1146
+ if index > 0:
1147
+ normalisation_description += ", "
1148
+ normalisation_description += str(key) + " : " + str(value)
1149
+ self.normalisation_list.addItem(normalisation_description)
1150
+
1151
+ def remove_item_from_list(self):
1152
+ current_item = self.normalisation_list.currentRow()
1153
+ if current_item > -1:
1154
+ del self.background_correction[current_item]
1155
+ self.normalisation_list.takeItem(current_item)
1156
+
1157
+ def check_the_information(self):
1158
+ if self.tabs.currentIndex() == 0:
1159
+ if self.background_correction is None:
1160
+ self.background_correction = []
1161
+ for index, normalisation_opt in enumerate(self.background_correction ):
1162
+ if self.tab1_channel_dropdown.currentText() in normalisation_opt['target channel']:
1163
+ result = self.channel_already_in_list()
1164
+ if result != QMessageBox.Yes:
1165
+ return False
1166
+ else:
1167
+ self.background_correction .remove(normalisation_opt)
1168
+ self.normalisation_list.takeItem(index)
1169
+ return True
1170
+ if self.tab1_txt_distance.text() == "":
1171
+ self.display_message_box('provide the outer distance')
1172
+ return False
1173
+ if not self.tab1_subtract.isChecked() and not self.tab1_divide.isChecked():
1174
+ self.display_message_box('choose the operation')
1175
+ return False
1176
+ else:
1177
+ return True
1178
+
1179
+ elif self.tabs.currentIndex() == 1:
1180
+ if self.background_correction is None:
1181
+ self.background_correction = []
1182
+ for index, normalisation_opt in enumerate(self.background_correction ):
1183
+ if self.tab2_channel_dropdown.currentText() in normalisation_opt['target channel']:
1184
+ result = self.channel_already_in_list()
1185
+ if result != QMessageBox.Yes:
1186
+ return False
1187
+ else:
1188
+ self.background_correction .remove(normalisation_opt)
1189
+ self.normalisation_list.takeItem(index)
1190
+ return True
1191
+ if self.tab2_txt_threshold.text() == "":
1192
+ self.display_message_box('provide the threshold')
1193
+ return False
1194
+ elif not self.tab2_divide.isChecked() and not self.tab2_subtract.isChecked():
1195
+ self.display_message_box('choose subtraction or division')
1196
+ return False
1197
+ elif self.tab2_subtract.isChecked():
1198
+ if not self.tab2_clip.isChecked() and not self.tab2_no_clip.isChecked():
1199
+ self.display_message_box('provide the clipping mode')
1200
+ return False
1201
+ return True
1202
+
1203
+ def display_message_box(self, missing_info):
1204
+ QMessageBox.about(self, "Message Box Title", "Please " + missing_info + " for background correction")
1205
+
1206
+ def channel_already_in_list(self):
1207
+ response = QMessageBox.question(self, "Message Box Title",
1208
+ "The background correction parameters for this channel already exist, "
1209
+ "continuing will erase the previous configurations. Are you sure you want to "
1210
+ "proceed?",
1211
+ QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
1212
+ return response
1213
+
1214
+ def fun(self, x, y):
1215
+ return x ** 2 + y
1216
+
1217
+ def preview_normalisation(self):
1218
+ plt.close('all')
1219
+ plt.figure("Intensity Profiles",figsize=(10, 5))
1220
+ self.locate_image()
1221
+ diagonal_length = min(self.test_frame[:, :, self.tab2_channel_dropdown.currentIndex()].shape[0], self.test_frame[:, :, self.tab2_channel_dropdown.currentIndex()].shape[1])
1222
+ if self.tab2_subtract.isChecked():
1223
+ norm_operation='Subtract'
1224
+ else:
1225
+ norm_operation='Divide'
1226
+ normalised, bg_fit = field_normalisation(self.test_frame[:, :, self.tab2_channel_dropdown.currentIndex()],
1227
+ threshold=self.tab2_txt_threshold.text(),
1228
+ normalisation_operation=norm_operation,
1229
+ clip=self.tab2_clip.isChecked(),
1230
+ mode=self.tab2_dropdown.currentText())
1231
+ diagonal_original = [self.test_frame[:, :, self.tab2_channel_dropdown.currentIndex()][i, i] for i in
1232
+ range(diagonal_length)]
1233
+ diagonal_corrected = [normalised[i, i] for i in range(diagonal_length)]
1234
+ diagonal_indices = np.arange(diagonal_length)
1235
+
1236
+ plt.subplot(1, 2, 1)
1237
+ plt.plot(diagonal_indices, diagonal_original, color='black', linewidth=0.2) # Adjust linewidth here
1238
+ plt.title('Original Image')
1239
+ plt.xlabel('Pixel Index along Diagonal')
1240
+ plt.ylabel('Intensity')
1241
+
1242
+ plt.subplot(1, 2, 2)
1243
+ plt.plot(diagonal_indices, diagonal_corrected, color='black', linewidth=0.2) # Adjust linewidth here
1244
+ plt.title('Corrected Image')
1245
+ plt.xlabel('Pixel Index along Diagonal')
1246
+ plt.ylabel('Intensity')
1247
+
1248
+ plt.tight_layout()
1249
+ plt.show()
1250
+
1251
+ self.fig, self.ax = plt.subplots()
1252
+ self.normalised_img = FigureCanvas(self.fig, "Corrected background image preview")
1253
+ self.ax.clear()
1254
+ self.ax.imshow(normalised, cmap='gray')
1255
+ self.normalised_img.canvas.draw()
1256
+ self.normalised_img.show()
1257
+
1258
+ def view_normalisation_contour(self):
1259
+
1260
+ """
1261
+ Show the ROI for the selected contour measurement on experimental data.
1262
+
1263
+ """
1264
+
1265
+ if self.parent.parent.position_list.currentText() == '*':
1266
+ msgBox = QMessageBox()
1267
+ msgBox.setIcon(QMessageBox.Warning)
1268
+ msgBox.setText("Please select a single position to visualize the border selection.")
1269
+ msgBox.setWindowTitle("Warning")
1270
+ msgBox.setStandardButtons(QMessageBox.Ok)
1271
+ returnValue = msgBox.exec()
1272
+ if returnValue == QMessageBox.Ok:
1273
+ return None
1274
+ else:
1275
+ return None
1276
+
1277
+ self.locate_image()
1278
+
1279
+ self.locate_mask()
1280
+ if self.test_mask is None:
1281
+ msgBox = QMessageBox()
1282
+ msgBox.setIcon(QMessageBox.Warning)
1283
+ msgBox.setText("The segmentation results could not be found for this position.")
1284
+ msgBox.setWindowTitle("Warning")
1285
+ msgBox.setStandardButtons(QMessageBox.Ok)
1286
+ returnValue = msgBox.exec()
1287
+ if returnValue == QMessageBox.Yes:
1288
+ return None
1289
+ else:
1290
+ return None
1291
+
1292
+ if (self.test_frame is not None) and (self.test_mask is not None):
1293
+ contour = float(self.tab1_txt_distance.text())
1294
+ contour = contour * (-1)
1295
+ border_label = contour_of_instance_segmentation(self.test_mask, contour)
1296
+
1297
+ self.fig_contour, self.ax_contour = plt.subplots(figsize=(5, 5))
1298
+ self.imshow_contour = FigureCanvas(self.fig_contour, title="Contour measurement", interactive=True)
1299
+ self.ax_contour.clear()
1300
+ self.im_contour = self.ax_contour.imshow(self.test_frame[:, :, self.tab1_channel_dropdown.currentIndex()],
1301
+ cmap='gray')
1302
+ self.im_mask = self.ax_contour.imshow(np.ma.masked_where(border_label == 0, border_label),
1303
+ cmap='viridis', interpolation='none')
1304
+ self.ax_contour.set_xticks([])
1305
+ self.ax_contour.set_yticks([])
1306
+ self.ax_contour.set_title(contour * (-1))
1307
+ self.fig_contour.set_facecolor('none') # or 'None'
1308
+ self.fig_contour.canvas.setStyleSheet("background-color: transparent;")
1309
+ self.imshow_contour.canvas.draw()
1310
+
1311
+ self.imshow_contour.show()
1312
+
1313
+ # def enable_step_size(self):
1314
+ # if self.radial_intensity_btn.isChecked():
1315
+ # self.step_lbl.setEnabled(True)
1316
+ # self.step_size.setEnabled(True)
1317
+ # for checkbox in self.channel_chechkboxes:
1318
+ # checkbox.setEnabled(True)
1319
+ # else:
1320
+ # self.step_lbl.setEnabled(False)
1321
+ # self.step_size.setEnabled(False)
1322
+ # for checkbox in self.channel_chechkboxes:
1323
+ # checkbox.setEnabled(False)
1324
+
1325
+ def populate_spot_detection(self):
1326
+ layout = QGridLayout(self.spot_detection_frame)
1327
+ self.spot_detection_lbl = QLabel("SPOT DETECTION")
1328
+ self.spot_detection_lbl.setStyleSheet("""font-weight: bold;padding: 0px;""")
1329
+ layout.addWidget(self.spot_detection_lbl, 0, 0, 1, 2, alignment=Qt.AlignCenter)
1330
+ self.spot_check= QCheckBox('Perform spot detection')
1331
+ self.spot_check.toggled.connect(self.enable_spot_detection)
1332
+ layout.addWidget(self.spot_check, 1, 0)
1333
+ self.spot_channel_lbl = QLabel("Choose channel for spot detection: ")
1334
+ self.spot_channel = QComboBox()
1335
+ self.spot_channel.addItems(self.channel_names)
1336
+ layout.addWidget(self.spot_channel_lbl, 2, 0)
1337
+ layout.addWidget(self.spot_channel, 2, 1)
1338
+ self.diameter_lbl = QLabel('Spot diameter: ')
1339
+ self.diameter_value = QLineEdit()
1340
+ self.diameter_value.setValidator(self.onlyFloat)
1341
+ self.diameter_value.setText('7')
1342
+ self.diameter_value.textChanged.connect(self.enable_spot_preview)
1343
+
1344
+ layout.addWidget(self.diameter_lbl, 3, 0)
1345
+ layout.addWidget(self.diameter_value, 3, 1)
1346
+ self.threshold_lbl = QLabel('Spot threshold: ')
1347
+ self.threshold_value = QLineEdit()
1348
+ self.threshold_value.setValidator(self.onlyFloat)
1349
+ self.threshold_value.setText('0')
1350
+ self.threshold_value.textChanged.connect(self.enable_spot_preview)
1351
+
1352
+ layout.addWidget(self.threshold_lbl, 4, 0)
1353
+ layout.addWidget(self.threshold_value, 4, 1)
1354
+ self.preview_spot = QPushButton('Preview')
1355
+ self.preview_spot.clicked.connect(self.spot_preview)
1356
+ self.preview_spot.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
1357
+ layout.addWidget(self.preview_spot, 5, 0, 1, 2)
1358
+ self.spot_channel.setEnabled(False)
1359
+ self.spot_channel_lbl.setEnabled(False)
1360
+ self.diameter_value.setEnabled(False)
1361
+ self.diameter_lbl.setEnabled(False)
1362
+ self.threshold_value.setEnabled(False)
1363
+ self.threshold_lbl.setEnabled(False)
1364
+ self.preview_spot.setEnabled(False)
1365
+
1366
+
1367
+ def enable_spot_preview(self):
1368
+
1369
+ diam = self.diameter_value.text().replace(',','').replace('.','')
1370
+ thresh = self.threshold_value.text().replace(',','').replace('.','')
1371
+ if diam.isnumeric() and thresh.isnumeric():
1372
+ self.preview_spot.setEnabled(True)
1373
+ else:
1374
+ self.preview_spot.setEnabled(False)
1375
+
1376
+ def spot_preview(self):
1377
+ self.locate_image()
1378
+ if self.test_frame is not None:
1379
+ self.locate_mask()
1380
+ if self.test_mask is not None:
1381
+ self.spot_visual = ThresholdSpot(current_channel=self.spot_channel.currentIndex(), img=self.test_frame,
1382
+ mask=self.test_mask, parent=self)
1383
+ # for dictionary in self.background_correction:
1384
+ # if self.spot_channel.currentText() in dictionary['target channel']:
1385
+ # if dictionary['mode'] == 'field':
1386
+ # if dictionary['operation'] == 'Divide':
1387
+ # normalised, bg_fit = field_normalisation(
1388
+ # self.test_frame[:, :, self.spot_channel.currentIndex()],
1389
+ # threshold=dictionary['threshold'],
1390
+ # normalisation_operation=dictionary['operation'],
1391
+ # clip=False,
1392
+ # mode=dictionary['type'])
1393
+ # else:
1394
+ # normalised, bg_fit = field_normalisation(
1395
+ # self.test_frame[:, :, self.spot_channel.currentIndex()],
1396
+ # threshold=dictionary['threshold'],
1397
+ # normalisation_operation=dictionary['operation'],
1398
+ # clip=dictionary['clip'],
1399
+ # mode=dictionary['type'])
1400
+ # self.test_frame[:, :, self.spot_channel.currentIndex()] = normalised
1401
+ # if dictionary['mode'] == 'local':
1402
+ # normalised_image = normalise_by_cell(self.test_frame[:, :, self.spot_channel.currentIndex()].copy(), self.test_mask,
1403
+ # distance=int(dictionary['distance']), mode=dictionary['type'],
1404
+ # operation=dictionary['operation'])
1405
+ # self.test_frame[:, :, self.spot_channel.currentIndex()] = normalised_image
1406
+
1407
+
1408
+ def enable_spot_detection(self):
1409
+ if self.spot_check.isChecked():
1410
+ self.spot_channel.setEnabled(True)
1411
+ self.spot_channel_lbl.setEnabled(True)
1412
+ self.diameter_value.setEnabled(True)
1413
+ self.diameter_lbl.setEnabled(True)
1414
+ self.threshold_value.setEnabled(True)
1415
+ self.threshold_lbl.setEnabled(True)
1416
+ self.preview_spot.setEnabled(True)
1417
+
1418
+ else:
1419
+ self.spot_channel.setEnabled(False)
1420
+ self.spot_channel_lbl.setEnabled(False)
1421
+ self.diameter_value.setEnabled(False)
1422
+ self.diameter_lbl.setEnabled(False)
1423
+ self.threshold_value.setEnabled(False)
1424
+ self.threshold_lbl.setEnabled(False)
1425
+ self.preview_spot.setEnabled(False)