celldetective 1.1.1__tar.gz → 1.1.1.post3__tar.gz
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-1.1.1 → celldetective-1.1.1.post3}/PKG-INFO +1 -1
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/extra_properties.py +1 -1
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/filters.py +39 -11
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/classifier_widget.py +56 -7
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/layouts.py +246 -3
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/retrain_segmentation_model_options.py +66 -164
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/retrain_signal_model_options.py +18 -164
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/tableUI.py +175 -61
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/thresholds_gui.py +44 -5
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/viewers.py +1 -1
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/io.py +29 -14
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/preprocessing.py +23 -11
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/scripts/segment_cells.py +13 -4
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/scripts/train_segmentation_model.py +11 -22
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/segmentation.py +16 -12
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/utils.py +6 -2
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective.egg-info/PKG-INFO +1 -1
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/setup.py +1 -1
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/LICENSE +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/README.md +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/__init__.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/__main__.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/datasets/segmentation_annotations/blank +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/datasets/signal_annotations/blank +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/events.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/__init__.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/about.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/analyze_block.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/btrack_options.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/configure_new_exp.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/control_panel.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/gui_utils.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/json_readers.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/measurement_options.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/neighborhood_options.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/plot_measurements.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/plot_signals_ui.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/process_block.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/seg_model_loader.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/signal_annotator.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/signal_annotator_options.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/styles.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/gui/survival_ui.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/logo-large.png +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/logo.png +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/signals_icon.png +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/splash-test.png +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/splash.png +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/splash0.png +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/survival2.png +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/vignette_signals2.png +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/icons/vignette_signals2.svg +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/links/zenodo.json +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/measure.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/segmentation_effectors/blank +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/segmentation_generic/blank +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/segmentation_targets/blank +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/signal_detection/blank +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/tracking_configs/mcf7.json +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/tracking_configs/ricm.json +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/models/tracking_configs/ricm2.json +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/neighborhood.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/scripts/analyze_signals.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/scripts/measure_cells.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/scripts/segment_cells_thresholds.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/scripts/track_cells.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/scripts/train_signal_model.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/signals.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective/tracking.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective.egg-info/SOURCES.txt +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective.egg-info/dependency_links.txt +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective.egg-info/entry_points.txt +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective.egg-info/not-zip-safe +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective.egg-info/requires.txt +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/celldetective.egg-info/top_level.txt +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/setup.cfg +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/__init__.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_events.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_filters.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_io.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_measure.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_neighborhood.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_preprocessing.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_segmentation.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_signals.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_tracking.py +0 -0
- {celldetective-1.1.1 → celldetective-1.1.1.post3}/tests/test_utils.py +0 -0
|
@@ -7,7 +7,7 @@ If intensity is in function name, it will be replaced by the name of the channel
|
|
|
7
7
|
import warnings
|
|
8
8
|
|
|
9
9
|
import numpy as np
|
|
10
|
-
from scipy.ndimage import distance_transform_edt
|
|
10
|
+
from scipy.ndimage import distance_transform_edt, center_of_mass
|
|
11
11
|
from scipy.spatial.distance import euclidean
|
|
12
12
|
|
|
13
13
|
|
|
@@ -1,21 +1,36 @@
|
|
|
1
1
|
from skimage.filters import difference_of_gaussians, threshold_otsu, threshold_local, threshold_niblack, threshold_sauvola
|
|
2
|
+
from celldetective.utils import interpolate_nan
|
|
2
3
|
import scipy.ndimage as snd
|
|
3
4
|
import numpy as np
|
|
4
5
|
|
|
5
|
-
def gauss_filter(img, sigma, *kwargs):
|
|
6
|
+
def gauss_filter(img, sigma, interpolate=True, *kwargs):
|
|
7
|
+
if interpolate:
|
|
8
|
+
img = interpolate_nan(img.astype(float))
|
|
6
9
|
return snd.gaussian_filter(img.astype(float), sigma, *kwargs)
|
|
7
10
|
|
|
8
|
-
def median_filter(img, size, *kwargs):
|
|
11
|
+
def median_filter(img, size, interpolate=True, *kwargs):
|
|
12
|
+
if interpolate:
|
|
13
|
+
img = interpolate_nan(img.astype(float))
|
|
14
|
+
|
|
9
15
|
size = int(size)
|
|
10
16
|
return snd.median_filter(img, size, *kwargs)
|
|
11
17
|
|
|
12
|
-
def maximum_filter(img, size, *kwargs):
|
|
18
|
+
def maximum_filter(img, size, interpolate=True, *kwargs):
|
|
19
|
+
if interpolate:
|
|
20
|
+
img = interpolate_nan(img.astype(float))
|
|
21
|
+
|
|
13
22
|
return snd.maximum_filter(img.astype(float), size, *kwargs)
|
|
14
23
|
|
|
15
|
-
def minimum_filter(img, size, *kwargs):
|
|
24
|
+
def minimum_filter(img, size, interpolate=True, *kwargs):
|
|
25
|
+
if interpolate:
|
|
26
|
+
img = interpolate_nan(img.astype(float))
|
|
27
|
+
|
|
16
28
|
return snd.minimum_filter(img.astype(float), size, *kwargs)
|
|
17
29
|
|
|
18
|
-
def percentile_filter(img, percentile, size, *kwargs):
|
|
30
|
+
def percentile_filter(img, percentile, size, interpolate=True, *kwargs):
|
|
31
|
+
if interpolate:
|
|
32
|
+
img = interpolate_nan(img.astype(float))
|
|
33
|
+
|
|
19
34
|
return snd.percentile_filter(img.astype(float), percentile, size, *kwargs)
|
|
20
35
|
|
|
21
36
|
def subtract_filter(img, value, *kwargs):
|
|
@@ -24,14 +39,19 @@ def subtract_filter(img, value, *kwargs):
|
|
|
24
39
|
def abs_filter(img, *kwargs):
|
|
25
40
|
return np.abs(img)
|
|
26
41
|
|
|
27
|
-
def ln_filter(img, *kwargs):
|
|
42
|
+
def ln_filter(img, interpolate=True, *kwargs):
|
|
43
|
+
if interpolate:
|
|
44
|
+
img = interpolate_nan(img.astype(float))
|
|
28
45
|
|
|
29
46
|
img[np.where(img>0.)] = np.log(img[np.where(img>0.)])
|
|
30
47
|
img[np.where(img<=0.)] = 0.
|
|
31
48
|
|
|
32
49
|
return img
|
|
33
50
|
|
|
34
|
-
def variance_filter(img, size):
|
|
51
|
+
def variance_filter(img, size, interpolate=True):
|
|
52
|
+
|
|
53
|
+
if interpolate:
|
|
54
|
+
img = interpolate_nan(img.astype(float))
|
|
35
55
|
|
|
36
56
|
size = int(size)
|
|
37
57
|
img = img.astype(float)
|
|
@@ -41,8 +61,10 @@ def variance_filter(img, size):
|
|
|
41
61
|
|
|
42
62
|
return img
|
|
43
63
|
|
|
44
|
-
def std_filter(img, size):
|
|
64
|
+
def std_filter(img, size, interpolate=True):
|
|
45
65
|
|
|
66
|
+
if interpolate:
|
|
67
|
+
img = interpolate_nan(img.astype(float))
|
|
46
68
|
size = int(size)
|
|
47
69
|
img = img.astype(float)
|
|
48
70
|
win_mean = snd.uniform_filter(img, (size,size), mode='wrap')
|
|
@@ -53,10 +75,14 @@ def std_filter(img, size):
|
|
|
53
75
|
|
|
54
76
|
return img
|
|
55
77
|
|
|
56
|
-
def laplace_filter(img, output=float, *kwargs):
|
|
78
|
+
def laplace_filter(img, output=float, interpolate=True, *kwargs):
|
|
79
|
+
if interpolate:
|
|
80
|
+
img = interpolate_nan(img.astype(float))
|
|
57
81
|
return snd.laplace(img.astype(float), *kwargs)
|
|
58
82
|
|
|
59
|
-
def dog_filter(img, sigma_low, sigma_high, *kwargs):
|
|
83
|
+
def dog_filter(img, sigma_low, sigma_high, interpolate=True, *kwargs):
|
|
84
|
+
if interpolate:
|
|
85
|
+
img = interpolate_nan(img.astype(float))
|
|
60
86
|
return difference_of_gaussians(img.astype(float), sigma_low, sigma_high, *kwargs)
|
|
61
87
|
|
|
62
88
|
def otsu_filter(img, *kwargs):
|
|
@@ -82,7 +108,9 @@ def sauvola_filter(img, *kwargs):
|
|
|
82
108
|
def log_filter(img, sigma, *kwargs):
|
|
83
109
|
return snd.gaussian_laplace(img.astype(float), sigma, *kwargs)
|
|
84
110
|
|
|
85
|
-
def tophat_filter(img, size, connectivity=4, *kwargs):
|
|
111
|
+
def tophat_filter(img, size, connectivity=4, interpolate=True, *kwargs):
|
|
112
|
+
if interpolate:
|
|
113
|
+
img = interpolate_nan(img.astype(float))
|
|
86
114
|
structure = snd.generate_binary_structure(rank=2, connectivity=connectivity)
|
|
87
115
|
img = snd.white_tophat(img.astype(float), structure=structure, size=size, *kwargs)
|
|
88
116
|
return img
|
|
@@ -136,9 +136,10 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
136
136
|
|
|
137
137
|
self.irreversible_event_btn = QRadioButton('irreversible event')
|
|
138
138
|
self.unique_state_btn = QRadioButton('unique state')
|
|
139
|
-
self.time_corr_options = [self.irreversible_event_btn, self.unique_state_btn]
|
|
140
139
|
time_corr_btn_group = QButtonGroup()
|
|
141
140
|
self.unique_state_btn.click()
|
|
141
|
+
self.time_corr_options = [self.irreversible_event_btn, self.unique_state_btn]
|
|
142
|
+
|
|
142
143
|
for btn in self.time_corr_options:
|
|
143
144
|
time_corr_btn_group.addButton(btn)
|
|
144
145
|
btn.setEnabled(False)
|
|
@@ -148,8 +149,28 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
148
149
|
time_corr_layout.addWidget(self.irreversible_event_btn, 50,alignment=Qt.AlignCenter)
|
|
149
150
|
layout.addLayout(time_corr_layout)
|
|
150
151
|
|
|
152
|
+
self.r2_slider = QLabeledDoubleSlider()
|
|
153
|
+
self.r2_slider.setValue(0.75)
|
|
154
|
+
self.r2_slider.setRange(0,1)
|
|
155
|
+
self.r2_slider.setSingleStep(0.01)
|
|
156
|
+
self.r2_slider.setOrientation(1)
|
|
157
|
+
self.r2_label = QLabel('R2 tolerance:')
|
|
158
|
+
self.r2_label.setToolTip('Minimum R2 between the fit sigmoid and the binary response to the filters to accept the event.')
|
|
159
|
+
r2_threshold_layout = QHBoxLayout()
|
|
160
|
+
r2_threshold_layout.addWidget(QLabel(''), 50)
|
|
161
|
+
r2_threshold_layout.addWidget(self.r2_label, 15)
|
|
162
|
+
r2_threshold_layout.addWidget(self.r2_slider, 35)
|
|
163
|
+
layout.addLayout(r2_threshold_layout)
|
|
164
|
+
|
|
165
|
+
self.irreversible_event_btn.clicked.connect(self.activate_r2)
|
|
166
|
+
self.unique_state_btn.clicked.connect(self.activate_r2)
|
|
167
|
+
|
|
168
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
169
|
+
wg.setEnabled(False)
|
|
170
|
+
|
|
151
171
|
layout.addWidget(QLabel())
|
|
152
172
|
|
|
173
|
+
|
|
153
174
|
self.submit_btn = QPushButton('apply')
|
|
154
175
|
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
155
176
|
self.submit_btn.clicked.connect(self.submit_classification)
|
|
@@ -158,14 +179,30 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
158
179
|
self.frame_slider.valueChanged.connect(self.set_frame)
|
|
159
180
|
self.alpha_slider.valueChanged.connect(self.set_transparency)
|
|
160
181
|
|
|
182
|
+
def activate_r2(self):
|
|
183
|
+
if self.irreversible_event_btn.isChecked() and self.time_corr.isChecked():
|
|
184
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
185
|
+
wg.setEnabled(True)
|
|
186
|
+
else:
|
|
187
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
188
|
+
wg.setEnabled(False)
|
|
189
|
+
|
|
161
190
|
def activate_time_corr_options(self):
|
|
162
191
|
|
|
163
192
|
if self.time_corr.isChecked():
|
|
164
193
|
for btn in self.time_corr_options:
|
|
165
194
|
btn.setEnabled(True)
|
|
195
|
+
if self.irreversible_event_btn.isChecked():
|
|
196
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
197
|
+
wg.setEnabled(True)
|
|
198
|
+
else:
|
|
199
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
200
|
+
wg.setEnabled(False)
|
|
166
201
|
else:
|
|
167
202
|
for btn in self.time_corr_options:
|
|
168
203
|
btn.setEnabled(False)
|
|
204
|
+
for wg in [self.r2_slider, self.r2_label]:
|
|
205
|
+
wg.setEnabled(False)
|
|
169
206
|
|
|
170
207
|
def init_class(self):
|
|
171
208
|
|
|
@@ -207,8 +244,20 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
207
244
|
self.scat_props.set_alpha(self.currentAlpha)
|
|
208
245
|
self.ax_props.set_xlabel(self.features_cb[1].currentText())
|
|
209
246
|
self.ax_props.set_ylabel(self.features_cb[0].currentText())
|
|
210
|
-
|
|
211
|
-
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
feat_x = self.features_cb[1].currentText()
|
|
250
|
+
feat_y = self.features_cb[0].currentText()
|
|
251
|
+
min_x = self.df.dropna(subset=feat_x)[feat_x].min()
|
|
252
|
+
max_x = self.df.dropna(subset=feat_x)[feat_x].max()
|
|
253
|
+
min_y = self.df.dropna(subset=feat_y)[feat_y].min()
|
|
254
|
+
max_y = self.df.dropna(subset=feat_y)[feat_y].max()
|
|
255
|
+
|
|
256
|
+
if min_x==min_x and max_x==max_x:
|
|
257
|
+
self.ax_props.set_xlim(min_x, max_x)
|
|
258
|
+
if min_y==min_y and max_y==max_y:
|
|
259
|
+
self.ax_props.set_ylim(min_y, max_y)
|
|
260
|
+
|
|
212
261
|
if feature_changed:
|
|
213
262
|
self.propscanvas.canvas.toolbar.update()
|
|
214
263
|
self.propscanvas.canvas.draw_idle()
|
|
@@ -400,15 +449,16 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
400
449
|
timeline = group['FRAME'].values
|
|
401
450
|
|
|
402
451
|
try:
|
|
403
|
-
popt, pcov = curve_fit(step_function, timeline, status_signal,p0=[self.df['FRAME'].max()//2, 0.
|
|
404
|
-
|
|
452
|
+
popt, pcov = curve_fit(step_function, timeline.astype(int), status_signal, p0=[self.df['FRAME'].max()//2, 0.8],maxfev=30000)
|
|
453
|
+
values = [step_function(t, *popt) for t in timeline]
|
|
454
|
+
r2 = r2_score(status_signal,values)
|
|
405
455
|
except Exception as e:
|
|
406
456
|
print(e)
|
|
407
457
|
self.df.loc[indices, self.class_name_user] = 2.0
|
|
408
458
|
self.df.loc[indices, self.class_name_user.replace('class','t')] = -1
|
|
409
459
|
continue
|
|
410
460
|
|
|
411
|
-
if r2 >
|
|
461
|
+
if r2 > float(self.r2_slider.value()):
|
|
412
462
|
t0 = popt[0]
|
|
413
463
|
self.df.loc[indices, self.class_name_user.replace('class','t')] = t0
|
|
414
464
|
self.df.loc[indices, self.class_name_user] = 0.0
|
|
@@ -426,4 +476,3 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
426
476
|
|
|
427
477
|
|
|
428
478
|
|
|
429
|
-
|
|
@@ -3,7 +3,7 @@ from celldetective.gui.gui_utils import ThresholdLineEdit
|
|
|
3
3
|
from PyQt5.QtCore import Qt, QSize
|
|
4
4
|
from PyQt5.QtGui import QIntValidator
|
|
5
5
|
|
|
6
|
-
from superqt import QLabeledRangeSlider, QLabeledSlider, QLabeledDoubleRangeSlider
|
|
6
|
+
from superqt import QLabeledRangeSlider, QLabeledSlider, QLabeledDoubleRangeSlider, QSearchableComboBox
|
|
7
7
|
|
|
8
8
|
from superqt.fonticon import icon
|
|
9
9
|
from fonticon_mdi6 import MDI6
|
|
@@ -12,6 +12,249 @@ from celldetective.gui.viewers import ThresholdedStackVisualizer, CellEdgeVisual
|
|
|
12
12
|
from celldetective.gui import Styles
|
|
13
13
|
from celldetective.gui.gui_utils import QuickSliderLayout
|
|
14
14
|
from celldetective.preprocessing import correct_background_model, correct_background_model_free, estimate_background_per_condition
|
|
15
|
+
from functools import partial
|
|
16
|
+
from glob import glob
|
|
17
|
+
import os
|
|
18
|
+
import pandas as pd
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
class ChannelNormGenerator(QVBoxLayout, Styles):
|
|
22
|
+
|
|
23
|
+
"""Generator for list of channels"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, parent_window=None, init_n_channels=4, mode='signals', *args):
|
|
26
|
+
super().__init__(*args)
|
|
27
|
+
|
|
28
|
+
self.parent_window = parent_window
|
|
29
|
+
self.mode = mode
|
|
30
|
+
self.init_n_channels = init_n_channels
|
|
31
|
+
|
|
32
|
+
if hasattr(self.parent_window.parent_window, 'locate_image'):
|
|
33
|
+
self.attr_parent = self.parent_window.parent_window
|
|
34
|
+
elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
|
|
35
|
+
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
36
|
+
else:
|
|
37
|
+
self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
|
|
38
|
+
|
|
39
|
+
self.channel_names = self.attr_parent.exp_channels
|
|
40
|
+
self.setContentsMargins(15,15,15,15)
|
|
41
|
+
self.generate_widgets()
|
|
42
|
+
self.add_to_layout()
|
|
43
|
+
|
|
44
|
+
def generate_widgets(self):
|
|
45
|
+
|
|
46
|
+
self.channel_cbs = [QSearchableComboBox() for i in range(self.init_n_channels)]
|
|
47
|
+
self.normalization_mode_btns = [QPushButton('') for i in range(self.init_n_channels)]
|
|
48
|
+
self.normalization_mode = [True for i in range(self.init_n_channels)]
|
|
49
|
+
self.normalization_clip_btns = [QPushButton('') for i in range(self.init_n_channels)]
|
|
50
|
+
self.clip_option = [False for i in range(self.init_n_channels)]
|
|
51
|
+
|
|
52
|
+
for i in range(self.init_n_channels):
|
|
53
|
+
self.normalization_mode_btns[i].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
|
|
54
|
+
self.normalization_mode_btns[i].setIconSize(QSize(20, 20))
|
|
55
|
+
self.normalization_mode_btns[i].setStyleSheet(self.button_select_all)
|
|
56
|
+
self.normalization_mode_btns[i].setToolTip("Switch to absolute normalization values.")
|
|
57
|
+
self.normalization_mode_btns[i].clicked.connect(partial(self.switch_normalization_mode, i))
|
|
58
|
+
|
|
59
|
+
self.normalization_clip_btns[i].setIcon(icon(MDI6.content_cut,color="black"))
|
|
60
|
+
self.normalization_clip_btns[i].setIconSize(QSize(20, 20))
|
|
61
|
+
self.normalization_clip_btns[i].setStyleSheet(self.button_select_all)
|
|
62
|
+
self.normalization_clip_btns[i].clicked.connect(partial(self.switch_clipping_mode, i))
|
|
63
|
+
self.normalization_clip_btns[i].setToolTip('clip')
|
|
64
|
+
|
|
65
|
+
self.normalization_min_value_lbl = [QLabel('Min %: ') for i in range(self.init_n_channels)]
|
|
66
|
+
self.normalization_min_value_le = [QLineEdit('0.1') for i in range(self.init_n_channels)]
|
|
67
|
+
self.normalization_max_value_lbl = [QLabel('Max %: ') for i in range(self.init_n_channels)]
|
|
68
|
+
self.normalization_max_value_le = [QLineEdit('99.99') for i in range(self.init_n_channels)]
|
|
69
|
+
|
|
70
|
+
if self.mode=='signals':
|
|
71
|
+
tables = glob(self.parent_window.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{self.parent_window.mode}.csv']))
|
|
72
|
+
all_measurements = []
|
|
73
|
+
for tab in tables:
|
|
74
|
+
cols = pd.read_csv(tab, nrows=1).columns.tolist()
|
|
75
|
+
all_measurements.extend(cols)
|
|
76
|
+
all_measurements = np.unique(all_measurements)
|
|
77
|
+
|
|
78
|
+
if self.mode=='signals':
|
|
79
|
+
generic_measurements = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel',
|
|
80
|
+
'effector_fluo_channel', 'adhesion_channel', 'fluo_channel_1', 'fluo_channel_2',
|
|
81
|
+
"area", "area_bbox","area_convex","area_filled","major_axis_length",
|
|
82
|
+
"minor_axis_length",
|
|
83
|
+
"eccentricity",
|
|
84
|
+
"equivalent_diameter_area",
|
|
85
|
+
"euler_number",
|
|
86
|
+
"extent",
|
|
87
|
+
"feret_diameter_max",
|
|
88
|
+
"orientation",
|
|
89
|
+
"perimeter",
|
|
90
|
+
"perimeter_crofton",
|
|
91
|
+
"solidity",
|
|
92
|
+
"angular_second_moment",
|
|
93
|
+
"contrast",
|
|
94
|
+
"correlation",
|
|
95
|
+
"sum_of_square_variance",
|
|
96
|
+
"inverse_difference_moment",
|
|
97
|
+
"sum_average",
|
|
98
|
+
"sum_variance",
|
|
99
|
+
"sum_entropy",
|
|
100
|
+
"entropy",
|
|
101
|
+
"difference_variance",
|
|
102
|
+
"difference_entropy",
|
|
103
|
+
"information_measure_of_correlation_1",
|
|
104
|
+
"information_measure_of_correlation_2",
|
|
105
|
+
"maximal_correlation_coefficient",
|
|
106
|
+
"POSITION_X",
|
|
107
|
+
"POSITION_Y",
|
|
108
|
+
]
|
|
109
|
+
elif self.mode=='channels':
|
|
110
|
+
generic_measurements = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel',
|
|
111
|
+
'effector_fluo_channel', 'adhesion_channel', 'fluo_channel_1', 'fluo_channel_2', 'None']
|
|
112
|
+
|
|
113
|
+
if self.mode=='channels':
|
|
114
|
+
all_measurements = []
|
|
115
|
+
exp_ch = self.attr_parent.exp_channels
|
|
116
|
+
for c in exp_ch:
|
|
117
|
+
all_measurements.append(c)
|
|
118
|
+
|
|
119
|
+
self.channel_items = np.unique(generic_measurements + list(all_measurements))
|
|
120
|
+
self.channel_items = np.insert(self.channel_items, 0, '--')
|
|
121
|
+
|
|
122
|
+
self.add_col_btn = QPushButton('Add channel')
|
|
123
|
+
self.add_col_btn.clicked.connect(self.add_channel)
|
|
124
|
+
self.add_col_btn.setStyleSheet(self.button_add)
|
|
125
|
+
self.add_col_btn.setIcon(icon(MDI6.plus,color="black"))
|
|
126
|
+
|
|
127
|
+
def add_channel(self):
|
|
128
|
+
|
|
129
|
+
self.channel_cbs.append(QSearchableComboBox())
|
|
130
|
+
self.channel_cbs[-1].addItems(self.channel_items)
|
|
131
|
+
self.channel_cbs[-1].currentIndexChanged.connect(self.check_valid_channels)
|
|
132
|
+
|
|
133
|
+
self.normalization_mode_btns.append(QPushButton(''))
|
|
134
|
+
self.normalization_mode.append(True)
|
|
135
|
+
self.normalization_clip_btns.append(QPushButton(''))
|
|
136
|
+
self.clip_option.append(False)
|
|
137
|
+
|
|
138
|
+
self.normalization_mode_btns[-1].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
|
|
139
|
+
self.normalization_mode_btns[-1].setIconSize(QSize(20, 20))
|
|
140
|
+
self.normalization_mode_btns[-1].setStyleSheet(self.button_select_all)
|
|
141
|
+
self.normalization_mode_btns[-1].setToolTip("Switch to absolute normalization values.")
|
|
142
|
+
self.normalization_mode_btns[-1].clicked.connect(partial(self.switch_normalization_mode, len(self.channel_cbs)-1))
|
|
143
|
+
|
|
144
|
+
self.normalization_clip_btns[-1].setIcon(icon(MDI6.content_cut,color="black"))
|
|
145
|
+
self.normalization_clip_btns[-1].setIconSize(QSize(20, 20))
|
|
146
|
+
self.normalization_clip_btns[-1].setStyleSheet(self.button_select_all)
|
|
147
|
+
self.normalization_clip_btns[-1].clicked.connect(partial(self.switch_clipping_mode, len(self.channel_cbs)-1))
|
|
148
|
+
self.normalization_clip_btns[-1].setToolTip('clip')
|
|
149
|
+
|
|
150
|
+
self.normalization_min_value_lbl.append(QLabel('Min %: '))
|
|
151
|
+
self.normalization_min_value_le.append(QLineEdit('0.1'))
|
|
152
|
+
self.normalization_max_value_lbl.append(QLabel('Max %: '))
|
|
153
|
+
self.normalization_max_value_le.append(QLineEdit('99.99'))
|
|
154
|
+
|
|
155
|
+
ch_layout = QHBoxLayout()
|
|
156
|
+
ch_layout.addWidget(QLabel(f'channel {len(self.channel_cbs)-1}: '), 30)
|
|
157
|
+
ch_layout.addWidget(self.channel_cbs[-1], 70)
|
|
158
|
+
self.channels_vb.addLayout(ch_layout)
|
|
159
|
+
|
|
160
|
+
channel_norm_options_layout = QHBoxLayout()
|
|
161
|
+
channel_norm_options_layout.setContentsMargins(130,0,0,0)
|
|
162
|
+
channel_norm_options_layout.addWidget(self.normalization_min_value_lbl[-1])
|
|
163
|
+
channel_norm_options_layout.addWidget(self.normalization_min_value_le[-1])
|
|
164
|
+
channel_norm_options_layout.addWidget(self.normalization_max_value_lbl[-1])
|
|
165
|
+
channel_norm_options_layout.addWidget(self.normalization_max_value_le[-1])
|
|
166
|
+
channel_norm_options_layout.addWidget(self.normalization_clip_btns[-1])
|
|
167
|
+
channel_norm_options_layout.addWidget(self.normalization_mode_btns[-1])
|
|
168
|
+
self.channels_vb.addLayout(channel_norm_options_layout)
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def add_to_layout(self):
|
|
172
|
+
|
|
173
|
+
self.channels_vb = QVBoxLayout()
|
|
174
|
+
self.channel_option_layouts = []
|
|
175
|
+
for i in range(len(self.channel_cbs)):
|
|
176
|
+
|
|
177
|
+
ch_layout = QHBoxLayout()
|
|
178
|
+
ch_layout.addWidget(QLabel(f'channel {i}: '), 30)
|
|
179
|
+
self.channel_cbs[i].addItems(self.channel_items)
|
|
180
|
+
self.channel_cbs[i].currentIndexChanged.connect(self.check_valid_channels)
|
|
181
|
+
ch_layout.addWidget(self.channel_cbs[i], 70)
|
|
182
|
+
self.channels_vb.addLayout(ch_layout)
|
|
183
|
+
|
|
184
|
+
channel_norm_options_layout = QHBoxLayout()
|
|
185
|
+
channel_norm_options_layout.setContentsMargins(130,0,0,0)
|
|
186
|
+
channel_norm_options_layout.addWidget(self.normalization_min_value_lbl[i])
|
|
187
|
+
channel_norm_options_layout.addWidget(self.normalization_min_value_le[i])
|
|
188
|
+
channel_norm_options_layout.addWidget(self.normalization_max_value_lbl[i])
|
|
189
|
+
channel_norm_options_layout.addWidget(self.normalization_max_value_le[i])
|
|
190
|
+
channel_norm_options_layout.addWidget(self.normalization_clip_btns[i])
|
|
191
|
+
channel_norm_options_layout.addWidget(self.normalization_mode_btns[i])
|
|
192
|
+
self.channels_vb.addLayout(channel_norm_options_layout)
|
|
193
|
+
|
|
194
|
+
self.addLayout(self.channels_vb)
|
|
195
|
+
|
|
196
|
+
add_hbox = QHBoxLayout()
|
|
197
|
+
add_hbox.addWidget(QLabel(''), 66)
|
|
198
|
+
add_hbox.addWidget(self.add_col_btn, 33, alignment=Qt.AlignRight)
|
|
199
|
+
self.addLayout(add_hbox)
|
|
200
|
+
|
|
201
|
+
def switch_normalization_mode(self, index):
|
|
202
|
+
|
|
203
|
+
"""
|
|
204
|
+
Use absolute or percentile values for the normalization of each individual channel.
|
|
205
|
+
|
|
206
|
+
"""
|
|
207
|
+
|
|
208
|
+
currentNormMode = self.normalization_mode[index]
|
|
209
|
+
self.normalization_mode[index] = not currentNormMode
|
|
210
|
+
|
|
211
|
+
if self.normalization_mode[index]:
|
|
212
|
+
self.normalization_mode_btns[index].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
|
|
213
|
+
self.normalization_mode_btns[index].setIconSize(QSize(20, 20))
|
|
214
|
+
self.normalization_mode_btns[index].setStyleSheet(self.button_select_all)
|
|
215
|
+
self.normalization_mode_btns[index].setToolTip("Switch to absolute normalization values.")
|
|
216
|
+
self.normalization_min_value_lbl[index].setText('Min %: ')
|
|
217
|
+
self.normalization_max_value_lbl[index].setText('Max %: ')
|
|
218
|
+
self.normalization_min_value_le[index].setText('0.1')
|
|
219
|
+
self.normalization_max_value_le[index].setText('99.99')
|
|
220
|
+
|
|
221
|
+
else:
|
|
222
|
+
self.normalization_mode_btns[index].setIcon(icon(MDI6.percent_circle_outline,color="black"))
|
|
223
|
+
self.normalization_mode_btns[index].setIconSize(QSize(20, 20))
|
|
224
|
+
self.normalization_mode_btns[index].setStyleSheet(self.button_select_all)
|
|
225
|
+
self.normalization_mode_btns[index].setToolTip("Switch to percentile normalization values.")
|
|
226
|
+
self.normalization_min_value_lbl[index].setText('Min: ')
|
|
227
|
+
self.normalization_min_value_le[index].setText('0')
|
|
228
|
+
self.normalization_max_value_lbl[index].setText('Max: ')
|
|
229
|
+
self.normalization_max_value_le[index].setText('1000')
|
|
230
|
+
|
|
231
|
+
def switch_clipping_mode(self, index):
|
|
232
|
+
|
|
233
|
+
currentClipMode = self.clip_option[index]
|
|
234
|
+
self.clip_option[index] = not currentClipMode
|
|
235
|
+
|
|
236
|
+
if self.clip_option[index]:
|
|
237
|
+
self.normalization_clip_btns[index].setIcon(icon(MDI6.content_cut,color="#1565c0"))
|
|
238
|
+
self.normalization_clip_btns[index].setIconSize(QSize(20, 20))
|
|
239
|
+
self.normalization_clip_btns[index].setStyleSheet(self.button_select_all)
|
|
240
|
+
|
|
241
|
+
else:
|
|
242
|
+
self.normalization_clip_btns[index].setIcon(icon(MDI6.content_cut,color="black"))
|
|
243
|
+
self.normalization_clip_btns[index].setIconSize(QSize(20, 20))
|
|
244
|
+
self.normalization_clip_btns[index].setStyleSheet(self.button_select_all)
|
|
245
|
+
|
|
246
|
+
def check_valid_channels(self):
|
|
247
|
+
|
|
248
|
+
if np.all([cb.currentText()=='--' for cb in self.channel_cbs]):
|
|
249
|
+
self.parent_window.submit_btn.setEnabled(False)
|
|
250
|
+
|
|
251
|
+
if hasattr(self.parent_window, "spatial_calib_le"):
|
|
252
|
+
if self.parent_window.spatial_calib_le.text()!='--':
|
|
253
|
+
self.parent_window.submit_btn.setEnabled(True)
|
|
254
|
+
else:
|
|
255
|
+
self.parent_window.submit_btn.setEnabled(True)
|
|
256
|
+
|
|
257
|
+
|
|
15
258
|
|
|
16
259
|
class BackgroundFitCorrectionLayout(QGridLayout, Styles):
|
|
17
260
|
|
|
@@ -702,12 +945,12 @@ class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
|
|
|
702
945
|
show_progress_per_pos = False,
|
|
703
946
|
)
|
|
704
947
|
|
|
705
|
-
|
|
706
948
|
self.viewer = StackVisualizer(
|
|
707
949
|
stack=corrected_stacks[0],
|
|
708
950
|
window_title='Corrected channel',
|
|
709
951
|
frame_slider = True,
|
|
710
|
-
contrast_slider = True
|
|
952
|
+
contrast_slider = True,
|
|
953
|
+
target_channel=self.channels_cb.currentIndex(),
|
|
711
954
|
)
|
|
712
955
|
self.viewer.show()
|
|
713
956
|
|