celldetective 1.2.2.post2__py3-none-any.whl → 1.3.0.post1__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.
@@ -1,11 +1,11 @@
1
1
 
2
2
  from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QScrollArea, QComboBox, QFrame, QCheckBox, \
3
- QGridLayout, QLineEdit, QVBoxLayout, QWidget, QLabel, QHBoxLayout, QPushButton
3
+ QGridLayout, QLineEdit, QVBoxLayout, QWidget, QLabel, QHBoxLayout, QPushButton
4
4
  from PyQt5.QtCore import Qt, QSize
5
5
  from PyQt5.QtGui import QIcon, QDoubleValidator, QIntValidator
6
6
 
7
7
  from celldetective.gui.gui_utils import center_window, FeatureChoice, ListWidget, QHSeperationLine, FigureCanvas, \
8
- GeometryChoice, OperationChoice
8
+ GeometryChoice, OperationChoice
9
9
  from superqt import QLabeledDoubleSlider
10
10
  from superqt.fonticon import icon
11
11
  from fonticon_mdi6 import MDI6
@@ -32,970 +32,970 @@ from celldetective.gui.gui_utils import ThresholdLineEdit
32
32
  from celldetective.gui import Styles
33
33
 
34
34
  class ConfigMeasurements(QMainWindow, Styles):
