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.
- celldetective/__init__.py +2 -0
- celldetective/__main__.py +432 -0
- celldetective/datasets/segmentation_annotations/blank +0 -0
- celldetective/datasets/signal_annotations/blank +0 -0
- celldetective/events.py +149 -0
- celldetective/extra_properties.py +100 -0
- celldetective/filters.py +89 -0
- celldetective/gui/__init__.py +20 -0
- celldetective/gui/about.py +44 -0
- celldetective/gui/analyze_block.py +563 -0
- celldetective/gui/btrack_options.py +898 -0
- celldetective/gui/classifier_widget.py +386 -0
- celldetective/gui/configure_new_exp.py +532 -0
- celldetective/gui/control_panel.py +438 -0
- celldetective/gui/gui_utils.py +495 -0
- celldetective/gui/json_readers.py +113 -0
- celldetective/gui/measurement_options.py +1425 -0
- celldetective/gui/neighborhood_options.py +452 -0
- celldetective/gui/plot_signals_ui.py +1042 -0
- celldetective/gui/process_block.py +1055 -0
- celldetective/gui/retrain_segmentation_model_options.py +706 -0
- celldetective/gui/retrain_signal_model_options.py +643 -0
- celldetective/gui/seg_model_loader.py +460 -0
- celldetective/gui/signal_annotator.py +2388 -0
- celldetective/gui/signal_annotator_options.py +340 -0
- celldetective/gui/styles.py +217 -0
- celldetective/gui/survival_ui.py +903 -0
- celldetective/gui/tableUI.py +608 -0
- celldetective/gui/thresholds_gui.py +1300 -0
- celldetective/icons/logo-large.png +0 -0
- celldetective/icons/logo.png +0 -0
- celldetective/icons/signals_icon.png +0 -0
- celldetective/icons/splash-test.png +0 -0
- celldetective/icons/splash.png +0 -0
- celldetective/icons/splash0.png +0 -0
- celldetective/icons/survival2.png +0 -0
- celldetective/icons/vignette_signals2.png +0 -0
- celldetective/icons/vignette_signals2.svg +114 -0
- celldetective/io.py +2050 -0
- celldetective/links/zenodo.json +561 -0
- celldetective/measure.py +1258 -0
- celldetective/models/segmentation_effectors/blank +0 -0
- celldetective/models/segmentation_generic/blank +0 -0
- celldetective/models/segmentation_targets/blank +0 -0
- celldetective/models/signal_detection/blank +0 -0
- celldetective/models/tracking_configs/mcf7.json +68 -0
- celldetective/models/tracking_configs/ricm.json +203 -0
- celldetective/models/tracking_configs/ricm2.json +203 -0
- celldetective/neighborhood.py +717 -0
- celldetective/scripts/analyze_signals.py +51 -0
- celldetective/scripts/measure_cells.py +275 -0
- celldetective/scripts/segment_cells.py +212 -0
- celldetective/scripts/segment_cells_thresholds.py +140 -0
- celldetective/scripts/track_cells.py +206 -0
- celldetective/scripts/train_segmentation_model.py +246 -0
- celldetective/scripts/train_signal_model.py +49 -0
- celldetective/segmentation.py +712 -0
- celldetective/signals.py +2826 -0
- celldetective/tracking.py +974 -0
- celldetective/utils.py +1681 -0
- celldetective-1.0.2.dist-info/LICENSE +674 -0
- celldetective-1.0.2.dist-info/METADATA +192 -0
- celldetective-1.0.2.dist-info/RECORD +66 -0
- celldetective-1.0.2.dist-info/WHEEL +5 -0
- celldetective-1.0.2.dist-info/entry_points.txt +2 -0
- 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)
|