celldetective 1.3.9.post4__py3-none-any.whl → 1.4.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celldetective/__init__.py +0 -3
- celldetective/_version.py +1 -1
- celldetective/events.py +2 -4
- celldetective/extra_properties.py +320 -24
- celldetective/gui/InitWindow.py +33 -45
- celldetective/gui/__init__.py +1 -0
- celldetective/gui/about.py +19 -15
- celldetective/gui/analyze_block.py +34 -19
- celldetective/gui/base_components.py +23 -0
- celldetective/gui/btrack_options.py +26 -34
- celldetective/gui/classifier_widget.py +71 -80
- celldetective/gui/configure_new_exp.py +113 -17
- celldetective/gui/control_panel.py +68 -141
- celldetective/gui/generic_signal_plot.py +9 -12
- celldetective/gui/gui_utils.py +49 -21
- celldetective/gui/json_readers.py +5 -4
- celldetective/gui/layouts.py +246 -22
- celldetective/gui/measurement_options.py +32 -17
- celldetective/gui/neighborhood_options.py +10 -13
- celldetective/gui/plot_measurements.py +21 -17
- celldetective/gui/plot_signals_ui.py +131 -75
- celldetective/gui/process_block.py +180 -123
- celldetective/gui/processes/compute_neighborhood.py +594 -0
- celldetective/gui/processes/measure_cells.py +5 -0
- celldetective/gui/processes/segment_cells.py +27 -6
- celldetective/gui/processes/track_cells.py +6 -0
- celldetective/gui/retrain_segmentation_model_options.py +12 -20
- celldetective/gui/retrain_signal_model_options.py +57 -56
- celldetective/gui/seg_model_loader.py +21 -62
- celldetective/gui/signal_annotator.py +139 -72
- celldetective/gui/signal_annotator2.py +431 -635
- celldetective/gui/signal_annotator_options.py +8 -11
- celldetective/gui/survival_ui.py +49 -95
- celldetective/gui/tableUI.py +28 -25
- celldetective/gui/thresholds_gui.py +617 -1221
- celldetective/gui/viewers.py +106 -39
- celldetective/gui/workers.py +9 -3
- celldetective/io.py +73 -27
- celldetective/measure.py +63 -27
- celldetective/neighborhood.py +342 -268
- celldetective/preprocessing.py +25 -17
- celldetective/relative_measurements.py +50 -29
- celldetective/scripts/analyze_signals.py +4 -1
- celldetective/scripts/measure_relative.py +4 -1
- celldetective/scripts/segment_cells.py +0 -6
- celldetective/scripts/track_cells.py +3 -1
- celldetective/scripts/train_segmentation_model.py +7 -4
- celldetective/signals.py +29 -14
- celldetective/tracking.py +7 -2
- celldetective/utils.py +36 -8
- {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/METADATA +24 -16
- {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/RECORD +57 -55
- {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/WHEEL +1 -1
- tests/test_qt.py +21 -21
- {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info/licenses}/LICENSE +0 -0
- {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import
|
|
1
|
+
from PyQt5.QtWidgets import QLineEdit, QMessageBox, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, \
|
|
2
2
|
QCheckBox, QRadioButton, QButtonGroup, QComboBox
|
|
3
3
|
from PyQt5.QtCore import Qt, QSize
|
|
4
|
-
from superqt import QLabeledSlider,QLabeledDoubleSlider, QSearchableComboBox
|
|
4
|
+
from superqt import QLabeledSlider, QLabeledDoubleSlider, QSearchableComboBox
|
|
5
5
|
from superqt.fonticon import icon
|
|
6
6
|
from fonticon_mdi6 import MDI6
|
|
7
7
|
|
|
@@ -11,11 +11,11 @@ import matplotlib.pyplot as plt
|
|
|
11
11
|
import json
|
|
12
12
|
|
|
13
13
|
from celldetective.gui.gui_utils import FigureCanvas, center_window, color_from_status, help_generic
|
|
14
|
-
from celldetective.gui import
|
|
14
|
+
from celldetective.gui.base_components import CelldetectiveWidget
|
|
15
15
|
from celldetective.utils import get_software_location
|
|
16
16
|
from celldetective.measure import classify_cells_from_query, interpret_track_classification
|
|
17
17
|
|
|
18
|
-
class ClassifierWidget(
|
|
18
|
+
class ClassifierWidget(CelldetectiveWidget):
|
|
19
19
|
|
|
20
20
|
def __init__(self, parent_window):
|
|
21
21
|
|
|
@@ -33,7 +33,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
33
33
|
|
|
34
34
|
is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
|
|
35
35
|
is_number_test = is_number(self.df.dtypes)
|
|
36
|
-
self.cols = [col for t,col in zip(is_number_test,self.df.columns) if t]
|
|
36
|
+
self.cols = [col for t, col in zip(is_number_test, self.df.columns) if t]
|
|
37
37
|
|
|
38
38
|
self.class_name = 'custom'
|
|
39
39
|
self.name_le = QLineEdit(self.class_name)
|
|
@@ -44,7 +44,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
44
44
|
|
|
45
45
|
|
|
46
46
|
layout = QVBoxLayout(self)
|
|
47
|
-
layout.setContentsMargins(30,30,30,30)
|
|
47
|
+
layout.setContentsMargins(30, 30, 30, 30)
|
|
48
48
|
|
|
49
49
|
name_layout = QHBoxLayout()
|
|
50
50
|
name_layout.addWidget(QLabel('class name: '), 33)
|
|
@@ -55,7 +55,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
55
55
|
fig_btn_hbox.addWidget(QLabel(''), 95)
|
|
56
56
|
self.project_times_btn = QPushButton('')
|
|
57
57
|
self.project_times_btn.setStyleSheet(self.parent_window.parent_window.parent_window.button_select_all)
|
|
58
|
-
self.project_times_btn.setIcon(icon(MDI6.math_integral,color="black"))
|
|
58
|
+
self.project_times_btn.setIcon(icon(MDI6.math_integral, color="black"))
|
|
59
59
|
self.project_times_btn.setToolTip("Project measurements at all times.")
|
|
60
60
|
self.project_times_btn.setIconSize(QSize(20, 20))
|
|
61
61
|
self.project_times = False
|
|
@@ -70,8 +70,8 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
70
70
|
# slider
|
|
71
71
|
self.frame_slider = QLabeledSlider()
|
|
72
72
|
self.frame_slider.setSingleStep(1)
|
|
73
|
-
self.frame_slider.setOrientation(
|
|
74
|
-
self.frame_slider.setRange(0,int(self.df.FRAME.max()) - 1)
|
|
73
|
+
self.frame_slider.setOrientation(Qt.Horizontal)
|
|
74
|
+
self.frame_slider.setRange(0, int(self.df.FRAME.max()) - 1)
|
|
75
75
|
self.frame_slider.setValue(0)
|
|
76
76
|
self.currentFrame = 0
|
|
77
77
|
|
|
@@ -84,8 +84,8 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
84
84
|
# transparency slider
|
|
85
85
|
self.alpha_slider = QLabeledDoubleSlider()
|
|
86
86
|
self.alpha_slider.setSingleStep(0.001)
|
|
87
|
-
self.alpha_slider.setOrientation(
|
|
88
|
-
self.alpha_slider.setRange(0,1)
|
|
87
|
+
self.alpha_slider.setOrientation(Qt.Horizontal)
|
|
88
|
+
self.alpha_slider.setRange(0, 1)
|
|
89
89
|
self.alpha_slider.setValue(1.0)
|
|
90
90
|
self.alpha_slider.setDecimals(3)
|
|
91
91
|
|
|
@@ -95,8 +95,8 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
95
95
|
layout.addLayout(slider_alpha_hbox)
|
|
96
96
|
|
|
97
97
|
|
|
98
|
-
self.features_cb = [QSearchableComboBox() for
|
|
99
|
-
self.log_btns = [QPushButton() for
|
|
98
|
+
self.features_cb = [QSearchableComboBox() for _ in range(2)]
|
|
99
|
+
self.log_btns = [QPushButton() for _ in range(2)]
|
|
100
100
|
|
|
101
101
|
for i in range(2):
|
|
102
102
|
hbox_feat = QHBoxLayout()
|
|
@@ -106,7 +106,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
106
106
|
layout.addLayout(hbox_feat)
|
|
107
107
|
|
|
108
108
|
self.features_cb[i].clear()
|
|
109
|
-
self.features_cb[i].addItems(sorted(list(self.cols),key=str.lower))
|
|
109
|
+
self.features_cb[i].addItems(sorted(list(self.cols), key=str.lower))
|
|
110
110
|
self.features_cb[i].currentTextChanged.connect(self.update_props_scatter)
|
|
111
111
|
self.features_cb[i].setCurrentIndex(i)
|
|
112
112
|
|
|
@@ -134,15 +134,15 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
134
134
|
self.time_corr.setEnabled(False)
|
|
135
135
|
|
|
136
136
|
time_prop_hbox = QHBoxLayout()
|
|
137
|
-
time_prop_hbox.addWidget(self.time_corr,alignment=Qt.AlignCenter)
|
|
137
|
+
time_prop_hbox.addWidget(self.time_corr, alignment=Qt.AlignCenter)
|
|
138
138
|
|
|
139
139
|
self.help_propagate_btn = QPushButton()
|
|
140
|
-
self.help_propagate_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
|
|
140
|
+
self.help_propagate_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
|
|
141
141
|
self.help_propagate_btn.setIconSize(QSize(20, 20))
|
|
142
142
|
self.help_propagate_btn.clicked.connect(self.help_propagate)
|
|
143
143
|
self.help_propagate_btn.setStyleSheet(self.button_select_all)
|
|
144
144
|
self.help_propagate_btn.setToolTip("Help.")
|
|
145
|
-
time_prop_hbox.addWidget(self.help_propagate_btn,5,alignment=Qt.AlignRight)
|
|
145
|
+
time_prop_hbox.addWidget(self.help_propagate_btn, 5, alignment=Qt.AlignRight)
|
|
146
146
|
|
|
147
147
|
layout.addLayout(time_prop_hbox)
|
|
148
148
|
|
|
@@ -154,23 +154,23 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
154
154
|
|
|
155
155
|
time_corr_layout = QHBoxLayout()
|
|
156
156
|
time_corr_layout.addWidget(self.unique_state_btn, 33, alignment=Qt.AlignCenter)
|
|
157
|
-
time_corr_layout.addWidget(self.irreversible_event_btn, 33,alignment=Qt.AlignCenter)
|
|
158
|
-
time_corr_layout.addWidget(self.transient_event_btn, 33,alignment=Qt.AlignCenter)
|
|
157
|
+
time_corr_layout.addWidget(self.irreversible_event_btn, 33, alignment=Qt.AlignCenter)
|
|
158
|
+
time_corr_layout.addWidget(self.transient_event_btn, 33, alignment=Qt.AlignCenter)
|
|
159
159
|
layout.addLayout(time_corr_layout)
|
|
160
160
|
|
|
161
161
|
self.prereq_event_check = QCheckBox('prerequisite event:')
|
|
162
162
|
self.prereq_event_check.toggled.connect(self.activate_prereq_cb)
|
|
163
163
|
self.prereq_event_cb = QComboBox()
|
|
164
|
-
event_cols = ['--'] + [c.replace('t_','') for c in self.cols if c.startswith('t_')]
|
|
164
|
+
event_cols = ['--'] + [c.replace('t_', '') for c in self.cols if c.startswith('t_')]
|
|
165
165
|
self.prereq_event_cb.addItems(event_cols)
|
|
166
166
|
self.prereq_event_check.setEnabled(False)
|
|
167
167
|
self.prereq_event_cb.setEnabled(False)
|
|
168
168
|
|
|
169
169
|
self.r2_slider = QLabeledDoubleSlider()
|
|
170
170
|
self.r2_slider.setValue(0.75)
|
|
171
|
-
self.r2_slider.setRange(0,1)
|
|
171
|
+
self.r2_slider.setRange(0, 1)
|
|
172
172
|
self.r2_slider.setSingleStep(0.01)
|
|
173
|
-
self.r2_slider.setOrientation(
|
|
173
|
+
self.r2_slider.setOrientation(Qt.Horizontal)
|
|
174
174
|
self.r2_label = QLabel('R2 tolerance:')
|
|
175
175
|
self.r2_label.setToolTip('Minimum R2 between the fit sigmoid and the binary response to the filters to accept the event.')
|
|
176
176
|
|
|
@@ -196,7 +196,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
196
196
|
wg.setEnabled(False)
|
|
197
197
|
|
|
198
198
|
prereq_layout = QHBoxLayout()
|
|
199
|
-
prereq_layout.setContentsMargins(30,0,0,0)
|
|
199
|
+
prereq_layout.setContentsMargins(30, 0, 0, 0)
|
|
200
200
|
prereq_layout.addWidget(self.prereq_event_check, 20)
|
|
201
201
|
prereq_layout.addWidget(self.prereq_event_cb, 80)
|
|
202
202
|
layout.addLayout(prereq_layout)
|
|
@@ -212,7 +212,6 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
212
212
|
self.frame_slider.valueChanged.connect(self.set_frame)
|
|
213
213
|
self.alpha_slider.valueChanged.connect(self.set_transparency)
|
|
214
214
|
|
|
215
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
216
215
|
|
|
217
216
|
def activate_prereq_cb(self):
|
|
218
217
|
if self.prereq_event_check.isChecked():
|
|
@@ -270,11 +269,10 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
270
269
|
Define properties scatter.
|
|
271
270
|
"""
|
|
272
271
|
|
|
273
|
-
self.fig_props, self.ax_props = plt.subplots(figsize=(4,4),tight_layout=True)
|
|
272
|
+
self.fig_props, self.ax_props = plt.subplots(figsize=(4, 4), tight_layout=True)
|
|
274
273
|
self.propscanvas = FigureCanvas(self.fig_props, interactive=True)
|
|
275
274
|
self.fig_props.set_facecolor('none')
|
|
276
|
-
self.
|
|
277
|
-
self.scat_props = self.ax_props.scatter([],[], color="k", alpha=self.currentAlpha)
|
|
275
|
+
self.scat_props = self.ax_props.scatter([], [], color="k", alpha=self.currentAlpha)
|
|
278
276
|
self.propscanvas.canvas.draw_idle()
|
|
279
277
|
self.propscanvas.canvas.setMinimumHeight(self.screen_height//5)
|
|
280
278
|
|
|
@@ -295,25 +293,24 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
295
293
|
self.log_btns[0].setEnabled(True)
|
|
296
294
|
|
|
297
295
|
if np.any(self.df[self.features_cb[1].currentText()].to_numpy() <= 0.):
|
|
298
|
-
if self.ax_props.get_xscale()=='log':
|
|
296
|
+
if self.ax_props.get_xscale() == 'log':
|
|
299
297
|
self.log_btns[1].click()
|
|
300
298
|
self.log_btns[1].setEnabled(False)
|
|
301
299
|
else:
|
|
302
300
|
self.log_btns[1].setEnabled(True)
|
|
303
301
|
except Exception as e:
|
|
304
|
-
|
|
305
|
-
pass
|
|
302
|
+
print(e)
|
|
306
303
|
|
|
307
304
|
class_name = self.class_name
|
|
308
305
|
|
|
309
306
|
try:
|
|
310
307
|
|
|
311
308
|
if not self.project_times:
|
|
312
|
-
self.scat_props.set_offsets(self.df.loc[self.df['FRAME']==self.currentFrame,[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
|
|
313
|
-
colors = [color_from_status(c) for c in self.df.loc[self.df['FRAME']==self.currentFrame,class_name].to_numpy()]
|
|
309
|
+
self.scat_props.set_offsets(self.df.loc[self.df['FRAME'] == self.currentFrame, [self.features_cb[1].currentText(), self.features_cb[0].currentText()]].to_numpy())
|
|
310
|
+
colors = [color_from_status(c) for c in self.df.loc[self.df['FRAME'] == self.currentFrame, class_name].to_numpy()]
|
|
314
311
|
self.scat_props.set_facecolor(colors)
|
|
315
312
|
else:
|
|
316
|
-
self.scat_props.set_offsets(self.df[[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
|
|
313
|
+
self.scat_props.set_offsets(self.df[[self.features_cb[1].currentText(), self.features_cb[0].currentText()]].to_numpy())
|
|
317
314
|
colors = [color_from_status(c) for c in self.df[class_name].to_numpy()]
|
|
318
315
|
self.scat_props.set_facecolor(colors)
|
|
319
316
|
|
|
@@ -333,18 +330,18 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
333
330
|
|
|
334
331
|
x_padding = (max_x - min_x) * 0.05
|
|
335
332
|
y_padding = (max_y - min_y) * 0.05
|
|
336
|
-
if x_padding==0:
|
|
333
|
+
if x_padding == 0:
|
|
337
334
|
x_padding = 0.05
|
|
338
|
-
if y_padding==0:
|
|
335
|
+
if y_padding == 0:
|
|
339
336
|
y_padding = 0.05
|
|
340
337
|
|
|
341
|
-
if min_x==min_x and max_x==max_x:
|
|
342
|
-
if self.ax_props.get_xscale()=='linear':
|
|
338
|
+
if min_x == min_x and max_x == max_x:
|
|
339
|
+
if self.ax_props.get_xscale() == 'linear':
|
|
343
340
|
self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
|
|
344
341
|
else:
|
|
345
342
|
self.ax_props.set_xlim(min_x, max_x)
|
|
346
|
-
if min_y==min_y and max_y==max_y:
|
|
347
|
-
if self.ax_props.get_yscale()=='linear':
|
|
343
|
+
if min_y == min_y and max_y == max_y:
|
|
344
|
+
if self.ax_props.get_yscale() == 'linear':
|
|
348
345
|
self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
|
|
349
346
|
else:
|
|
350
347
|
self.ax_props.set_ylim(min_y, max_y)
|
|
@@ -379,7 +376,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
379
376
|
if self.df is None:
|
|
380
377
|
msgBox = QMessageBox()
|
|
381
378
|
msgBox.setIcon(QMessageBox.Warning)
|
|
382
|
-
msgBox.setText(f"The query could not be understood. No filtering was applied.
|
|
379
|
+
msgBox.setText(f"The query could not be understood. No filtering was applied.")
|
|
383
380
|
msgBox.setWindowTitle("Warning")
|
|
384
381
|
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
385
382
|
returnValue = msgBox.exec()
|
|
@@ -390,22 +387,18 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
390
387
|
self.update_props_scatter(feature_changed=False)
|
|
391
388
|
|
|
392
389
|
def set_frame(self, value):
|
|
393
|
-
xlim=self.ax_props.get_xlim()
|
|
394
|
-
ylim=self.ax_props.get_ylim()
|
|
390
|
+
xlim = self.ax_props.get_xlim()
|
|
391
|
+
ylim = self.ax_props.get_ylim()
|
|
395
392
|
self.currentFrame = value
|
|
396
393
|
self.update_props_scatter(feature_changed=False)
|
|
397
394
|
self.ax_props.set_xlim(xlim)
|
|
398
395
|
self.ax_props.set_ylim(ylim)
|
|
399
396
|
|
|
400
|
-
|
|
401
397
|
def set_transparency(self, value):
|
|
402
|
-
xlim=self.ax_props.get_xlim()
|
|
403
|
-
ylim=self.ax_props.get_ylim()
|
|
398
|
+
xlim = self.ax_props.get_xlim()
|
|
399
|
+
ylim = self.ax_props.get_ylim()
|
|
404
400
|
self.currentAlpha = value
|
|
405
|
-
|
|
406
|
-
#fc[:, 3] = value
|
|
407
|
-
#self.scat_props.set_facecolors(fc)
|
|
408
|
-
#self.propscanvas.canvas.draw_idle()
|
|
401
|
+
|
|
409
402
|
self.update_props_scatter(feature_changed=False)
|
|
410
403
|
self.ax_props.set_xlim(xlim)
|
|
411
404
|
self.ax_props.set_ylim(ylim)
|
|
@@ -413,12 +406,12 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
413
406
|
def switch_projection(self):
|
|
414
407
|
if self.project_times:
|
|
415
408
|
self.project_times = False
|
|
416
|
-
self.project_times_btn.setIcon(icon(MDI6.math_integral,color="black"))
|
|
409
|
+
self.project_times_btn.setIcon(icon(MDI6.math_integral, color="black"))
|
|
417
410
|
self.project_times_btn.setIconSize(QSize(20, 20))
|
|
418
411
|
self.frame_slider.setEnabled(True)
|
|
419
412
|
else:
|
|
420
413
|
self.project_times = True
|
|
421
|
-
self.project_times_btn.setIcon(icon(MDI6.math_integral_box,color="black"))
|
|
414
|
+
self.project_times_btn.setIcon(icon(MDI6.math_integral_box, color="black"))
|
|
422
415
|
self.project_times_btn.setIconSize(QSize(20, 20))
|
|
423
416
|
self.frame_slider.setEnabled(False)
|
|
424
417
|
self.update_props_scatter(feature_changed=False)
|
|
@@ -432,12 +425,13 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
432
425
|
|
|
433
426
|
if self.time_corr.isChecked():
|
|
434
427
|
self.class_name_user = 'class_'+self.name_le.text()
|
|
435
|
-
print(f'User defined class name: {self.class_name_user}
|
|
428
|
+
print(f'User defined class name: {self.class_name_user}...')
|
|
436
429
|
if self.class_name_user in self.df.columns:
|
|
437
430
|
|
|
438
431
|
msgBox = QMessageBox()
|
|
439
432
|
msgBox.setIcon(QMessageBox.Information)
|
|
440
|
-
msgBox.setText(f"The class column {self.class_name_user} already exists in the table.\nProceeding will
|
|
433
|
+
msgBox.setText(f"The class column {self.class_name_user} already exists in the table.\nProceeding will "
|
|
434
|
+
f"reclassify. Do you want to continue?")
|
|
441
435
|
msgBox.setWindowTitle("Warning")
|
|
442
436
|
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
443
437
|
returnValue = msgBox.exec()
|
|
@@ -447,7 +441,6 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
447
441
|
return None
|
|
448
442
|
|
|
449
443
|
name_map = {self.class_name: self.class_name_user}
|
|
450
|
-
print(f"{name_map=}")
|
|
451
444
|
self.df = self.df.drop(list(set(name_map.values()) & set(self.df.columns)), axis=1).rename(columns=name_map)
|
|
452
445
|
self.df.reset_index(inplace=True, drop=True)
|
|
453
446
|
|
|
@@ -455,7 +448,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
455
448
|
if self.prereq_event_check.isChecked() and "t_"+self.prereq_event_cb.currentText() in self.cols:
|
|
456
449
|
pre_event = self.prereq_event_cb.currentText()
|
|
457
450
|
|
|
458
|
-
self.df = interpret_track_classification(self.df, self.class_name_user, irreversible_event=self.irreversible_event_btn.isChecked(), unique_state=self.unique_state_btn.isChecked(), transient_event=self.transient_event_btn.isChecked(),r2_threshold=self.r2_slider.value(), pre_event=pre_event)
|
|
451
|
+
self.df = interpret_track_classification(self.df, self.class_name_user, irreversible_event=self.irreversible_event_btn.isChecked(), unique_state=self.unique_state_btn.isChecked(), transient_event=self.transient_event_btn.isChecked(), r2_threshold=self.r2_slider.value(), pre_event=pre_event)
|
|
459
452
|
|
|
460
453
|
else:
|
|
461
454
|
self.group_name_user = 'group_' + self.name_le.text()
|
|
@@ -465,7 +458,8 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
465
458
|
msgBox = QMessageBox()
|
|
466
459
|
msgBox.setIcon(QMessageBox.Information)
|
|
467
460
|
msgBox.setText(
|
|
468
|
-
f"The group column {self.group_name_user} already exists in the table.\nProceeding will
|
|
461
|
+
f"The group column {self.group_name_user} already exists in the table.\nProceeding will "
|
|
462
|
+
f"reclassify. Do you want to continue?")
|
|
469
463
|
msgBox.setWindowTitle("Warning")
|
|
470
464
|
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
471
465
|
returnValue = msgBox.exec()
|
|
@@ -477,14 +471,18 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
477
471
|
name_map = {self.class_name: self.group_name_user}
|
|
478
472
|
self.df = self.df.drop(list(set(name_map.values()) & set(self.df.columns)), axis=1).rename(columns=name_map)
|
|
479
473
|
print(self.df.columns)
|
|
480
|
-
#self.df[self.group_name_user] = self.df[self.group_name_user].replace({0: 1, 1: 0})
|
|
474
|
+
# self.df[self.group_name_user] = self.df[self.group_name_user].replace({0: 1, 1: 0})
|
|
481
475
|
self.df.reset_index(inplace=True, drop=True)
|
|
482
476
|
|
|
483
477
|
if 'custom' in list(self.df.columns):
|
|
484
|
-
self.df = self.df.drop(['custom'],axis=1)
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
478
|
+
self.df = self.df.drop(['custom'], axis=1)
|
|
479
|
+
|
|
480
|
+
self.fig_props.set_size_inches(4, 3)
|
|
481
|
+
self.fig_props.suptitle(self.property_query_le.text(), fontsize=10)
|
|
482
|
+
self.fig_props.tight_layout()
|
|
483
|
+
for pos, pos_group in self.df.groupby('position'):
|
|
484
|
+
self.fig_props.savefig(str(pos)+os.sep.join(['output', f'{self.class_name}.png']), bbox_inches='tight', dpi=300)
|
|
485
|
+
pos_group.to_csv(str(pos)+os.sep.join(['output', 'tables', f'trajectories_{self.mode}.csv']), index=False)
|
|
488
486
|
|
|
489
487
|
self.parent_window.parent_window.update_position_options()
|
|
490
488
|
self.close()
|
|
@@ -496,7 +494,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
496
494
|
Helper for segmentation strategy between threshold-based and Deep learning.
|
|
497
495
|
"""
|
|
498
496
|
|
|
499
|
-
dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','propagate-classification.json'])
|
|
497
|
+
dict_path = os.sep.join([get_software_location(), 'celldetective', 'gui', 'help', 'propagate-classification.json'])
|
|
500
498
|
|
|
501
499
|
with open(dict_path) as f:
|
|
502
500
|
d = json.load(f)
|
|
@@ -520,53 +518,46 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
520
518
|
Switch threshold histogram to log scale. Auto adjust.
|
|
521
519
|
"""
|
|
522
520
|
|
|
523
|
-
if i==1:
|
|
521
|
+
if i == 1:
|
|
524
522
|
try:
|
|
525
523
|
feat_x = self.features_cb[1].currentText()
|
|
526
524
|
min_x = self.df.dropna(subset=feat_x)[feat_x].min()
|
|
527
525
|
max_x = self.df.dropna(subset=feat_x)[feat_x].max()
|
|
528
526
|
x_padding = (max_x - min_x) * 0.05
|
|
529
|
-
if x_padding==0:
|
|
527
|
+
if x_padding == 0:
|
|
530
528
|
x_padding = 0.05
|
|
531
529
|
|
|
532
|
-
if self.ax_props.get_xscale()=='linear':
|
|
530
|
+
if self.ax_props.get_xscale() == 'linear':
|
|
533
531
|
self.ax_props.set_xlim(min_x, max_x)
|
|
534
532
|
self.ax_props.set_xscale('log')
|
|
535
|
-
self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
533
|
+
self.log_btns[i].setIcon(icon(MDI6.math_log, color="#1565c0"))
|
|
536
534
|
else:
|
|
537
535
|
self.ax_props.set_xscale('linear')
|
|
538
536
|
self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
|
|
539
|
-
self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
537
|
+
self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
|
|
540
538
|
except Exception as e:
|
|
541
539
|
print(e)
|
|
542
|
-
elif i==0:
|
|
540
|
+
elif i == 0:
|
|
543
541
|
try:
|
|
544
542
|
feat_y = self.features_cb[0].currentText()
|
|
545
543
|
min_y = self.df.dropna(subset=feat_y)[feat_y].min()
|
|
546
544
|
max_y = self.df.dropna(subset=feat_y)[feat_y].max()
|
|
547
545
|
y_padding = (max_y - min_y) * 0.05
|
|
548
|
-
if y_padding==0:
|
|
546
|
+
if y_padding == 0:
|
|
549
547
|
y_padding = 0.05
|
|
550
548
|
|
|
551
|
-
if self.ax_props.get_yscale()=='linear':
|
|
549
|
+
if self.ax_props.get_yscale() == 'linear':
|
|
552
550
|
self.ax_props.set_ylim(min_y, max_y)
|
|
553
551
|
self.ax_props.set_yscale('log')
|
|
554
|
-
self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
552
|
+
self.log_btns[i].setIcon(icon(MDI6.math_log, color="#1565c0"))
|
|
555
553
|
else:
|
|
556
554
|
self.ax_props.set_yscale('linear')
|
|
557
555
|
self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
|
|
558
|
-
self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
556
|
+
self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
|
|
559
557
|
except Exception as e:
|
|
560
558
|
print(e)
|
|
561
559
|
|
|
562
560
|
self.ax_props.autoscale()
|
|
563
561
|
self.propscanvas.canvas.draw_idle()
|
|
564
562
|
|
|
565
|
-
print('Done.')
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
563
|
+
print('Done.')
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import
|
|
1
|
+
from PyQt5.QtWidgets import QApplication, QMessageBox, QHBoxLayout, QFileDialog, QVBoxLayout, QScrollArea, QCheckBox, QGridLayout, QLabel, QLineEdit, QPushButton
|
|
2
2
|
from PyQt5.QtGui import QIntValidator, QDoubleValidator
|
|
3
3
|
from celldetective.gui.gui_utils import center_window, help_generic
|
|
4
|
-
from celldetective.gui.styles import Styles
|
|
5
4
|
from celldetective.utils import get_software_location
|
|
6
5
|
import json
|
|
7
6
|
|
|
@@ -13,9 +12,10 @@ from configparser import ConfigParser
|
|
|
13
12
|
import os
|
|
14
13
|
from functools import partial
|
|
15
14
|
import numpy as np
|
|
16
|
-
from celldetective.gui import
|
|
15
|
+
from celldetective.gui import CelldetectiveMainWindow, CelldetectiveWidget
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
|
|
18
|
+
class ConfigNewExperiment(CelldetectiveMainWindow):
|
|
19
19
|
|
|
20
20
|
def __init__(self, parent_window=None):
|
|
21
21
|
|
|
@@ -34,11 +34,11 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
34
34
|
|
|
35
35
|
# Create button widget and layout
|
|
36
36
|
self.scroll_area = QScrollArea(self)
|
|
37
|
-
button_widget =
|
|
37
|
+
button_widget = CelldetectiveWidget()
|
|
38
38
|
self.grid = QGridLayout()
|
|
39
39
|
button_widget.setLayout(self.grid)
|
|
40
40
|
|
|
41
|
-
self.grid.setContentsMargins(30,30,30,30)
|
|
41
|
+
self.grid.setContentsMargins(30, 30, 30, 30)
|
|
42
42
|
self.grid.addWidget(QLabel("Folder:"), 0, 0, 1, 3)
|
|
43
43
|
self.supFolder = QLineEdit()
|
|
44
44
|
self.supFolder.setAlignment(Qt.AlignLeft)
|
|
@@ -64,17 +64,20 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
64
64
|
self.grid.addWidget(self.expName, 3, 0, 1, 3)
|
|
65
65
|
|
|
66
66
|
self.generate_movie_settings()
|
|
67
|
-
self.grid.addLayout(self.ms_grid,29,0,1,3)
|
|
67
|
+
self.grid.addLayout(self.ms_grid, 29, 0, 1, 3)
|
|
68
68
|
|
|
69
69
|
self.generate_channel_params_box()
|
|
70
|
-
self.grid.addLayout(self.channel_grid,30,0,1,3)
|
|
70
|
+
self.grid.addLayout(self.channel_grid, 30, 0, 1, 3)
|
|
71
|
+
|
|
72
|
+
self.generate_population_params_box()
|
|
73
|
+
self.grid.addLayout(self.population_grid, 31, 0, 1, 3)
|
|
71
74
|
|
|
72
75
|
self.validate_button = QPushButton("Submit")
|
|
73
76
|
self.validate_button.clicked.connect(self.create_config)
|
|
74
77
|
self.validate_button.setStyleSheet(self.button_style_sheet)
|
|
75
78
|
#self.validate_button.setIcon(QIcon_from_svg(abs_path+f"/icons/process.svg", color='white'))
|
|
76
79
|
|
|
77
|
-
self.grid.addWidget(self.validate_button,
|
|
80
|
+
self.grid.addWidget(self.validate_button, 32, 0, 1, 3, alignment = Qt.AlignBottom)
|
|
78
81
|
button_widget.adjustSize()
|
|
79
82
|
|
|
80
83
|
self.scroll_area.setAlignment(Qt.AlignCenter)
|
|
@@ -128,10 +131,9 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
128
131
|
self.help_btn.setToolTip("Help.")
|
|
129
132
|
self.ms_grid.addWidget(self.help_btn, 1, 0, 1, 3, alignment=Qt.AlignRight)
|
|
130
133
|
|
|
131
|
-
|
|
132
134
|
self.SliderWells = QLabeledSlider(Qt.Horizontal, self)
|
|
133
135
|
self.SliderWells.setMinimum(1)
|
|
134
|
-
self.SliderWells.setMaximum(
|
|
136
|
+
self.SliderWells.setMaximum(512)
|
|
135
137
|
self.ms_grid.addWidget(self.SliderWells, 2, 0, 1, 3, alignment=Qt.AlignTop)
|
|
136
138
|
|
|
137
139
|
self.number_of_positions = QLabel("Number of positions per well:")
|
|
@@ -139,7 +141,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
139
141
|
|
|
140
142
|
self.SliderPos = QLabeledSlider(Qt.Horizontal, self)
|
|
141
143
|
self.SliderPos.setMinimum(1)
|
|
142
|
-
self.SliderPos.setMaximum(
|
|
144
|
+
self.SliderPos.setMaximum(512)
|
|
143
145
|
|
|
144
146
|
self.ms_grid.addWidget(self.SliderPos, 4, 0, 1, 3)
|
|
145
147
|
|
|
@@ -168,7 +170,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
168
170
|
self.ms_grid.addWidget(self.movie_length,9, 0, 1, 3)
|
|
169
171
|
self.MovieLengthSlider = QLabeledSlider(Qt.Horizontal, self)
|
|
170
172
|
self.MovieLengthSlider.setMinimum(2)
|
|
171
|
-
self.MovieLengthSlider.setMaximum(128)
|
|
173
|
+
#self.MovieLengthSlider.setMaximum(128)
|
|
172
174
|
self.ms_grid.addWidget(self.MovieLengthSlider, 10, 0, 1, 3)
|
|
173
175
|
|
|
174
176
|
self.prefix_lbl = QLabel("Prefix for the movies:")
|
|
@@ -181,7 +183,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
181
183
|
self.movie_prefix_field.setText("")
|
|
182
184
|
self.ms_grid.addWidget(self.movie_prefix_field, 12, 0, 1, 3)
|
|
183
185
|
|
|
184
|
-
self.ms_grid.addWidget(QLabel("
|
|
186
|
+
self.ms_grid.addWidget(QLabel("Image width:"), 13, 0, 1, 3)
|
|
185
187
|
self.shape_x_field = QLineEdit()
|
|
186
188
|
self.shape_x_field.setValidator(onlyInt)
|
|
187
189
|
self.shape_x_field.setAlignment(Qt.AlignLeft)
|
|
@@ -190,7 +192,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
190
192
|
self.shape_x_field.setText("2048")
|
|
191
193
|
self.ms_grid.addWidget(self.shape_x_field, 14, 0, 1, 3)
|
|
192
194
|
|
|
193
|
-
self.ms_grid.addWidget(QLabel("
|
|
195
|
+
self.ms_grid.addWidget(QLabel("Image height:"), 15, 0, 1, 3)
|
|
194
196
|
self.shape_y_field = QLineEdit()
|
|
195
197
|
self.shape_y_field.setValidator(onlyInt)
|
|
196
198
|
self.shape_y_field.setAlignment(Qt.AlignLeft)
|
|
@@ -271,7 +273,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
271
273
|
|
|
272
274
|
def add_custom_channel(self):
|
|
273
275
|
|
|
274
|
-
self.CustomChannelWidget =
|
|
276
|
+
self.CustomChannelWidget = CelldetectiveWidget()
|
|
275
277
|
self.CustomChannelWidget.setWindowTitle("Custom channel")
|
|
276
278
|
layout = QVBoxLayout()
|
|
277
279
|
self.CustomChannelWidget.setLayout(layout)
|
|
@@ -323,6 +325,83 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
323
325
|
else:
|
|
324
326
|
self.sliders[index].setEnabled(False)
|
|
325
327
|
|
|
328
|
+
def generate_population_params_box(self):
|
|
329
|
+
|
|
330
|
+
"""
|
|
331
|
+
Parameters related to the movie channels
|
|
332
|
+
Rewrite all of it
|
|
333
|
+
|
|
334
|
+
"""
|
|
335
|
+
|
|
336
|
+
self.population_grid = QGridLayout()
|
|
337
|
+
self.population_grid.setContentsMargins(21,30,20,30)
|
|
338
|
+
|
|
339
|
+
pop_lbl = QLabel("CELL POPULATIONS")
|
|
340
|
+
pop_lbl.setStyleSheet("""
|
|
341
|
+
font-weight: bold;
|
|
342
|
+
""")
|
|
343
|
+
self.population_grid.addWidget(pop_lbl, 0,0,1,3, alignment=Qt.AlignCenter)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
self.populations = ['effectors','targets']
|
|
347
|
+
self.population_checkboxes = [QCheckBox() for i in range(len(self.populations))]
|
|
348
|
+
|
|
349
|
+
for i in range(len(self.populations)):
|
|
350
|
+
|
|
351
|
+
self.population_checkboxes[i].setText(self.populations[i])
|
|
352
|
+
self.population_checkboxes[i].setChecked(True)
|
|
353
|
+
self.population_grid.addWidget(self.population_checkboxes[i], i+1, 0, 1, 1)
|
|
354
|
+
|
|
355
|
+
# Add channel button
|
|
356
|
+
self.addPopBtn = QPushButton('Add a cell population')
|
|
357
|
+
self.addPopBtn.setIcon(icon(MDI6.plus,color="white"))
|
|
358
|
+
self.addPopBtn.setIconSize(QSize(25, 25))
|
|
359
|
+
self.addPopBtn.setStyleSheet(self.button_style_sheet)
|
|
360
|
+
self.addPopBtn.clicked.connect(self.add_custom_population)
|
|
361
|
+
self.population_grid.addWidget(self.addPopBtn, 1000, 0, 1, 1)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def add_custom_population(self):
|
|
365
|
+
self.CustomPopWidget = CelldetectiveWidget()
|
|
366
|
+
self.CustomPopWidget.setWindowTitle("Define custom population")
|
|
367
|
+
layout = QVBoxLayout()
|
|
368
|
+
self.CustomPopWidget.setLayout(layout)
|
|
369
|
+
|
|
370
|
+
self.name_le = QLineEdit()
|
|
371
|
+
self.name_le.setPlaceholderText('name')
|
|
372
|
+
self.name_le.textChanged.connect(self.check_population_name)
|
|
373
|
+
hbox = QHBoxLayout()
|
|
374
|
+
hbox.addWidget(QLabel('population name: '), 33)
|
|
375
|
+
hbox.addWidget(self.name_le, 66)
|
|
376
|
+
layout.addLayout(hbox)
|
|
377
|
+
|
|
378
|
+
self.addPopBtn = QPushButton('add')
|
|
379
|
+
self.addPopBtn.setStyleSheet(self.button_style_sheet)
|
|
380
|
+
self.addPopBtn.setEnabled(False)
|
|
381
|
+
self.addPopBtn.clicked.connect(self.write_custom_population)
|
|
382
|
+
layout.addWidget(self.addPopBtn)
|
|
383
|
+
center_window(self.CustomPopWidget)
|
|
384
|
+
self.CustomPopWidget.show()
|
|
385
|
+
|
|
386
|
+
def check_population_name(self, text):
|
|
387
|
+
# define all conditions for valid population name (like no space)
|
|
388
|
+
if len(text)>0 and ' ' not in text:
|
|
389
|
+
self.addPopBtn.setEnabled(True)
|
|
390
|
+
else:
|
|
391
|
+
self.addPopBtn.setEnabled(False)
|
|
392
|
+
|
|
393
|
+
def write_custom_population(self):
|
|
394
|
+
|
|
395
|
+
self.new_population_name = self.name_le.text()
|
|
396
|
+
name_map = self.new_population_name
|
|
397
|
+
|
|
398
|
+
self.populations.append(self.new_population_name)
|
|
399
|
+
self.population_checkboxes.append(QCheckBox())
|
|
400
|
+
self.CustomPopWidget.close()
|
|
401
|
+
|
|
402
|
+
self.population_checkboxes[-1].setText(self.populations[-1])
|
|
403
|
+
self.population_grid.addWidget(self.population_checkboxes[-1], len(self.populations)+1, 0, 1, 1)
|
|
404
|
+
|
|
326
405
|
def browse_experiment_folder(self):
|
|
327
406
|
|
|
328
407
|
"""
|
|
@@ -362,6 +441,17 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
362
441
|
if returnValue == QMessageBox.Ok:
|
|
363
442
|
return None
|
|
364
443
|
|
|
444
|
+
populations_checked = [self.population_checkboxes[i].isChecked() for i in range(len(self.population_checkboxes))]
|
|
445
|
+
if not np.any(populations_checked):
|
|
446
|
+
msgBox = QMessageBox()
|
|
447
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
448
|
+
msgBox.setText("Please set at least one cell population before proceeding...")
|
|
449
|
+
msgBox.setWindowTitle("Warning")
|
|
450
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
451
|
+
returnValue = msgBox.exec()
|
|
452
|
+
if returnValue == QMessageBox.Ok:
|
|
453
|
+
return None
|
|
454
|
+
|
|
365
455
|
sorted_list = set(channel_indices)
|
|
366
456
|
expected_list = set(np.arange(max(sorted_list)+1))
|
|
367
457
|
print(sorted_list, expected_list, sorted_list==expected_list)
|
|
@@ -429,8 +519,13 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
429
519
|
Write all user input parameters to a configuration file associated to an experiment.
|
|
430
520
|
"""
|
|
431
521
|
|
|
522
|
+
|
|
432
523
|
config = ConfigParser(interpolation=None)
|
|
433
524
|
|
|
525
|
+
config.add_section('Populations')
|
|
526
|
+
pops = ','.join([self.populations[i].lower() for i in range(len(self.population_checkboxes)) if self.population_checkboxes[i].isChecked()])
|
|
527
|
+
config.set('Populations','populations', pops)
|
|
528
|
+
|
|
434
529
|
# add a new section and some values
|
|
435
530
|
config.add_section('MovieSettings')
|
|
436
531
|
config.set('MovieSettings', 'PxToUm', self.PxToUm_field.text().replace(',','.'))
|
|
@@ -456,6 +551,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
456
551
|
config.add_section('Metadata')
|
|
457
552
|
config.set('Metadata', 'concentration_units', self.concentration_units)
|
|
458
553
|
|
|
554
|
+
|
|
459
555
|
# save to a file
|
|
460
556
|
with open('config.ini', 'w') as configfile:
|
|
461
557
|
config.write(configfile)
|
|
@@ -464,7 +560,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
|
|
|
464
560
|
print(f'New experiment successfully configured in folder {self.directory}...')
|
|
465
561
|
self.close()
|
|
466
562
|
|
|
467
|
-
class SetupConditionLabels(
|
|
563
|
+
class SetupConditionLabels(CelldetectiveWidget):
|
|
468
564
|
def __init__(self, parent_window, n_wells):
|
|
469
565
|
super().__init__()
|
|
470
566
|
self.parent_window = parent_window
|