35
- """
36
- UI to set measurement instructions.
37
-
38
- """
39
-
40
- def __init__(self, parent_window=None):
41
-
42
- super().__init__()
43
-
44
- self.parent_window = parent_window
45
- self.setWindowTitle("Configure measurements")
46
- self.setWindowIcon(QIcon(os.sep.join(['celldetective', 'icons', 'mexican-hat.png'])))
47
- self.mode = self.parent_window.mode
48
- self.exp_dir = self.parent_window.exp_dir
49
- self.background_correction = []
50
- if self.mode == "targets":
51
- self.config_name = "btrack_config_targets.json"
52
- self.measure_instructions_path = self.parent_window.exp_dir + "configs/measurement_instructions_targets.json"
53
- elif self.mode == "effectors":
54
- self.config_name = "btrack_config_effectors.json"
55
- self.measure_instructions_path = self.parent_window.exp_dir + "configs/measurement_instructions_effectors.json"
56
- self.soft_path = get_software_location()
57
- self.clear_previous = False
58
-
59
- exp_config = self.exp_dir + "config.ini"
60
- self.config_path = self.exp_dir + self.config_name
61
- self.channel_names, self.channels = extract_experiment_channels(exp_config)
62
- self.channel_names = np.array(self.channel_names)
63
- self.channels = np.array(self.channels)
64
-
65
- self.screen_height = self.parent_window.parent_window.parent_window.screen_height
66
- center_window(self)
67
-
68
- self.onlyFloat = QDoubleValidator()
69
- self.onlyInt = QIntValidator()
70
-
71
- self.setMinimumWidth(500)
72
- self.setMinimumHeight(int(0.3 * self.screen_height))
73
- self.setMaximumHeight(int(0.8 * self.screen_height))
74
- self.populate_widget()
75
- self.load_previous_measurement_instructions()
76
-
77
- def populate_widget(self):
78
-
79
- """
80
- Create the multibox design.
81
-
82
- """
83
-
84
- # Create button widget and layout
85
- self.scroll_area = QScrollArea(self)
86
- self.button_widget = QWidget()
87
- main_layout = QVBoxLayout()
88
- self.button_widget.setLayout(main_layout)
89
- main_layout.setContentsMargins(30, 30, 30, 30)
90
-
91
- self.normalisation_frame = QFrame()
92
- self.normalisation_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
93
-
94
- self.local_correction_layout = LocalCorrectionLayout(self)
95
- self.fit_correction_layout = BackgroundFitCorrectionLayout(self)
96
-
97
- self.protocol_layout = ProtocolDesignerLayout(parent_window=self,
98
- tab_layouts=[ self.local_correction_layout, self.fit_correction_layout],
99
- tab_names=['Local', 'Fit'],
100
- title='BACKGROUND CORRECTION',
101
- list_title='Corrections to apply:'
102
- )
103
-
104
- self.normalisation_frame.setLayout(self.protocol_layout)
105
-
106
- #self.populate_normalisation_tabs()
107
- main_layout.addWidget(self.normalisation_frame)
108
-
109
- # first frame for FEATURES
110
- self.features_frame = QFrame()
111
- self.features_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
112
- self.populate_features_frame()
113
- main_layout.addWidget(self.features_frame)
114
-
115
- # second frame for ISOTROPIC MEASUREMENTS
116
- self.iso_frame = QFrame()
117
- self.iso_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
118
- self.populate_iso_frame()
119
- main_layout.addWidget(self.iso_frame)
120
-
121
- self.spot_detection_frame = QFrame()
122
- self.spot_detection_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
123
- self.populate_spot_detection()
124
- main_layout.addWidget(self.spot_detection_frame)
125
-
126
- self.clear_previous_btn = QCheckBox('clear previous measurements')
127
- main_layout.addWidget(self.clear_previous_btn)
128
-
129
- self.submit_btn = QPushButton('Save')
130
- self.submit_btn.setStyleSheet(self.button_style_sheet)
131
- self.submit_btn.clicked.connect(self.write_instructions)
132
- main_layout.addWidget(self.submit_btn)
133
-
134
- # self.populate_left_panel()
135
- # grid.addLayout(self.left_side, 0, 0, 1, 1)
136
- self.button_widget.adjustSize()
137
-
138
- self.scroll_area.setAlignment(Qt.AlignCenter)
139
- self.scroll_area.setWidget(self.button_widget)
140
- self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
141
- self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
142
- self.scroll_area.setWidgetResizable(True)
143
- self.setCentralWidget(self.scroll_area)
144
- self.show()
145
-
146
- QApplication.processEvents()
147
- self.adjustScrollArea()
148
-
149
- def populate_iso_frame(self):
150
-
151
- """
152
- Add widgets and layout in the POST-PROCESSING frame.
153
- """
154
-
155
- grid = QGridLayout(self.iso_frame)
156
-
157
- self.iso_lbl = QLabel("ISOTROPIC MEASUREMENTS")
158
- self.iso_lbl.setStyleSheet("""
159
- font-weight: bold;
160
- padding: 0px;
161
- """)
162
- grid.addWidget(self.iso_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
163
- self.generate_iso_contents()
164
- grid.addWidget(self.ContentsIso, 1, 0, 1, 4, alignment=Qt.AlignTop)
165
-
166
- def populate_features_frame(self):
167
-
168
- """
169
- Add widgets and layout in the FEATURES frame.
170
- """
171
-
172
- grid = QGridLayout(self.features_frame)
173
-
174
- self.feature_lbl = QLabel("FEATURES")
175
- self.feature_lbl.setStyleSheet("""
176
- font-weight: bold;
177
- padding: 0px;
178
- """)
179
- grid.addWidget(self.feature_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
180
-
181
- self.generate_feature_panel_contents()
182
- grid.addWidget(self.ContentsFeatures, 1, 0, 1, 4, alignment=Qt.AlignTop)
183
-
184
- def generate_iso_contents(self):
185
-
186
- self.ContentsIso = QFrame()
187
- layout = QVBoxLayout(self.ContentsIso)
188
- layout.setContentsMargins(0, 0, 0, 0)
189
-
190
- radii_layout = QHBoxLayout()
191
- self.radii_lbl = QLabel('Measurement radii (from center):')
192
- self.radii_lbl.setToolTip('Define radii or donughts for intensity measurements.')
193
- radii_layout.addWidget(self.radii_lbl, 90)
194
-
195
- self.del_radius_btn = QPushButton("")
196
- self.del_radius_btn.setStyleSheet(self.button_select_all)
197
- self.del_radius_btn.setIcon(icon(MDI6.trash_can, color="black"))
198
- self.del_radius_btn.setToolTip("Remove radius")
199
- self.del_radius_btn.setIconSize(QSize(20, 20))
200
- radii_layout.addWidget(self.del_radius_btn, 5)
201
-
202
- self.add_radius_btn = QPushButton("")
203
- self.add_radius_btn.setStyleSheet(self.button_select_all)
204
- self.add_radius_btn.setIcon(icon(MDI6.plus, color="black"))
205
- self.add_radius_btn.setToolTip("Add radius")
206
- self.add_radius_btn.setIconSize(QSize(20, 20))
207
- radii_layout.addWidget(self.add_radius_btn, 5)
208
- layout.addLayout(radii_layout)
209
-
210
- self.radii_list = ListWidget(GeometryChoice, initial_features=["10"], dtype=int)
211
- layout.addWidget(self.radii_list)
212
-
213
- self.del_radius_btn.clicked.connect(self.radii_list.removeSel)
214
- self.add_radius_btn.clicked.connect(self.radii_list.addItem)
215
-
216
- # Operation
217
- operation_layout = QHBoxLayout()
218
- self.op_lbl = QLabel('Operation to perform:')
219
- self.op_lbl.setToolTip('Set the operations to perform inside the ROI.')
220
- operation_layout.addWidget(self.op_lbl, 90)
221
-
222
- self.del_op_btn = QPushButton("")
223
- self.del_op_btn.setStyleSheet(self.button_select_all)
224
- self.del_op_btn.setIcon(icon(MDI6.trash_can, color="black"))
225
- self.del_op_btn.setToolTip("Remove operation")
226
- self.del_op_btn.setIconSize(QSize(20, 20))
227
- operation_layout.addWidget(self.del_op_btn, 5)
228
-
229
- self.add_op_btn = QPushButton("")
230
- self.add_op_btn.setStyleSheet(self.button_select_all)
231
- self.add_op_btn.setIcon(icon(MDI6.plus, color="black"))
232
- self.add_op_btn.setToolTip("Add operation")
233
- self.add_op_btn.setIconSize(QSize(20, 20))
234
- operation_layout.addWidget(self.add_op_btn, 5)
235
- layout.addLayout(operation_layout)
236
-
237
- self.operations_list = ListWidget(OperationChoice, initial_features=["mean"])
238
- layout.addWidget(self.operations_list)
239
-
240
- self.del_op_btn.clicked.connect(self.operations_list.removeSel)
241
- self.add_op_btn.clicked.connect(self.operations_list.addItem)
242
-
243
- def generate_feature_panel_contents(self):
244
-
245
- self.ContentsFeatures = QFrame()
246
- layout = QVBoxLayout(self.ContentsFeatures)
247
- layout.setContentsMargins(0, 0, 0, 0)
248
-
249
- feature_layout = QHBoxLayout()
250
- feature_layout.setContentsMargins(0, 0, 0, 0)
251
-
252
- self.feature_lbl = QLabel("Add features:")
253
- self.del_feature_btn = QPushButton("")
254
- self.del_feature_btn.setStyleSheet(self.button_select_all)
255
- self.del_feature_btn.setIcon(icon(MDI6.trash_can, color="black"))
256
- self.del_feature_btn.setToolTip("Remove feature")
257
- self.del_feature_btn.setIconSize(QSize(20, 20))
258
-
259
- self.add_feature_btn = QPushButton("")
260
- self.add_feature_btn.setStyleSheet(self.button_select_all)
261
- self.add_feature_btn.setIcon(icon(MDI6.filter_plus, color="black"))
262
- self.add_feature_btn.setToolTip("Add feature")
263
- self.add_feature_btn.setIconSize(QSize(20, 20))
264
-
265
- self.features_list = ListWidget(FeatureChoice, initial_features=['area', 'intensity_mean', ])
266
-
267
- self.del_feature_btn.clicked.connect(self.features_list.removeSel)
268
- self.add_feature_btn.clicked.connect(self.features_list.addItem)
269
-
270
- feature_layout.addWidget(self.feature_lbl, 90)
271
- feature_layout.addWidget(self.del_feature_btn, 5)
272
- feature_layout.addWidget(self.add_feature_btn, 5)
273
- layout.addLayout(feature_layout)
274
- layout.addWidget(self.features_list)
275
-
276
- self.feat_sep2 = QHSeperationLine()
277
- layout.addWidget(self.feat_sep2)
278
-
279
- contour_layout = QHBoxLayout()
280
- self.border_dist_lbl = QLabel('Contour measurements (from edge of mask):')
281
- self.border_dist_lbl.setToolTip(
282
- 'Apply the intensity measurements defined above\nto a slice of each cell mask, defined as distance\nfrom the edge.')
283
- contour_layout.addWidget(self.border_dist_lbl, 90)
284
-
285
- self.del_contour_btn = QPushButton("")
286
- self.del_contour_btn.setStyleSheet(self.button_select_all)
287
- self.del_contour_btn.setIcon(icon(MDI6.trash_can, color="black"))
288
- self.del_contour_btn.setToolTip("Remove distance")
289
- self.del_contour_btn.setIconSize(QSize(20, 20))
290
- contour_layout.addWidget(self.del_contour_btn, 5)
291
-
292
- self.add_contour_btn = QPushButton("")
293
- self.add_contour_btn.setStyleSheet(self.button_select_all)
294
- self.add_contour_btn.setIcon(icon(MDI6.plus, color="black"))
295
- self.add_contour_btn.setToolTip("Add distance")
296
- self.add_contour_btn.setIconSize(QSize(20, 20))
297
- contour_layout.addWidget(self.add_contour_btn, 5)
298
-
299
- self.view_contour_btn = QPushButton("")
300
- self.view_contour_btn.setStyleSheet(self.button_select_all)
301
- self.view_contour_btn.setIcon(icon(MDI6.eye_plus_outline, color="black"))
302
- self.view_contour_btn.setToolTip("View contour")
303
- self.view_contour_btn.setIconSize(QSize(20, 20))
304
- contour_layout.addWidget(self.view_contour_btn, 5)
305
-
306
- layout.addLayout(contour_layout)
307
-
308
- self.contours_list = ListWidget(GeometryChoice, initial_features=[], dtype=int)
309
- layout.addWidget(self.contours_list)
310
-
311
- self.del_contour_btn.clicked.connect(self.contours_list.removeSel)
312
- self.add_contour_btn.clicked.connect(self.contours_list.addItem)
313
- self.view_contour_btn.clicked.connect(self.view_selected_contour)
314
-
315
- self.feat_sep3 = QHSeperationLine()
316
- layout.addWidget(self.feat_sep3)
317
- # self.radial_intensity_btn = QCheckBox('Measure radial intensity distribution')
318
- # layout.addWidget(self.radial_intensity_btn)
319
- # self.radial_intensity_btn.clicked.connect(self.enable_step_size)
320
- # self.channel_chechkboxes=[]
321
- # for channel in self.channel_names:
322
- # channel_checkbox=QCheckBox(channel)
323
- # self.channel_chechkboxes.append(channel_checkbox)
324
- # layout.addWidget(channel_checkbox)
325
- # channel_checkbox.setEnabled(False)
326
- # step_box=QHBoxLayout()
327
- # self.step_lbl=QLabel("Step size (in px)")
328
- # self.step_size=QLineEdit()
329
- # self.step_lbl.setEnabled(False)
330
- # self.step_size.setEnabled(False)
331
- # step_box.addWidget(self.step_lbl)
332
- # step_box.addWidget(self.step_size)
333
- # layout.addLayout(step_box)
334
- # self.feat_sep4 = QHSeperationLine()
335
- # layout.addWidget(self.feat_sep4)
336
-
337
- # Haralick features parameters
338
- self.activate_haralick_btn = QCheckBox('Measure Haralick texture features')
339
- self.activate_haralick_btn.toggled.connect(self.show_haralick_options)
340
-
341
- self.haralick_channel_choice = QComboBox()
342
- self.haralick_channel_choice.addItems(self.channel_names)
343
- self.haralick_channel_lbl = QLabel('Target channel: ')
344
-
345
- self.haralick_distance_le = QLineEdit("1")
346
- self.haralick_distance_lbl = QLabel('Distance: ')
347
-
348
- self.haralick_n_gray_levels_le = QLineEdit("256")
349
- self.haralick_n_gray_levels_lbl = QLabel('# gray levels: ')
350
-
351
- # Slider to set vmin & vmax
352
- self.haralick_scale_slider = QLabeledDoubleSlider()
353
- self.haralick_scale_slider.setSingleStep(0.05)
354
- self.haralick_scale_slider.setTickInterval(0.05)
355
- self.haralick_scale_slider.setSingleStep(1)
356
- self.haralick_scale_slider.setOrientation(1)
357
- self.haralick_scale_slider.setRange(0, 1)
358
- self.haralick_scale_slider.setValue(0.5)
359
- self.haralick_scale_lbl = QLabel('Scale: ')
360
-
361
- self.haralick_percentile_min_le = QLineEdit('0.01')
362
- self.haralick_percentile_max_le = QLineEdit('99.9')
363
- self.haralick_normalization_mode_btn = QPushButton()
364
- self.haralick_normalization_mode_btn.setStyleSheet(self.button_select_all)
365
- self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
366
- self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
367
- self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
368
- self.percentile_mode = True
369
-
370
- min_percentile_hbox = QHBoxLayout()
371
- min_percentile_hbox.addWidget(self.haralick_percentile_min_le, 90)
372
- min_percentile_hbox.addWidget(self.haralick_normalization_mode_btn, 10)
373
- min_percentile_hbox.setContentsMargins(0, 0, 0, 0)
374
-
375
- self.haralick_percentile_min_lbl = QLabel('Min percentile: ')
376
- self.haralick_percentile_max_lbl = QLabel('Max percentile: ')
377
-
378
- self.haralick_hist_btn = QPushButton()
379
- self.haralick_hist_btn.clicked.connect(self.control_haralick_intensity_histogram)
380
- self.haralick_hist_btn.setIcon(icon(MDI6.poll, color="k"))
381
- self.haralick_hist_btn.setStyleSheet(self.button_select_all)
382
-
383
- self.haralick_digit_btn = QPushButton()
384
- self.haralick_digit_btn.clicked.connect(self.control_haralick_digitalization)
385
- self.haralick_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
386
- self.haralick_digit_btn.setStyleSheet(self.button_select_all)
387
-
388
- self.haralick_layout = QVBoxLayout()
389
- self.haralick_layout.setContentsMargins(20, 20, 20, 20)
390
-
391
- activate_layout = QHBoxLayout()
392
- activate_layout.addWidget(self.activate_haralick_btn, 80)
393
- activate_layout.addWidget(self.haralick_hist_btn, 10)
394
- activate_layout.addWidget(self.haralick_digit_btn, 10)
395
- self.haralick_layout.addLayout(activate_layout)
396
-
397
- channel_layout = QHBoxLayout()
398
- channel_layout.addWidget(self.haralick_channel_lbl, 40)
399
- channel_layout.addWidget(self.haralick_channel_choice, 60)
400
- self.haralick_layout.addLayout(channel_layout)
401
-
402
- distance_layout = QHBoxLayout()
403
- distance_layout.addWidget(self.haralick_distance_lbl, 40)
404
- distance_layout.addWidget(self.haralick_distance_le, 60)
405
- self.haralick_layout.addLayout(distance_layout)
406
-
407
- gl_layout = QHBoxLayout()
408
- gl_layout.addWidget(self.haralick_n_gray_levels_lbl, 40)
409
- gl_layout.addWidget(self.haralick_n_gray_levels_le, 60)
410
- self.haralick_layout.addLayout(gl_layout)
411
-
412
- slider_layout = QHBoxLayout()
413
- slider_layout.addWidget(self.haralick_scale_lbl, 40)
414
- slider_layout.addWidget(self.haralick_scale_slider, 60)
415
- self.haralick_layout.addLayout(slider_layout)
416
-
417
- slider_min_percentile_layout = QHBoxLayout()
418
- slider_min_percentile_layout.addWidget(self.haralick_percentile_min_lbl, 40)
419
- # slider_min_percentile_layout.addWidget(self.haralick_percentile_min_le,55)
420
- slider_min_percentile_layout.addLayout(min_percentile_hbox, 60)
421
- # slider_min_percentile_layout.addWidget(self.haralick_normalization_mode_btn, 5)
422
- self.haralick_layout.addLayout(slider_min_percentile_layout)
423
-
424
- slider_max_percentile_layout = QHBoxLayout()
425
- slider_max_percentile_layout.addWidget(self.haralick_percentile_max_lbl, 40)
426
- slider_max_percentile_layout.addWidget(self.haralick_percentile_max_le, 60)
427
- self.haralick_layout.addLayout(slider_max_percentile_layout)
428
-
429
- self.haralick_to_hide = [self.haralick_hist_btn, self.haralick_digit_btn, self.haralick_channel_lbl,
430
- self.haralick_channel_choice,
431
- self.haralick_distance_le, self.haralick_distance_lbl, self.haralick_n_gray_levels_le,
432
- self.haralick_n_gray_levels_lbl,
433
- self.haralick_scale_lbl, self.haralick_scale_slider, self.haralick_percentile_min_lbl,
434
- self.haralick_percentile_min_le,
435
- self.haralick_percentile_max_lbl, self.haralick_percentile_max_le,
436
- self.haralick_normalization_mode_btn]
437
-
438
- self.features_to_disable = [self.feature_lbl, self.del_feature_btn, self.add_feature_btn, self.features_list,
439
- self.activate_haralick_btn]
440
-
441
- self.activate_haralick_btn.setChecked(False)
442
- for f in self.haralick_to_hide:
443
- f.setEnabled(False)
444
-
445
- self.haralick_normalization_mode_btn.clicked.connect(self.switch_to_absolute_normalization_mode)
446
- layout.addLayout(self.haralick_layout)
447
-
448
- def switch_to_absolute_normalization_mode(self):
449
-
450
- if self.percentile_mode:
451
- self.percentile_mode = False
452
- self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle_outline, color="black"))
453
- self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
454
- self.haralick_normalization_mode_btn.setToolTip("Switch to percentile normalization values.")
455
- self.haralick_percentile_min_lbl.setText('Min value: ')
456
- self.haralick_percentile_max_lbl.setText('Max value: ')
457
- self.haralick_percentile_min_le.setText('0')
458
- self.haralick_percentile_max_le.setText('10000')
459
-
460
- else:
461
- self.percentile_mode = True
462
- self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
463
- self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
464
- self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
465
- self.haralick_percentile_min_lbl.setText('Min percentile: ')
466
- self.haralick_percentile_max_lbl.setText('Max percentile: ')
467
- self.haralick_percentile_min_le.setText('0.01')
468
- self.haralick_percentile_max_le.setText('99.99')
469
-
470
- def show_haralick_options(self):
471
-
472
- """
473
- Show the Haralick texture options.
474
- """
475
-
476
- if self.activate_haralick_btn.isChecked():
477
- for element in self.haralick_to_hide:
478
- element.setEnabled(True)
479
- else:
480
- for element in self.haralick_to_hide:
481
- element.setEnabled(False)
482
-
483
- def adjustScrollArea(self):
484
-
485
- """
486
- Auto-adjust scroll area to fill space
487
- (from https://stackoverflow.com/questions/66417576/make-qscrollarea-use-all-available-space-of-qmainwindow-height-axis)
488
- """
489
-
490
- step = 5
491
- while self.scroll_area.verticalScrollBar().isVisible() and self.height() < self.maximumHeight():
492
- self.resize(self.width(), self.height() + step)
493
-
494
- def write_instructions(self):
495
-
496
- """
497
- Write the selected options in a json file for later reading by the software.
498
- """
499
-
500
- print('Writing instructions...')
501
- measurement_options = {}
502
- background_correction = self.protocol_layout.protocols
503
- if not background_correction:
504
- background_correction = None
505
- measurement_options.update({'background_correction': background_correction})
506
- features = self.features_list.getItems()
507
- if not features:
508
- features = None
509
- measurement_options.update({'features': features})
510
-
511
- border_distances = self.contours_list.getItems()
512
- if not border_distances:
513
- border_distances = None
514
- measurement_options.update({'border_distances': border_distances})
515
- # radial_intensity = {}
516
- # radial_step = int(self.step_size.text())
517
- # radial_channels = []
518
- # for checkbox in self.channel_chechkboxes:
519
- # if checkbox.isChecked():
520
- # radial_channels.append(checkbox.text())
521
- # radial_intensity={'radial_step': radial_step, 'radial_channels': radial_channels}
522
- # if not self.radial_intensity_btn.isChecked():
523
- # radial_intensity = None
524
- # measurement_options.update({'radial_intensity' : radial_intensity})
525
-
526
- self.extract_haralick_options()
527
- measurement_options.update({'haralick_options': self.haralick_options})
528
-
529
- intensity_measurement_radii = self.radii_list.getItems()
530
- if not intensity_measurement_radii:
531
- intensity_measurement_radii = None
532
-
533
- isotropic_operations = self.operations_list.getItems()
534
- if not isotropic_operations:
535
- isotropic_operations = None
536
- intensity_measurement_radii = None
537
- measurement_options.update({'intensity_measurement_radii': intensity_measurement_radii,
538
- 'isotropic_operations': isotropic_operations})
539
- spot_detection = None
540
- if self.spot_check.isChecked():
541
- spot_detection = {'channel': self.spot_channel.currentText(), 'diameter': float(self.diameter_value.text().replace(',','.')),
542
- 'threshold': float(self.threshold_value.text().replace(',','.'))}
543
- measurement_options.update({'spot_detection': spot_detection})
544
- if self.clear_previous_btn.isChecked():
545
- self.clear_previous = True
546
- else:
547
- self.clear_previous = False
548
- measurement_options.update({'clear_previous': self.clear_previous})
549
-
550
-
551
-
552
- print('Measurement instructions: ', measurement_options)
553
- file_name = self.measure_instructions_path
554
- with open(file_name, 'w') as f:
555
- json.dump(measurement_options, f, indent=4)
556
-
557
- print('Done.')
558
- self.close()
559
-
560
- def extract_haralick_options(self):
561
-
562
- if self.activate_haralick_btn.isChecked():
563
- self.haralick_options = {"target_channel": self.haralick_channel_choice.currentIndex(),
564
- "scale_factor": float(self.haralick_scale_slider.value()),
565
- "n_intensity_bins": int(self.haralick_n_gray_levels_le.text()),
566
- "distance": int(self.haralick_distance_le.text()),
567
- }
568
- if self.percentile_mode:
569
- self.haralick_options.update({"percentiles": (
570
- float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text())),
571
- "clip_values": None})
572
- else:
573
- self.haralick_options.update({"percentiles": None, "clip_values": (
574
- float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text()))})
575
-
576
- else:
577
- self.haralick_options = None
578
-
579
- def load_previous_measurement_instructions(self):
580
-
581
- """
582
- Read the measurmeent options from a previously written json file and format properly for the UI.
583
- """
584
-
585
- print('Reading instructions..')
586
- if os.path.exists(self.measure_instructions_path):
587
- with open(self.measure_instructions_path, 'r') as f:
588
- measurement_instructions = json.load(f)
589
- print(measurement_instructions)
590
- if 'background_correction' in measurement_instructions:
591
- self.protocol_layout.protocols = measurement_instructions['background_correction']
592
- if self.protocol_layout.protocols is None:
593
- self.protocol_layout.protocols = []
594
- if (self.protocol_layout.protocols is not None) and len(self.protocol_layout.protocols) > 0:
595
- self.protocol_layout.protocol_list.clear()
596
- for norm_params in self.protocol_layout.protocols:
597
- normalisation_description = ""
598
- for index, (key, value) in enumerate(norm_params.items()):
599
- if index > 0:
600
- normalisation_description += ", "
601
- normalisation_description += str(key) + " : " + str(value)
602
- self.protocol_layout.protocol_list.addItem(normalisation_description)
603
- else:
604
- self.protocol_layout.protocol_list.clear()
605
- if 'features' in measurement_instructions:
606
- features = measurement_instructions['features']
607
- if (features is not None) and len(features) > 0:
608
- self.features_list.list_widget.clear()
609
- self.features_list.list_widget.addItems(features)
610
- else:
611
- self.features_list.list_widget.clear()
612
-
613
- if 'spot_detection' in measurement_instructions:
614
- spot_detection = measurement_instructions['spot_detection']
615
- if spot_detection is not None:
616
- self.spot_check.setChecked(True)
617
- if 'channel' in spot_detection:
618
- idx = spot_detection['channel']
619
- self.spot_channel.setCurrentText(idx)
620
- self.diameter_value.setText(str(spot_detection['diameter']))
621
- self.threshold_value.setText(str(spot_detection['threshold']))
622
-
623
-
624
- if 'border_distances' in measurement_instructions:
625
- border_distances = measurement_instructions['border_distances']
626
- if border_distances is not None:
627
- if isinstance(border_distances, int):
628
- distances = [border_distances]
629
- elif isinstance(border_distances, list):
630
- distances = []
631
- for d in border_distances:
632
- if isinstance(d, int) | isinstance(d, float):
633
- distances.append(str(int(d)))
634
- elif isinstance(d, list):
635
- distances.append(str(int(d[0])) + '-' + str(int(d[1])))
636
- self.contours_list.list_widget.clear()
637
- self.contours_list.list_widget.addItems(distances)
638
-
639
- if 'haralick_options' in measurement_instructions:
640
- haralick_options = measurement_instructions['haralick_options']
641
- if haralick_options is None:
642
- self.activate_haralick_btn.setChecked(False)
643
- self.show_haralick_options()
644
- else:
645
- self.activate_haralick_btn.setChecked(True)
646
- self.show_haralick_options()
647
- if 'target_channel' in haralick_options:
648
- idx = haralick_options['target_channel']
649
- self.haralick_channel_choice.setCurrentIndex(idx)
650
- if 'scale_factor' in haralick_options:
651
- self.haralick_scale_slider.setValue(float(haralick_options['scale_factor']))
652
- if ('percentiles' in haralick_options) and (haralick_options['percentiles'] is not None):
653
- perc = list(haralick_options['percentiles'])
654
- self.haralick_percentile_min_le.setText(str(perc[0]))
655
- self.haralick_percentile_max_le.setText(str(perc[1]))
656
- if ('clip_values' in haralick_options) and (haralick_options['clip_values'] is not None):
657
- values = list(haralick_options['clip_values'])
658
- self.haralick_percentile_min_le.setText(str(values[0]))
659
- self.haralick_percentile_max_le.setText(str(values[1]))
660
- self.percentile_mode = True
661
- self.switch_to_absolute_normalization_mode()
662
- if 'n_intensity_bins' in haralick_options:
663
- self.haralick_n_gray_levels_le.setText(str(haralick_options['n_intensity_bins']))
664
- if 'distance' in haralick_options:
665
- self.haralick_distance_le.setText(str(haralick_options['distance']))
666
-
667
- if 'intensity_measurement_radii' in measurement_instructions:
668
- intensity_measurement_radii = measurement_instructions['intensity_measurement_radii']
669
- if intensity_measurement_radii is not None:
670
- if isinstance(intensity_measurement_radii, int):
671
- radii = [intensity_measurement_radii]
672
- elif isinstance(intensity_measurement_radii, list):
673
- radii = []
674
- for r in intensity_measurement_radii:
675
- if isinstance(r, int) | isinstance(r, float):
676
- radii.append(str(int(r)))
677
- elif isinstance(r, list):
678
- radii.append(str(int(r[0])) + '-' + str(int(r[1])))
679
- self.radii_list.list_widget.clear()
680
- self.radii_list.list_widget.addItems(radii)
681
- else:
682
- self.radii_list.list_widget.clear()
683
-
684
- if 'isotropic_operations' in measurement_instructions:
685
- isotropic_operations = measurement_instructions['isotropic_operations']
686
- if (isotropic_operations is not None) and len(isotropic_operations) > 0:
687
- self.operations_list.list_widget.clear()
688
- self.operations_list.list_widget.addItems(isotropic_operations)
689
- else:
690
- self.operations_list.list_widget.clear()
691
-
692
- # if 'radial_intensity' in measurement_instructions:
693
- # radial_intensity = measurement_instructions['radial_intensity']
694
- # if radial_intensity is not None:
695
- # self.radial_intensity_btn.setChecked(True)
696
- # self.step_size.setText(str(radial_intensity['radial_step']))
697
- # self.step_size.setEnabled(True)
698
- # self.step_lbl.setEnabled(True)
699
- # for checkbox in self.channel_chechkboxes:
700
- # checkbox.setEnabled(True)
701
- # if checkbox.text() in radial_intensity['radial_channels']:
702
- # checkbox.setChecked(True)
703
-
704
- if 'clear_previous' in measurement_instructions:
705
- self.clear_previous = measurement_instructions['clear_previous']
706
- self.clear_previous_btn.setChecked(self.clear_previous)
707
-
708
- def locate_image(self):
709
-
710
- """
711
- Load the first frame of the first movie found in the experiment folder as a sample.
712
- """
713
-
714
- movies = glob(self.parent_window.parent_window.pos + os.sep.join(['movie', f"{self.parent_window.movie_prefix}*.tif"]))
715
-
716
- if len(movies) == 0:
717
- msgBox = QMessageBox()
718
- msgBox.setIcon(QMessageBox.Warning)
719
- msgBox.setText("Please select a position containing a movie...")
720
- msgBox.setWindowTitle("Warning")
721
- msgBox.setStandardButtons(QMessageBox.Ok)
722
- returnValue = msgBox.exec()
723
- if returnValue == QMessageBox.Ok:
724
- self.current_stack = None
725
- return None
726
- else:
727
- self.current_stack = movies[0]
728
- self.stack_length = auto_load_number_of_frames(self.current_stack)
729
-
730
- if self.stack_length is None:
731
- stack = imread(self.current_stack)
732
- self.stack_length = len(stack)
733
- del stack
734
- gc.collect()
735
-
736
- self.mid_time = self.stack_length // 2
737
- indices = self.mid_time + np.arange(len(self.channel_names))
738
- self.test_frame = load_frames(list(indices.astype(int)),self.current_stack, normalize_input=False)
739
-
740
-
741
- def control_haralick_digitalization(self):
742
-
743
- """
744
- Load an image for the first experiment movie found.
745
- Apply the Haralick parameters and check the result of the digitization (normalization + binning of intensities).
746
-
747
- """
748
-
749
- self.locate_image()
750
- self.extract_haralick_options()
751
- if self.test_frame is not None:
752
- digitized_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
753
- channels=self.channel_names, return_digit_image_only=True,
754
- **self.haralick_options
755
- )
756
-
757
- self.fig, self.ax = plt.subplots()
758
- divider = make_axes_locatable(self.ax)
759
- cax = divider.append_axes('right', size='5%', pad=0.05)
760
-
761
- self.imshow_digit_window = FigureCanvas(self.fig, title="Haralick: control digitization")
762
- self.ax.clear()
763
- im = self.ax.imshow(digitized_img, cmap='gray')
764
- self.fig.colorbar(im, cax=cax, orientation='vertical')
765
- self.ax.set_xticks([])
766
- self.ax.set_yticks([])
767
- self.fig.set_facecolor('none') # or 'None'
768
- self.fig.canvas.setStyleSheet("background-color: transparent;")
769
- self.imshow_digit_window.canvas.draw()
770
- self.imshow_digit_window.show()
771
-
772
- def control_haralick_intensity_histogram(self):
773
-
774
- """
775
- Load an image for the first experiment movie found.
776
- Apply the Haralick normalization parameters and check the normalized intensity histogram.
777
-
778
- """
779
-
780
- self.locate_image()
781
- self.extract_haralick_options()
782
- if self.test_frame is not None:
783
- norm_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
784
- channels=self.channel_names, return_norm_image_only=True,
785
- **self.haralick_options
786
- )
787
- self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
788
- self.hist_window = FigureCanvas(self.fig, title="Haralick: control digitized histogram")
789
- self.ax.clear()
790
- flat = norm_img.flatten()
791
- self.ax.hist(flat[flat==flat], bins=self.haralick_options['n_intensity_bins'])
792
- self.ax.set_xlabel('gray level value')
793
- self.ax.set_ylabel('#')
794
- plt.tight_layout()
795
- self.fig.set_facecolor('none') # or 'None'
796
- self.fig.canvas.setStyleSheet("background-color: transparent;")
797
- self.hist_window.canvas.draw()
798
- self.hist_window.show()
799
-
800
- def view_selected_contour(self):
801
-
802
- """
803
- Show the ROI for the selected contour measurement on experimental data.
804
-
805
- """
806
- self.locate_image()
807
-
808
- if self.current_stack is not None:
809
-
810
- self.viewer = CellEdgeVisualizer(cell_type=self.mode,
811
- stack_path=self.current_stack,
812
- parent_list_widget=self.contours_list.list_widget,
813
- n_channels=len(self.channel_names),
814
- target_channel=0,
815
- window_title='Set an edge measurement',
816
- channel_cb=True,
817
- channel_names = self.channel_names,
818
- PxToUm = 1,
819
- )
820
- self.viewer.show()
821
-
822
- def locate_mask(self):
823
-
824
- """
825
- Load the first mask of the detected movie.
826
- """
827
-
828
- labels_path = str(Path(self.current_stack).parent.parent) + os.sep+f'labels_{self.mode}'+os.sep
829
- masks = natsorted(glob(labels_path + '*.tif'))
830
- if len(masks) == 0:
831
- print('no mask found')
832
- self.test_mask = None
833
- else:
834
- self.test_mask = imread(masks[self.mid_time])
835
-
836
- def switch_channel_contour(self, value):
837
-
838
- """
839
- Adjust intensity values when changing channels in the contour visualizer.
840
-
841
- """
842
-
843
- self.im_contour.set_array(self.test_frame[:, :, value])
844
- self.im_contour.set_clim(vmin=np.percentile(self.test_frame[:, :, value].flatten(), 1),
845
- vmax=np.percentile(self.test_frame[:, :, value].flatten(), 99.99))
846
- self.contrast_slider_contour.setRange(np.amin(self.test_frame[:, :, value]),
847
- np.amax(self.test_frame[:, :, value]))
848
- self.contrast_slider_contour.setValue([np.percentile(self.test_frame[:, :, value].flatten(), 1),
849
- np.percentile(self.test_frame[:, :, value].flatten(), 99.99)])
850
- self.fig_contour.canvas.draw_idle()
851
-
852
- def contrast_im_contour(self, value):
853
- vmin = value[0]
854
- vmax = value[1]
855
- self.im_contour.set_clim(vmin=vmin, vmax=vmax)
856
- self.fig_contour.canvas.draw_idle()
857
-
858
- def make_contour_transparent(self, value):
859
-
860
- self.im_mask.set_alpha(value)
861
- self.fig_contour.canvas.draw_idle()
862
-
863
- def remove_item_from_list(self):
864
- current_item = self.normalisation_list.currentRow()
865
- if current_item > -1:
866
- del self.background_correction[current_item]
867
- self.normalisation_list.takeItem(current_item)
868
-
869
- def check_the_information(self):
870
-
871
- if self.tabs.currentIndex() == 0:
872
- if self.background_correction is None:
873
- self.background_correction = []
874
- for index, normalisation_opt in enumerate(self.background_correction ):
875
- if self.tab1_channel_dropdown.currentText() in normalisation_opt['target channel']:
876
- result = self.channel_already_in_list()
877
- if result != QMessageBox.Yes:
878
- return False
879
- else:
880
- self.background_correction .remove(normalisation_opt)
881
- self.normalisation_list.takeItem(index)
882
- return True
883
-
884
- def display_message_box(self, missing_info):
885
- QMessageBox.about(self, "Message Box Title", "Please " + missing_info + " for background correction")
886
-
887
- def channel_already_in_list(self):
888
- response = QMessageBox.question(self, "Message Box Title",
889
- "The background correction parameters for this channel already exist, "
890
- "continuing will erase the previous configurations. Are you sure you want to "
891
- "proceed?",
892
- QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
893
- return response
894
-
895
- def fun(self, x, y):
896
- return x ** 2 + y
897
-
898
- def view_normalisation_contour(self):
899
-
900
- """
901
- Show the ROI for the selected contour measurement on experimental data.
902
-
903
- """
904
-
905
- self.locate_image()
906
-
907
- if self.current_stack is not None:
908
-
909
- self.viewer = CellEdgeVisualizer(cell_type=self.mode,
910
- stack_path=self.current_stack,
911
- parent_le = self.tab1_txt_distance,
912
- n_channels=len(self.channel_names),
913
- target_channel=self.tab1_channel_dropdown.currentIndex(),
914
- edge_range = (0,30),
915
- initial_edge= self.tab1_txt_distance.get_threshold(),
916
- invert=True,
917
- window_title='Set an edge distance to estimate local intensity',
918
- channel_cb=False,
919
- PxToUm = 1,
920
- )
921
- self.viewer.show()
922
-
923
-
924
- def populate_spot_detection(self):
925
-
926
- layout = QGridLayout(self.spot_detection_frame)
927
- self.spot_detection_lbl = QLabel("SPOT DETECTION")
928
- self.spot_detection_lbl.setStyleSheet("""font-weight: bold;padding: 0px;""")
929
- layout.addWidget(self.spot_detection_lbl, 0, 0, 1, 2, alignment=Qt.AlignCenter)
930
- self.spot_check= QCheckBox('Perform spot detection')
931
- self.spot_check.toggled.connect(self.enable_spot_detection)
932
- layout.addWidget(self.spot_check, 1, 0)
933
- self.spot_channel_lbl = QLabel("Choose channel for spot detection: ")
934
- self.spot_channel = QComboBox()
935
- self.spot_channel.addItems(self.channel_names)
936
- layout.addWidget(self.spot_channel_lbl, 2, 0)
937
- layout.addWidget(self.spot_channel, 2, 1)
938
- self.diameter_lbl = QLabel('Spot diameter: ')
939
- self.diameter_value = QLineEdit()
940
- self.diameter_value.setValidator(self.onlyFloat)
941
- self.diameter_value.setText('7')
942
- self.diameter_value.textChanged.connect(self.enable_spot_preview)
943
-
944
- layout.addWidget(self.diameter_lbl, 3, 0)
945
- layout.addWidget(self.diameter_value, 3, 1)
946
- self.threshold_lbl = QLabel('Spot threshold: ')
947
- self.threshold_value = QLineEdit()
948
- self.threshold_value.setValidator(self.onlyFloat)
949
- self.threshold_value.setText('0')
950
- self.threshold_value.textChanged.connect(self.enable_spot_preview)
951
-
952
- layout.addWidget(self.threshold_lbl, 4, 0)
953
- layout.addWidget(self.threshold_value, 4, 1)
954
- self.preview_spot = QPushButton('Preview')
955
- self.preview_spot.clicked.connect(self.spot_preview)
956
- self.preview_spot.setStyleSheet(self.button_style_sheet_2)
957
- layout.addWidget(self.preview_spot, 5, 0, 1, 2)
958
- self.spot_channel.setEnabled(False)
959
- self.spot_channel_lbl.setEnabled(False)
960
- self.diameter_value.setEnabled(False)
961
- self.diameter_lbl.setEnabled(False)
962
- self.threshold_value.setEnabled(False)
963
- self.threshold_lbl.setEnabled(False)
964
- self.preview_spot.setEnabled(False)
965
-
966
-
967
- def enable_spot_preview(self):
968
-
969
- diam = self.diameter_value.text().replace(',','').replace('.','')
970
- thresh = self.threshold_value.text().replace(',','').replace('.','')
971
- if diam.isnumeric() and thresh.isnumeric():
972
- self.preview_spot.setEnabled(True)
973
- else:
974
- self.preview_spot.setEnabled(False)
975
-
976
- def spot_preview(self):
977
- self.locate_image()
978
- if self.test_frame is not None:
979
- self.locate_mask()
980
- if self.test_mask is not None:
981
- self.spot_visual = ThresholdSpot(current_channel=self.spot_channel.currentIndex(), img=self.test_frame,
982
- mask=self.test_mask, parent_window=self)
983
-
984
- def enable_spot_detection(self):
985
- if self.spot_check.isChecked():
986
- self.spot_channel.setEnabled(True)
987
- self.spot_channel_lbl.setEnabled(True)
988
- self.diameter_value.setEnabled(True)
989
- self.diameter_lbl.setEnabled(True)
990
- self.threshold_value.setEnabled(True)
991
- self.threshold_lbl.setEnabled(True)
992
- self.preview_spot.setEnabled(True)
993
-
994
- else:
995
- self.spot_channel.setEnabled(False)
996
- self.spot_channel_lbl.setEnabled(False)
997
- self.diameter_value.setEnabled(False)
998
- self.diameter_lbl.setEnabled(False)
999
- self.threshold_value.setEnabled(False)
1000
- self.threshold_lbl.setEnabled(False)
1001
- self.preview_spot.setEnabled(False)
35
+ """
36
+ UI to set measurement instructions.
37
+
38
+ """
39
+
40
+ def __init__(self, parent_window=None):
41
+
42
+ super().__init__()
43
+
44
+ self.parent_window = parent_window
45
+ self.setWindowTitle("Configure measurements")
46
+ self.setWindowIcon(QIcon(os.sep.join(['celldetective', 'icons', 'mexican-hat.png'])))
47
+ self.mode = self.parent_window.mode
48
+ self.exp_dir = self.parent_window.exp_dir
49
+ self.background_correction = []
50
+ if self.mode == "targets":
51
+ self.config_name = "btrack_config_targets.json"
52
+ self.measure_instructions_path = self.parent_window.exp_dir + "configs/measurement_instructions_targets.json"
53
+ elif self.mode == "effectors":
54
+ self.config_name = "btrack_config_effectors.json"
55
+ self.measure_instructions_path = self.parent_window.exp_dir + "configs/measurement_instructions_effectors.json"
56
+ self.soft_path = get_software_location()
57
+ self.clear_previous = False
58
+
59
+ exp_config = self.exp_dir + "config.ini"
60
+ self.config_path = self.exp_dir + self.config_name
61
+ self.channel_names, self.channels = extract_experiment_channels(exp_config)
62
+ self.channel_names = np.array(self.channel_names)
63
+ self.channels = np.array(self.channels)
64
+
65
+ self.screen_height = self.parent_window.parent_window.parent_window.screen_height
66
+ center_window(self)
67
+
68
+ self.onlyFloat = QDoubleValidator()
69
+ self.onlyInt = QIntValidator()
70
+
71
+ self.setMinimumWidth(500)
72
+ self.setMinimumHeight(int(0.3 * self.screen_height))
73
+ self.setMaximumHeight(int(0.8 * self.screen_height))
74
+ self.populate_widget()
75
+ self.load_previous_measurement_instructions()
76
+
77
+ def populate_widget(self):
78
+
79
+ """
80
+ Create the multibox design.
81
+
82
+ """
83
+
84
+ # Create button widget and layout
85
+ self.scroll_area = QScrollArea(self)
86
+ self.button_widget = QWidget()
87
+ main_layout = QVBoxLayout()
88
+ self.button_widget.setLayout(main_layout)
89
+ main_layout.setContentsMargins(30, 30, 30, 30)
90
+
91
+ self.normalisation_frame = QFrame()
92
+ self.normalisation_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
93
+
94
+ self.local_correction_layout = LocalCorrectionLayout(self)
95
+ self.fit_correction_layout = BackgroundFitCorrectionLayout(self)
96
+
97
+ self.protocol_layout = ProtocolDesignerLayout(parent_window=self,
98
+ tab_layouts=[ self.local_correction_layout, self.fit_correction_layout],
99
+ tab_names=['Local', 'Fit'],
100
+ title='BACKGROUND CORRECTION',
101
+ list_title='Corrections to apply:'
102
+ )
103
+
104
+ self.normalisation_frame.setLayout(self.protocol_layout)
105
+
106
+ #self.populate_normalisation_tabs()
107
+ main_layout.addWidget(self.normalisation_frame)
108
+
109
+ # first frame for FEATURES
110
+ self.features_frame = QFrame()
111
+ self.features_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
112
+ self.populate_features_frame()
113
+ main_layout.addWidget(self.features_frame)
114
+
115
+ # second frame for ISOTROPIC MEASUREMENTS
116
+ self.iso_frame = QFrame()
117
+ self.iso_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
118
+ self.populate_iso_frame()
119
+ main_layout.addWidget(self.iso_frame)
120
+
121
+ self.spot_detection_frame = QFrame()
122
+ self.spot_detection_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
123
+ self.populate_spot_detection()
124
+ main_layout.addWidget(self.spot_detection_frame)
125
+
126
+ self.clear_previous_btn = QCheckBox('clear previous measurements')
127
+ main_layout.addWidget(self.clear_previous_btn)
128
+
129
+ self.submit_btn = QPushButton('Save')
130
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
131
+ self.submit_btn.clicked.connect(self.write_instructions)
132
+ main_layout.addWidget(self.submit_btn)
133
+
134
+ # self.populate_left_panel()
135
+ # grid.addLayout(self.left_side, 0, 0, 1, 1)
136
+ self.button_widget.adjustSize()
137
+
138
+ self.scroll_area.setAlignment(Qt.AlignCenter)
139
+ self.scroll_area.setWidget(self.button_widget)
140
+ self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
141
+ self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
142
+ self.scroll_area.setWidgetResizable(True)
143
+ self.setCentralWidget(self.scroll_area)
144
+ self.show()
145
+
146
+ QApplication.processEvents()
147
+ self.adjustScrollArea()
148
+
149
+ def populate_iso_frame(self):
150
+
151
+ """
152
+ Add widgets and layout in the POST-PROCESSING frame.
153
+ """
154
+
155
+ grid = QGridLayout(self.iso_frame)
156
+
157
+ self.iso_lbl = QLabel("ISOTROPIC MEASUREMENTS")
158
+ self.iso_lbl.setStyleSheet("""
159
+ font-weight: bold;
160
+ padding: 0px;
161
+ """)
162
+ grid.addWidget(self.iso_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
163
+ self.generate_iso_contents()
164
+ grid.addWidget(self.ContentsIso, 1, 0, 1, 4, alignment=Qt.AlignTop)
165
+
166
+ def populate_features_frame(self):
167
+
168
+ """
169
+ Add widgets and layout in the FEATURES frame.
170
+ """
171
+
172
+ grid = QGridLayout(self.features_frame)
173
+
174
+ self.feature_lbl = QLabel("FEATURES")
175
+ self.feature_lbl.setStyleSheet("""
176
+ font-weight: bold;
177
+ padding: 0px;
178
+ """)
179
+ grid.addWidget(self.feature_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
180
+
181
+ self.generate_feature_panel_contents()
182
+ grid.addWidget(self.ContentsFeatures, 1, 0, 1, 4, alignment=Qt.AlignTop)
183
+
184
+ def generate_iso_contents(self):
185
+
186
+ self.ContentsIso = QFrame()
187
+ layout = QVBoxLayout(self.ContentsIso)
188
+ layout.setContentsMargins(0, 0, 0, 0)
189
+
190
+ radii_layout = QHBoxLayout()
191
+ self.radii_lbl = QLabel('Measurement radii (from center):')
192
+ self.radii_lbl.setToolTip('Define radii or donughts for intensity measurements.')
193
+ radii_layout.addWidget(self.radii_lbl, 90)
194
+
195
+ self.del_radius_btn = QPushButton("")
196
+ self.del_radius_btn.setStyleSheet(self.button_select_all)
197
+ self.del_radius_btn.setIcon(icon(MDI6.trash_can, color="black"))
198
+ self.del_radius_btn.setToolTip("Remove radius")
199
+ self.del_radius_btn.setIconSize(QSize(20, 20))
200
+ radii_layout.addWidget(self.del_radius_btn, 5)
201
+
202
+ self.add_radius_btn = QPushButton("")
203
+ self.add_radius_btn.setStyleSheet(self.button_select_all)
204
+ self.add_radius_btn.setIcon(icon(MDI6.plus, color="black"))
205
+ self.add_radius_btn.setToolTip("Add radius")
206
+ self.add_radius_btn.setIconSize(QSize(20, 20))
207
+ radii_layout.addWidget(self.add_radius_btn, 5)
208
+ layout.addLayout(radii_layout)
209
+
210
+ self.radii_list = ListWidget(GeometryChoice, initial_features=["10"], dtype=int)
211
+ layout.addWidget(self.radii_list)
212
+
213
+ self.del_radius_btn.clicked.connect(self.radii_list.removeSel)
214
+ self.add_radius_btn.clicked.connect(self.radii_list.addItem)
215
+
216
+ # Operation
217
+ operation_layout = QHBoxLayout()
218
+ self.op_lbl = QLabel('Operation to perform:')
219
+ self.op_lbl.setToolTip('Set the operations to perform inside the ROI.')
220
+ operation_layout.addWidget(self.op_lbl, 90)
221
+
222
+ self.del_op_btn = QPushButton("")
223
+ self.del_op_btn.setStyleSheet(self.button_select_all)
224
+ self.del_op_btn.setIcon(icon(MDI6.trash_can, color="black"))
225
+ self.del_op_btn.setToolTip("Remove operation")
226
+ self.del_op_btn.setIconSize(QSize(20, 20))
227
+ operation_layout.addWidget(self.del_op_btn, 5)
228
+
229
+ self.add_op_btn = QPushButton("")
230
+ self.add_op_btn.setStyleSheet(self.button_select_all)
231
+ self.add_op_btn.setIcon(icon(MDI6.plus, color="black"))
232
+ self.add_op_btn.setToolTip("Add operation")
233
+ self.add_op_btn.setIconSize(QSize(20, 20))
234
+ operation_layout.addWidget(self.add_op_btn, 5)
235
+ layout.addLayout(operation_layout)
236
+
237
+ self.operations_list = ListWidget(OperationChoice, initial_features=["mean"])
238
+ layout.addWidget(self.operations_list)
239
+
240
+ self.del_op_btn.clicked.connect(self.operations_list.removeSel)
241
+ self.add_op_btn.clicked.connect(self.operations_list.addItem)
242
+
243
+ def generate_feature_panel_contents(self):
244
+
245
+ self.ContentsFeatures = QFrame()
246
+ layout = QVBoxLayout(self.ContentsFeatures)
247
+ layout.setContentsMargins(0, 0, 0, 0)
248
+
249
+ feature_layout = QHBoxLayout()
250
+ feature_layout.setContentsMargins(0, 0, 0, 0)
251
+
252
+ self.feature_lbl = QLabel("Add features:")
253
+ self.del_feature_btn = QPushButton("")
254
+ self.del_feature_btn.setStyleSheet(self.button_select_all)
255
+ self.del_feature_btn.setIcon(icon(MDI6.trash_can, color="black"))
256
+ self.del_feature_btn.setToolTip("Remove feature")
257
+ self.del_feature_btn.setIconSize(QSize(20, 20))
258
+
259
+ self.add_feature_btn = QPushButton("")
260
+ self.add_feature_btn.setStyleSheet(self.button_select_all)
261
+ self.add_feature_btn.setIcon(icon(MDI6.filter_plus, color="black"))
262
+ self.add_feature_btn.setToolTip("Add feature")
263
+ self.add_feature_btn.setIconSize(QSize(20, 20))
264
+
265
+ self.features_list = ListWidget(FeatureChoice, initial_features=['area', 'intensity_mean', ])
266
+
267
+ self.del_feature_btn.clicked.connect(self.features_list.removeSel)
268
+ self.add_feature_btn.clicked.connect(self.features_list.addItem)
269
+
270
+ feature_layout.addWidget(self.feature_lbl, 90)
271
+ feature_layout.addWidget(self.del_feature_btn, 5)
272
+ feature_layout.addWidget(self.add_feature_btn, 5)
273
+ layout.addLayout(feature_layout)
274
+ layout.addWidget(self.features_list)
275
+
276
+ self.feat_sep2 = QHSeperationLine()
277
+ layout.addWidget(self.feat_sep2)
278
+
279
+ contour_layout = QHBoxLayout()
280
+ self.border_dist_lbl = QLabel('Contour measurements (from edge of mask):')
281
+ self.border_dist_lbl.setToolTip(
282
+ 'Apply the intensity measurements defined above\nto a slice of each cell mask, defined as distance\nfrom the edge.')
283
+ contour_layout.addWidget(self.border_dist_lbl, 90)
284
+
285
+ self.del_contour_btn = QPushButton("")
286
+ self.del_contour_btn.setStyleSheet(self.button_select_all)
287
+ self.del_contour_btn.setIcon(icon(MDI6.trash_can, color="black"))
288
+ self.del_contour_btn.setToolTip("Remove distance")
289
+ self.del_contour_btn.setIconSize(QSize(20, 20))
290
+ contour_layout.addWidget(self.del_contour_btn, 5)
291
+
292
+ self.add_contour_btn = QPushButton("")
293
+ self.add_contour_btn.setStyleSheet(self.button_select_all)
294
+ self.add_contour_btn.setIcon(icon(MDI6.plus, color="black"))
295
+ self.add_contour_btn.setToolTip("Add distance")
296
+ self.add_contour_btn.setIconSize(QSize(20, 20))
297
+ contour_layout.addWidget(self.add_contour_btn, 5)
298
+
299
+ self.view_contour_btn = QPushButton("")
300
+ self.view_contour_btn.setStyleSheet(self.button_select_all)
301
+ self.view_contour_btn.setIcon(icon(MDI6.eye_plus_outline, color="black"))
302
+ self.view_contour_btn.setToolTip("View contour")
303
+ self.view_contour_btn.setIconSize(QSize(20, 20))
304
+ contour_layout.addWidget(self.view_contour_btn, 5)
305
+
306
+ layout.addLayout(contour_layout)
307
+
308
+ self.contours_list = ListWidget(GeometryChoice, initial_features=[], dtype=int)
309
+ layout.addWidget(self.contours_list)
310
+
311
+ self.del_contour_btn.clicked.connect(self.contours_list.removeSel)
312
+ self.add_contour_btn.clicked.connect(self.contours_list.addItem)
313
+ self.view_contour_btn.clicked.connect(self.view_selected_contour)
314
+
315
+ self.feat_sep3 = QHSeperationLine()
316
+ layout.addWidget(self.feat_sep3)
317
+ # self.radial_intensity_btn = QCheckBox('Measure radial intensity distribution')
318
+ # layout.addWidget(self.radial_intensity_btn)
319
+ # self.radial_intensity_btn.clicked.connect(self.enable_step_size)
320
+ # self.channel_chechkboxes=[]
321
+ # for channel in self.channel_names:
322
+ # channel_checkbox=QCheckBox(channel)
323
+ # self.channel_chechkboxes.append(channel_checkbox)
324
+ # layout.addWidget(channel_checkbox)
325
+ # channel_checkbox.setEnabled(False)
326
+ # step_box=QHBoxLayout()
327
+ # self.step_lbl=QLabel("Step size (in px)")
328
+ # self.step_size=QLineEdit()
329
+ # self.step_lbl.setEnabled(False)
330
+ # self.step_size.setEnabled(False)
331
+ # step_box.addWidget(self.step_lbl)
332
+ # step_box.addWidget(self.step_size)
333
+ # layout.addLayout(step_box)
334
+ # self.feat_sep4 = QHSeperationLine()
335
+ # layout.addWidget(self.feat_sep4)
336
+
337
+ # Haralick features parameters
338
+ self.activate_haralick_btn = QCheckBox('Measure Haralick texture features')
339
+ self.activate_haralick_btn.toggled.connect(self.show_haralick_options)
340
+
341
+ self.haralick_channel_choice = QComboBox()
342
+ self.haralick_channel_choice.addItems(self.channel_names)
343
+ self.haralick_channel_lbl = QLabel('Target channel: ')
344
+
345
+ self.haralick_distance_le = QLineEdit("1")
346
+ self.haralick_distance_lbl = QLabel('Distance: ')
347
+
348
+ self.haralick_n_gray_levels_le = QLineEdit("256")
349
+ self.haralick_n_gray_levels_lbl = QLabel('# gray levels: ')
350
+
351
+ # Slider to set vmin & vmax
352
+ self.haralick_scale_slider = QLabeledDoubleSlider()
353
+ self.haralick_scale_slider.setSingleStep(0.05)
354
+ self.haralick_scale_slider.setTickInterval(0.05)
355
+ self.haralick_scale_slider.setSingleStep(1)
356
+ self.haralick_scale_slider.setOrientation(1)
357
+ self.haralick_scale_slider.setRange(0, 1)
358
+ self.haralick_scale_slider.setValue(0.5)
359
+ self.haralick_scale_lbl = QLabel('Scale: ')
360
+
361
+ self.haralick_percentile_min_le = QLineEdit('0.01')
362
+ self.haralick_percentile_max_le = QLineEdit('99.9')
363
+ self.haralick_normalization_mode_btn = QPushButton()
364
+ self.haralick_normalization_mode_btn.setStyleSheet(self.button_select_all)
365
+ self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
366
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
367
+ self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
368
+ self.percentile_mode = True
369
+
370
+ min_percentile_hbox = QHBoxLayout()
371
+ min_percentile_hbox.addWidget(self.haralick_percentile_min_le, 90)
372
+ min_percentile_hbox.addWidget(self.haralick_normalization_mode_btn, 10)
373
+ min_percentile_hbox.setContentsMargins(0, 0, 0, 0)
374
+
375
+ self.haralick_percentile_min_lbl = QLabel('Min percentile: ')
376
+ self.haralick_percentile_max_lbl = QLabel('Max percentile: ')
377
+
378
+ self.haralick_hist_btn = QPushButton()
379
+ self.haralick_hist_btn.clicked.connect(self.control_haralick_intensity_histogram)
380
+ self.haralick_hist_btn.setIcon(icon(MDI6.poll, color="k"))
381
+ self.haralick_hist_btn.setStyleSheet(self.button_select_all)
382
+
383
+ self.haralick_digit_btn = QPushButton()
384
+ self.haralick_digit_btn.clicked.connect(self.control_haralick_digitalization)
385
+ self.haralick_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
386
+ self.haralick_digit_btn.setStyleSheet(self.button_select_all)
387
+
388
+ self.haralick_layout = QVBoxLayout()
389
+ self.haralick_layout.setContentsMargins(20, 20, 20, 20)
390
+
391
+ activate_layout = QHBoxLayout()
392
+ activate_layout.addWidget(self.activate_haralick_btn, 80)
393
+ activate_layout.addWidget(self.haralick_hist_btn, 10)
394
+ activate_layout.addWidget(self.haralick_digit_btn, 10)
395
+ self.haralick_layout.addLayout(activate_layout)
396
+
397
+ channel_layout = QHBoxLayout()
398
+ channel_layout.addWidget(self.haralick_channel_lbl, 40)
399
+ channel_layout.addWidget(self.haralick_channel_choice, 60)
400
+ self.haralick_layout.addLayout(channel_layout)
401
+
402
+ distance_layout = QHBoxLayout()
403
+ distance_layout.addWidget(self.haralick_distance_lbl, 40)
404
+ distance_layout.addWidget(self.haralick_distance_le, 60)
405
+ self.haralick_layout.addLayout(distance_layout)
406
+
407
+ gl_layout = QHBoxLayout()
408
+ gl_layout.addWidget(self.haralick_n_gray_levels_lbl, 40)
409
+ gl_layout.addWidget(self.haralick_n_gray_levels_le, 60)
410
+ self.haralick_layout.addLayout(gl_layout)
411
+
412
+ slider_layout = QHBoxLayout()
413
+ slider_layout.addWidget(self.haralick_scale_lbl, 40)
414
+ slider_layout.addWidget(self.haralick_scale_slider, 60)
415
+ self.haralick_layout.addLayout(slider_layout)
416
+
417
+ slider_min_percentile_layout = QHBoxLayout()
418
+ slider_min_percentile_layout.addWidget(self.haralick_percentile_min_lbl, 40)
419
+ # slider_min_percentile_layout.addWidget(self.haralick_percentile_min_le,55)
420
+ slider_min_percentile_layout.addLayout(min_percentile_hbox, 60)
421
+ # slider_min_percentile_layout.addWidget(self.haralick_normalization_mode_btn, 5)
422
+ self.haralick_layout.addLayout(slider_min_percentile_layout)
423
+
424
+ slider_max_percentile_layout = QHBoxLayout()
425
+ slider_max_percentile_layout.addWidget(self.haralick_percentile_max_lbl, 40)
426
+ slider_max_percentile_layout.addWidget(self.haralick_percentile_max_le, 60)
427
+ self.haralick_layout.addLayout(slider_max_percentile_layout)
428
+
429
+ self.haralick_to_hide = [self.haralick_hist_btn, self.haralick_digit_btn, self.haralick_channel_lbl,
430
+ self.haralick_channel_choice,
431
+ self.haralick_distance_le, self.haralick_distance_lbl, self.haralick_n_gray_levels_le,
432
+ self.haralick_n_gray_levels_lbl,
433
+ self.haralick_scale_lbl, self.haralick_scale_slider, self.haralick_percentile_min_lbl,
434
+ self.haralick_percentile_min_le,
435
+ self.haralick_percentile_max_lbl, self.haralick_percentile_max_le,
436
+ self.haralick_normalization_mode_btn]
437
+
438
+ self.features_to_disable = [self.feature_lbl, self.del_feature_btn, self.add_feature_btn, self.features_list,
439
+ self.activate_haralick_btn]
440
+
441
+ self.activate_haralick_btn.setChecked(False)
442
+ for f in self.haralick_to_hide:
443
+ f.setEnabled(False)
444
+
445
+ self.haralick_normalization_mode_btn.clicked.connect(self.switch_to_absolute_normalization_mode)
446
+ layout.addLayout(self.haralick_layout)
447
+
448
+ def switch_to_absolute_normalization_mode(self):
449
+
450
+ if self.percentile_mode:
451
+ self.percentile_mode = False
452
+ self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle_outline, color="black"))
453
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
454
+ self.haralick_normalization_mode_btn.setToolTip("Switch to percentile normalization values.")
455
+ self.haralick_percentile_min_lbl.setText('Min value: ')
456
+ self.haralick_percentile_max_lbl.setText('Max value: ')
457
+ self.haralick_percentile_min_le.setText('0')
458
+ self.haralick_percentile_max_le.setText('10000')
459
+
460
+ else:
461
+ self.percentile_mode = True
462
+ self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
463
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
464
+ self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
465
+ self.haralick_percentile_min_lbl.setText('Min percentile: ')
466
+ self.haralick_percentile_max_lbl.setText('Max percentile: ')
467
+ self.haralick_percentile_min_le.setText('0.01')
468
+ self.haralick_percentile_max_le.setText('99.99')
469
+
470
+ def show_haralick_options(self):
471
+
472
+ """
473
+ Show the Haralick texture options.
474
+ """
475
+
476
+ if self.activate_haralick_btn.isChecked():
477
+ for element in self.haralick_to_hide:
478
+ element.setEnabled(True)
479
+ else:
480
+ for element in self.haralick_to_hide:
481
+ element.setEnabled(False)
482
+
483
+ def adjustScrollArea(self):
484
+
485
+ """
486
+ Auto-adjust scroll area to fill space
487
+ (from https://stackoverflow.com/questions/66417576/make-qscrollarea-use-all-available-space-of-qmainwindow-height-axis)
488
+ """
489
+
490
+ step = 5
491
+ while self.scroll_area.verticalScrollBar().isVisible() and self.height() < self.maximumHeight():
492
+ self.resize(self.width(), self.height() + step)
493
+
494
+ def write_instructions(self):
495
+
496
+ """
497
+ Write the selected options in a json file for later reading by the software.
498
+ """
499
+
500
+ print('Writing instructions...')
501
+ measurement_options = {}
502
+ background_correction = self.protocol_layout.protocols
503
+ if not background_correction:
504
+ background_correction = None
505
+ measurement_options.update({'background_correction': background_correction})
506
+ features = self.features_list.getItems()
507
+ if not features:
508
+ features = None
509
+ measurement_options.update({'features': features})
510
+
511
+ border_distances = self.contours_list.getItems()
512
+ if not border_distances:
513
+ border_distances = None
514
+ measurement_options.update({'border_distances': border_distances})
515
+ # radial_intensity = {}
516
+ # radial_step = int(self.step_size.text())
517
+ # radial_channels = []
518
+ # for checkbox in self.channel_chechkboxes:
519
+ # if checkbox.isChecked():
520
+ # radial_channels.append(checkbox.text())
521
+ # radial_intensity={'radial_step': radial_step, 'radial_channels': radial_channels}
522
+ # if not self.radial_intensity_btn.isChecked():
523
+ # radial_intensity = None
524
+ # measurement_options.update({'radial_intensity' : radial_intensity})
525
+
526
+ self.extract_haralick_options()
527
+ measurement_options.update({'haralick_options': self.haralick_options})
528
+
529
+ intensity_measurement_radii = self.radii_list.getItems()
530
+ if not intensity_measurement_radii:
531
+ intensity_measurement_radii = None
532
+
533
+ isotropic_operations = self.operations_list.getItems()
534
+ if not isotropic_operations:
535
+ isotropic_operations = None
536
+ intensity_measurement_radii = None
537
+ measurement_options.update({'intensity_measurement_radii': intensity_measurement_radii,
538
+ 'isotropic_operations': isotropic_operations})
539
+ spot_detection = None
540
+ if self.spot_check.isChecked():
541
+ spot_detection = {'channel': self.spot_channel.currentText(), 'diameter': float(self.diameter_value.text().replace(',','.')),
542
+ 'threshold': float(self.threshold_value.text().replace(',','.'))}
543
+ measurement_options.update({'spot_detection': spot_detection})
544
+ if self.clear_previous_btn.isChecked():
545
+ self.clear_previous = True
546
+ else:
547
+ self.clear_previous = False
548
+ measurement_options.update({'clear_previous': self.clear_previous})
549
+
550
+
551
+
552
+ print('Measurement instructions: ', measurement_options)
553
+ file_name = self.measure_instructions_path
554
+ with open(file_name, 'w') as f:
555
+ json.dump(measurement_options, f, indent=4)
556
+
557
+ print('Done.')
558
+ self.close()
559
+
560
+ def extract_haralick_options(self):
561
+
562
+ if self.activate_haralick_btn.isChecked():
563
+ self.haralick_options = {"target_channel": self.haralick_channel_choice.currentIndex(),
564
+ "scale_factor": float(self.haralick_scale_slider.value()),
565
+ "n_intensity_bins": int(self.haralick_n_gray_levels_le.text()),
566
+ "distance": int(self.haralick_distance_le.text()),
567
+ }
568
+ if self.percentile_mode:
569
+ self.haralick_options.update({"percentiles": (
570
+ float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text())),
571
+ "clip_values": None})
572
+ else:
573
+ self.haralick_options.update({"percentiles": None, "clip_values": (
574
+ float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text()))})
575
+
576
+ else:
577
+ self.haralick_options = None
578
+
579
+ def load_previous_measurement_instructions(self):
580
+
581
+ """
582
+ Read the measurmeent options from a previously written json file and format properly for the UI.
583
+ """
584
+
585
+ print('Reading instructions..')
586
+ if os.path.exists(self.measure_instructions_path):
587
+ with open(self.measure_instructions_path, 'r') as f:
588
+ measurement_instructions = json.load(f)
589
+ print(measurement_instructions)
590
+ if 'background_correction' in measurement_instructions:
591
+ self.protocol_layout.protocols = measurement_instructions['background_correction']
592
+ if self.protocol_layout.protocols is None:
593
+ self.protocol_layout.protocols = []
594
+ if (self.protocol_layout.protocols is not None) and len(self.protocol_layout.protocols) > 0:
595
+ self.protocol_layout.protocol_list.clear()
596
+ for norm_params in self.protocol_layout.protocols:
597
+ normalisation_description = ""
598
+ for index, (key, value) in enumerate(norm_params.items()):
599
+ if index > 0:
600
+ normalisation_description += ", "
601
+ normalisation_description += str(key) + " : " + str(value)
602
+ self.protocol_layout.protocol_list.addItem(normalisation_description)
603
+ else:
604
+ self.protocol_layout.protocol_list.clear()
605
+ if 'features' in measurement_instructions:
606
+ features = measurement_instructions['features']
607
+ if (features is not None) and len(features) > 0:
608
+ self.features_list.list_widget.clear()
609
+ self.features_list.list_widget.addItems(features)
610
+ else:
611
+ self.features_list.list_widget.clear()
612
+
613
+ if 'spot_detection' in measurement_instructions:
614
+ spot_detection = measurement_instructions['spot_detection']
615
+ if spot_detection is not None:
616
+ self.spot_check.setChecked(True)
617
+ if 'channel' in spot_detection:
618
+ idx = spot_detection['channel']
619
+ self.spot_channel.setCurrentText(idx)
620
+ self.diameter_value.setText(str(spot_detection['diameter']))
621
+ self.threshold_value.setText(str(spot_detection['threshold']))
622
+
623
+
624
+ if 'border_distances' in measurement_instructions:
625
+ border_distances = measurement_instructions['border_distances']
626
+ if border_distances is not None:
627
+ if isinstance(border_distances, int):
628
+ distances = [border_distances]
629
+ elif isinstance(border_distances, list):
630
+ distances = []
631
+ for d in border_distances:
632
+ if isinstance(d, int) | isinstance(d, float):
633
+ distances.append(str(int(d)))
634
+ elif isinstance(d, list):
635
+ distances.append(str(int(d[0])) + '-' + str(int(d[1])))
636
+ self.contours_list.list_widget.clear()
637
+ self.contours_list.list_widget.addItems(distances)
638
+
639
+ if 'haralick_options' in measurement_instructions:
640
+ haralick_options = measurement_instructions['haralick_options']
641
+ if haralick_options is None:
642
+ self.activate_haralick_btn.setChecked(False)
643
+ self.show_haralick_options()
644
+ else:
645
+ self.activate_haralick_btn.setChecked(True)
646
+ self.show_haralick_options()
647
+ if 'target_channel' in haralick_options:
648
+ idx = haralick_options['target_channel']
649
+ self.haralick_channel_choice.setCurrentIndex(idx)
650
+ if 'scale_factor' in haralick_options:
651
+ self.haralick_scale_slider.setValue(float(haralick_options['scale_factor']))
652
+ if ('percentiles' in haralick_options) and (haralick_options['percentiles'] is not None):
653
+ perc = list(haralick_options['percentiles'])
654
+ self.haralick_percentile_min_le.setText(str(perc[0]))
655
+ self.haralick_percentile_max_le.setText(str(perc[1]))
656
+ if ('clip_values' in haralick_options) and (haralick_options['clip_values'] is not None):
657
+ values = list(haralick_options['clip_values'])
658
+ self.percentile_mode = True
659
+ self.switch_to_absolute_normalization_mode()
660
+ self.haralick_percentile_min_le.setText(str(values[0]))
661
+ self.haralick_percentile_max_le.setText(str(values[1]))
662
+ if 'n_intensity_bins' in haralick_options:
663
+ self.haralick_n_gray_levels_le.setText(str(haralick_options['n_intensity_bins']))
664
+ if 'distance' in haralick_options:
665
+ self.haralick_distance_le.setText(str(haralick_options['distance']))
666
+
667
+ if 'intensity_measurement_radii' in measurement_instructions:
668
+ intensity_measurement_radii = measurement_instructions['intensity_measurement_radii']
669
+ if intensity_measurement_radii is not None:
670
+ if isinstance(intensity_measurement_radii, int):
671
+ radii = [intensity_measurement_radii]
672
+ elif isinstance(intensity_measurement_radii, list):
673
+ radii = []
674
+ for r in intensity_measurement_radii:
675
+ if isinstance(r, int) | isinstance(r, float):
676
+ radii.append(str(int(r)))
677
+ elif isinstance(r, list):
678
+ radii.append(str(int(r[0])) + '-' + str(int(r[1])))
679
+ self.radii_list.list_widget.clear()
680
+ self.radii_list.list_widget.addItems(radii)
681
+ else:
682
+ self.radii_list.list_widget.clear()
683
+
684
+ if 'isotropic_operations' in measurement_instructions:
685
+ isotropic_operations = measurement_instructions['isotropic_operations']
686
+ if (isotropic_operations is not None) and len(isotropic_operations) > 0:
687
+ self.operations_list.list_widget.clear()
688
+ self.operations_list.list_widget.addItems(isotropic_operations)
689
+ else:
690
+ self.operations_list.list_widget.clear()
691
+
692
+ # if 'radial_intensity' in measurement_instructions:
693
+ # radial_intensity = measurement_instructions['radial_intensity']
694
+ # if radial_intensity is not None:
695
+ # self.radial_intensity_btn.setChecked(True)
696
+ # self.step_size.setText(str(radial_intensity['radial_step']))
697
+ # self.step_size.setEnabled(True)
698
+ # self.step_lbl.setEnabled(True)
699
+ # for checkbox in self.channel_chechkboxes:
700
+ # checkbox.setEnabled(True)
701
+ # if checkbox.text() in radial_intensity['radial_channels']:
702
+ # checkbox.setChecked(True)
703
+
704
+ if 'clear_previous' in measurement_instructions:
705
+ self.clear_previous = measurement_instructions['clear_previous']
706
+ self.clear_previous_btn.setChecked(self.clear_previous)
707
+
708
+ def locate_image(self):
709
+
710
+ """
711
+ Load the first frame of the first movie found in the experiment folder as a sample.
712
+ """
713
+
714
+ movies = glob(self.parent_window.parent_window.pos + os.sep.join(['movie', f"{self.parent_window.movie_prefix}*.tif"]))
715
+
716
+ if len(movies) == 0:
717
+ msgBox = QMessageBox()
718
+ msgBox.setIcon(QMessageBox.Warning)
719
+ msgBox.setText("Please select a position containing a movie...")
720
+ msgBox.setWindowTitle("Warning")
721
+ msgBox.setStandardButtons(QMessageBox.Ok)
722
+ returnValue = msgBox.exec()
723
+ if returnValue == QMessageBox.Ok:
724
+ self.current_stack = None
725
+ return None
726
+ else:
727
+ self.current_stack = movies[0]
728
+ self.stack_length = auto_load_number_of_frames(self.current_stack)
729
+
730
+ if self.stack_length is None:
731
+ stack = imread(self.current_stack)
732
+ self.stack_length = len(stack)
733
+ del stack
734
+ gc.collect()
735
+
736
+ self.mid_time = self.stack_length // 2
737
+ indices = self.mid_time + np.arange(len(self.channel_names))
738
+ self.test_frame = load_frames(list(indices.astype(int)),self.current_stack, normalize_input=False)
739
+
740
+
741
+ def control_haralick_digitalization(self):
742
+
743
+ """
744
+ Load an image for the first experiment movie found.
745
+ Apply the Haralick parameters and check the result of the digitization (normalization + binning of intensities).
746
+
747
+ """
748
+
749
+ self.locate_image()
750
+ self.extract_haralick_options()
751
+ if self.test_frame is not None:
752
+ digitized_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
753
+ channels=self.channel_names, return_digit_image_only=True,
754
+ **self.haralick_options
755
+ )
756
+
757
+ self.fig, self.ax = plt.subplots()
758
+ divider = make_axes_locatable(self.ax)
759
+ cax = divider.append_axes('right', size='5%', pad=0.05)
760
+
761
+ self.imshow_digit_window = FigureCanvas(self.fig, title="Haralick: control digitization")
762
+ self.ax.clear()
763
+ im = self.ax.imshow(digitized_img, cmap='gray')
764
+ self.fig.colorbar(im, cax=cax, orientation='vertical')
765
+ self.ax.set_xticks([])
766
+ self.ax.set_yticks([])
767
+ self.fig.set_facecolor('none') # or 'None'
768
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
769
+ self.imshow_digit_window.canvas.draw()
770
+ self.imshow_digit_window.show()
771
+
772
+ def control_haralick_intensity_histogram(self):
773
+
774
+ """
775
+ Load an image for the first experiment movie found.
776
+ Apply the Haralick normalization parameters and check the normalized intensity histogram.
777
+
778
+ """
779
+
780
+ self.locate_image()
781
+ self.extract_haralick_options()
782
+ if self.test_frame is not None:
783
+ norm_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
784
+ channels=self.channel_names, return_norm_image_only=True,
785
+ **self.haralick_options
786
+ )
787
+ self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
788
+ self.hist_window = FigureCanvas(self.fig, title="Haralick: control digitized histogram")
789
+ self.ax.clear()
790
+ flat = norm_img.flatten()
791
+ self.ax.hist(flat[flat==flat], bins=self.haralick_options['n_intensity_bins'])
792
+ self.ax.set_xlabel('gray level value')
793
+ self.ax.set_ylabel('#')
794
+ plt.tight_layout()
795
+ self.fig.set_facecolor('none') # or 'None'
796
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
797
+ self.hist_window.canvas.draw()
798
+ self.hist_window.show()
799
+
800
+ def view_selected_contour(self):
801
+
802
+ """
803
+ Show the ROI for the selected contour measurement on experimental data.
804
+
805
+ """
806
+ self.locate_image()
807
+
808
+ if self.current_stack is not None:
809
+
810
+ self.viewer = CellEdgeVisualizer(cell_type=self.mode,
811
+ stack_path=self.current_stack,
812
+ parent_list_widget=self.contours_list.list_widget,
813
+ n_channels=len(self.channel_names),
814
+ target_channel=0,
815
+ window_title='Set an edge measurement',
816
+ channel_cb=True,
817
+ channel_names = self.channel_names,
818
+ PxToUm = 1,
819
+ )
820
+ self.viewer.show()
821
+
822
+ def locate_mask(self):
823
+
824
+ """
825
+ Load the first mask of the detected movie.
826
+ """
827
+
828
+ labels_path = str(Path(self.current_stack).parent.parent) + os.sep+f'labels_{self.mode}'+os.sep
829
+ masks = natsorted(glob(labels_path + '*.tif'))
830
+ if len(masks) == 0:
831
+ print('no mask found')
832
+ self.test_mask = None
833
+ else:
834
+ self.test_mask = imread(masks[self.mid_time])
835
+
836
+ def switch_channel_contour(self, value):
837
+
838
+ """
839
+ Adjust intensity values when changing channels in the contour visualizer.
840
+
841
+ """
842
+
843
+ self.im_contour.set_array(self.test_frame[:, :, value])
844
+ self.im_contour.set_clim(vmin=np.percentile(self.test_frame[:, :, value].flatten(), 1),
845
+ vmax=np.percentile(self.test_frame[:, :, value].flatten(), 99.99))
846
+ self.contrast_slider_contour.setRange(np.amin(self.test_frame[:, :, value]),
847
+ np.amax(self.test_frame[:, :, value]))
848
+ self.contrast_slider_contour.setValue([np.percentile(self.test_frame[:, :, value].flatten(), 1),
849
+ np.percentile(self.test_frame[:, :, value].flatten(), 99.99)])
850
+ self.fig_contour.canvas.draw_idle()
851
+
852
+ def contrast_im_contour(self, value):
853
+ vmin = value[0]
854
+ vmax = value[1]
855
+ self.im_contour.set_clim(vmin=vmin, vmax=vmax)
856
+ self.fig_contour.canvas.draw_idle()
857
+
858
+ def make_contour_transparent(self, value):
859
+
860
+ self.im_mask.set_alpha(value)
861
+ self.fig_contour.canvas.draw_idle()
862
+
863
+ def remove_item_from_list(self):
864
+ current_item = self.normalisation_list.currentRow()
865
+ if current_item > -1:
866
+ del self.background_correction[current_item]
867
+ self.normalisation_list.takeItem(current_item)
868
+
869
+ def check_the_information(self):
870
+
871
+ if self.tabs.currentIndex() == 0:
872
+ if self.background_correction is None:
873
+ self.background_correction = []
874
+ for index, normalisation_opt in enumerate(self.background_correction ):
875
+ if self.tab1_channel_dropdown.currentText() in normalisation_opt['target channel']:
876
+ result = self.channel_already_in_list()
877
+ if result != QMessageBox.Yes:
878
+ return False
879
+ else:
880
+ self.background_correction .remove(normalisation_opt)
881
+ self.normalisation_list.takeItem(index)
882
+ return True
883
+
884
+ def display_message_box(self, missing_info):
885
+ QMessageBox.about(self, "Message Box Title", "Please " + missing_info + " for background correction")
886
+
887
+ def channel_already_in_list(self):
888
+ response = QMessageBox.question(self, "Message Box Title",
889
+ "The background correction parameters for this channel already exist, "
890
+ "continuing will erase the previous configurations. Are you sure you want to "
891
+ "proceed?",
892
+ QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
893
+ return response
894
+
895
+ def fun(self, x, y):
896
+ return x ** 2 + y
897
+
898
+ def view_normalisation_contour(self):
899
+
900
+ """
901
+ Show the ROI for the selected contour measurement on experimental data.
902
+
903
+ """
904
+
905
+ self.locate_image()
906
+
907
+ if self.current_stack is not None:
908
+
909
+ self.viewer = CellEdgeVisualizer(cell_type=self.mode,
910
+ stack_path=self.current_stack,
911
+ parent_le = self.tab1_txt_distance,
912
+ n_channels=len(self.channel_names),
913
+ target_channel=self.tab1_channel_dropdown.currentIndex(),
914
+ edge_range = (0,30),
915
+ initial_edge= self.tab1_txt_distance.get_threshold(),
916
+ invert=True,
917
+ window_title='Set an edge distance to estimate local intensity',
918
+ channel_cb=False,
919
+ PxToUm = 1,
920
+ )
921
+ self.viewer.show()
922
+
923
+
924
+ def populate_spot_detection(self):
925
+
926
+ layout = QGridLayout(self.spot_detection_frame)
927
+ self.spot_detection_lbl = QLabel("SPOT DETECTION")
928
+ self.spot_detection_lbl.setStyleSheet("""font-weight: bold;padding: 0px;""")
929
+ layout.addWidget(self.spot_detection_lbl, 0, 0, 1, 2, alignment=Qt.AlignCenter)
930
+ self.spot_check= QCheckBox('Perform spot detection')
931
+ self.spot_check.toggled.connect(self.enable_spot_detection)
932
+ layout.addWidget(self.spot_check, 1, 0)
933
+ self.spot_channel_lbl = QLabel("Choose channel for spot detection: ")
934
+ self.spot_channel = QComboBox()
935
+ self.spot_channel.addItems(self.channel_names)
936
+ layout.addWidget(self.spot_channel_lbl, 2, 0)
937
+ layout.addWidget(self.spot_channel, 2, 1)
938
+ self.diameter_lbl = QLabel('Spot diameter: ')
939
+ self.diameter_value = QLineEdit()
940
+ self.diameter_value.setValidator(self.onlyFloat)
941
+ self.diameter_value.setText('7')
942
+ self.diameter_value.textChanged.connect(self.enable_spot_preview)
943
+
944
+ layout.addWidget(self.diameter_lbl, 3, 0)
945
+ layout.addWidget(self.diameter_value, 3, 1)
946
+ self.threshold_lbl = QLabel('Spot threshold: ')
947
+ self.threshold_value = QLineEdit()
948
+ self.threshold_value.setValidator(self.onlyFloat)
949
+ self.threshold_value.setText('0')
950
+ self.threshold_value.textChanged.connect(self.enable_spot_preview)
951
+
952
+ layout.addWidget(self.threshold_lbl, 4, 0)
953
+ layout.addWidget(self.threshold_value, 4, 1)
954
+ self.preview_spot = QPushButton('Preview')
955
+ self.preview_spot.clicked.connect(self.spot_preview)
956
+ self.preview_spot.setStyleSheet(self.button_style_sheet_2)
957
+ layout.addWidget(self.preview_spot, 5, 0, 1, 2)
958
+ self.spot_channel.setEnabled(False)
959
+ self.spot_channel_lbl.setEnabled(False)
960
+ self.diameter_value.setEnabled(False)
961
+ self.diameter_lbl.setEnabled(False)
962
+ self.threshold_value.setEnabled(False)
963
+ self.threshold_lbl.setEnabled(False)
964
+ self.preview_spot.setEnabled(False)
965
+
966
+
967
+ def enable_spot_preview(self):
968
+
969
+ diam = self.diameter_value.text().replace(',','').replace('.','')
970
+ thresh = self.threshold_value.text().replace(',','').replace('.','')
971
+ if diam.isnumeric() and thresh.isnumeric():
972
+ self.preview_spot.setEnabled(True)
973
+ else:
974
+ self.preview_spot.setEnabled(False)
975
+
976
+ def spot_preview(self):
977
+ self.locate_image()
978
+ if self.test_frame is not None:
979
+ self.locate_mask()
980
+ if self.test_mask is not None:
981
+ self.spot_visual = ThresholdSpot(current_channel=self.spot_channel.currentIndex(), img=self.test_frame,
982
+ mask=self.test_mask, parent_window=self)
983
+
984
+ def enable_spot_detection(self):
985
+ if self.spot_check.isChecked():
986
+ self.spot_channel.setEnabled(True)
987
+ self.spot_channel_lbl.setEnabled(True)
988
+ self.diameter_value.setEnabled(True)
989
+ self.diameter_lbl.setEnabled(True)
990
+ self.threshold_value.setEnabled(True)
991
+ self.threshold_lbl.setEnabled(True)
992
+ self.preview_spot.setEnabled(True)
993
+
994
+ else:
995
+ self.spot_channel.setEnabled(False)
996
+ self.spot_channel_lbl.setEnabled(False)
997
+ self.diameter_value.setEnabled(False)
998
+ self.diameter_lbl.setEnabled(False)
999
+ self.threshold_value.setEnabled(False)
1000
+ self.threshold_lbl.setEnabled(False)
1001
+ self.preview_spot.setEnabled(False)