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
celldetective/gui/survival_ui.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import
|
|
1
|
+
from PyQt5.QtWidgets import QComboBox, QLineEdit, QVBoxLayout, QLabel, QHBoxLayout, QPushButton
|
|
2
2
|
from PyQt5.QtCore import Qt
|
|
3
3
|
from PyQt5.QtGui import QDoubleValidator
|
|
4
|
-
from celldetective.gui.gui_utils import center_window
|
|
4
|
+
from celldetective.gui.gui_utils import center_window, generic_message
|
|
5
5
|
from superqt import QColormapComboBox
|
|
6
6
|
from celldetective.gui.generic_signal_plot import SurvivalPlotWidget
|
|
7
7
|
from celldetective.utils import get_software_location, _extract_labels_from_config, extract_cols_from_table_list
|
|
@@ -11,14 +11,14 @@ import os
|
|
|
11
11
|
import matplotlib.pyplot as plt
|
|
12
12
|
plt.rcParams['svg.fonttype'] = 'none'
|
|
13
13
|
from glob import glob
|
|
14
|
-
from celldetective.gui import Styles
|
|
14
|
+
from celldetective.gui import Styles, CelldetectiveWidget
|
|
15
15
|
from matplotlib import colormaps
|
|
16
16
|
from celldetective.events import compute_survival
|
|
17
17
|
from celldetective.relative_measurements import expand_pair_table
|
|
18
18
|
import matplotlib.cm
|
|
19
19
|
from celldetective.neighborhood import extract_neighborhood_in_pair_table
|
|
20
20
|
|
|
21
|
-
class ConfigSurvival(
|
|
21
|
+
class ConfigSurvival(CelldetectiveWidget):
|
|
22
22
|
|
|
23
23
|
"""
|
|
24
24
|
UI to set survival instructions.
|
|
@@ -30,7 +30,6 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
30
30
|
super().__init__()
|
|
31
31
|
self.parent_window = parent_window
|
|
32
32
|
self.setWindowTitle("Configure survival")
|
|
33
|
-
self.setWindowIcon(self.celldetective_icon)
|
|
34
33
|
|
|
35
34
|
self.exp_dir = self.parent_window.exp_dir
|
|
36
35
|
self.soft_path = get_software_location()
|
|
@@ -53,8 +52,6 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
53
52
|
self.populate_widget()
|
|
54
53
|
if self.auto_close:
|
|
55
54
|
self.close()
|
|
56
|
-
|
|
57
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
58
55
|
|
|
59
56
|
def interpret_pos_location(self):
|
|
60
57
|
|
|
@@ -91,51 +88,27 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
91
88
|
|
|
92
89
|
|
|
93
90
|
pops = []
|
|
94
|
-
|
|
91
|
+
self.cols_per_pop = {}
|
|
92
|
+
for population in self.parent_window.parent_window.populations:
|
|
95
93
|
tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{population}.csv']))
|
|
96
94
|
if len(tables)>0:
|
|
97
95
|
pops.append(population)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
96
|
+
cols = extract_cols_from_table_list(tables)
|
|
97
|
+
|
|
98
|
+
# check for neighbor pairs
|
|
99
|
+
neigh_cols = [c for c in cols if c.startswith('inclusive_count_neighborhood')]
|
|
100
|
+
neigh_pairs = [c.split('_(')[-1].split(')_')[0].split('-') for c in neigh_cols]
|
|
101
|
+
neigh_pairs = ['-'.join(c) for c in neigh_pairs]
|
|
102
|
+
for k in range(len(neigh_pairs)):
|
|
103
|
+
if "_self_" in neigh_pairs[k]:
|
|
104
|
+
neigh_pairs[k] = '-'.join([population, population])
|
|
105
|
+
pops.extend(neigh_pairs)
|
|
106
|
+
|
|
107
|
+
self.cols_per_pop.update({population: cols})
|
|
135
108
|
|
|
136
109
|
labels = [QLabel('population: '), QLabel('time of\nreference: '), QLabel('time of\ninterest: '), QLabel('cmap: ')] #QLabel('class: '),
|
|
137
110
|
self.cb_options = [pops, ['0'], [], []] #['class'],
|
|
138
|
-
self.cbs = [QComboBox() for
|
|
111
|
+
self.cbs = [QComboBox() for _ in range(len(labels))]
|
|
139
112
|
|
|
140
113
|
self.cbs[-1] = QColormapComboBox()
|
|
141
114
|
self.cbs[0].currentIndexChanged.connect(self.set_classes_and_times)
|
|
@@ -155,12 +128,8 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
155
128
|
if hasattr(matplotlib.cm, str(cm).lower()):
|
|
156
129
|
try:
|
|
157
130
|
self.cbs[-1].addColormap(cm.lower())
|
|
158
|
-
except:
|
|
131
|
+
except Exception as _:
|
|
159
132
|
pass
|
|
160
|
-
#try:
|
|
161
|
-
# self.cbs[-1].addColormap(cm)
|
|
162
|
-
# except:
|
|
163
|
-
# pass
|
|
164
133
|
|
|
165
134
|
main_layout.addLayout(choice_layout)
|
|
166
135
|
|
|
@@ -213,18 +182,16 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
213
182
|
|
|
214
183
|
self.population = 'pairs'
|
|
215
184
|
tables_pairs = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_pairs.csv']))
|
|
185
|
+
if not tables_pairs:
|
|
186
|
+
print('No pair table found... please compute the pair measurements...')
|
|
187
|
+
return None
|
|
216
188
|
self.cols_pairs = extract_cols_from_table_list(tables_pairs)
|
|
217
189
|
|
|
218
190
|
self.population_reference = pop_split[0]
|
|
219
191
|
self.population_neigh = pop_split[1]
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
cols_ref = self.cols_effectors
|
|
224
|
-
if self.population_neigh=='targets':
|
|
225
|
-
cols_neigh = self.cols_targets
|
|
226
|
-
else:
|
|
227
|
-
cols_neigh = self.cols_effectors
|
|
192
|
+
|
|
193
|
+
cols_ref = self.cols_per_pop[self.population_reference]
|
|
194
|
+
cols_neigh = self.cols_per_pop[self.population_neigh]
|
|
228
195
|
|
|
229
196
|
time_cols_ref = np.array([s.startswith('t_') or s=='t0' for s in cols_ref])
|
|
230
197
|
if len(time_cols_ref)>0:
|
|
@@ -237,9 +204,11 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
237
204
|
time_cols_neigh = ['neighbor_'+t for t in time_cols_neigh]
|
|
238
205
|
|
|
239
206
|
if self.population_reference!=self.population_neigh:
|
|
240
|
-
self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and
|
|
207
|
+
self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and str(self.population_neigh) in c]
|
|
241
208
|
else:
|
|
242
|
-
self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and
|
|
209
|
+
self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and str(self.population_neigh) not in c]
|
|
210
|
+
|
|
211
|
+
print(f"{self.neighborhood_keys=}")
|
|
243
212
|
|
|
244
213
|
time_idx = np.array([s.startswith('t_') or s.startswith('t0') for s in self.cols_pairs])
|
|
245
214
|
time_cols_pairs = list(self.cols_pairs[time_idx])
|
|
@@ -247,10 +216,7 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
247
216
|
time_columns = time_cols_ref + time_cols_neigh + time_cols_pairs
|
|
248
217
|
|
|
249
218
|
else:
|
|
250
|
-
|
|
251
|
-
self.all_columns = self.cols_targets
|
|
252
|
-
else:
|
|
253
|
-
self.all_columns = self.cols_effectors
|
|
219
|
+
self.all_columns = self.cols_per_pop[self.population]
|
|
254
220
|
time_idx = np.array([s.startswith('t_') or s=='t0' for s in self.all_columns])
|
|
255
221
|
|
|
256
222
|
try:
|
|
@@ -274,6 +240,8 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
274
240
|
self.time_of_interest = self.cbs[2].currentText()
|
|
275
241
|
if self.time_of_interest=="t0":
|
|
276
242
|
self.class_of_interest = "class"
|
|
243
|
+
elif self.time_of_interest.startswith('t0'):
|
|
244
|
+
self.class_of_interest = self.time_of_interest.replace('t0_','class_')
|
|
277
245
|
else:
|
|
278
246
|
self.class_of_interest = self.time_of_interest.replace('t_','class_')
|
|
279
247
|
|
|
@@ -290,29 +258,19 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
290
258
|
print(e, ' The query is misunderstood and will not be applied...')
|
|
291
259
|
|
|
292
260
|
self.interpret_pos_location()
|
|
261
|
+
|
|
293
262
|
if self.class_of_interest in list(self.df.columns) and self.cbs[2].currentText() in list(self.df.columns):
|
|
294
263
|
self.compute_survival_functions()
|
|
295
264
|
else:
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
msgBox.setWindowTitle("Warning")
|
|
300
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
301
|
-
returnValue = msgBox.exec()
|
|
302
|
-
if returnValue == QMessageBox.Ok:
|
|
303
|
-
return None
|
|
265
|
+
generic_message("The class and/or event time of interest is not found in the dataframe...")
|
|
266
|
+
return None
|
|
267
|
+
|
|
304
268
|
if 'survival_fit' in list(self.df_pos_info.columns):
|
|
305
269
|
self.plot_window = SurvivalPlotWidget(parent_window=self, df=self.df, df_pos_info = self.df_pos_info, df_well_info = self.df_well_info, title='plot survivals')
|
|
306
270
|
self.plot_window.show()
|
|
307
271
|
else:
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
msgBox.setText("No survival function was successfully computed...\nCheck your parameter choice.")
|
|
311
|
-
msgBox.setWindowTitle("Warning")
|
|
312
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
313
|
-
returnValue = msgBox.exec()
|
|
314
|
-
if returnValue == QMessageBox.Ok:
|
|
315
|
-
return None
|
|
272
|
+
generic_message("No survival function was successfully computed...\nCheck your parameter choice.")
|
|
273
|
+
return None
|
|
316
274
|
|
|
317
275
|
def load_available_tables_local(self):
|
|
318
276
|
|
|
@@ -327,32 +285,26 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
327
285
|
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)
|
|
328
286
|
|
|
329
287
|
if self.df is None:
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
msgBox.setText("No table could be found.. Abort.")
|
|
333
|
-
msgBox.setWindowTitle("Warning")
|
|
334
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
335
|
-
returnValue = msgBox.exec()
|
|
336
|
-
if returnValue == QMessageBox.Ok:
|
|
337
|
-
return None
|
|
288
|
+
generic_message("No table could be found.. Abort.")
|
|
289
|
+
return None
|
|
338
290
|
else:
|
|
339
291
|
self.df_well_info = self.df_pos_info.loc[:,['well_path', 'well_index', 'well_name', 'well_number', 'well_alias']].drop_duplicates()
|
|
340
|
-
#print(f"{self.df_well_info=}")
|
|
341
292
|
|
|
342
293
|
if self.population=='pairs':
|
|
343
294
|
self.df = expand_pair_table(self.df)
|
|
344
295
|
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)
|
|
345
296
|
|
|
346
|
-
|
|
347
297
|
def compute_survival_functions(self):
|
|
348
298
|
|
|
349
299
|
cut_observation_time = None
|
|
350
300
|
try:
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
301
|
+
if self.query_time_cut.text()!='':
|
|
302
|
+
cut_observation_time = float(self.query_time_cut.text().replace(',','.')) / self.FrameToMin
|
|
303
|
+
if not 0<cut_observation_time<=(self.df['FRAME'].max()):
|
|
304
|
+
print('Invalid cut time (larger than movie length)... Not applied.')
|
|
305
|
+
cut_observation_time = None
|
|
355
306
|
except Exception as e:
|
|
307
|
+
print(f"{e=}")
|
|
356
308
|
pass
|
|
357
309
|
|
|
358
310
|
pairs = False
|
|
@@ -361,7 +313,9 @@ class ConfigSurvival(QWidget, Styles):
|
|
|
361
313
|
|
|
362
314
|
# Per position survival
|
|
363
315
|
for block,movie_group in self.df.groupby(['well','position']):
|
|
316
|
+
print(f"{block=}")
|
|
364
317
|
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)
|
|
318
|
+
print(f"{ks=}")
|
|
365
319
|
if ks is not None:
|
|
366
320
|
self.df_pos_info.loc[self.df_pos_info['pos_path']==block[1],'survival_fit'] = ks
|
|
367
321
|
|
celldetective/gui/tableUI.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import QRadioButton, QButtonGroup,
|
|
1
|
+
from PyQt5.QtWidgets import QRadioButton, QButtonGroup, QTableView, QAction, QMenu,QFileDialog, QLineEdit, QHBoxLayout, QPushButton, QVBoxLayout, QComboBox, QLabel, QCheckBox, QMessageBox
|
|
2
2
|
from PyQt5.QtCore import Qt
|
|
3
3
|
from PyQt5.QtGui import QBrush, QColor, QDoubleValidator
|
|
4
4
|
import pandas as pd
|
|
5
5
|
import matplotlib.pyplot as plt
|
|
6
|
+
|
|
7
|
+
from celldetective.gui.table_ops.merge_groups import MergeGroupWidget
|
|
8
|
+
|
|
6
9
|
plt.rcParams['svg.fonttype'] = 'none'
|
|
7
10
|
from celldetective.gui.gui_utils import FigureCanvas, center_window, QHSeperationLine, GenericOpColWidget, PandasModel
|
|
8
11
|
from celldetective.utils import differentiate_per_track, collapse_trajectories_by_status, test_2samp_generic, safe_log
|
|
@@ -12,7 +15,7 @@ import numpy as np
|
|
|
12
15
|
import seaborn as sns
|
|
13
16
|
import matplotlib.cm as mcm
|
|
14
17
|
import os
|
|
15
|
-
from celldetective.gui import
|
|
18
|
+
from celldetective.gui import CelldetectiveWidget, CelldetectiveMainWindow
|
|
16
19
|
from superqt import QColormapComboBox, QLabeledSlider, QSearchableComboBox
|
|
17
20
|
from superqt.fonticon import icon
|
|
18
21
|
from fonticon_mdi6 import MDI6
|
|
@@ -22,12 +25,13 @@ from matplotlib import colormaps
|
|
|
22
25
|
import matplotlib.cm
|
|
23
26
|
|
|
24
27
|
|
|
25
|
-
class QueryWidget(
|
|
28
|
+
class QueryWidget(CelldetectiveWidget):
|
|
26
29
|
|
|
27
30
|
def __init__(self, parent_window):
|
|
28
31
|
|
|
29
32
|
super().__init__()
|
|
30
33
|
self.parent_window = parent_window
|
|
34
|
+
|
|
31
35
|
self.setWindowTitle("Filter table")
|
|
32
36
|
# Create the QComboBox and add some items
|
|
33
37
|
|
|
@@ -39,7 +43,6 @@ class QueryWidget(QWidget):
|
|
|
39
43
|
self.submit_btn = QPushButton('submit')
|
|
40
44
|
self.submit_btn.clicked.connect(self.filter_table)
|
|
41
45
|
layout.addWidget(self.submit_btn, 30)
|
|
42
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
43
46
|
center_window(self)
|
|
44
47
|
|
|
45
48
|
def filter_table(self):
|
|
@@ -54,7 +57,7 @@ class QueryWidget(QWidget):
|
|
|
54
57
|
return None
|
|
55
58
|
|
|
56
59
|
|
|
57
|
-
class MergeOneHotWidget(
|
|
60
|
+
class MergeOneHotWidget(CelldetectiveWidget):
|
|
58
61
|
|
|
59
62
|
def __init__(self, parent_window, selected_columns=None):
|
|
60
63
|
|
|
@@ -139,7 +142,7 @@ class MergeOneHotWidget(QWidget, Styles):
|
|
|
139
142
|
self.submit_btn.setEnabled(True)
|
|
140
143
|
|
|
141
144
|
|
|
142
|
-
class DifferentiateColWidget(
|
|
145
|
+
class DifferentiateColWidget(CelldetectiveWidget):
|
|
143
146
|
|
|
144
147
|
def __init__(self, parent_window, column=None):
|
|
145
148
|
|
|
@@ -217,7 +220,7 @@ class DifferentiateColWidget(QWidget, Styles):
|
|
|
217
220
|
|
|
218
221
|
|
|
219
222
|
|
|
220
|
-
class OperationOnColsWidget(
|
|
223
|
+
class OperationOnColsWidget(CelldetectiveWidget):
|
|
221
224
|
|
|
222
225
|
def __init__(self, parent_window, column1=None, column2=None, operation='divide'):
|
|
223
226
|
|
|
@@ -394,7 +397,7 @@ class LogColWidget(GenericOpColWidget):
|
|
|
394
397
|
self.parent_window.data['log10('+self.measurements_cb.currentText()+')'] = safe_log(self.parent_window.data[self.measurements_cb.currentText()].values)
|
|
395
398
|
|
|
396
399
|
|
|
397
|
-
class RenameColWidget(
|
|
400
|
+
class RenameColWidget(CelldetectiveWidget):
|
|
398
401
|
|
|
399
402
|
def __init__(self, parent_window, column=None):
|
|
400
403
|
|
|
@@ -429,11 +432,11 @@ class RenameColWidget(QWidget):
|
|
|
429
432
|
self.parent_window.table_view.setModel(self.parent_window.model)
|
|
430
433
|
self.close()
|
|
431
434
|
|
|
432
|
-
class PivotTableUI(
|
|
435
|
+
class PivotTableUI(CelldetectiveWidget):
|
|
433
436
|
|
|
434
437
|
def __init__(self, data, title="", mode=None, *args, **kwargs):
|
|
435
438
|
|
|
436
|
-
|
|
439
|
+
CelldetectiveWidget.__init__(self, *args, **kwargs)
|
|
437
440
|
|
|
438
441
|
self.data = data
|
|
439
442
|
self.title = title
|
|
@@ -535,11 +538,11 @@ class PivotTableUI(QWidget):
|
|
|
535
538
|
"""
|
|
536
539
|
self.information_label.setText(html_caption)
|
|
537
540
|
|
|
538
|
-
class TableUI(
|
|
541
|
+
class TableUI(CelldetectiveMainWindow):
|
|
539
542
|
|
|
540
543
|
def __init__(self, data, title, population='targets',plot_mode="plot_track_signals", save_inplace_option=False, collapse_tracks_option=True, *args, **kwargs):
|
|
541
544
|
|
|
542
|
-
|
|
545
|
+
CelldetectiveMainWindow.__init__(self, *args, **kwargs)
|
|
543
546
|
|
|
544
547
|
self.setWindowTitle(title)
|
|
545
548
|
self.setGeometry(100,100,1000,400)
|
|
@@ -574,7 +577,6 @@ class TableUI(QMainWindow, Styles):
|
|
|
574
577
|
self.model = PandasModel(data)
|
|
575
578
|
self.table_view.setModel(self.model)
|
|
576
579
|
self.table_view.resizeColumnsToContents()
|
|
577
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
578
580
|
|
|
579
581
|
def resizeEvent(self, event):
|
|
580
582
|
|
|
@@ -651,11 +653,17 @@ class TableUI(QMainWindow, Styles):
|
|
|
651
653
|
self.calibrate_action.triggered.connect(self.calibrate_selected_feature)
|
|
652
654
|
self.calibrate_action.setShortcut("Ctrl+C")
|
|
653
655
|
self.mathMenu.addAction(self.calibrate_action)
|
|
656
|
+
|
|
657
|
+
self.merge_classification_action = QAction('&Merge states...', self)
|
|
658
|
+
self.merge_classification_action.triggered.connect(self.merge_classification_features)
|
|
659
|
+
self.mathMenu.addAction(self.merge_classification_action)
|
|
654
660
|
|
|
655
661
|
self.derivative_action = QAction('&Differentiate...', self)
|
|
656
662
|
self.derivative_action.triggered.connect(self.differenciate_selected_feature)
|
|
657
663
|
self.derivative_action.setShortcut("Ctrl+D")
|
|
658
664
|
self.mathMenu.addAction(self.derivative_action)
|
|
665
|
+
if not self.tracks:
|
|
666
|
+
self.derivative_action.setEnabled(False)
|
|
659
667
|
|
|
660
668
|
self.abs_action = QAction('&Absolute value...', self)
|
|
661
669
|
self.abs_action.triggered.connect(self.take_abs_of_selected_feature)
|
|
@@ -696,7 +704,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
696
704
|
|
|
697
705
|
def collapse_pairs_in_neigh(self):
|
|
698
706
|
|
|
699
|
-
self.selectNeighWidget =
|
|
707
|
+
self.selectNeighWidget = CelldetectiveWidget()
|
|
700
708
|
self.selectNeighWidget.setMinimumWidth(480)
|
|
701
709
|
self.selectNeighWidget.setWindowTitle('Set neighborhood of interest')
|
|
702
710
|
|
|
@@ -757,14 +765,20 @@ class TableUI(QMainWindow, Styles):
|
|
|
757
765
|
ref_pop = self.reference_pop_cb.currentText()
|
|
758
766
|
neighborhood = self.neigh_cb.currentText()
|
|
759
767
|
status_neigh = 'status_'+neighborhood
|
|
768
|
+
|
|
760
769
|
if 'self' in neighborhood:
|
|
761
770
|
neighbor_pop = ref_pop
|
|
762
|
-
elif ref_pop=='targets':
|
|
763
|
-
neighbor_pop = 'effectors'
|
|
764
|
-
elif ref_pop=='effectors':
|
|
765
|
-
neighbor_pop = "targets"
|
|
766
771
|
|
|
767
|
-
|
|
772
|
+
neigh_col = neighborhood.replace('status_','')
|
|
773
|
+
if '_(' in neigh_col and ')_' in neigh_col:
|
|
774
|
+
neighbor_pop = neigh_col.split('_(')[-1].split(')_')[0].split('-')[-1]
|
|
775
|
+
else:
|
|
776
|
+
if ref_pop=='targets':
|
|
777
|
+
neighbor_pop = 'effectors'
|
|
778
|
+
if ref_pop=='effectors':
|
|
779
|
+
neighbor_pop = "targets"
|
|
780
|
+
|
|
781
|
+
data = extract_neighborhood_in_pair_table(self.data, neighborhood_key=neighborhood, contact_only=self.contact_only_check.isChecked(), reference_population=ref_pop)
|
|
768
782
|
|
|
769
783
|
if self.groupby_pair_rb.isChecked():
|
|
770
784
|
self.groupby_cols = ['position', 'REFERENCE_ID', 'NEIGHBOR_ID']
|
|
@@ -774,6 +788,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
774
788
|
self.current_data = data
|
|
775
789
|
skip_projection = False
|
|
776
790
|
if 'reference_tracked' in list(self.current_data.columns):
|
|
791
|
+
print(f"{self.current_data['reference_tracked']=} {(self.current_data['reference_tracked']==False)=} {np.all(self.current_data['reference_tracked']==False)=}")
|
|
777
792
|
if np.all(self.current_data['reference_tracked'].astype(bool)==False):
|
|
778
793
|
# reference not tracked
|
|
779
794
|
if self.groupby_reference_rb.isChecked():
|
|
@@ -987,6 +1002,22 @@ class TableUI(QMainWindow, Styles):
|
|
|
987
1002
|
|
|
988
1003
|
self.LogWidget = LogColWidget(self, selected_col)
|
|
989
1004
|
self.LogWidget.show()
|
|
1005
|
+
|
|
1006
|
+
def merge_classification_features(self):
|
|
1007
|
+
|
|
1008
|
+
x = self.table_view.selectedIndexes()
|
|
1009
|
+
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
1010
|
+
|
|
1011
|
+
col_selection = []
|
|
1012
|
+
if isinstance(col_idx, (list, np.ndarray)):
|
|
1013
|
+
cols = np.array(list(self.data.columns))
|
|
1014
|
+
if len(col_idx) > 0:
|
|
1015
|
+
selected_cols = cols[col_idx]
|
|
1016
|
+
col_selection.extend(selected_cols)
|
|
1017
|
+
|
|
1018
|
+
self.merge_classification_widget = MergeGroupWidget(self, columns = col_selection)
|
|
1019
|
+
self.merge_classification_widget.show()
|
|
1020
|
+
|
|
990
1021
|
|
|
991
1022
|
def calibrate_selected_feature(self):
|
|
992
1023
|
|
|
@@ -1030,14 +1061,11 @@ class TableUI(QMainWindow, Styles):
|
|
|
1030
1061
|
|
|
1031
1062
|
x = self.table_view.selectedIndexes()
|
|
1032
1063
|
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
1064
|
+
selected_cols = None
|
|
1033
1065
|
if isinstance(col_idx, (list, np.ndarray)):
|
|
1034
1066
|
cols = np.array(list(self.data.columns))
|
|
1035
1067
|
if len(col_idx)>0:
|
|
1036
1068
|
selected_col = str(cols[col_idx[0]])
|
|
1037
|
-
else:
|
|
1038
|
-
selected_col = None
|
|
1039
|
-
else:
|
|
1040
|
-
selected_col = None
|
|
1041
1069
|
|
|
1042
1070
|
self.mergewidget = MergeOneHotWidget(self, selected_columns=selected_cols)
|
|
1043
1071
|
self.mergewidget.show()
|
|
@@ -1091,7 +1119,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
1091
1119
|
|
|
1092
1120
|
self.current_data = self.data
|
|
1093
1121
|
|
|
1094
|
-
self.projectionWidget =
|
|
1122
|
+
self.projectionWidget = CelldetectiveWidget()
|
|
1095
1123
|
self.projectionWidget.setMinimumWidth(500)
|
|
1096
1124
|
self.projectionWidget.setWindowTitle('Set projection mode')
|
|
1097
1125
|
|
|
@@ -1191,7 +1219,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
1191
1219
|
|
|
1192
1220
|
def set_1D_plot_params(self):
|
|
1193
1221
|
|
|
1194
|
-
self.plot1Dparams =
|
|
1222
|
+
self.plot1Dparams = CelldetectiveWidget()
|
|
1195
1223
|
self.plot1Dparams.setWindowTitle('Set 1D plot parameters')
|
|
1196
1224
|
|
|
1197
1225
|
layout = QVBoxLayout()
|
|
File without changes
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from PyQt5.QtCore import QSize, Qt
|
|
5
|
+
from PyQt5.QtWidgets import QComboBox, QHBoxLayout, QLabel, QLineEdit, QPushButton, QVBoxLayout
|
|
6
|
+
from fonticon_mdi6 import MDI6
|
|
7
|
+
from superqt.fonticon import icon
|
|
8
|
+
|
|
9
|
+
from celldetective.gui import CelldetectiveWidget
|
|
10
|
+
from celldetective.gui.gui_utils import PandasModel, center_window
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class MergeGroupWidget(CelldetectiveWidget):
|
|
14
|
+
def __init__(self, parent_window, columns: List[str] = [], n_cols_init: int = 3):
|
|
15
|
+
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.parent_window = parent_window
|
|
18
|
+
|
|
19
|
+
self.setWindowTitle("Merge classifications")
|
|
20
|
+
self.group_cols = [c for c in list(self.parent_window.data.columns) if c.startswith("group_") or c.startswith("status_")]
|
|
21
|
+
self.group_cols.insert(0, "--")
|
|
22
|
+
if len(columns) > n_cols_init:
|
|
23
|
+
n_cols_init = len(columns)
|
|
24
|
+
|
|
25
|
+
center_window(self)
|
|
26
|
+
|
|
27
|
+
layout = QVBoxLayout(self)
|
|
28
|
+
layout.setContentsMargins(30, 10, 30, 30)
|
|
29
|
+
|
|
30
|
+
label = QLabel(
|
|
31
|
+
"Merge several binary or multi-label classification features into a multi-label classification feature where each state is one of the possible combinations.\n")
|
|
32
|
+
|
|
33
|
+
label.setWordWrap(True) # enables automatic line breaking
|
|
34
|
+
label.setTextInteractionFlags(Qt.TextSelectableByMouse) # optional, to allow copy
|
|
35
|
+
label.setStyleSheet("color: gray;") # optional style
|
|
36
|
+
|
|
37
|
+
layout.addWidget(label)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
self.name_le = QLineEdit("group_multilabel")
|
|
41
|
+
name_layout = QHBoxLayout()
|
|
42
|
+
name_layout.addWidget(QLabel("name: "), 25)
|
|
43
|
+
name_layout.addWidget(self.name_le, 75)
|
|
44
|
+
layout.addLayout(name_layout)
|
|
45
|
+
|
|
46
|
+
self.cbs_layout = QVBoxLayout()
|
|
47
|
+
self.cbs_layout.setContentsMargins(0,10,0,0)
|
|
48
|
+
|
|
49
|
+
self.cbs = []
|
|
50
|
+
for i in range(n_cols_init):
|
|
51
|
+
cb_i = QComboBox()
|
|
52
|
+
cb_i.addItems(self.group_cols)
|
|
53
|
+
if i < len(columns):
|
|
54
|
+
selection = columns[i]
|
|
55
|
+
idx = cb_i.findText(selection)
|
|
56
|
+
if idx>=0:
|
|
57
|
+
cb_i.setCurrentIndex(idx)
|
|
58
|
+
else:
|
|
59
|
+
cb_i.setCurrentIndex(0)
|
|
60
|
+
self.cbs.append(cb_i)
|
|
61
|
+
|
|
62
|
+
col_layout = QHBoxLayout()
|
|
63
|
+
col_layout.addWidget(QLabel(f'state {i}: '), 25)
|
|
64
|
+
col_layout.addWidget(cb_i, 75)
|
|
65
|
+
self.cbs_layout.addLayout(col_layout)
|
|
66
|
+
|
|
67
|
+
layout.addLayout(self.cbs_layout)
|
|
68
|
+
|
|
69
|
+
self.add_feature_btn = QPushButton()
|
|
70
|
+
self.add_feature_btn.setIcon(icon(MDI6.plus, color=self.help_color))
|
|
71
|
+
self.add_feature_btn.setIconSize(QSize(20, 20))
|
|
72
|
+
self.add_feature_btn.clicked.connect(self.add_col)
|
|
73
|
+
self.add_feature_btn.setStyleSheet(self.button_select_all)
|
|
74
|
+
layout.addWidget(self.add_feature_btn, alignment=Qt.AlignRight)
|
|
75
|
+
|
|
76
|
+
self.submit_btn = QPushButton('Compute')
|
|
77
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
78
|
+
self.submit_btn.clicked.connect(self.compute)
|
|
79
|
+
layout.addWidget(self.submit_btn, 30)
|
|
80
|
+
|
|
81
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
82
|
+
|
|
83
|
+
def add_col(self):
|
|
84
|
+
cb_i = QComboBox()
|
|
85
|
+
cb_i.addItems(self.group_cols)
|
|
86
|
+
self.cbs.append(cb_i)
|
|
87
|
+
|
|
88
|
+
col_layout = QHBoxLayout()
|
|
89
|
+
col_layout.addWidget(QLabel(f'state {len(self.cbs)}: '), 25)
|
|
90
|
+
col_layout.addWidget(cb_i, 75)
|
|
91
|
+
|
|
92
|
+
self.cbs_layout.addLayout(col_layout)
|
|
93
|
+
|
|
94
|
+
def compute(self):
|
|
95
|
+
|
|
96
|
+
cols_to_merge = [cb_i.currentText() for cb_i in self.cbs if cb_i.currentText() != "--"]
|
|
97
|
+
name = self.name_le.text()
|
|
98
|
+
if " " in name:
|
|
99
|
+
name.replace(" ","_")
|
|
100
|
+
if name == '':
|
|
101
|
+
name = "multilabel"
|
|
102
|
+
if not name.startswith("group_"):
|
|
103
|
+
name = "group_" + name
|
|
104
|
+
|
|
105
|
+
if len(cols_to_merge) > 1:
|
|
106
|
+
print("Computing a multi-label classification from the classification feature sources...")
|
|
107
|
+
bases = [int(self.parent_window.data[c].max()) + 1 for c in cols_to_merge]
|
|
108
|
+
multipliers = np.concatenate(([1], np.cumprod(bases[:-1])))
|
|
109
|
+
self.parent_window.data[name] = (self.parent_window.data[cols_to_merge] * multipliers).sum(axis=1)
|
|
110
|
+
self.parent_window.data.loc[self.parent_window.data[cols_to_merge].isna().any(axis=1), name] = np.nan
|
|
111
|
+
|
|
112
|
+
self.parent_window.model = PandasModel(self.parent_window.data)
|
|
113
|
+
self.parent_window.table_view.setModel(self.parent_window.model)
|
|
114
|
+
self.close()
|
|
115
|
+
elif len(cols_to_merge) == 1:
|
|
116
|
+
print("Only one classification feature was selected, nothing to merge...")
|
|
117
|
+
else:
|
|
118
|
+
print("No classification feature was selected...")
|