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,898 @@
|
|
|
1
|
+
from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QScrollArea, QComboBox, QFrame, QCheckBox, QFileDialog, QGridLayout, QTextEdit, QLineEdit, QVBoxLayout, QWidget, QLabel, QHBoxLayout, QPushButton
|
|
2
|
+
from PyQt5.QtCore import Qt, QSize
|
|
3
|
+
from celldetective.gui.gui_utils import center_window, FeatureChoice, ListWidget, QHSeperationLine, FigureCanvas
|
|
4
|
+
from superqt import QLabeledDoubleRangeSlider, QLabeledDoubleSlider,QLabeledSlider
|
|
5
|
+
from superqt.fonticon import icon
|
|
6
|
+
from fonticon_mdi6 import MDI6
|
|
7
|
+
from celldetective.utils import extract_experiment_channels, get_software_location
|
|
8
|
+
from celldetective.io import interpret_tracking_configuration, load_frames
|
|
9
|
+
from celldetective.measure import compute_haralick_features
|
|
10
|
+
import numpy as np
|
|
11
|
+
import json
|
|
12
|
+
from shutil import copyfile
|
|
13
|
+
import os
|
|
14
|
+
import matplotlib.pyplot as plt
|
|
15
|
+
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
16
|
+
from glob import glob
|
|
17
|
+
|
|
18
|
+
class ConfigTracking(QMainWindow):
|
|
19
|
+
|
|
20
|
+
"""
|
|
21
|
+
UI to set tracking parameters for bTrack.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, parent=None):
|
|
26
|
+
|
|
27
|
+
super().__init__()
|
|
28
|
+
self.parent = parent
|
|
29
|
+
self.setWindowTitle("Configure tracking")
|
|
30
|
+
self.mode = self.parent.mode
|
|
31
|
+
self.exp_dir = self.parent.exp_dir
|
|
32
|
+
if self.mode=="targets":
|
|
33
|
+
self.config_name = os.sep.join(["configs", "btrack_config_targets.json"])
|
|
34
|
+
self.track_instructions_write_path = self.parent.exp_dir + os.sep.join(["configs","tracking_instructions_targets.json"])
|
|
35
|
+
elif self.mode=="effectors":
|
|
36
|
+
self.config_name = os.sep.join(["configs","btrack_config_effectors.json"])
|
|
37
|
+
self.track_instructions_write_path = self.parent.exp_dir + os.sep.join(["configs", "tracking_instructions_effectors.json"])
|
|
38
|
+
self.soft_path = get_software_location()
|
|
39
|
+
|
|
40
|
+
exp_config = self.exp_dir +"config.ini"
|
|
41
|
+
self.config_path = self.exp_dir + self.config_name
|
|
42
|
+
self.channel_names, self.channels = extract_experiment_channels(exp_config)
|
|
43
|
+
self.channel_names = np.array(self.channel_names)
|
|
44
|
+
self.channels = np.array(self.channels)
|
|
45
|
+
self.screen_height = self.parent.parent.parent.screen_height
|
|
46
|
+
|
|
47
|
+
center_window(self)
|
|
48
|
+
self.setMinimumWidth(540)
|
|
49
|
+
self.setMinimumHeight(int(0.3*self.screen_height))
|
|
50
|
+
self.setMaximumHeight(int(0.8*self.screen_height))
|
|
51
|
+
self.populate_widget()
|
|
52
|
+
self.load_previous_tracking_instructions()
|
|
53
|
+
|
|
54
|
+
def populate_widget(self):
|
|
55
|
+
|
|
56
|
+
"""
|
|
57
|
+
Create the multibox design with collapsable frames.
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
# Create button widget and layout
|
|
62
|
+
self.scroll_area = QScrollArea(self)
|
|
63
|
+
self.button_widget = QWidget()
|
|
64
|
+
main_layout = QVBoxLayout()
|
|
65
|
+
self.button_widget.setLayout(main_layout)
|
|
66
|
+
main_layout.setContentsMargins(30,30,30,30)
|
|
67
|
+
|
|
68
|
+
# First collapsable Frame CONFIG
|
|
69
|
+
self.config_frame = QFrame()
|
|
70
|
+
self.config_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
|
|
71
|
+
self.populate_config_frame()
|
|
72
|
+
main_layout.addWidget(self.config_frame)
|
|
73
|
+
|
|
74
|
+
# Second collapsable frame FEATURES
|
|
75
|
+
self.features_frame = QFrame()
|
|
76
|
+
self.features_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
|
|
77
|
+
self.populate_features_frame()
|
|
78
|
+
main_layout.addWidget(self.features_frame)
|
|
79
|
+
|
|
80
|
+
# Third collapsable frame POST-PROCESSING
|
|
81
|
+
self.post_proc_frame = QFrame()
|
|
82
|
+
self.post_proc_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
|
|
83
|
+
self.populate_post_proc_frame()
|
|
84
|
+
main_layout.addWidget(self.post_proc_frame)
|
|
85
|
+
|
|
86
|
+
self.submit_btn = QPushButton('Save')
|
|
87
|
+
self.submit_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet)
|
|
88
|
+
self.submit_btn.clicked.connect(self.write_instructions)
|
|
89
|
+
main_layout.addWidget(self.submit_btn)
|
|
90
|
+
|
|
91
|
+
#self.populate_left_panel()
|
|
92
|
+
#grid.addLayout(self.left_side, 0, 0, 1, 1)
|
|
93
|
+
self.button_widget.adjustSize()
|
|
94
|
+
|
|
95
|
+
self.scroll_area.setAlignment(Qt.AlignCenter)
|
|
96
|
+
self.scroll_area.setWidget(self.button_widget)
|
|
97
|
+
self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
98
|
+
self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
|
99
|
+
self.scroll_area.setWidgetResizable(True)
|
|
100
|
+
self.setCentralWidget(self.scroll_area)
|
|
101
|
+
self.show()
|
|
102
|
+
|
|
103
|
+
QApplication.processEvents()
|
|
104
|
+
self.adjustScrollArea()
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def populate_post_proc_frame(self):
|
|
108
|
+
|
|
109
|
+
"""
|
|
110
|
+
Add widgets and layout in the POST-PROCESSING frame.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
grid = QGridLayout(self.post_proc_frame)
|
|
114
|
+
|
|
115
|
+
self.select_post_proc_btn = QPushButton()
|
|
116
|
+
self.select_post_proc_btn.clicked.connect(self.activate_post_proc_options)
|
|
117
|
+
self.select_post_proc_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
118
|
+
grid.addWidget(self.select_post_proc_btn, 0,0,1,4,alignment=Qt.AlignLeft)
|
|
119
|
+
|
|
120
|
+
self.post_proc_lbl = QLabel("POST-PROCESSING")
|
|
121
|
+
self.post_proc_lbl.setStyleSheet("""
|
|
122
|
+
font-weight: bold;
|
|
123
|
+
padding: 0px;
|
|
124
|
+
""")
|
|
125
|
+
grid.addWidget(self.post_proc_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
|
|
126
|
+
|
|
127
|
+
self.collapse_post_proc_btn = QPushButton()
|
|
128
|
+
self.collapse_post_proc_btn.setIcon(icon(MDI6.chevron_down,color="black"))
|
|
129
|
+
self.collapse_post_proc_btn.setIconSize(QSize(20, 20))
|
|
130
|
+
self.collapse_post_proc_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
131
|
+
grid.addWidget(self.collapse_post_proc_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
|
|
132
|
+
|
|
133
|
+
self.generate_post_proc_panel_contents()
|
|
134
|
+
grid.addWidget(self.ContentsPostProc, 1, 0, 1, 4, alignment=Qt.AlignTop)
|
|
135
|
+
self.collapse_post_proc_btn.clicked.connect(lambda: self.ContentsPostProc.setHidden(not self.ContentsPostProc.isHidden()))
|
|
136
|
+
# self.collapse_post_proc_btn.clicked.connect(self.collapse_features_advanced)
|
|
137
|
+
self.ContentsPostProc.hide()
|
|
138
|
+
self.uncheck_post_proc()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def populate_features_frame(self):
|
|
142
|
+
|
|
143
|
+
"""
|
|
144
|
+
Add widgets and layout in the FEATURES frame.
|
|
145
|
+
"""
|
|
146
|
+
|
|
147
|
+
grid = QGridLayout(self.features_frame)
|
|
148
|
+
|
|
149
|
+
self.select_features_btn = QPushButton()
|
|
150
|
+
self.select_features_btn.clicked.connect(self.activate_feature_options)
|
|
151
|
+
self.select_features_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
152
|
+
grid.addWidget(self.select_features_btn, 0,0,1,4,alignment=Qt.AlignLeft)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
self.feature_lbl = QLabel("FEATURES")
|
|
156
|
+
self.feature_lbl.setStyleSheet("""
|
|
157
|
+
font-weight: bold;
|
|
158
|
+
padding: 0px;
|
|
159
|
+
""")
|
|
160
|
+
grid.addWidget(self.feature_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
|
|
161
|
+
|
|
162
|
+
self.collapse_features_btn = QPushButton()
|
|
163
|
+
self.collapse_features_btn.setIcon(icon(MDI6.chevron_down,color="black"))
|
|
164
|
+
self.collapse_features_btn.setIconSize(QSize(20, 20))
|
|
165
|
+
self.collapse_features_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
166
|
+
grid.addWidget(self.collapse_features_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
|
|
167
|
+
|
|
168
|
+
self.generate_feature_panel_contents()
|
|
169
|
+
grid.addWidget(self.ContentsFeatures, 1, 0, 1, 4, alignment=Qt.AlignTop)
|
|
170
|
+
self.collapse_features_btn.clicked.connect(lambda: self.ContentsFeatures.setHidden(not self.ContentsFeatures.isHidden()))
|
|
171
|
+
self.collapse_features_btn.clicked.connect(self.collapse_features_advanced)
|
|
172
|
+
#self.ContentsFeatures.hide()
|
|
173
|
+
self.check_features()
|
|
174
|
+
|
|
175
|
+
def collapse_features_advanced(self):
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
Switch the chevron icon and adjust the size for the FEATURES frame.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
if self.ContentsFeatures.isHidden():
|
|
182
|
+
self.collapse_features_btn.setIcon(icon(MDI6.chevron_down,color="black"))
|
|
183
|
+
self.collapse_features_btn.setIconSize(QSize(20, 20))
|
|
184
|
+
self.button_widget.adjustSize()
|
|
185
|
+
self.adjustSize()
|
|
186
|
+
else:
|
|
187
|
+
self.collapse_features_btn.setIcon(icon(MDI6.chevron_up,color="black"))
|
|
188
|
+
self.collapse_features_btn.setIconSize(QSize(20, 20))
|
|
189
|
+
self.button_widget.adjustSize()
|
|
190
|
+
self.adjustSize()
|
|
191
|
+
|
|
192
|
+
def generate_post_proc_panel_contents(self):
|
|
193
|
+
|
|
194
|
+
self.ContentsPostProc = QFrame()
|
|
195
|
+
layout = QVBoxLayout(self.ContentsPostProc)
|
|
196
|
+
layout.setContentsMargins(0,0,0,0)
|
|
197
|
+
|
|
198
|
+
post_proc_layout = QHBoxLayout()
|
|
199
|
+
self.post_proc_lbl = QLabel("Post processing on the tracks:")
|
|
200
|
+
post_proc_layout.addWidget(self.post_proc_lbl, 90)
|
|
201
|
+
layout.addLayout(post_proc_layout)
|
|
202
|
+
|
|
203
|
+
clean_traj_sublayout = QVBoxLayout()
|
|
204
|
+
clean_traj_sublayout.setContentsMargins(15,15,15,15)
|
|
205
|
+
|
|
206
|
+
tracklength_layout = QHBoxLayout()
|
|
207
|
+
self.min_tracklength_slider = QLabeledSlider()
|
|
208
|
+
self.min_tracklength_slider.setSingleStep(1)
|
|
209
|
+
self.min_tracklength_slider.setTickInterval(1)
|
|
210
|
+
self.min_tracklength_slider.setSingleStep(1)
|
|
211
|
+
self.min_tracklength_slider.setOrientation(1)
|
|
212
|
+
self.min_tracklength_slider.setRange(0,self.parent.parent.len_movie)
|
|
213
|
+
self.min_tracklength_slider.setValue(0)
|
|
214
|
+
tracklength_layout.addWidget(QLabel('Min. tracklength: '),40)
|
|
215
|
+
tracklength_layout.addWidget(self.min_tracklength_slider, 60)
|
|
216
|
+
clean_traj_sublayout.addLayout(tracklength_layout)
|
|
217
|
+
|
|
218
|
+
self.remove_not_in_first_checkbox = QCheckBox('Remove tracks that do not start at the beginning')
|
|
219
|
+
self.remove_not_in_first_checkbox.setIcon(icon(MDI6.arrow_expand_right,color="k"))
|
|
220
|
+
clean_traj_sublayout.addWidget(self.remove_not_in_first_checkbox)
|
|
221
|
+
|
|
222
|
+
self.remove_not_in_last_checkbox = QCheckBox('Remove tracks that do not end at the end')
|
|
223
|
+
self.remove_not_in_last_checkbox.setIcon(icon(MDI6.arrow_expand_left,color="k"))
|
|
224
|
+
clean_traj_sublayout.addWidget(self.remove_not_in_last_checkbox)
|
|
225
|
+
|
|
226
|
+
self.interpolate_gaps_checkbox = QCheckBox('Interpolate missed detections within tracks')
|
|
227
|
+
self.interpolate_gaps_checkbox.setIcon(icon(MDI6.chart_timeline_variant_shimmer,color="k"))
|
|
228
|
+
clean_traj_sublayout.addWidget(self.interpolate_gaps_checkbox)
|
|
229
|
+
|
|
230
|
+
self.extrapolate_post_checkbox = QCheckBox('Sustain last position until the end of the movie')
|
|
231
|
+
self.extrapolate_post_checkbox.setIcon(icon(MDI6.repeat,color="k"))
|
|
232
|
+
|
|
233
|
+
self.extrapolate_pre_checkbox = QCheckBox('Sustain first position from the beginning of the movie')
|
|
234
|
+
self.extrapolate_pre_checkbox.setIcon(icon(MDI6.repeat,color="k"))
|
|
235
|
+
|
|
236
|
+
clean_traj_sublayout.addWidget(self.extrapolate_post_checkbox)
|
|
237
|
+
clean_traj_sublayout.addWidget(self.extrapolate_pre_checkbox)
|
|
238
|
+
|
|
239
|
+
self.interpolate_na_features_checkbox = QCheckBox('Interpolate features of missed detections')
|
|
240
|
+
self.interpolate_na_features_checkbox.setIcon(icon(MDI6.format_color_fill,color="k"))
|
|
241
|
+
|
|
242
|
+
clean_traj_sublayout.addWidget(self.interpolate_na_features_checkbox)
|
|
243
|
+
clean_traj_sublayout.addStretch()
|
|
244
|
+
|
|
245
|
+
self.post_proc_options_to_disable = [self.post_proc_lbl, self.min_tracklength_slider, self.remove_not_in_first_checkbox,
|
|
246
|
+
self.remove_not_in_last_checkbox, self.interpolate_gaps_checkbox, self.extrapolate_post_checkbox,
|
|
247
|
+
self.extrapolate_pre_checkbox, self.interpolate_na_features_checkbox]
|
|
248
|
+
|
|
249
|
+
layout.addLayout(clean_traj_sublayout)
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def generate_feature_panel_contents(self):
|
|
253
|
+
|
|
254
|
+
self.ContentsFeatures = QFrame()
|
|
255
|
+
layout = QVBoxLayout(self.ContentsFeatures)
|
|
256
|
+
layout.setContentsMargins(0,0,0,0)
|
|
257
|
+
|
|
258
|
+
feature_layout = QHBoxLayout()
|
|
259
|
+
feature_layout.setContentsMargins(0,0,0,0)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
self.feature_lbl = QLabel("Add features:")
|
|
263
|
+
self.del_feature_btn = QPushButton("")
|
|
264
|
+
self.del_feature_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
265
|
+
self.del_feature_btn.setIcon(icon(MDI6.trash_can,color="black"))
|
|
266
|
+
self.del_feature_btn.setToolTip("Remove feature")
|
|
267
|
+
self.del_feature_btn.setIconSize(QSize(20, 20))
|
|
268
|
+
|
|
269
|
+
self.add_feature_btn = QPushButton("")
|
|
270
|
+
self.add_feature_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
271
|
+
self.add_feature_btn.setIcon(icon(MDI6.filter_plus,color="black"))
|
|
272
|
+
self.add_feature_btn.setToolTip("Add feature")
|
|
273
|
+
self.add_feature_btn.setIconSize(QSize(20, 20))
|
|
274
|
+
|
|
275
|
+
self.features_list = ListWidget(self, FeatureChoice, initial_features=['area','intensity_mean',])
|
|
276
|
+
|
|
277
|
+
self.del_feature_btn.clicked.connect(self.features_list.removeSel)
|
|
278
|
+
self.add_feature_btn.clicked.connect(self.features_list.addItem)
|
|
279
|
+
|
|
280
|
+
feature_layout.addWidget(self.feature_lbl, 90)
|
|
281
|
+
feature_layout.addWidget(self.del_feature_btn, 5)
|
|
282
|
+
feature_layout.addWidget(self.add_feature_btn, 5)
|
|
283
|
+
layout.addLayout(feature_layout)
|
|
284
|
+
layout.addWidget(self.features_list)
|
|
285
|
+
|
|
286
|
+
self.feat_sep2 = QHSeperationLine()
|
|
287
|
+
layout.addWidget(self.feat_sep2)
|
|
288
|
+
|
|
289
|
+
self.use_channel_lbl = QLabel('Use channel:')
|
|
290
|
+
layout.addWidget(self.use_channel_lbl)
|
|
291
|
+
self.mask_channels_cb = [QCheckBox() for i in range(len(self.channels))]
|
|
292
|
+
for cb,chn in zip(self.mask_channels_cb,self.channel_names):
|
|
293
|
+
cb.setText(chn)
|
|
294
|
+
cb.setChecked(True)
|
|
295
|
+
layout.addWidget(cb)
|
|
296
|
+
|
|
297
|
+
self.feat_sep1 = QHSeperationLine()
|
|
298
|
+
layout.addWidget(self.feat_sep1)
|
|
299
|
+
|
|
300
|
+
self.activate_haralick_btn = QCheckBox('activate Haralick texture features')
|
|
301
|
+
self.activate_haralick_btn.toggled.connect(self.show_haralick_options)
|
|
302
|
+
# Haralick features parameters
|
|
303
|
+
|
|
304
|
+
self.haralick_channel_choice = QComboBox()
|
|
305
|
+
self.haralick_channel_choice.addItems(self.channel_names)
|
|
306
|
+
self.haralick_channel_lbl = QLabel('Target channel: ')
|
|
307
|
+
|
|
308
|
+
self.haralick_distance_le = QLineEdit("1")
|
|
309
|
+
self.haralick_distance_lbl = QLabel('Distance: ')
|
|
310
|
+
|
|
311
|
+
self.haralick_n_gray_levels_le = QLineEdit("256")
|
|
312
|
+
self.haralick_n_gray_levels_lbl = QLabel('# gray levels: ')
|
|
313
|
+
|
|
314
|
+
# Slider to set vmin & vmax
|
|
315
|
+
self.haralick_scale_slider = QLabeledDoubleSlider()
|
|
316
|
+
self.haralick_scale_slider.setSingleStep(0.05)
|
|
317
|
+
self.haralick_scale_slider.setTickInterval(0.05)
|
|
318
|
+
self.haralick_scale_slider.setSingleStep(1)
|
|
319
|
+
self.haralick_scale_slider.setOrientation(1)
|
|
320
|
+
self.haralick_scale_slider.setRange(0,1)
|
|
321
|
+
self.haralick_scale_slider.setValue(0.5)
|
|
322
|
+
self.haralick_scale_lbl = QLabel('Scale: ')
|
|
323
|
+
|
|
324
|
+
self.haralick_percentile_min_le = QLineEdit('0.01')
|
|
325
|
+
self.haralick_percentile_max_le = QLineEdit('99.9')
|
|
326
|
+
self.haralick_normalization_mode_btn = QPushButton()
|
|
327
|
+
self.haralick_normalization_mode_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
328
|
+
self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle,color="black"))
|
|
329
|
+
self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
|
|
330
|
+
self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
|
|
331
|
+
self.percentile_mode = True
|
|
332
|
+
|
|
333
|
+
self.haralick_percentile_min_lbl = QLabel('Min percentile: ')
|
|
334
|
+
self.haralick_percentile_max_lbl = QLabel('Max percentile: ')
|
|
335
|
+
|
|
336
|
+
self.haralick_hist_btn = QPushButton()
|
|
337
|
+
self.haralick_hist_btn.clicked.connect(self.control_haralick_intensity_histogram)
|
|
338
|
+
self.haralick_hist_btn.setIcon(icon(MDI6.poll,color="k"))
|
|
339
|
+
self.haralick_hist_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
340
|
+
|
|
341
|
+
self.haralick_digit_btn = QPushButton()
|
|
342
|
+
self.haralick_digit_btn.clicked.connect(self.control_haralick_digitalization)
|
|
343
|
+
self.haralick_digit_btn.setIcon(icon(MDI6.image_check,color="k"))
|
|
344
|
+
self.haralick_digit_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
345
|
+
|
|
346
|
+
self.haralick_layout = QVBoxLayout()
|
|
347
|
+
self.haralick_layout.setContentsMargins(20,20,20,20)
|
|
348
|
+
|
|
349
|
+
activate_layout = QHBoxLayout()
|
|
350
|
+
activate_layout.addWidget(self.activate_haralick_btn, 80)
|
|
351
|
+
activate_layout.addWidget(self.haralick_hist_btn, 10)
|
|
352
|
+
activate_layout.addWidget(self.haralick_digit_btn, 10)
|
|
353
|
+
self.haralick_layout.addLayout(activate_layout)
|
|
354
|
+
|
|
355
|
+
channel_layout = QHBoxLayout()
|
|
356
|
+
channel_layout.addWidget(self.haralick_channel_lbl, 40)
|
|
357
|
+
channel_layout.addWidget(self.haralick_channel_choice, 60)
|
|
358
|
+
self.haralick_layout.addLayout(channel_layout)
|
|
359
|
+
|
|
360
|
+
distance_layout = QHBoxLayout()
|
|
361
|
+
distance_layout.addWidget(self.haralick_distance_lbl,40)
|
|
362
|
+
distance_layout.addWidget(self.haralick_distance_le, 60)
|
|
363
|
+
self.haralick_layout.addLayout(distance_layout)
|
|
364
|
+
|
|
365
|
+
gl_layout = QHBoxLayout()
|
|
366
|
+
gl_layout.addWidget(self.haralick_n_gray_levels_lbl,40)
|
|
367
|
+
gl_layout.addWidget(self.haralick_n_gray_levels_le,60)
|
|
368
|
+
self.haralick_layout.addLayout(gl_layout)
|
|
369
|
+
|
|
370
|
+
slider_layout = QHBoxLayout()
|
|
371
|
+
slider_layout.addWidget(self.haralick_scale_lbl,40)
|
|
372
|
+
slider_layout.addWidget(self.haralick_scale_slider,60)
|
|
373
|
+
self.haralick_layout.addLayout(slider_layout)
|
|
374
|
+
|
|
375
|
+
slider_min_percentile_layout = QHBoxLayout()
|
|
376
|
+
slider_min_percentile_layout.addWidget(self.haralick_percentile_min_lbl,40)
|
|
377
|
+
slider_min_percentile_layout.addWidget(self.haralick_percentile_min_le,55)
|
|
378
|
+
slider_min_percentile_layout.addWidget(self.haralick_normalization_mode_btn, 5)
|
|
379
|
+
self.haralick_layout.addLayout(slider_min_percentile_layout)
|
|
380
|
+
|
|
381
|
+
slider_max_percentile_layout = QHBoxLayout()
|
|
382
|
+
slider_max_percentile_layout.addWidget(self.haralick_percentile_max_lbl,40)
|
|
383
|
+
slider_max_percentile_layout.addWidget(self.haralick_percentile_max_le,60)
|
|
384
|
+
self.haralick_layout.addLayout(slider_max_percentile_layout)
|
|
385
|
+
|
|
386
|
+
self.haralick_to_hide = [self.haralick_hist_btn, self.haralick_digit_btn, self.haralick_channel_lbl, self.haralick_channel_choice,
|
|
387
|
+
self.haralick_distance_le, self.haralick_distance_lbl, self.haralick_n_gray_levels_le, self.haralick_n_gray_levels_lbl,
|
|
388
|
+
self.haralick_scale_lbl, self.haralick_scale_slider, self.haralick_percentile_min_lbl, self.haralick_percentile_min_le,
|
|
389
|
+
self.haralick_percentile_max_lbl, self.haralick_percentile_max_le, self.haralick_normalization_mode_btn]
|
|
390
|
+
for element in self.haralick_to_hide:
|
|
391
|
+
element.hide()
|
|
392
|
+
|
|
393
|
+
self.features_to_disable = [self.feature_lbl, self.del_feature_btn, self.add_feature_btn, self.features_list,
|
|
394
|
+
self.use_channel_lbl, *self.mask_channels_cb, self.activate_haralick_btn]
|
|
395
|
+
|
|
396
|
+
self.haralick_normalization_mode_btn.clicked.connect(self.switch_to_absolute_normalization_mode)
|
|
397
|
+
layout.addLayout(self.haralick_layout)
|
|
398
|
+
|
|
399
|
+
def switch_to_absolute_normalization_mode(self):
|
|
400
|
+
if self.percentile_mode:
|
|
401
|
+
self.percentile_mode = False
|
|
402
|
+
self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle_outline,color="black"))
|
|
403
|
+
self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
|
|
404
|
+
self.haralick_normalization_mode_btn.setToolTip("Switch to percentile normalization values.")
|
|
405
|
+
self.haralick_percentile_min_lbl.setText('Min value: ')
|
|
406
|
+
self.haralick_percentile_max_lbl.setText('Max value: ')
|
|
407
|
+
self.haralick_percentile_min_le.setText('0')
|
|
408
|
+
self.haralick_percentile_max_le.setText('10000')
|
|
409
|
+
|
|
410
|
+
else:
|
|
411
|
+
self.percentile_mode = True
|
|
412
|
+
self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle,color="black"))
|
|
413
|
+
self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
|
|
414
|
+
self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
|
|
415
|
+
self.haralick_percentile_min_lbl.setText('Min percentile: ')
|
|
416
|
+
self.haralick_percentile_max_lbl.setText('Max percentile: ')
|
|
417
|
+
self.haralick_percentile_min_le.setText('0.01')
|
|
418
|
+
self.haralick_percentile_max_le.setText('99.99')
|
|
419
|
+
|
|
420
|
+
def populate_config_frame(self):
|
|
421
|
+
|
|
422
|
+
grid = QGridLayout(self.config_frame)
|
|
423
|
+
panel_title = QLabel(f"CONFIGURATION")
|
|
424
|
+
panel_title.setStyleSheet("""
|
|
425
|
+
font-weight: bold;
|
|
426
|
+
padding: 0px;
|
|
427
|
+
""")
|
|
428
|
+
|
|
429
|
+
grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
|
|
430
|
+
|
|
431
|
+
self.collapse_config_btn = QPushButton()
|
|
432
|
+
self.collapse_config_btn.setIcon(icon(MDI6.chevron_down,color="black"))
|
|
433
|
+
self.collapse_config_btn.setIconSize(QSize(20, 20))
|
|
434
|
+
self.collapse_config_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
435
|
+
grid.addWidget(self.collapse_config_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
|
|
436
|
+
|
|
437
|
+
self.generate_config_panel_contents()
|
|
438
|
+
grid.addWidget(self.ContentsConfig, 1, 0, 1, 4, alignment=Qt.AlignTop)
|
|
439
|
+
self.collapse_config_btn.clicked.connect(lambda: self.ContentsConfig.setHidden(not self.ContentsConfig.isHidden()))
|
|
440
|
+
self.collapse_config_btn.clicked.connect(self.collapse_config_advanced)
|
|
441
|
+
#self.ContentsConfig.hide()
|
|
442
|
+
|
|
443
|
+
def collapse_config_advanced(self):
|
|
444
|
+
|
|
445
|
+
"""
|
|
446
|
+
Switch the chevron icon and adjust the size for the CONFIG frame.
|
|
447
|
+
"""
|
|
448
|
+
|
|
449
|
+
if self.ContentsConfig.isHidden():
|
|
450
|
+
self.collapse_config_btn.setIcon(icon(MDI6.chevron_down,color="black"))
|
|
451
|
+
self.collapse_config_btn.setIconSize(QSize(20, 20))
|
|
452
|
+
self.button_widget.adjustSize()
|
|
453
|
+
self.adjustSize()
|
|
454
|
+
else:
|
|
455
|
+
self.collapse_config_btn.setIcon(icon(MDI6.chevron_up,color="black"))
|
|
456
|
+
self.collapse_config_btn.setIconSize(QSize(20, 20))
|
|
457
|
+
self.button_widget.adjustSize()
|
|
458
|
+
self.adjustSize()
|
|
459
|
+
|
|
460
|
+
def generate_config_panel_contents(self):
|
|
461
|
+
|
|
462
|
+
self.ContentsConfig = QFrame()
|
|
463
|
+
layout = QVBoxLayout(self.ContentsConfig)
|
|
464
|
+
layout.setContentsMargins(0,0,0,0)
|
|
465
|
+
|
|
466
|
+
btrack_config_layout = QHBoxLayout()
|
|
467
|
+
self.config_lbl = QLabel("bTrack configuration: ")
|
|
468
|
+
btrack_config_layout.addWidget(self.config_lbl, 90)
|
|
469
|
+
|
|
470
|
+
self.upload_btrack_config_btn = QPushButton()
|
|
471
|
+
self.upload_btrack_config_btn.setIcon(icon(MDI6.plus,color="black"))
|
|
472
|
+
self.upload_btrack_config_btn.setIconSize(QSize(20, 20))
|
|
473
|
+
self.upload_btrack_config_btn.setToolTip("Upload a new bTrack configuration.")
|
|
474
|
+
self.upload_btrack_config_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
475
|
+
self.upload_btrack_config_btn.clicked.connect(self.upload_btrack_config)
|
|
476
|
+
btrack_config_layout.addWidget(self.upload_btrack_config_btn, 5) #4,3,1,1, alignment=Qt.AlignLeft
|
|
477
|
+
|
|
478
|
+
self.reset_config_btn = QPushButton()
|
|
479
|
+
self.reset_config_btn.setIcon(icon(MDI6.arrow_u_right_top,color="black"))
|
|
480
|
+
self.reset_config_btn.setIconSize(QSize(20, 20))
|
|
481
|
+
self.reset_config_btn.setToolTip("Reset the configuration to the default bTrack config.")
|
|
482
|
+
self.reset_config_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
483
|
+
self.reset_config_btn.clicked.connect(self.reset_btrack_config)
|
|
484
|
+
btrack_config_layout.addWidget(self.reset_config_btn, 5) #4,3,1,1, alignment=Qt.AlignLeft
|
|
485
|
+
|
|
486
|
+
layout.addLayout(btrack_config_layout)
|
|
487
|
+
|
|
488
|
+
self.config_le = QTextEdit()
|
|
489
|
+
self.config_le.setMinimumHeight(150)
|
|
490
|
+
#self.config_le.setStyleSheet("""
|
|
491
|
+
# background: #EEEDEB;
|
|
492
|
+
# border: 2px solid black;
|
|
493
|
+
# """)
|
|
494
|
+
layout.addWidget(self.config_le)
|
|
495
|
+
self.load_cell_config()
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def show_haralick_options(self):
|
|
499
|
+
|
|
500
|
+
"""
|
|
501
|
+
Show the Haralick texture options.
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
if self.activate_haralick_btn.isChecked():
|
|
505
|
+
for element in self.haralick_to_hide:
|
|
506
|
+
element.show()
|
|
507
|
+
else:
|
|
508
|
+
for element in self.haralick_to_hide:
|
|
509
|
+
element.hide()
|
|
510
|
+
|
|
511
|
+
def upload_btrack_config(self):
|
|
512
|
+
|
|
513
|
+
"""
|
|
514
|
+
Upload a specific bTrack config to the experiment folder for the cell population.
|
|
515
|
+
"""
|
|
516
|
+
|
|
517
|
+
self.file_dialog = QFileDialog()
|
|
518
|
+
try:
|
|
519
|
+
modelpath = os.sep.join([self.soft_path, "celldetective","models","tracking_configs"]) + os.sep
|
|
520
|
+
print("Track config path: ", modelpath)
|
|
521
|
+
self.filename = self.file_dialog.getOpenFileName(None, "Load config", modelpath, "json files (*.json)")[0]
|
|
522
|
+
if self.filename!=self.config_path:
|
|
523
|
+
copyfile(self.filename, self.config_path)
|
|
524
|
+
self.load_cell_config()
|
|
525
|
+
except Exception as e:
|
|
526
|
+
print(e, modelpath)
|
|
527
|
+
return None
|
|
528
|
+
|
|
529
|
+
def reset_btrack_config(self):
|
|
530
|
+
|
|
531
|
+
"""
|
|
532
|
+
Set the bTrack config to the default bTrack config.
|
|
533
|
+
"""
|
|
534
|
+
|
|
535
|
+
msgBox = QMessageBox()
|
|
536
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
537
|
+
msgBox.setText("You are about to revert to the default bTrack configuration? Do you want to proceed?")
|
|
538
|
+
msgBox.setWindowTitle("Confirm")
|
|
539
|
+
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
540
|
+
returnValue = msgBox.exec()
|
|
541
|
+
if returnValue == QMessageBox.Yes:
|
|
542
|
+
config = interpret_tracking_configuration(None)
|
|
543
|
+
if config!=self.config_path:
|
|
544
|
+
copyfile(config, self.config_path)
|
|
545
|
+
self.load_cell_config()
|
|
546
|
+
else:
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
def activate_feature_options(self):
|
|
550
|
+
|
|
551
|
+
"""
|
|
552
|
+
Tick the features option.
|
|
553
|
+
"""
|
|
554
|
+
|
|
555
|
+
self.switch_feature_option()
|
|
556
|
+
if self.features_ticked:
|
|
557
|
+
for element in self.features_to_disable:
|
|
558
|
+
element.setEnabled(True)
|
|
559
|
+
self.select_features_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
|
|
560
|
+
self.select_features_btn.setIconSize(QSize(20, 20))
|
|
561
|
+
else:
|
|
562
|
+
for element in self.features_to_disable:
|
|
563
|
+
element.setEnabled(False)
|
|
564
|
+
self.select_features_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
565
|
+
self.select_features_btn.setIconSize(QSize(20, 20))
|
|
566
|
+
self.features_list.list_widget.clearSelection()
|
|
567
|
+
self.activate_haralick_btn.setChecked(False)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def activate_post_proc_options(self):
|
|
571
|
+
|
|
572
|
+
"""
|
|
573
|
+
Tick the features option.
|
|
574
|
+
"""
|
|
575
|
+
|
|
576
|
+
self.switch_post_proc_option()
|
|
577
|
+
if self.post_proc_ticked:
|
|
578
|
+
for element in self.post_proc_options_to_disable:
|
|
579
|
+
element.setEnabled(True)
|
|
580
|
+
self.select_post_proc_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
|
|
581
|
+
self.select_post_proc_btn.setIconSize(QSize(20, 20))
|
|
582
|
+
else:
|
|
583
|
+
for element in self.post_proc_options_to_disable:
|
|
584
|
+
element.setEnabled(False)
|
|
585
|
+
self.select_post_proc_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
586
|
+
self.select_post_proc_btn.setIconSize(QSize(20, 20))
|
|
587
|
+
|
|
588
|
+
def switch_feature_option(self):
|
|
589
|
+
|
|
590
|
+
"""
|
|
591
|
+
Switch the feature option.
|
|
592
|
+
"""
|
|
593
|
+
|
|
594
|
+
if self.features_ticked == True:
|
|
595
|
+
self.features_ticked = False
|
|
596
|
+
else:
|
|
597
|
+
self.features_ticked = True
|
|
598
|
+
|
|
599
|
+
def switch_post_proc_option(self):
|
|
600
|
+
|
|
601
|
+
"""
|
|
602
|
+
Switch the feature option.
|
|
603
|
+
"""
|
|
604
|
+
|
|
605
|
+
if self.post_proc_ticked == True:
|
|
606
|
+
self.post_proc_ticked = False
|
|
607
|
+
else:
|
|
608
|
+
self.post_proc_ticked = True
|
|
609
|
+
|
|
610
|
+
def adjustScrollArea(self):
|
|
611
|
+
|
|
612
|
+
"""
|
|
613
|
+
Auto-adjust scroll area to fill space
|
|
614
|
+
(from https://stackoverflow.com/questions/66417576/make-qscrollarea-use-all-available-space-of-qmainwindow-height-axis)
|
|
615
|
+
"""
|
|
616
|
+
|
|
617
|
+
step = 5
|
|
618
|
+
while self.scroll_area.verticalScrollBar().isVisible() and self.height() < self.maximumHeight():
|
|
619
|
+
self.resize(self.width(), self.height() + step)
|
|
620
|
+
|
|
621
|
+
def load_cell_config(self):
|
|
622
|
+
|
|
623
|
+
"""
|
|
624
|
+
Load the cell configuration and write in the QLineEdit.
|
|
625
|
+
"""
|
|
626
|
+
|
|
627
|
+
file_name = interpret_tracking_configuration(self.config_path)
|
|
628
|
+
with open(file_name, 'r') as f:
|
|
629
|
+
json_data = json.load(f)
|
|
630
|
+
self.config_le.setText(json.dumps(json_data, indent=4))
|
|
631
|
+
|
|
632
|
+
def write_instructions(self):
|
|
633
|
+
|
|
634
|
+
"""
|
|
635
|
+
Write the selected options in a json file for later reading by the software.
|
|
636
|
+
"""
|
|
637
|
+
|
|
638
|
+
print('Writing instructions...')
|
|
639
|
+
tracking_options = {'btrack_config_path': self.config_path}
|
|
640
|
+
if not self.features_ticked:
|
|
641
|
+
features = None
|
|
642
|
+
masked_channels = None
|
|
643
|
+
else:
|
|
644
|
+
features = self.features_list.getItems()
|
|
645
|
+
masked_channels = self.channel_names[np.array([not cb.isChecked() for cb in self.mask_channels_cb])]
|
|
646
|
+
if len(masked_channels)==0:
|
|
647
|
+
masked_channels = None
|
|
648
|
+
else:
|
|
649
|
+
masked_channels = list(masked_channels)
|
|
650
|
+
|
|
651
|
+
tracking_options.update({'features': features, 'mask_channels': masked_channels})
|
|
652
|
+
|
|
653
|
+
self.extract_haralick_options()
|
|
654
|
+
tracking_options.update({'haralick_options': self.haralick_options})
|
|
655
|
+
|
|
656
|
+
if self.post_proc_ticked:
|
|
657
|
+
post_processing_options = {"minimum_tracklength": int(self.min_tracklength_slider.value()),
|
|
658
|
+
"remove_not_in_first": self.remove_not_in_first_checkbox.isChecked(),
|
|
659
|
+
"remove_not_in_last": self.remove_not_in_last_checkbox.isChecked(),
|
|
660
|
+
"interpolate_position_gaps": self.interpolate_gaps_checkbox.isChecked(),
|
|
661
|
+
"extrapolate_tracks_pre": self.extrapolate_pre_checkbox.isChecked(),
|
|
662
|
+
"extrapolate_tracks_post": self.extrapolate_post_checkbox.isChecked(),
|
|
663
|
+
'interpolate_na': self.interpolate_na_features_checkbox.isChecked()
|
|
664
|
+
}
|
|
665
|
+
else:
|
|
666
|
+
|
|
667
|
+
post_processing_options = None
|
|
668
|
+
|
|
669
|
+
tracking_options.update({'post_processing_options': post_processing_options})
|
|
670
|
+
file_name = self.track_instructions_write_path
|
|
671
|
+
with open(file_name, 'w') as f:
|
|
672
|
+
json.dump(tracking_options, f, indent=4)
|
|
673
|
+
|
|
674
|
+
# Save the JSON data to the file
|
|
675
|
+
file_name = self.config_path
|
|
676
|
+
with open(file_name, 'w') as f:
|
|
677
|
+
f.write(self.config_le.toPlainText())
|
|
678
|
+
print('Done.')
|
|
679
|
+
self.close()
|
|
680
|
+
|
|
681
|
+
def uncheck_post_proc(self):
|
|
682
|
+
self.select_post_proc_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
683
|
+
self.select_post_proc_btn.setIconSize(QSize(20, 20))
|
|
684
|
+
self.post_proc_ticked = False
|
|
685
|
+
for element in self.post_proc_options_to_disable:
|
|
686
|
+
element.setEnabled(False)
|
|
687
|
+
|
|
688
|
+
def check_post_proc(self):
|
|
689
|
+
self.select_post_proc_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
|
|
690
|
+
self.select_post_proc_btn.setIconSize(QSize(20, 20))
|
|
691
|
+
self.post_proc_ticked = True
|
|
692
|
+
for element in self.post_proc_options_to_disable:
|
|
693
|
+
element.setEnabled(True)
|
|
694
|
+
|
|
695
|
+
def uncheck_features(self):
|
|
696
|
+
self.select_features_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
697
|
+
self.select_features_btn.setIconSize(QSize(20, 20))
|
|
698
|
+
self.features_ticked = False
|
|
699
|
+
for element in self.features_to_disable:
|
|
700
|
+
element.setEnabled(False)
|
|
701
|
+
|
|
702
|
+
def check_features(self):
|
|
703
|
+
self.select_features_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
|
|
704
|
+
self.select_features_btn.setIconSize(QSize(20, 20))
|
|
705
|
+
self.features_ticked = True
|
|
706
|
+
for element in self.features_to_disable:
|
|
707
|
+
element.setEnabled(True)
|
|
708
|
+
|
|
709
|
+
def extract_haralick_options(self):
|
|
710
|
+
|
|
711
|
+
if self.activate_haralick_btn.isChecked():
|
|
712
|
+
self.haralick_options = {"target_channel": self.haralick_channel_choice.currentIndex(),
|
|
713
|
+
"scale_factor": float(self.haralick_scale_slider.value()),
|
|
714
|
+
"n_intensity_bins": int(self.haralick_n_gray_levels_le.text()),
|
|
715
|
+
"distance" : int(self.haralick_distance_le.text()),
|
|
716
|
+
}
|
|
717
|
+
if self.percentile_mode:
|
|
718
|
+
self.haralick_options.update({"percentiles": (float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text())), "clip_values": None})
|
|
719
|
+
else:
|
|
720
|
+
self.haralick_options.update({"percentiles": None, "clip_values": (float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text()))})
|
|
721
|
+
|
|
722
|
+
else:
|
|
723
|
+
self.haralick_options = None
|
|
724
|
+
|
|
725
|
+
def load_previous_tracking_instructions(self):
|
|
726
|
+
|
|
727
|
+
"""
|
|
728
|
+
Read the tracking options from a previously written json file.
|
|
729
|
+
"""
|
|
730
|
+
|
|
731
|
+
print('Reading instructions..')
|
|
732
|
+
if os.path.exists(self.track_instructions_write_path):
|
|
733
|
+
with open(self.track_instructions_write_path, 'r') as f:
|
|
734
|
+
tracking_instructions = json.load(f)
|
|
735
|
+
print(tracking_instructions)
|
|
736
|
+
|
|
737
|
+
# Features
|
|
738
|
+
features = tracking_instructions['features']
|
|
739
|
+
if (features is not None) and len(features)>0:
|
|
740
|
+
self.check_features()
|
|
741
|
+
self.ContentsFeatures.show()
|
|
742
|
+
self.features_list.list_widget.clear()
|
|
743
|
+
self.features_list.list_widget.addItems(features)
|
|
744
|
+
else:
|
|
745
|
+
self.ContentsFeatures.hide()
|
|
746
|
+
self.uncheck_features()
|
|
747
|
+
|
|
748
|
+
# Uncheck channels that are masked
|
|
749
|
+
mask_channels = tracking_instructions['mask_channels']
|
|
750
|
+
if (mask_channels is not None) and len(mask_channels)>0:
|
|
751
|
+
for ch in mask_channels:
|
|
752
|
+
for cb in self.mask_channels_cb:
|
|
753
|
+
if cb.text()==ch:
|
|
754
|
+
cb.setChecked(False)
|
|
755
|
+
|
|
756
|
+
haralick_options = tracking_instructions['haralick_options']
|
|
757
|
+
if haralick_options is None:
|
|
758
|
+
self.activate_haralick_btn.setChecked(False)
|
|
759
|
+
self.show_haralick_options()
|
|
760
|
+
else:
|
|
761
|
+
self.activate_haralick_btn.setChecked(True)
|
|
762
|
+
self.show_haralick_options()
|
|
763
|
+
if 'target_channel' in haralick_options:
|
|
764
|
+
idx = haralick_options['target_channel']
|
|
765
|
+
#idx = self.haralick_channel_choice.findText(text_to_find)
|
|
766
|
+
self.haralick_channel_choice.setCurrentIndex(idx)
|
|
767
|
+
if 'scale_factor' in haralick_options:
|
|
768
|
+
self.haralick_scale_slider.setValue(float(haralick_options['scale_factor']))
|
|
769
|
+
if ('percentiles' in haralick_options) and (haralick_options['percentiles'] is not None):
|
|
770
|
+
perc = list(haralick_options['percentiles'])
|
|
771
|
+
self.haralick_percentile_min_le.setText(str(perc[0]))
|
|
772
|
+
self.haralick_percentile_max_le.setText(str(perc[1]))
|
|
773
|
+
if ('clip_values' in haralick_options) and (haralick_options['clip_values'] is not None):
|
|
774
|
+
values = list(haralick_options['clip_values'])
|
|
775
|
+
self.haralick_percentile_min_le.setText(str(values[0]))
|
|
776
|
+
self.haralick_percentile_max_le.setText(str(values[1]))
|
|
777
|
+
self.percentile_mode=True
|
|
778
|
+
self.switch_to_absolute_normalization_mode()
|
|
779
|
+
if 'n_intensity_bins' in haralick_options:
|
|
780
|
+
self.haralick_n_gray_levels_le.setText(str(haralick_options['n_intensity_bins']))
|
|
781
|
+
if 'distance' in haralick_options:
|
|
782
|
+
self.haralick_distance_le.setText(str(haralick_options['distance']))
|
|
783
|
+
|
|
784
|
+
|
|
785
|
+
|
|
786
|
+
# Post processing options
|
|
787
|
+
post_processing_options = tracking_instructions['post_processing_options']
|
|
788
|
+
if post_processing_options is None:
|
|
789
|
+
self.uncheck_post_proc()
|
|
790
|
+
self.ContentsPostProc.hide()
|
|
791
|
+
for element in [self.remove_not_in_last_checkbox, self.remove_not_in_first_checkbox, self.interpolate_gaps_checkbox,
|
|
792
|
+
self.extrapolate_post_checkbox, self.extrapolate_pre_checkbox, self.interpolate_na_features_checkbox]:
|
|
793
|
+
element.setChecked(False)
|
|
794
|
+
self.min_tracklength_slider.setValue(0)
|
|
795
|
+
|
|
796
|
+
else:
|
|
797
|
+
self.check_post_proc()
|
|
798
|
+
self.ContentsPostProc.show()
|
|
799
|
+
if "minimum_tracklength" in post_processing_options:
|
|
800
|
+
self.min_tracklength_slider.setValue(int(post_processing_options["minimum_tracklength"]))
|
|
801
|
+
if "remove_not_in_first" in post_processing_options:
|
|
802
|
+
self.remove_not_in_first_checkbox.setChecked(post_processing_options["remove_not_in_first"])
|
|
803
|
+
if "remove_not_in_last" in post_processing_options:
|
|
804
|
+
self.remove_not_in_last_checkbox.setChecked(post_processing_options["remove_not_in_last"])
|
|
805
|
+
if "interpolate_position_gaps" in post_processing_options:
|
|
806
|
+
self.interpolate_gaps_checkbox.setChecked(post_processing_options["interpolate_position_gaps"])
|
|
807
|
+
if "extrapolate_tracks_pre" in post_processing_options:
|
|
808
|
+
self.extrapolate_pre_checkbox.setChecked(post_processing_options["extrapolate_tracks_pre"])
|
|
809
|
+
if "extrapolate_tracks_post" in post_processing_options:
|
|
810
|
+
self.extrapolate_post_checkbox.setChecked(post_processing_options["extrapolate_tracks_post"])
|
|
811
|
+
if "interpolate_na" in post_processing_options:
|
|
812
|
+
self.interpolate_na_features_checkbox.setChecked(post_processing_options["interpolate_na"])
|
|
813
|
+
|
|
814
|
+
def locate_image(self):
|
|
815
|
+
|
|
816
|
+
"""
|
|
817
|
+
Load the first frame of the first movie found in the experiment folder as a sample.
|
|
818
|
+
"""
|
|
819
|
+
|
|
820
|
+
movies = glob(self.parent.parent.exp_dir + os.sep.join(["*","*","movie",self.parent.parent.movie_prefix+"*.tif"]))
|
|
821
|
+
if len(movies)==0:
|
|
822
|
+
msgBox = QMessageBox()
|
|
823
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
824
|
+
msgBox.setText("No movies are detected in the experiment folder. Cannot load an image to test Haralick.")
|
|
825
|
+
msgBox.setWindowTitle("Warning")
|
|
826
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
827
|
+
returnValue = msgBox.exec()
|
|
828
|
+
if returnValue == QMessageBox.Yes:
|
|
829
|
+
self.test_frame = None
|
|
830
|
+
return None
|
|
831
|
+
else:
|
|
832
|
+
stack0 = movies[0]
|
|
833
|
+
n_channels = len(self.channels)
|
|
834
|
+
self.test_frame = load_frames(np.arange(n_channels), stack0, scale=None, normalize_input=False)
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def control_haralick_digitalization(self):
|
|
838
|
+
|
|
839
|
+
"""
|
|
840
|
+
Load an image for the first experiment movie found.
|
|
841
|
+
Apply the Haralick parameters and check the result of the digitization (normalization + binning of intensities).
|
|
842
|
+
|
|
843
|
+
"""
|
|
844
|
+
|
|
845
|
+
self.locate_image()
|
|
846
|
+
self.extract_haralick_options()
|
|
847
|
+
if self.test_frame is not None:
|
|
848
|
+
digitized_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
|
|
849
|
+
channels=self.channel_names, return_digit_image_only=True,
|
|
850
|
+
**self.haralick_options
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
self.fig, self.ax = plt.subplots()
|
|
854
|
+
divider = make_axes_locatable(self.ax)
|
|
855
|
+
cax = divider.append_axes('right', size='5%', pad=0.05)
|
|
856
|
+
|
|
857
|
+
self.imshow_digit_window = FigureCanvas(self.fig, title="Haralick: control digitization")
|
|
858
|
+
self.ax.clear()
|
|
859
|
+
im = self.ax.imshow(digitized_img, cmap='gray')
|
|
860
|
+
self.fig.colorbar(im, cax=cax, orientation='vertical')
|
|
861
|
+
self.ax.set_xticks([])
|
|
862
|
+
self.ax.set_yticks([])
|
|
863
|
+
self.fig.set_facecolor('none') # or 'None'
|
|
864
|
+
self.fig.canvas.setStyleSheet("background-color: transparent;")
|
|
865
|
+
self.imshow_digit_window.canvas.draw()
|
|
866
|
+
self.imshow_digit_window.show()
|
|
867
|
+
|
|
868
|
+
def control_haralick_intensity_histogram(self):
|
|
869
|
+
|
|
870
|
+
"""
|
|
871
|
+
Load an image for the first experiment movie found.
|
|
872
|
+
Apply the Haralick normalization parameters and check the normalized intensity histogram.
|
|
873
|
+
|
|
874
|
+
"""
|
|
875
|
+
|
|
876
|
+
self.locate_image()
|
|
877
|
+
self.extract_haralick_options()
|
|
878
|
+
if self.test_frame is not None:
|
|
879
|
+
norm_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
|
|
880
|
+
channels=self.channel_names, return_norm_image_only=True,
|
|
881
|
+
**self.haralick_options
|
|
882
|
+
)
|
|
883
|
+
self.fig, self.ax = plt.subplots(1,1,figsize=(4,3))
|
|
884
|
+
self.hist_window = FigureCanvas(self.fig, title="Haralick: control digitized histogram")
|
|
885
|
+
self.ax.clear()
|
|
886
|
+
self.ax.hist(norm_img.flatten(), bins=self.haralick_options['n_intensity_bins'])
|
|
887
|
+
self.ax.set_xlabel('gray level value')
|
|
888
|
+
self.ax.set_ylabel('#')
|
|
889
|
+
plt.tight_layout()
|
|
890
|
+
self.fig.set_facecolor('none') # or 'None'
|
|
891
|
+
self.fig.canvas.setStyleSheet("background-color: transparent;")
|
|
892
|
+
self.hist_window.canvas.draw()
|
|
893
|
+
self.hist_window.show()
|
|
894
|
+
|
|
895
|
+
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
|