celldetective 1.3.4.post1__py3-none-any.whl → 1.3.6.post1__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/_version.py +1 -1
- celldetective/events.py +10 -5
- celldetective/filters.py +11 -0
- celldetective/gui/btrack_options.py +151 -1
- celldetective/gui/classifier_widget.py +44 -15
- celldetective/gui/configure_new_exp.py +13 -0
- celldetective/gui/control_panel.py +4 -2
- celldetective/gui/generic_signal_plot.py +2 -6
- celldetective/gui/gui_utils.py +170 -12
- celldetective/gui/measurement_options.py +85 -54
- celldetective/gui/neighborhood_options.py +1 -1
- celldetective/gui/plot_signals_ui.py +3 -4
- celldetective/gui/process_block.py +8 -6
- celldetective/gui/signal_annotator.py +10 -3
- celldetective/gui/signal_annotator2.py +146 -193
- celldetective/gui/survival_ui.py +121 -34
- celldetective/gui/tableUI.py +26 -12
- celldetective/gui/thresholds_gui.py +9 -52
- celldetective/gui/viewers.py +58 -21
- celldetective/io.py +1087 -161
- celldetective/measure.py +175 -102
- celldetective/preprocessing.py +2 -2
- celldetective/relative_measurements.py +6 -9
- celldetective/scripts/measure_cells.py +13 -3
- celldetective/scripts/segment_cells.py +0 -1
- celldetective/scripts/track_cells.py +25 -1
- celldetective/signals.py +9 -7
- celldetective/tracking.py +130 -81
- celldetective/utils.py +28 -7
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/METADATA +3 -2
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/RECORD +35 -35
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/WHEEL +0 -0
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/top_level.txt +0 -0
celldetective/gui/survival_ui.py
CHANGED
|
@@ -4,17 +4,19 @@ from PyQt5.QtGui import QDoubleValidator
|
|
|
4
4
|
from celldetective.gui.gui_utils import center_window
|
|
5
5
|
from superqt import QColormapComboBox
|
|
6
6
|
from celldetective.gui.generic_signal_plot import SurvivalPlotWidget
|
|
7
|
-
from celldetective.utils import get_software_location, _extract_labels_from_config
|
|
7
|
+
from celldetective.utils import get_software_location, _extract_labels_from_config, extract_cols_from_table_list
|
|
8
8
|
from celldetective.io import load_experiment_tables
|
|
9
9
|
import numpy as np
|
|
10
10
|
import os
|
|
11
11
|
import matplotlib.pyplot as plt
|
|
12
12
|
plt.rcParams['svg.fonttype'] = 'none'
|
|
13
13
|
from glob import glob
|
|
14
|
-
import pandas as pd
|
|
15
14
|
from celldetective.gui import Styles
|
|
16
15
|
from matplotlib import colormaps
|
|
17
16
|
from celldetective.events import compute_survival
|
|
17
|
+
from celldetective.relative_measurements import expand_pair_table
|
|
18
|
+
import matplotlib.cm
|
|
19
|
+
from celldetective.neighborhood import extract_neighborhood_in_pair_table
|
|
18
20
|
|
|
19
21
|
class ConfigSurvival(QWidget, Styles):
|
|
20
22
|
|
|
@@ -88,8 +90,51 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
88
90
|
main_layout.addWidget(panel_title, alignment=Qt.AlignCenter)
|
|
89
91
|
|
|
90
92
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
pops = []
|
|
94
|
+
for population in ['effectors','targets','pairs']:
|
|
95
|
+
tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{population}.csv']))
|
|
96
|
+
if len(tables)>0:
|
|
97
|
+
pops.append(population)
|
|
98
|
+
|
|
99
|
+
tables_targets = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_targets.csv']))
|
|
100
|
+
self.cols_targets = extract_cols_from_table_list(tables_targets)
|
|
101
|
+
tables_effectors = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_effectors.csv']))
|
|
102
|
+
self.cols_effectors = extract_cols_from_table_list(tables_effectors)
|
|
103
|
+
|
|
104
|
+
# Smart reading of existing neighborhoods (without loading tables in memory)
|
|
105
|
+
if 'pairs' in pops and not 'targets' in pops:
|
|
106
|
+
# must be effector-effector
|
|
107
|
+
effector_neighs = [c[16:] for c in self.cols_effectors if c.startswith('inclusive_count_neighborhood')]
|
|
108
|
+
if len(effector_neighs)>0:
|
|
109
|
+
pops.pop(pops.index('pairs'))
|
|
110
|
+
pops.append('effectors-effectors')
|
|
111
|
+
elif 'pairs' in pops and not 'effectors' in pops:
|
|
112
|
+
# must be target-target
|
|
113
|
+
target_neighs = [c for c in self.cols_targets if c.startswith('inclusive_count_neighborhood')]
|
|
114
|
+
if len(target_neighs)>0:
|
|
115
|
+
pops.pop(pops.index('pairs'))
|
|
116
|
+
pops.append('targets-targets')
|
|
117
|
+
elif 'pairs' in pops:
|
|
118
|
+
# either effector-target or target-effector
|
|
119
|
+
target_neighs_cross = [c for c in self.cols_targets if c.startswith('inclusive_count_neighborhood') and '_2_' in c]
|
|
120
|
+
if len(target_neighs_cross)>0:
|
|
121
|
+
pops.append('targets-effectors')
|
|
122
|
+
effector_neighs_cross = [c for c in self.cols_effectors if c.startswith('inclusive_count_neighborhood') and '_2_' in c]
|
|
123
|
+
if len(effector_neighs_cross)>0:
|
|
124
|
+
pops.append('effectors-targets')
|
|
125
|
+
target_neighs = [c for c in self.cols_targets if c.startswith('inclusive_count_neighborhood') and 'self' in c]
|
|
126
|
+
if len(target_neighs)>0:
|
|
127
|
+
pops.append('targets-targets')
|
|
128
|
+
effector_neighs = [c for c in self.cols_effectors if c.startswith('inclusive_count_neighborhood') and 'self' in c]
|
|
129
|
+
if len(effector_neighs)>0:
|
|
130
|
+
pops.append('effectors-effectors')
|
|
131
|
+
pops.pop(pops.index('pairs'))
|
|
132
|
+
else:
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
labels = [QLabel('population: '), QLabel('time of\nreference: '), QLabel('time of\ninterest: '), QLabel('cmap: ')] #QLabel('class: '),
|
|
137
|
+
self.cb_options = [pops, ['0'], [], []] #['class'],
|
|
93
138
|
self.cbs = [QComboBox() for i in range(len(labels))]
|
|
94
139
|
|
|
95
140
|
self.cbs[-1] = QColormapComboBox()
|
|
@@ -107,10 +152,12 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
107
152
|
|
|
108
153
|
all_cms = list(colormaps)
|
|
109
154
|
for cm in all_cms:
|
|
110
|
-
|
|
111
|
-
self.cbs[-1].addColormap(cm)
|
|
112
|
-
|
|
113
|
-
|
|
155
|
+
if hasattr(matplotlib.cm, str(cm).lower()):
|
|
156
|
+
self.cbs[-1].addColormap(cm.lower())
|
|
157
|
+
#try:
|
|
158
|
+
# self.cbs[-1].addColormap(cm)
|
|
159
|
+
# except:
|
|
160
|
+
# pass
|
|
114
161
|
|
|
115
162
|
main_layout.addLayout(choice_layout)
|
|
116
163
|
|
|
@@ -155,24 +202,60 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
155
202
|
def set_classes_and_times(self):
|
|
156
203
|
|
|
157
204
|
# Look for all classes and times
|
|
158
|
-
|
|
159
|
-
self.
|
|
160
|
-
|
|
161
|
-
cols = pd.read_csv(tab, nrows=1,encoding_errors='ignore').columns.tolist()
|
|
162
|
-
self.all_columns.extend(cols)
|
|
205
|
+
self.neighborhood_keys = None
|
|
206
|
+
self.population = self.cbs[0].currentText()
|
|
207
|
+
pop_split = self.population.split('-')
|
|
163
208
|
|
|
164
|
-
|
|
165
|
-
#class_idx = np.array([s.startswith('class_') for s in self.all_columns])
|
|
166
|
-
time_idx = np.array([s.startswith('t_') for s in self.all_columns])
|
|
209
|
+
if len(pop_split)==2:
|
|
167
210
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
self.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
211
|
+
self.population = 'pairs'
|
|
212
|
+
tables_pairs = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_pairs.csv']))
|
|
213
|
+
self.cols_pairs = extract_cols_from_table_list(tables_pairs)
|
|
214
|
+
|
|
215
|
+
self.population_reference = pop_split[0]
|
|
216
|
+
self.population_neigh = pop_split[1]
|
|
217
|
+
if self.population_reference=='targets':
|
|
218
|
+
cols_ref = self.cols_targets
|
|
219
|
+
else:
|
|
220
|
+
cols_ref = self.cols_effectors
|
|
221
|
+
if self.population_neigh=='targets':
|
|
222
|
+
cols_neigh = self.cols_targets
|
|
223
|
+
else:
|
|
224
|
+
cols_neigh = self.cols_effectors
|
|
225
|
+
|
|
226
|
+
time_cols_ref = np.array([s.startswith('t_') or s=='t0' for s in cols_ref])
|
|
227
|
+
if len(time_cols_ref)>0:
|
|
228
|
+
time_cols_ref = list(cols_ref[time_cols_ref])
|
|
229
|
+
time_cols_ref = ['reference_'+t for t in time_cols_ref]
|
|
230
|
+
|
|
231
|
+
time_cols_neigh = np.array([s.startswith('t_') or s=='t0' for s in cols_neigh])
|
|
232
|
+
if len(time_cols_neigh)>0:
|
|
233
|
+
time_cols_neigh = list(cols_neigh[time_cols_neigh])
|
|
234
|
+
time_cols_neigh = ['neighbor_'+t for t in time_cols_neigh]
|
|
235
|
+
|
|
236
|
+
if self.population_reference!=self.population_neigh:
|
|
237
|
+
self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and '_2_' in c]
|
|
238
|
+
else:
|
|
239
|
+
self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and 'self' in c]
|
|
240
|
+
|
|
241
|
+
time_idx = np.array([s.startswith('t_') or s.startswith('t0') for s in self.cols_pairs])
|
|
242
|
+
time_cols_pairs = list(self.cols_pairs[time_idx])
|
|
243
|
+
|
|
244
|
+
time_columns = time_cols_ref + time_cols_neigh + time_cols_pairs
|
|
245
|
+
|
|
246
|
+
else:
|
|
247
|
+
if self.population=='targets':
|
|
248
|
+
self.all_columns = self.cols_targets
|
|
249
|
+
else:
|
|
250
|
+
self.all_columns = self.cols_effectors
|
|
251
|
+
time_idx = np.array([s.startswith('t_') or s=='t0' for s in self.all_columns])
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
time_columns = list(self.all_columns[time_idx])
|
|
255
|
+
except:
|
|
256
|
+
print('no column starts with t')
|
|
257
|
+
self.auto_close = True
|
|
258
|
+
return None
|
|
176
259
|
|
|
177
260
|
self.cbs[1].clear()
|
|
178
261
|
self.cbs[1].addItems(np.unique(self.cb_options[1]+time_columns))
|
|
@@ -184,19 +267,16 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
184
267
|
|
|
185
268
|
def process_survival(self):
|
|
186
269
|
|
|
187
|
-
print('you clicked!!')
|
|
188
270
|
self.FrameToMin = float(self.time_calibration_le.text().replace(',','.'))
|
|
189
|
-
print(self.FrameToMin, 'set')
|
|
190
|
-
|
|
191
271
|
self.time_of_interest = self.cbs[2].currentText()
|
|
192
272
|
if self.time_of_interest=="t0":
|
|
193
273
|
self.class_of_interest = "class"
|
|
194
274
|
else:
|
|
195
275
|
self.class_of_interest = self.time_of_interest.replace('t_','class_')
|
|
196
276
|
|
|
277
|
+
|
|
197
278
|
# read instructions from combobox options
|
|
198
279
|
self.load_available_tables_local()
|
|
199
|
-
|
|
200
280
|
if self.df is not None:
|
|
201
281
|
|
|
202
282
|
try:
|
|
@@ -241,7 +321,8 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
241
321
|
self.well_option = self.parent_window.parent_window.well_list.getSelectedIndices()
|
|
242
322
|
self.position_option = self.parent_window.parent_window.position_list.getSelectedIndices()
|
|
243
323
|
|
|
244
|
-
self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population=self.
|
|
324
|
+
self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population=self.population, return_pos_info=True)
|
|
325
|
+
|
|
245
326
|
if self.df is None:
|
|
246
327
|
msgBox = QMessageBox()
|
|
247
328
|
msgBox.setIcon(QMessageBox.Warning)
|
|
@@ -255,6 +336,11 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
255
336
|
self.df_well_info = self.df_pos_info.loc[:,['well_path', 'well_index', 'well_name', 'well_number', 'well_alias']].drop_duplicates()
|
|
256
337
|
#print(f"{self.df_well_info=}")
|
|
257
338
|
|
|
339
|
+
if self.population=='pairs':
|
|
340
|
+
self.df = expand_pair_table(self.df)
|
|
341
|
+
self.df = extract_neighborhood_in_pair_table(self.df, reference_population=self.population_reference, neighbor_population=self.population_neigh, neighborhood_key=self.neighborhood_keys[0], contact_only=True)
|
|
342
|
+
|
|
343
|
+
|
|
258
344
|
def compute_survival_functions(self):
|
|
259
345
|
|
|
260
346
|
cut_observation_time = None
|
|
@@ -265,19 +351,20 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
265
351
|
cut_observation_time = None
|
|
266
352
|
except Exception as e:
|
|
267
353
|
pass
|
|
268
|
-
|
|
354
|
+
|
|
355
|
+
pairs = False
|
|
356
|
+
if self.neighborhood_keys is not None:
|
|
357
|
+
pairs = True
|
|
269
358
|
|
|
270
359
|
# Per position survival
|
|
271
360
|
for block,movie_group in self.df.groupby(['well','position']):
|
|
272
|
-
|
|
273
|
-
ks = compute_survival(movie_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time)
|
|
361
|
+
ks = compute_survival(movie_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time, pairs=pairs)
|
|
274
362
|
if ks is not None:
|
|
275
363
|
self.df_pos_info.loc[self.df_pos_info['pos_path']==block[1],'survival_fit'] = ks
|
|
276
364
|
|
|
277
365
|
# Per well survival
|
|
278
366
|
for well,well_group in self.df.groupby('well'):
|
|
279
|
-
|
|
280
|
-
ks = compute_survival(well_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time)
|
|
367
|
+
ks = compute_survival(well_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time, pairs=pairs)
|
|
281
368
|
if ks is not None:
|
|
282
369
|
self.df_well_info.loc[self.df_well_info['well_path']==well,'survival_fit'] = ks
|
|
283
370
|
|
celldetective/gui/tableUI.py
CHANGED
|
@@ -19,6 +19,7 @@ from fonticon_mdi6 import MDI6
|
|
|
19
19
|
from math import floor
|
|
20
20
|
|
|
21
21
|
from matplotlib import colormaps
|
|
22
|
+
import matplotlib.cm
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class QueryWidget(QWidget):
|
|
@@ -473,6 +474,14 @@ class TableUI(QMainWindow, Styles):
|
|
|
473
474
|
self.table_view.resizeColumnsToContents()
|
|
474
475
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
475
476
|
|
|
477
|
+
def resizeEvent(self, event):
|
|
478
|
+
|
|
479
|
+
super().resizeEvent(event)
|
|
480
|
+
|
|
481
|
+
try:
|
|
482
|
+
self.fig.tight_layout()
|
|
483
|
+
except:
|
|
484
|
+
pass
|
|
476
485
|
|
|
477
486
|
def _createActions(self):
|
|
478
487
|
|
|
@@ -718,8 +727,11 @@ class TableUI(QMainWindow, Styles):
|
|
|
718
727
|
def save_as_csv_inplace_per_pos(self):
|
|
719
728
|
|
|
720
729
|
print("Saving each table in its respective position folder...")
|
|
721
|
-
for pos,pos_group in self.data.groupby('position'):
|
|
722
|
-
pos_group.
|
|
730
|
+
for pos,pos_group in self.data.groupby(['position']):
|
|
731
|
+
invalid_cols = [c for c in list(pos_group.columns) if c.startswith('Unnamed')]
|
|
732
|
+
if len(invalid_cols)>0:
|
|
733
|
+
pos_group = pos_group.drop(invalid_cols, axis=1)
|
|
734
|
+
pos_group.to_csv(pos[0]+os.sep.join(['output', 'tables', f'trajectories_{self.population}.csv']), index=False)
|
|
723
735
|
print("Done...")
|
|
724
736
|
|
|
725
737
|
def differenciate_selected_feature(self):
|
|
@@ -812,7 +824,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
812
824
|
|
|
813
825
|
num_df = self.data.select_dtypes(include=self.numerics)
|
|
814
826
|
|
|
815
|
-
timeseries = num_df.groupby("FRAME").sum().copy()
|
|
827
|
+
timeseries = num_df.groupby(["FRAME"]).sum().copy()
|
|
816
828
|
timeseries["timeline"] = timeseries.index
|
|
817
829
|
self.subtable = TableUI(timeseries,"Group by frames", plot_mode="plot_timeseries")
|
|
818
830
|
self.subtable.show()
|
|
@@ -1029,11 +1041,10 @@ class TableUI(QMainWindow, Styles):
|
|
|
1029
1041
|
layout.addLayout(hbox)
|
|
1030
1042
|
|
|
1031
1043
|
self.cmap_cb = QColormapComboBox()
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
pass
|
|
1044
|
+
all_cms = list(colormaps)
|
|
1045
|
+
for cm in all_cms:
|
|
1046
|
+
if hasattr(matplotlib.cm, str(cm).lower()):
|
|
1047
|
+
self.cmap_cb.addColormap(cm.lower())
|
|
1037
1048
|
|
|
1038
1049
|
hbox = QHBoxLayout()
|
|
1039
1050
|
hbox.addWidget(QLabel('colormap: '), 33)
|
|
@@ -1324,6 +1335,9 @@ class TableUI(QMainWindow, Styles):
|
|
|
1324
1335
|
if file_name:
|
|
1325
1336
|
if not file_name.endswith(".csv"):
|
|
1326
1337
|
file_name += ".csv"
|
|
1338
|
+
invalid_cols = [c for c in list(self.data.columns) if c.startswith('Unnamed')]
|
|
1339
|
+
if len(invalid_cols)>0:
|
|
1340
|
+
self.data = self.data.drop(invalid_cols, axis=1)
|
|
1327
1341
|
self.data.to_csv(file_name, index=False)
|
|
1328
1342
|
|
|
1329
1343
|
def test_bool(self, array):
|
|
@@ -1417,8 +1431,8 @@ class TableUI(QMainWindow, Styles):
|
|
|
1417
1431
|
row_idx_i = row_idx[np.where(col_idx == unique_cols[k])[0]]
|
|
1418
1432
|
y = self.data.iloc[row_idx_i, unique_cols[k]]
|
|
1419
1433
|
print(unique_cols[k])
|
|
1420
|
-
for w,well_group in self.data.groupby('well_name'):
|
|
1421
|
-
for pos,pos_group in well_group.groupby('pos_name'):
|
|
1434
|
+
for w,well_group in self.data.groupby(['well_name']):
|
|
1435
|
+
for pos,pos_group in well_group.groupby(['pos_name']):
|
|
1422
1436
|
for tid,group_track in pos_group.groupby(self.groupby_cols[1:]):
|
|
1423
1437
|
ax.plot(group_track["FRAME"], group_track[column_names[unique_cols[k]]],label=column_names[unique_cols[k]])
|
|
1424
1438
|
#ax.plot(self.data["FRAME"][row_idx_i], y, label=column_names[unique_cols[k]])
|
|
@@ -1454,8 +1468,8 @@ class TableUI(QMainWindow, Styles):
|
|
|
1454
1468
|
# else:
|
|
1455
1469
|
# ref_time_col = 'FRAME'
|
|
1456
1470
|
|
|
1457
|
-
for w,well_group in self.data.groupby('well_name'):
|
|
1458
|
-
for pos,pos_group in well_group.groupby('pos_name'):
|
|
1471
|
+
for w,well_group in self.data.groupby(['well_name']):
|
|
1472
|
+
for pos,pos_group in well_group.groupby(['pos_name']):
|
|
1459
1473
|
for tid,group_track in pos_group.groupby(self.groupby_cols[1:]):
|
|
1460
1474
|
self.ax.plot(group_track["FRAME"], group_track[column_names[unique_cols[0]]],c="k", alpha = 0.1)
|
|
1461
1475
|
self.ax.set_xlabel(r"$t$ [frame]")
|
|
@@ -3,7 +3,8 @@ from PyQt5.QtWidgets import QAction, QMenu, QMainWindow, QMessageBox, QLabel, QW
|
|
|
3
3
|
from PyQt5.QtGui import QDoubleValidator, QIntValidator
|
|
4
4
|
|
|
5
5
|
from celldetective.filters import std_filter, gauss_filter
|
|
6
|
-
from celldetective.gui.gui_utils import center_window, FigureCanvas,
|
|
6
|
+
from celldetective.gui.gui_utils import center_window, FigureCanvas, color_from_class, help_generic
|
|
7
|
+
from celldetective.gui.gui_utils import PreprocessingLayout
|
|
7
8
|
from celldetective.utils import get_software_location, extract_experiment_channels, rename_intensity_column, estimate_unreliable_edge
|
|
8
9
|
from celldetective.io import auto_load_number_of_frames, load_frames
|
|
9
10
|
from celldetective.segmentation import threshold_image, identify_markers_from_binary, apply_watershed
|
|
@@ -23,6 +24,7 @@ import os
|
|
|
23
24
|
|
|
24
25
|
from celldetective.gui import Styles
|
|
25
26
|
|
|
27
|
+
|
|
26
28
|
class ThresholdConfigWizard(QMainWindow, Styles):
|
|
27
29
|
"""
|
|
28
30
|
UI to create a threshold pipeline for segmentation.
|
|
@@ -140,53 +142,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
140
142
|
|
|
141
143
|
def populate_left_panel(self):
|
|
142
144
|
|
|
143
|
-
self.
|
|
144
|
-
|
|
145
|
-
grid_preprocess = QGridLayout()
|
|
146
|
-
grid_preprocess.setContentsMargins(20, 20, 20, 20)
|
|
147
|
-
|
|
148
|
-
filter_list_option_grid = QHBoxLayout()
|
|
149
|
-
section_preprocess = QLabel("Preprocessing")
|
|
150
|
-
section_preprocess.setStyleSheet("font-weight: bold;")
|
|
151
|
-
filter_list_option_grid.addWidget(section_preprocess, 90, alignment=Qt.AlignLeft)
|
|
152
|
-
|
|
153
|
-
self.delete_filter = QPushButton("")
|
|
154
|
-
self.delete_filter.setStyleSheet(self.button_select_all)
|
|
155
|
-
self.delete_filter.setIcon(icon(MDI6.trash_can, color="black"))
|
|
156
|
-
self.delete_filter.setToolTip("Remove filter")
|
|
157
|
-
self.delete_filter.setIconSize(QSize(20, 20))
|
|
158
|
-
self.delete_filter.clicked.connect(self.filters_qlist.removeSel)
|
|
159
|
-
|
|
160
|
-
self.add_filter = QPushButton("")
|
|
161
|
-
self.add_filter.setStyleSheet(self.button_select_all)
|
|
162
|
-
self.add_filter.setIcon(icon(MDI6.filter_plus, color="black"))
|
|
163
|
-
self.add_filter.setToolTip("Add filter")
|
|
164
|
-
self.add_filter.setIconSize(QSize(20, 20))
|
|
165
|
-
self.add_filter.clicked.connect(self.filters_qlist.addItem)
|
|
166
|
-
|
|
167
|
-
self.help_prefilter_btn = QPushButton()
|
|
168
|
-
self.help_prefilter_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
|
|
169
|
-
self.help_prefilter_btn.setIconSize(QSize(20, 20))
|
|
170
|
-
self.help_prefilter_btn.clicked.connect(self.help_prefilter)
|
|
171
|
-
self.help_prefilter_btn.setStyleSheet(self.button_select_all)
|
|
172
|
-
self.help_prefilter_btn.setToolTip("Help.")
|
|
173
|
-
|
|
174
|
-
# filter_list_option_grid.addWidget(QLabel(""),90)
|
|
175
|
-
filter_list_option_grid.addWidget(self.delete_filter, 5)
|
|
176
|
-
filter_list_option_grid.addWidget(self.add_filter, 5)
|
|
177
|
-
filter_list_option_grid.addWidget(self.help_prefilter_btn, 5)
|
|
178
|
-
|
|
179
|
-
grid_preprocess.addLayout(filter_list_option_grid, 0, 0, 1, 3)
|
|
180
|
-
grid_preprocess.addWidget(self.filters_qlist, 1, 0, 1, 3)
|
|
181
|
-
|
|
182
|
-
self.apply_filters_btn = QPushButton("Apply")
|
|
183
|
-
self.apply_filters_btn.setIcon(icon(MDI6.filter_cog_outline, color="white"))
|
|
184
|
-
self.apply_filters_btn.setIconSize(QSize(20, 20))
|
|
185
|
-
self.apply_filters_btn.setStyleSheet(self.button_style_sheet)
|
|
186
|
-
self.apply_filters_btn.clicked.connect(self.preprocess_image)
|
|
187
|
-
grid_preprocess.addWidget(self.apply_filters_btn, 2, 0, 1, 3)
|
|
188
|
-
|
|
189
|
-
self.left_panel.addLayout(grid_preprocess)
|
|
145
|
+
self.preprocessing = PreprocessingLayout(self)
|
|
146
|
+
self.left_panel.addLayout(self.preprocessing)
|
|
190
147
|
|
|
191
148
|
###################
|
|
192
149
|
# THRESHOLD SECTION
|
|
@@ -484,7 +441,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
484
441
|
self.fcanvas = FigureCanvas(self.fig, interactive=True)
|
|
485
442
|
self.ax.clear()
|
|
486
443
|
|
|
487
|
-
self.im = self.ax.imshow(self.img, cmap='gray')
|
|
444
|
+
self.im = self.ax.imshow(self.img, cmap='gray', interpolation='none')
|
|
488
445
|
|
|
489
446
|
self.binary = threshold_image(self.img, self.threshold_slider.value()[0], self.threshold_slider.value()[1],
|
|
490
447
|
foreground_value=1., fill_holes=True, edge_exclusion=None)
|
|
@@ -640,7 +597,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
640
597
|
"""
|
|
641
598
|
|
|
642
599
|
self.reload_frame()
|
|
643
|
-
filters = self.
|
|
600
|
+
filters = self.preprocessing.list.items
|
|
644
601
|
self.edge = estimate_unreliable_edge(filters)
|
|
645
602
|
self.img = filter_image(self.img, filters)
|
|
646
603
|
self.refresh_imshow()
|
|
@@ -853,7 +810,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
853
810
|
instructions = {
|
|
854
811
|
"target_channel": self.channels_cb.currentText(), # for now index but would be more universal to use name
|
|
855
812
|
"thresholds": self.threshold_slider.value(),
|
|
856
|
-
"filters": self.
|
|
813
|
+
"filters": self.preprocessing.list.items,
|
|
857
814
|
"marker_min_distance": self.min_dist,
|
|
858
815
|
"marker_footprint_size": self.footprint,
|
|
859
816
|
"feature_queries": [self.property_query_le.text()],
|
|
@@ -906,7 +863,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
906
863
|
items_to_add = [f[0] + '_filter' for f in filters]
|
|
907
864
|
self.filters_qlist.list_widget.clear()
|
|
908
865
|
self.filters_qlist.list_widget.addItems(items_to_add)
|
|
909
|
-
self.
|
|
866
|
+
self.preprocessing.list.items = filters
|
|
910
867
|
|
|
911
868
|
self.apply_filters_btn.click()
|
|
912
869
|
|
celldetective/gui/viewers.py
CHANGED
|
@@ -14,7 +14,7 @@ import os
|
|
|
14
14
|
from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget, QShortcut
|
|
15
15
|
from PyQt5.QtCore import Qt, QSize
|
|
16
16
|
from PyQt5.QtGui import QKeySequence, QDoubleValidator
|
|
17
|
-
from celldetective.gui.gui_utils import FigureCanvas, center_window, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit
|
|
17
|
+
from celldetective.gui.gui_utils import FigureCanvas, center_window, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit, PreprocessingLayout2
|
|
18
18
|
from celldetective.gui import Styles
|
|
19
19
|
from superqt import QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider
|
|
20
20
|
from superqt.fonticon import icon
|
|
@@ -626,21 +626,26 @@ class CellEdgeVisualizer(StackVisualizer):
|
|
|
626
626
|
|
|
627
627
|
class SpotDetectionVisualizer(StackVisualizer):
|
|
628
628
|
|
|
629
|
-
def __init__(self, parent_channel_cb=None, parent_diameter_le=None, parent_threshold_le=None, cell_type='targets', labels=None, *args, **kwargs):
|
|
629
|
+
def __init__(self, parent_channel_cb=None, parent_diameter_le=None, parent_threshold_le=None, parent_preprocessing_list=None, cell_type='targets', labels=None, *args, **kwargs):
|
|
630
630
|
|
|
631
631
|
super().__init__(*args, **kwargs)
|
|
632
632
|
|
|
633
633
|
self.cell_type = cell_type
|
|
634
634
|
self.labels = labels
|
|
635
635
|
self.detection_channel = self.target_channel
|
|
636
|
+
|
|
636
637
|
self.parent_channel_cb = parent_channel_cb
|
|
637
638
|
self.parent_diameter_le = parent_diameter_le
|
|
638
639
|
self.parent_threshold_le = parent_threshold_le
|
|
639
|
-
self.
|
|
640
|
+
self.parent_preprocessing_list = parent_preprocessing_list
|
|
640
641
|
|
|
642
|
+
self.spot_sizes = []
|
|
641
643
|
self.floatValidator = QDoubleValidator()
|
|
642
644
|
self.init_scatter()
|
|
643
|
-
|
|
645
|
+
|
|
646
|
+
self.generate_detection_channel()
|
|
647
|
+
self.detection_channel = self.detection_channel_cb.currentIndex()
|
|
648
|
+
|
|
644
649
|
self.generate_spot_detection_params()
|
|
645
650
|
self.generate_add_measurement_btn()
|
|
646
651
|
self.load_labels()
|
|
@@ -663,10 +668,10 @@ class SpotDetectionVisualizer(StackVisualizer):
|
|
|
663
668
|
|
|
664
669
|
# Data-to-pixel scale
|
|
665
670
|
ax_width_in_pixels = self.ax.bbox.width
|
|
666
|
-
ax_height_in_pixels =
|
|
671
|
+
ax_height_in_pixels =self.ax.bbox.height
|
|
667
672
|
|
|
668
|
-
x_scale = (xlim[1] - xlim[0]) / ax_width_in_pixels
|
|
669
|
-
y_scale = (ylim[1] - ylim[0]) / ax_height_in_pixels
|
|
673
|
+
x_scale = (float(xlim[1]) - float(xlim[0])) / ax_width_in_pixels
|
|
674
|
+
y_scale = (float(ylim[1]) - float(ylim[0])) / ax_height_in_pixels
|
|
670
675
|
|
|
671
676
|
# Choose the smaller scale for square pixels
|
|
672
677
|
scale = min(x_scale, y_scale)
|
|
@@ -674,7 +679,7 @@ class SpotDetectionVisualizer(StackVisualizer):
|
|
|
674
679
|
# Convert radius_px to data units
|
|
675
680
|
if len(self.spot_sizes)>0:
|
|
676
681
|
|
|
677
|
-
radius_data_units = self.spot_sizes / scale
|
|
682
|
+
radius_data_units = self.spot_sizes / float(scale)
|
|
678
683
|
|
|
679
684
|
# Convert to scatter `s` size (points squared)
|
|
680
685
|
radius_pts = radius_data_units * (72. / self.fig.dpi )
|
|
@@ -697,8 +702,7 @@ class SpotDetectionVisualizer(StackVisualizer):
|
|
|
697
702
|
if self.mode=='virtual':
|
|
698
703
|
self.init_label = imread(self.mask_paths[value])
|
|
699
704
|
self.target_img = load_frames(self.img_num_per_channel[self.detection_channel, value],
|
|
700
|
-
self.stack_path,
|
|
701
|
-
normalize_input=False).astype(float)[:,:,0]
|
|
705
|
+
self.stack_path,normalize_input=False).astype(float)[:,:,0]
|
|
702
706
|
elif self.mode=='direct':
|
|
703
707
|
self.init_label = self.labels[value,:,:]
|
|
704
708
|
self.target_img = self.stack[value,:,:,self.detection_channel].copy()
|
|
@@ -707,18 +711,28 @@ class SpotDetectionVisualizer(StackVisualizer):
|
|
|
707
711
|
|
|
708
712
|
self.reset_detection()
|
|
709
713
|
self.control_valid_parameters() # set current diam and threshold
|
|
710
|
-
|
|
714
|
+
#self.change_frame(self.frame_slider.value())
|
|
715
|
+
#self.set_detection_channel_index(self.detection_channel_cb.currentIndex())
|
|
716
|
+
|
|
717
|
+
image_preprocessing = self.preprocessing.list.items
|
|
718
|
+
if image_preprocessing==[]:
|
|
719
|
+
image_preprocessing = None
|
|
720
|
+
|
|
721
|
+
blobs_filtered = extract_blobs_in_image(self.target_img, self.init_label,threshold=self.thresh, diameter=self.diameter, image_preprocessing=image_preprocessing)
|
|
711
722
|
if blobs_filtered is not None:
|
|
712
723
|
self.spot_positions = np.array([[x,y] for y,x,_ in blobs_filtered])
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
print(f"{self.spot_sizes=}")
|
|
724
|
+
if len(self.spot_positions)>0:
|
|
725
|
+
self.spot_sizes = np.sqrt(2)*np.array([sig for _,_,sig in blobs_filtered])
|
|
716
726
|
#radius_pts = self.spot_sizes * (self.fig.dpi / 72.0)
|
|
717
727
|
#sizes = np.pi*(radius_pts**2)
|
|
718
|
-
|
|
719
|
-
|
|
728
|
+
if len(self.spot_positions)>0:
|
|
729
|
+
self.spot_scat.set_offsets(self.spot_positions)
|
|
730
|
+
else:
|
|
731
|
+
empty_offset = np.ma.masked_array([0, 0], mask=True)
|
|
732
|
+
self.spot_scat.set_offsets(empty_offset)
|
|
720
733
|
#self.spot_scat.set_sizes(sizes)
|
|
721
|
-
self.
|
|
734
|
+
if len(self.spot_positions)>0:
|
|
735
|
+
self.update_marker_sizes()
|
|
722
736
|
self.canvas.canvas.draw()
|
|
723
737
|
|
|
724
738
|
def reset_detection(self):
|
|
@@ -778,17 +792,34 @@ class SpotDetectionVisualizer(StackVisualizer):
|
|
|
778
792
|
self.detection_channel_cb.addItems(self.channel_names)
|
|
779
793
|
self.detection_channel_cb.currentIndexChanged.connect(self.set_detection_channel_index)
|
|
780
794
|
channel_layout.addWidget(self.detection_channel_cb, 75)
|
|
795
|
+
|
|
796
|
+
# self.invert_check = QCheckBox('invert')
|
|
797
|
+
# if self.invert:
|
|
798
|
+
# self.invert_check.setChecked(True)
|
|
799
|
+
# self.invert_check.toggled.connect(self.set_invert)
|
|
800
|
+
# channel_layout.addWidget(self.invert_check, 10)
|
|
801
|
+
|
|
781
802
|
self.canvas.layout.addLayout(channel_layout)
|
|
803
|
+
|
|
804
|
+
self.preprocessing = PreprocessingLayout2(fraction=25, parent_window=self)
|
|
805
|
+
self.preprocessing.setContentsMargins(15,0,15,0)
|
|
806
|
+
self.canvas.layout.addLayout(self.preprocessing)
|
|
807
|
+
|
|
808
|
+
|
|
809
|
+
# def set_invert(self):
|
|
810
|
+
# if self.invert_check.isChecked():
|
|
811
|
+
# self.invert = True
|
|
812
|
+
# else:
|
|
813
|
+
# self.invert = False
|
|
782
814
|
|
|
783
815
|
def set_detection_channel_index(self, value):
|
|
784
816
|
|
|
785
817
|
self.detection_channel = value
|
|
786
818
|
if self.mode == 'direct':
|
|
787
|
-
self.
|
|
819
|
+
self.target_img = self.stack[-1,:,:,self.detection_channel]
|
|
788
820
|
elif self.mode == 'virtual':
|
|
789
|
-
self.target_img = load_frames(self.img_num_per_channel[self.detection_channel, self.
|
|
790
|
-
self.stack_path,
|
|
791
|
-
normalize_input=False).astype(float)[:,:,0]
|
|
821
|
+
self.target_img = load_frames(self.img_num_per_channel[self.detection_channel, self.frame_slider.value()],
|
|
822
|
+
self.stack_path,normalize_input=False).astype(float)[:,:,0]
|
|
792
823
|
|
|
793
824
|
def generate_spot_detection_params(self):
|
|
794
825
|
|
|
@@ -865,6 +896,12 @@ class SpotDetectionVisualizer(StackVisualizer):
|
|
|
865
896
|
self.parent_diameter_le.setText(self.spot_diam_le.text())
|
|
866
897
|
if self.parent_threshold_le is not None:
|
|
867
898
|
self.parent_threshold_le.setText(self.spot_thresh_le.text())
|
|
899
|
+
if self.parent_preprocessing_list is not None:
|
|
900
|
+
self.parent_preprocessing_list.clear()
|
|
901
|
+
items = self.preprocessing.list.getItems()
|
|
902
|
+
for item in items:
|
|
903
|
+
self.parent_preprocessing_list.addItemToList(item)
|
|
904
|
+
self.parent_preprocessing_list.items = self.preprocessing.list.items
|
|
868
905
|
self.close()
|
|
869
906
|
|
|
870
907
|
class CellSizeViewer(StackVisualizer):
|