celldetective 1.3.9.post5__py3-none-any.whl → 1.4.1__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/exceptions.py +11 -0
- celldetective/extra_properties.py +132 -0
- celldetective/filters.py +7 -1
- celldetective/gui/InitWindow.py +37 -46
- celldetective/gui/__init__.py +3 -9
- celldetective/gui/about.py +19 -15
- celldetective/gui/analyze_block.py +34 -19
- celldetective/gui/base_annotator.py +786 -0
- celldetective/gui/base_components.py +23 -0
- celldetective/gui/classifier_widget.py +86 -94
- celldetective/gui/configure_new_exp.py +163 -46
- celldetective/gui/control_panel.py +76 -146
- celldetective/gui/{signal_annotator.py → event_annotator.py} +533 -1438
- celldetective/gui/generic_signal_plot.py +11 -13
- celldetective/gui/gui_utils.py +54 -23
- celldetective/gui/help/neighborhood.json +2 -2
- celldetective/gui/json_readers.py +5 -4
- celldetective/gui/layouts.py +265 -31
- celldetective/gui/{signal_annotator2.py → pair_event_annotator.py} +433 -635
- celldetective/gui/plot_measurements.py +21 -17
- celldetective/gui/plot_signals_ui.py +125 -72
- celldetective/gui/process_block.py +283 -188
- celldetective/gui/processes/compute_neighborhood.py +594 -0
- celldetective/gui/processes/downloader.py +37 -34
- celldetective/gui/processes/measure_cells.py +19 -8
- celldetective/gui/processes/segment_cells.py +47 -11
- celldetective/gui/processes/track_cells.py +18 -13
- celldetective/gui/seg_model_loader.py +21 -62
- celldetective/gui/settings/__init__.py +7 -0
- celldetective/gui/settings/_settings_base.py +70 -0
- celldetective/gui/{retrain_signal_model_options.py → settings/_settings_event_model_training.py} +54 -109
- celldetective/gui/{measurement_options.py → settings/_settings_measurements.py} +54 -92
- celldetective/gui/{neighborhood_options.py → settings/_settings_neighborhood.py} +10 -13
- celldetective/gui/settings/_settings_segmentation.py +49 -0
- celldetective/gui/{retrain_segmentation_model_options.py → settings/_settings_segmentation_model_training.py} +38 -92
- celldetective/gui/{signal_annotator_options.py → settings/_settings_signal_annotator.py} +78 -103
- celldetective/gui/{btrack_options.py → settings/_settings_tracking.py} +85 -116
- celldetective/gui/styles.py +2 -1
- celldetective/gui/survival_ui.py +49 -95
- celldetective/gui/tableUI.py +53 -25
- celldetective/gui/table_ops/__init__.py +0 -0
- celldetective/gui/table_ops/merge_groups.py +118 -0
- celldetective/gui/thresholds_gui.py +617 -1221
- celldetective/gui/viewers.py +107 -42
- celldetective/gui/workers.py +8 -4
- celldetective/io.py +137 -57
- celldetective/links/zenodo.json +145 -144
- celldetective/measure.py +94 -53
- celldetective/neighborhood.py +342 -268
- celldetective/preprocessing.py +56 -35
- celldetective/regionprops/_regionprops.py +16 -5
- celldetective/relative_measurements.py +50 -29
- celldetective/scripts/analyze_signals.py +4 -1
- celldetective/scripts/measure_cells.py +5 -5
- celldetective/scripts/measure_relative.py +20 -12
- celldetective/scripts/segment_cells.py +4 -10
- celldetective/scripts/segment_cells_thresholds.py +3 -3
- celldetective/scripts/track_cells.py +10 -8
- celldetective/scripts/train_segmentation_model.py +18 -6
- celldetective/signals.py +29 -14
- celldetective/tracking.py +14 -3
- celldetective/utils.py +91 -62
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/METADATA +24 -16
- celldetective-1.4.1.dist-info/RECORD +123 -0
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/WHEEL +1 -1
- tests/gui/__init__.py +0 -0
- tests/gui/test_new_project.py +228 -0
- tests/gui/test_project.py +99 -0
- tests/test_preprocessing.py +2 -2
- celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +0 -79
- celldetective/models/segmentation_effectors/ricm_bf_all_last/ricm_bf_all_last +0 -0
- celldetective/models/segmentation_effectors/ricm_bf_all_last/training_instructions.json +0 -37
- celldetective/models/segmentation_effectors/test-transfer/config_input.json +0 -39
- celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
- celldetective/models/signal_detection/NucCond/classification_loss.png +0 -0
- celldetective/models/signal_detection/NucCond/classifier.h5 +0 -0
- celldetective/models/signal_detection/NucCond/config_input.json +0 -1
- celldetective/models/signal_detection/NucCond/log_classifier.csv +0 -126
- celldetective/models/signal_detection/NucCond/log_regressor.csv +0 -282
- celldetective/models/signal_detection/NucCond/regression_loss.png +0 -0
- celldetective/models/signal_detection/NucCond/regressor.h5 +0 -0
- celldetective/models/signal_detection/NucCond/scores.npy +0 -0
- celldetective/models/signal_detection/NucCond/test_confusion_matrix.png +0 -0
- celldetective/models/signal_detection/NucCond/test_regression.png +0 -0
- celldetective/models/signal_detection/NucCond/validation_confusion_matrix.png +0 -0
- celldetective/models/signal_detection/NucCond/validation_regression.png +0 -0
- celldetective-1.3.9.post5.dist-info/RECORD +0 -129
- tests/test_qt.py +0 -103
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info/licenses}/LICENSE +0 -0
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from PyQt5.QtWidgets import QMainWindow, QWidget, QDialog
|
|
2
|
+
from PyQt5.QtCore import Qt
|
|
3
|
+
from celldetective.gui import Styles
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CelldetectiveWidget(QWidget, Styles):
|
|
7
|
+
def __init__(self, *args, **kwargs):
|
|
8
|
+
super().__init__(*args, **kwargs)
|
|
9
|
+
self.setWindowIcon(self.celldetective_icon)
|
|
10
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CelldetectiveMainWindow(QMainWindow, Styles):
|
|
14
|
+
def __init__(self, *args, **kwargs):
|
|
15
|
+
super().__init__(*args, **kwargs)
|
|
16
|
+
self.setWindowIcon(self.celldetective_icon)
|
|
17
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class CelldetectiveDialog(QDialog, Styles):
|
|
21
|
+
def __init__(self, *args, **kwargs):
|
|
22
|
+
super().__init__(*args, **kwargs)
|
|
23
|
+
self.setWindowIcon(self.celldetective_icon)
|
|
@@ -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
|
|
|
@@ -10,12 +10,13 @@ import numpy as np
|
|
|
10
10
|
import matplotlib.pyplot as plt
|
|
11
11
|
import json
|
|
12
12
|
|
|
13
|
+
from celldetective.exceptions import EmptyQueryError, MissingColumnsError, QueryError
|
|
13
14
|
from celldetective.gui.gui_utils import FigureCanvas, center_window, color_from_status, help_generic
|
|
14
|
-
from celldetective.gui import
|
|
15
|
+
from celldetective.gui.base_components import CelldetectiveWidget
|
|
15
16
|
from celldetective.utils import get_software_location
|
|
16
17
|
from celldetective.measure import classify_cells_from_query, interpret_track_classification
|
|
17
18
|
|
|
18
|
-
class ClassifierWidget(
|
|
19
|
+
class ClassifierWidget(CelldetectiveWidget):
|
|
19
20
|
|
|
20
21
|
def __init__(self, parent_window):
|
|
21
22
|
|
|
@@ -33,7 +34,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
33
34
|
|
|
34
35
|
is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
|
|
35
36
|
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]
|
|
37
|
+
self.cols = [col for t, col in zip(is_number_test, self.df.columns) if t]
|
|
37
38
|
|
|
38
39
|
self.class_name = 'custom'
|
|
39
40
|
self.name_le = QLineEdit(self.class_name)
|
|
@@ -44,7 +45,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
44
45
|
|
|
45
46
|
|
|
46
47
|
layout = QVBoxLayout(self)
|
|
47
|
-
layout.setContentsMargins(30,30,30,30)
|
|
48
|
+
layout.setContentsMargins(30, 30, 30, 30)
|
|
48
49
|
|
|
49
50
|
name_layout = QHBoxLayout()
|
|
50
51
|
name_layout.addWidget(QLabel('class name: '), 33)
|
|
@@ -55,7 +56,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
55
56
|
fig_btn_hbox.addWidget(QLabel(''), 95)
|
|
56
57
|
self.project_times_btn = QPushButton('')
|
|
57
58
|
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"))
|
|
59
|
+
self.project_times_btn.setIcon(icon(MDI6.math_integral, color="black"))
|
|
59
60
|
self.project_times_btn.setToolTip("Project measurements at all times.")
|
|
60
61
|
self.project_times_btn.setIconSize(QSize(20, 20))
|
|
61
62
|
self.project_times = False
|
|
@@ -70,8 +71,8 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
70
71
|
# slider
|
|
71
72
|
self.frame_slider = QLabeledSlider()
|
|
72
73
|
self.frame_slider.setSingleStep(1)
|
|
73
|
-
self.frame_slider.setOrientation(
|
|
74
|
-
self.frame_slider.setRange(0,int(self.df.FRAME.max()) - 1)
|
|
74
|
+
self.frame_slider.setOrientation(Qt.Horizontal)
|
|
75
|
+
self.frame_slider.setRange(0, int(self.df.FRAME.max()) - 1)
|
|
75
76
|
self.frame_slider.setValue(0)
|
|
76
77
|
self.currentFrame = 0
|
|
77
78
|
|
|
@@ -84,8 +85,8 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
84
85
|
# transparency slider
|
|
85
86
|
self.alpha_slider = QLabeledDoubleSlider()
|
|
86
87
|
self.alpha_slider.setSingleStep(0.001)
|
|
87
|
-
self.alpha_slider.setOrientation(
|
|
88
|
-
self.alpha_slider.setRange(0,1)
|
|
88
|
+
self.alpha_slider.setOrientation(Qt.Horizontal)
|
|
89
|
+
self.alpha_slider.setRange(0, 1)
|
|
89
90
|
self.alpha_slider.setValue(1.0)
|
|
90
91
|
self.alpha_slider.setDecimals(3)
|
|
91
92
|
|
|
@@ -95,8 +96,8 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
95
96
|
layout.addLayout(slider_alpha_hbox)
|
|
96
97
|
|
|
97
98
|
|
|
98
|
-
self.features_cb = [QSearchableComboBox() for
|
|
99
|
-
self.log_btns = [QPushButton() for
|
|
99
|
+
self.features_cb = [QSearchableComboBox() for _ in range(2)]
|
|
100
|
+
self.log_btns = [QPushButton() for _ in range(2)]
|
|
100
101
|
|
|
101
102
|
for i in range(2):
|
|
102
103
|
hbox_feat = QHBoxLayout()
|
|
@@ -106,7 +107,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
106
107
|
layout.addLayout(hbox_feat)
|
|
107
108
|
|
|
108
109
|
self.features_cb[i].clear()
|
|
109
|
-
self.features_cb[i].addItems(sorted(list(self.cols),key=str.lower))
|
|
110
|
+
self.features_cb[i].addItems(sorted(list(self.cols), key=str.lower))
|
|
110
111
|
self.features_cb[i].currentTextChanged.connect(self.update_props_scatter)
|
|
111
112
|
self.features_cb[i].setCurrentIndex(i)
|
|
112
113
|
|
|
@@ -134,15 +135,15 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
134
135
|
self.time_corr.setEnabled(False)
|
|
135
136
|
|
|
136
137
|
time_prop_hbox = QHBoxLayout()
|
|
137
|
-
time_prop_hbox.addWidget(self.time_corr,alignment=Qt.AlignCenter)
|
|
138
|
+
time_prop_hbox.addWidget(self.time_corr, alignment=Qt.AlignCenter)
|
|
138
139
|
|
|
139
140
|
self.help_propagate_btn = QPushButton()
|
|
140
|
-
self.help_propagate_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
|
|
141
|
+
self.help_propagate_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
|
|
141
142
|
self.help_propagate_btn.setIconSize(QSize(20, 20))
|
|
142
143
|
self.help_propagate_btn.clicked.connect(self.help_propagate)
|
|
143
144
|
self.help_propagate_btn.setStyleSheet(self.button_select_all)
|
|
144
145
|
self.help_propagate_btn.setToolTip("Help.")
|
|
145
|
-
time_prop_hbox.addWidget(self.help_propagate_btn,5,alignment=Qt.AlignRight)
|
|
146
|
+
time_prop_hbox.addWidget(self.help_propagate_btn, 5, alignment=Qt.AlignRight)
|
|
146
147
|
|
|
147
148
|
layout.addLayout(time_prop_hbox)
|
|
148
149
|
|
|
@@ -154,23 +155,23 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
154
155
|
|
|
155
156
|
time_corr_layout = QHBoxLayout()
|
|
156
157
|
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)
|
|
158
|
+
time_corr_layout.addWidget(self.irreversible_event_btn, 33, alignment=Qt.AlignCenter)
|
|
159
|
+
time_corr_layout.addWidget(self.transient_event_btn, 33, alignment=Qt.AlignCenter)
|
|
159
160
|
layout.addLayout(time_corr_layout)
|
|
160
161
|
|
|
161
162
|
self.prereq_event_check = QCheckBox('prerequisite event:')
|
|
162
163
|
self.prereq_event_check.toggled.connect(self.activate_prereq_cb)
|
|
163
164
|
self.prereq_event_cb = QComboBox()
|
|
164
|
-
event_cols = ['--'] + [c.replace('t_','') for c in self.cols if c.startswith('t_')]
|
|
165
|
+
event_cols = ['--'] + [c.replace('t_', '') for c in self.cols if c.startswith('t_')]
|
|
165
166
|
self.prereq_event_cb.addItems(event_cols)
|
|
166
167
|
self.prereq_event_check.setEnabled(False)
|
|
167
168
|
self.prereq_event_cb.setEnabled(False)
|
|
168
169
|
|
|
169
170
|
self.r2_slider = QLabeledDoubleSlider()
|
|
170
171
|
self.r2_slider.setValue(0.75)
|
|
171
|
-
self.r2_slider.setRange(0,1)
|
|
172
|
+
self.r2_slider.setRange(0, 1)
|
|
172
173
|
self.r2_slider.setSingleStep(0.01)
|
|
173
|
-
self.r2_slider.setOrientation(
|
|
174
|
+
self.r2_slider.setOrientation(Qt.Horizontal)
|
|
174
175
|
self.r2_label = QLabel('R2 tolerance:')
|
|
175
176
|
self.r2_label.setToolTip('Minimum R2 between the fit sigmoid and the binary response to the filters to accept the event.')
|
|
176
177
|
|
|
@@ -196,7 +197,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
196
197
|
wg.setEnabled(False)
|
|
197
198
|
|
|
198
199
|
prereq_layout = QHBoxLayout()
|
|
199
|
-
prereq_layout.setContentsMargins(30,0,0,0)
|
|
200
|
+
prereq_layout.setContentsMargins(30, 0, 0, 0)
|
|
200
201
|
prereq_layout.addWidget(self.prereq_event_check, 20)
|
|
201
202
|
prereq_layout.addWidget(self.prereq_event_cb, 80)
|
|
202
203
|
layout.addLayout(prereq_layout)
|
|
@@ -212,7 +213,6 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
212
213
|
self.frame_slider.valueChanged.connect(self.set_frame)
|
|
213
214
|
self.alpha_slider.valueChanged.connect(self.set_transparency)
|
|
214
215
|
|
|
215
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
216
216
|
|
|
217
217
|
def activate_prereq_cb(self):
|
|
218
218
|
if self.prereq_event_check.isChecked():
|
|
@@ -270,11 +270,10 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
270
270
|
Define properties scatter.
|
|
271
271
|
"""
|
|
272
272
|
|
|
273
|
-
self.fig_props, self.ax_props = plt.subplots(figsize=(4,4),tight_layout=True)
|
|
273
|
+
self.fig_props, self.ax_props = plt.subplots(figsize=(4, 4), tight_layout=True)
|
|
274
274
|
self.propscanvas = FigureCanvas(self.fig_props, interactive=True)
|
|
275
275
|
self.fig_props.set_facecolor('none')
|
|
276
|
-
self.
|
|
277
|
-
self.scat_props = self.ax_props.scatter([],[], color="k", alpha=self.currentAlpha)
|
|
276
|
+
self.scat_props = self.ax_props.scatter([], [], color="k", alpha=self.currentAlpha)
|
|
278
277
|
self.propscanvas.canvas.draw_idle()
|
|
279
278
|
self.propscanvas.canvas.setMinimumHeight(self.screen_height//5)
|
|
280
279
|
|
|
@@ -295,25 +294,24 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
295
294
|
self.log_btns[0].setEnabled(True)
|
|
296
295
|
|
|
297
296
|
if np.any(self.df[self.features_cb[1].currentText()].to_numpy() <= 0.):
|
|
298
|
-
if self.ax_props.get_xscale()=='log':
|
|
297
|
+
if self.ax_props.get_xscale() == 'log':
|
|
299
298
|
self.log_btns[1].click()
|
|
300
299
|
self.log_btns[1].setEnabled(False)
|
|
301
300
|
else:
|
|
302
301
|
self.log_btns[1].setEnabled(True)
|
|
303
302
|
except Exception as e:
|
|
304
|
-
|
|
305
|
-
pass
|
|
303
|
+
print(e)
|
|
306
304
|
|
|
307
305
|
class_name = self.class_name
|
|
308
306
|
|
|
309
307
|
try:
|
|
310
308
|
|
|
311
309
|
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()]
|
|
310
|
+
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())
|
|
311
|
+
colors = [color_from_status(c) for c in self.df.loc[self.df['FRAME'] == self.currentFrame, class_name].to_numpy()]
|
|
314
312
|
self.scat_props.set_facecolor(colors)
|
|
315
313
|
else:
|
|
316
|
-
self.scat_props.set_offsets(self.df[[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
|
|
314
|
+
self.scat_props.set_offsets(self.df[[self.features_cb[1].currentText(), self.features_cb[0].currentText()]].to_numpy())
|
|
317
315
|
colors = [color_from_status(c) for c in self.df[class_name].to_numpy()]
|
|
318
316
|
self.scat_props.set_facecolor(colors)
|
|
319
317
|
|
|
@@ -333,18 +331,18 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
333
331
|
|
|
334
332
|
x_padding = (max_x - min_x) * 0.05
|
|
335
333
|
y_padding = (max_y - min_y) * 0.05
|
|
336
|
-
if x_padding==0:
|
|
334
|
+
if x_padding == 0:
|
|
337
335
|
x_padding = 0.05
|
|
338
|
-
if y_padding==0:
|
|
336
|
+
if y_padding == 0:
|
|
339
337
|
y_padding = 0.05
|
|
340
338
|
|
|
341
|
-
if min_x==min_x and max_x==max_x:
|
|
342
|
-
if self.ax_props.get_xscale()=='linear':
|
|
339
|
+
if min_x == min_x and max_x == max_x:
|
|
340
|
+
if self.ax_props.get_xscale() == 'linear':
|
|
343
341
|
self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
|
|
344
342
|
else:
|
|
345
343
|
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':
|
|
344
|
+
if min_y == min_y and max_y == max_y:
|
|
345
|
+
if self.ax_props.get_yscale() == 'linear':
|
|
348
346
|
self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
|
|
349
347
|
else:
|
|
350
348
|
self.ax_props.set_ylim(min_y, max_y)
|
|
@@ -354,32 +352,36 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
354
352
|
self.propscanvas.canvas.draw_idle()
|
|
355
353
|
|
|
356
354
|
except Exception as e:
|
|
357
|
-
|
|
355
|
+
print("Exception L355 ", e)
|
|
356
|
+
|
|
357
|
+
def show_warning(self, message: str):
|
|
358
|
+
msgBox = QMessageBox()
|
|
359
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
360
|
+
link = "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.query.html"
|
|
361
|
+
msgBox.setText(f"{message}<br><br>See <a href='{link}'>documentation</a>.")
|
|
362
|
+
msgBox.setWindowTitle("Warning")
|
|
363
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
364
|
+
msgBox.exec()
|
|
358
365
|
|
|
359
366
|
def apply_property_query(self):
|
|
360
367
|
|
|
361
368
|
query = self.property_query_le.text()
|
|
362
|
-
|
|
363
369
|
try:
|
|
364
370
|
self.df = classify_cells_from_query(self.df, self.name_le.text(), query)
|
|
371
|
+
except EmptyQueryError as e:
|
|
372
|
+
self.show_warning(str(e))
|
|
373
|
+
except MissingColumnsError as e:
|
|
374
|
+
self.show_warning(f"{e}. Please check your column names.")
|
|
375
|
+
except QueryError as e:
|
|
376
|
+
self.show_warning(f"{e}. Wrap features in backticks if needed.")
|
|
365
377
|
except Exception as e:
|
|
366
|
-
|
|
367
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
368
|
-
link = "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.query.html"
|
|
369
|
-
msg = "The query could not be understood. Please write a valid query following <a href='%s'>the documentation</a>. Wrap features in backticks (e.g. `feature` > 1) to facilitate query interpretation. " % link
|
|
370
|
-
msgBox.setText(msg)
|
|
371
|
-
msgBox.setWindowTitle("Warning")
|
|
372
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
373
|
-
returnValue = msgBox.exec()
|
|
374
|
-
if returnValue == QMessageBox.Ok:
|
|
375
|
-
self.auto_close = False
|
|
376
|
-
return None
|
|
378
|
+
self.show_warning(f"Unexpected error: {e}")
|
|
377
379
|
|
|
378
380
|
self.class_name = "status_"+self.name_le.text()
|
|
379
381
|
if self.df is None:
|
|
380
382
|
msgBox = QMessageBox()
|
|
381
383
|
msgBox.setIcon(QMessageBox.Warning)
|
|
382
|
-
msgBox.setText(f"The query could not be understood. No filtering was applied.
|
|
384
|
+
msgBox.setText(f"The query could not be understood. No filtering was applied.")
|
|
383
385
|
msgBox.setWindowTitle("Warning")
|
|
384
386
|
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
385
387
|
returnValue = msgBox.exec()
|
|
@@ -390,22 +392,18 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
390
392
|
self.update_props_scatter(feature_changed=False)
|
|
391
393
|
|
|
392
394
|
def set_frame(self, value):
|
|
393
|
-
xlim=self.ax_props.get_xlim()
|
|
394
|
-
ylim=self.ax_props.get_ylim()
|
|
395
|
+
xlim = self.ax_props.get_xlim()
|
|
396
|
+
ylim = self.ax_props.get_ylim()
|
|
395
397
|
self.currentFrame = value
|
|
396
398
|
self.update_props_scatter(feature_changed=False)
|
|
397
399
|
self.ax_props.set_xlim(xlim)
|
|
398
400
|
self.ax_props.set_ylim(ylim)
|
|
399
401
|
|
|
400
|
-
|
|
401
402
|
def set_transparency(self, value):
|
|
402
|
-
xlim=self.ax_props.get_xlim()
|
|
403
|
-
ylim=self.ax_props.get_ylim()
|
|
403
|
+
xlim = self.ax_props.get_xlim()
|
|
404
|
+
ylim = self.ax_props.get_ylim()
|
|
404
405
|
self.currentAlpha = value
|
|
405
|
-
|
|
406
|
-
#fc[:, 3] = value
|
|
407
|
-
#self.scat_props.set_facecolors(fc)
|
|
408
|
-
#self.propscanvas.canvas.draw_idle()
|
|
406
|
+
|
|
409
407
|
self.update_props_scatter(feature_changed=False)
|
|
410
408
|
self.ax_props.set_xlim(xlim)
|
|
411
409
|
self.ax_props.set_ylim(ylim)
|
|
@@ -413,12 +411,12 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
413
411
|
def switch_projection(self):
|
|
414
412
|
if self.project_times:
|
|
415
413
|
self.project_times = False
|
|
416
|
-
self.project_times_btn.setIcon(icon(MDI6.math_integral,color="black"))
|
|
414
|
+
self.project_times_btn.setIcon(icon(MDI6.math_integral, color="black"))
|
|
417
415
|
self.project_times_btn.setIconSize(QSize(20, 20))
|
|
418
416
|
self.frame_slider.setEnabled(True)
|
|
419
417
|
else:
|
|
420
418
|
self.project_times = True
|
|
421
|
-
self.project_times_btn.setIcon(icon(MDI6.math_integral_box,color="black"))
|
|
419
|
+
self.project_times_btn.setIcon(icon(MDI6.math_integral_box, color="black"))
|
|
422
420
|
self.project_times_btn.setIconSize(QSize(20, 20))
|
|
423
421
|
self.frame_slider.setEnabled(False)
|
|
424
422
|
self.update_props_scatter(feature_changed=False)
|
|
@@ -432,12 +430,13 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
432
430
|
|
|
433
431
|
if self.time_corr.isChecked():
|
|
434
432
|
self.class_name_user = 'class_'+self.name_le.text()
|
|
435
|
-
print(f'User defined class name: {self.class_name_user}
|
|
433
|
+
print(f'User defined class name: {self.class_name_user}...')
|
|
436
434
|
if self.class_name_user in self.df.columns:
|
|
437
435
|
|
|
438
436
|
msgBox = QMessageBox()
|
|
439
437
|
msgBox.setIcon(QMessageBox.Information)
|
|
440
|
-
msgBox.setText(f"The class column {self.class_name_user} already exists in the table.\nProceeding will
|
|
438
|
+
msgBox.setText(f"The class column {self.class_name_user} already exists in the table.\nProceeding will "
|
|
439
|
+
f"reclassify. Do you want to continue?")
|
|
441
440
|
msgBox.setWindowTitle("Warning")
|
|
442
441
|
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
443
442
|
returnValue = msgBox.exec()
|
|
@@ -447,7 +446,6 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
447
446
|
return None
|
|
448
447
|
|
|
449
448
|
name_map = {self.class_name: self.class_name_user}
|
|
450
|
-
print(f"{name_map=}")
|
|
451
449
|
self.df = self.df.drop(list(set(name_map.values()) & set(self.df.columns)), axis=1).rename(columns=name_map)
|
|
452
450
|
self.df.reset_index(inplace=True, drop=True)
|
|
453
451
|
|
|
@@ -455,7 +453,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
455
453
|
if self.prereq_event_check.isChecked() and "t_"+self.prereq_event_cb.currentText() in self.cols:
|
|
456
454
|
pre_event = self.prereq_event_cb.currentText()
|
|
457
455
|
|
|
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)
|
|
456
|
+
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
457
|
|
|
460
458
|
else:
|
|
461
459
|
self.group_name_user = 'group_' + self.name_le.text()
|
|
@@ -465,7 +463,8 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
465
463
|
msgBox = QMessageBox()
|
|
466
464
|
msgBox.setIcon(QMessageBox.Information)
|
|
467
465
|
msgBox.setText(
|
|
468
|
-
f"The group column {self.group_name_user} already exists in the table.\nProceeding will
|
|
466
|
+
f"The group column {self.group_name_user} already exists in the table.\nProceeding will "
|
|
467
|
+
f"reclassify. Do you want to continue?")
|
|
469
468
|
msgBox.setWindowTitle("Warning")
|
|
470
469
|
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
471
470
|
returnValue = msgBox.exec()
|
|
@@ -477,18 +476,18 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
477
476
|
name_map = {self.class_name: self.group_name_user}
|
|
478
477
|
self.df = self.df.drop(list(set(name_map.values()) & set(self.df.columns)), axis=1).rename(columns=name_map)
|
|
479
478
|
print(self.df.columns)
|
|
480
|
-
#self.df[self.group_name_user] = self.df[self.group_name_user].replace({0: 1, 1: 0})
|
|
479
|
+
# self.df[self.group_name_user] = self.df[self.group_name_user].replace({0: 1, 1: 0})
|
|
481
480
|
self.df.reset_index(inplace=True, drop=True)
|
|
482
481
|
|
|
483
482
|
if 'custom' in list(self.df.columns):
|
|
484
|
-
self.df = self.df.drop(['custom'],axis=1)
|
|
483
|
+
self.df = self.df.drop(['custom'], axis=1)
|
|
485
484
|
|
|
486
|
-
self.fig_props.set_size_inches(4,3)
|
|
485
|
+
self.fig_props.set_size_inches(4, 3)
|
|
487
486
|
self.fig_props.suptitle(self.property_query_le.text(), fontsize=10)
|
|
488
487
|
self.fig_props.tight_layout()
|
|
489
|
-
for pos,pos_group in self.df.groupby('position'):
|
|
490
|
-
self.fig_props.savefig(pos+os.sep.join(['output',f'{self.class_name}.png']), bbox_inches='tight', dpi=300)
|
|
491
|
-
pos_group.to_csv(pos+os.sep.join(['output', 'tables', f'trajectories_{self.mode}.csv']), index=False)
|
|
488
|
+
for pos, pos_group in self.df.groupby('position'):
|
|
489
|
+
self.fig_props.savefig(str(pos)+os.sep.join(['output', f'{self.class_name}.png']), bbox_inches='tight', dpi=300)
|
|
490
|
+
pos_group.to_csv(str(pos)+os.sep.join(['output', 'tables', f'trajectories_{self.mode}.csv']), index=False)
|
|
492
491
|
|
|
493
492
|
self.parent_window.parent_window.update_position_options()
|
|
494
493
|
self.close()
|
|
@@ -500,7 +499,7 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
500
499
|
Helper for segmentation strategy between threshold-based and Deep learning.
|
|
501
500
|
"""
|
|
502
501
|
|
|
503
|
-
dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','propagate-classification.json'])
|
|
502
|
+
dict_path = os.sep.join([get_software_location(), 'celldetective', 'gui', 'help', 'propagate-classification.json'])
|
|
504
503
|
|
|
505
504
|
with open(dict_path) as f:
|
|
506
505
|
d = json.load(f)
|
|
@@ -524,53 +523,46 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
524
523
|
Switch threshold histogram to log scale. Auto adjust.
|
|
525
524
|
"""
|
|
526
525
|
|
|
527
|
-
if i==1:
|
|
526
|
+
if i == 1:
|
|
528
527
|
try:
|
|
529
528
|
feat_x = self.features_cb[1].currentText()
|
|
530
529
|
min_x = self.df.dropna(subset=feat_x)[feat_x].min()
|
|
531
530
|
max_x = self.df.dropna(subset=feat_x)[feat_x].max()
|
|
532
531
|
x_padding = (max_x - min_x) * 0.05
|
|
533
|
-
if x_padding==0:
|
|
532
|
+
if x_padding == 0:
|
|
534
533
|
x_padding = 0.05
|
|
535
534
|
|
|
536
|
-
if self.ax_props.get_xscale()=='linear':
|
|
535
|
+
if self.ax_props.get_xscale() == 'linear':
|
|
537
536
|
self.ax_props.set_xlim(min_x, max_x)
|
|
538
537
|
self.ax_props.set_xscale('log')
|
|
539
|
-
self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
538
|
+
self.log_btns[i].setIcon(icon(MDI6.math_log, color="#1565c0"))
|
|
540
539
|
else:
|
|
541
540
|
self.ax_props.set_xscale('linear')
|
|
542
541
|
self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
|
|
543
|
-
self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
542
|
+
self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
|
|
544
543
|
except Exception as e:
|
|
545
544
|
print(e)
|
|
546
|
-
elif i==0:
|
|
545
|
+
elif i == 0:
|
|
547
546
|
try:
|
|
548
547
|
feat_y = self.features_cb[0].currentText()
|
|
549
548
|
min_y = self.df.dropna(subset=feat_y)[feat_y].min()
|
|
550
549
|
max_y = self.df.dropna(subset=feat_y)[feat_y].max()
|
|
551
550
|
y_padding = (max_y - min_y) * 0.05
|
|
552
|
-
if y_padding==0:
|
|
551
|
+
if y_padding == 0:
|
|
553
552
|
y_padding = 0.05
|
|
554
553
|
|
|
555
|
-
if self.ax_props.get_yscale()=='linear':
|
|
554
|
+
if self.ax_props.get_yscale() == 'linear':
|
|
556
555
|
self.ax_props.set_ylim(min_y, max_y)
|
|
557
556
|
self.ax_props.set_yscale('log')
|
|
558
|
-
self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
557
|
+
self.log_btns[i].setIcon(icon(MDI6.math_log, color="#1565c0"))
|
|
559
558
|
else:
|
|
560
559
|
self.ax_props.set_yscale('linear')
|
|
561
560
|
self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
|
|
562
|
-
self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
561
|
+
self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
|
|
563
562
|
except Exception as e:
|
|
564
563
|
print(e)
|
|
565
564
|
|
|
566
565
|
self.ax_props.autoscale()
|
|
567
566
|
self.propscanvas.canvas.draw_idle()
|
|
568
567
|
|
|
569
|
-
print('Done.')
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
568
|
+
print('Done.')
|