celldetective 1.3.1__py3-none-any.whl → 1.3.3.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 +2 -0
- celldetective/gui/classifier_widget.py +51 -3
- celldetective/gui/control_panel.py +9 -3
- celldetective/gui/generic_signal_plot.py +161 -2
- celldetective/gui/gui_utils.py +90 -1
- celldetective/gui/measurement_options.py +35 -32
- celldetective/gui/plot_signals_ui.py +8 -3
- celldetective/gui/process_block.py +36 -114
- celldetective/gui/retrain_segmentation_model_options.py +3 -1
- celldetective/gui/signal_annotator.py +53 -26
- celldetective/gui/signal_annotator2.py +17 -30
- celldetective/gui/survival_ui.py +7 -3
- celldetective/gui/tableUI.py +300 -183
- celldetective/gui/thresholds_gui.py +195 -199
- celldetective/gui/viewers.py +267 -13
- celldetective/io.py +110 -10
- celldetective/measure.py +128 -88
- celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +79 -0
- 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 +37 -0
- celldetective/models/segmentation_effectors/test-transfer/config_input.json +39 -0
- celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
- celldetective/neighborhood.py +154 -69
- celldetective/relative_measurements.py +128 -4
- celldetective/scripts/measure_cells.py +3 -3
- celldetective/signals.py +207 -213
- celldetective/utils.py +16 -0
- {celldetective-1.3.1.dist-info → celldetective-1.3.3.post1.dist-info}/METADATA +11 -10
- {celldetective-1.3.1.dist-info → celldetective-1.3.3.post1.dist-info}/RECORD +34 -29
- {celldetective-1.3.1.dist-info → celldetective-1.3.3.post1.dist-info}/WHEEL +1 -1
- {celldetective-1.3.1.dist-info → celldetective-1.3.3.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.3.1.dist-info → celldetective-1.3.3.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.1.dist-info → celldetective-1.3.3.post1.dist-info}/top_level.txt +0 -0
celldetective/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.3.
|
|
1
|
+
__version__ = "1.3.3.post1"
|
celldetective/events.py
CHANGED
|
@@ -104,6 +104,8 @@ def switch_to_events(classes, event_times, max_times, origin_times=None, left_ce
|
|
|
104
104
|
pass
|
|
105
105
|
elif c==1:
|
|
106
106
|
delta_t = mt - ot
|
|
107
|
+
if cut_observation_time is not None:
|
|
108
|
+
delta_t = cut_observation_time - ot
|
|
107
109
|
if delta_t>0:
|
|
108
110
|
events.append(0)
|
|
109
111
|
survival_times.append(delta_t)
|
|
@@ -253,6 +253,24 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
253
253
|
|
|
254
254
|
def update_props_scatter(self, feature_changed=True):
|
|
255
255
|
|
|
256
|
+
try:
|
|
257
|
+
if np.any(self.df[self.features_cb[0].currentText()].to_numpy() <= 0.):
|
|
258
|
+
if self.ax_props.get_yscale()=='log':
|
|
259
|
+
self.log_btns[0].click()
|
|
260
|
+
self.log_btns[0].setEnabled(False)
|
|
261
|
+
else:
|
|
262
|
+
self.log_btns[0].setEnabled(True)
|
|
263
|
+
|
|
264
|
+
if np.any(self.df[self.features_cb[1].currentText()].to_numpy() <= 0.):
|
|
265
|
+
if self.ax_props.get_xscale()=='log':
|
|
266
|
+
self.log_btns[1].click()
|
|
267
|
+
self.log_btns[1].setEnabled(False)
|
|
268
|
+
else:
|
|
269
|
+
self.log_btns[1].setEnabled(True)
|
|
270
|
+
except Exception as e:
|
|
271
|
+
#print(e)
|
|
272
|
+
pass
|
|
273
|
+
|
|
256
274
|
class_name = self.class_name
|
|
257
275
|
|
|
258
276
|
try:
|
|
@@ -279,11 +297,24 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
279
297
|
max_x = self.df.dropna(subset=feat_x)[feat_x].max()
|
|
280
298
|
min_y = self.df.dropna(subset=feat_y)[feat_y].min()
|
|
281
299
|
max_y = self.df.dropna(subset=feat_y)[feat_y].max()
|
|
300
|
+
|
|
301
|
+
x_padding = (max_x - min_x) * 0.05
|
|
302
|
+
y_padding = (max_y - min_y) * 0.05
|
|
303
|
+
if x_padding==0:
|
|
304
|
+
x_padding = 0.05
|
|
305
|
+
if y_padding==0:
|
|
306
|
+
y_padding = 0.05
|
|
282
307
|
|
|
283
308
|
if min_x==min_x and max_x==max_x:
|
|
284
|
-
self.ax_props.
|
|
309
|
+
if self.ax_props.get_xscale()=='linear':
|
|
310
|
+
self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
|
|
311
|
+
else:
|
|
312
|
+
self.ax_props.set_xlim(min_x, max_x)
|
|
285
313
|
if min_y==min_y and max_y==max_y:
|
|
286
|
-
self.ax_props.
|
|
314
|
+
if self.ax_props.get_yscale()=='linear':
|
|
315
|
+
self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
|
|
316
|
+
else:
|
|
317
|
+
self.ax_props.set_ylim(min_y, max_y)
|
|
287
318
|
|
|
288
319
|
self.propscanvas.canvas.toolbar.update()
|
|
289
320
|
|
|
@@ -452,22 +483,39 @@ class ClassifierWidget(QWidget, Styles):
|
|
|
452
483
|
|
|
453
484
|
if i==1:
|
|
454
485
|
try:
|
|
486
|
+
feat_x = self.features_cb[1].currentText()
|
|
487
|
+
min_x = self.df.dropna(subset=feat_x)[feat_x].min()
|
|
488
|
+
max_x = self.df.dropna(subset=feat_x)[feat_x].max()
|
|
489
|
+
x_padding = (max_x - min_x) * 0.05
|
|
490
|
+
if x_padding==0:
|
|
491
|
+
x_padding = 0.05
|
|
492
|
+
|
|
455
493
|
if self.ax_props.get_xscale()=='linear':
|
|
494
|
+
self.ax_props.set_xlim(min_x, max_x)
|
|
456
495
|
self.ax_props.set_xscale('log')
|
|
457
496
|
self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
458
497
|
else:
|
|
459
498
|
self.ax_props.set_xscale('linear')
|
|
499
|
+
self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
|
|
460
500
|
self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
461
501
|
except Exception as e:
|
|
462
502
|
print(e)
|
|
463
503
|
elif i==0:
|
|
464
504
|
try:
|
|
505
|
+
feat_y = self.features_cb[0].currentText()
|
|
506
|
+
min_y = self.df.dropna(subset=feat_y)[feat_y].min()
|
|
507
|
+
max_y = self.df.dropna(subset=feat_y)[feat_y].max()
|
|
508
|
+
y_padding = (max_y - min_y) * 0.05
|
|
509
|
+
if y_padding==0:
|
|
510
|
+
y_padding = 0.05
|
|
511
|
+
|
|
465
512
|
if self.ax_props.get_yscale()=='linear':
|
|
466
|
-
|
|
513
|
+
self.ax_props.set_ylim(min_y, max_y)
|
|
467
514
|
self.ax_props.set_yscale('log')
|
|
468
515
|
self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
469
516
|
else:
|
|
470
517
|
self.ax_props.set_yscale('linear')
|
|
518
|
+
self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
|
|
471
519
|
self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
472
520
|
except Exception as e:
|
|
473
521
|
print(e)
|
|
@@ -181,7 +181,13 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
181
181
|
exp_hbox = QHBoxLayout()
|
|
182
182
|
exp_hbox.addWidget(experiment_label, 25, alignment=Qt.AlignRight)
|
|
183
183
|
exp_subhbox = QHBoxLayout()
|
|
184
|
-
|
|
184
|
+
if len(name)>thresh:
|
|
185
|
+
name_cut = name[:thresh - 3]+'...'
|
|
186
|
+
else:
|
|
187
|
+
name_cut = name
|
|
188
|
+
exp_name_lbl = QLabel(name_cut)
|
|
189
|
+
exp_name_lbl.setToolTip(name)
|
|
190
|
+
exp_subhbox.addWidget(exp_name_lbl, 90, alignment=Qt.AlignLeft)
|
|
185
191
|
exp_subhbox.addWidget(self.folder_exp_btn, 5, alignment=Qt.AlignRight)
|
|
186
192
|
exp_subhbox.addWidget(self.edit_config_button, 5, alignment=Qt.AlignRight)
|
|
187
193
|
exp_hbox.addLayout(exp_subhbox, 75)
|
|
@@ -444,8 +450,8 @@ class ControlPanel(QMainWindow, Styles):
|
|
|
444
450
|
self.view_stack_btn.setEnabled(False)
|
|
445
451
|
self.ProcessEffectors.signal_analysis_action.setEnabled(True)
|
|
446
452
|
self.ProcessTargets.signal_analysis_action.setEnabled(True)
|
|
447
|
-
|
|
448
|
-
|
|
453
|
+
if hasattr(self,'delete_tracks_btn'):
|
|
454
|
+
self.delete_tracks_btn.hide()
|
|
449
455
|
self.ProcessTargets.delete_tracks_btn.hide()
|
|
450
456
|
self.ProcessEffectors.delete_tracks_btn.hide()
|
|
451
457
|
else:
|
|
@@ -5,7 +5,11 @@ from PyQt5.QtCore import Qt, QSize
|
|
|
5
5
|
from PyQt5.QtGui import QDoubleValidator
|
|
6
6
|
|
|
7
7
|
from celldetective.gui.gui_utils import center_window, FigureCanvas, ExportPlotBtn
|
|
8
|
+
from celldetective.gui.tableUI import TableUI
|
|
9
|
+
from celldetective.io import collect_experiment_metadata
|
|
10
|
+
|
|
8
11
|
from superqt.fonticon import icon
|
|
12
|
+
from superqt import QLabeledSlider
|
|
9
13
|
from fonticon_mdi6 import MDI6
|
|
10
14
|
import numpy as np
|
|
11
15
|
import json
|
|
@@ -16,7 +20,9 @@ from glob import glob
|
|
|
16
20
|
from matplotlib.cm import tab10
|
|
17
21
|
from celldetective.gui import Styles
|
|
18
22
|
import matplotlib.cm as mcm
|
|
23
|
+
import pandas as pd
|
|
19
24
|
|
|
25
|
+
from lifelines.utils import qth_survival_times
|
|
20
26
|
|
|
21
27
|
class GenericSignalPlotWidget(QWidget, Styles):
|
|
22
28
|
|
|
@@ -136,6 +142,14 @@ class GenericSignalPlotWidget(QWidget, Styles):
|
|
|
136
142
|
plot_buttons_hbox.addWidget(self.export_btn, 5, alignment=Qt.AlignRight)
|
|
137
143
|
self.layout.addLayout(plot_buttons_hbox)
|
|
138
144
|
|
|
145
|
+
self.export_tabular_btn = QPushButton('')
|
|
146
|
+
self.export_tabular_btn.setIcon(icon(MDI6.table,color="black"))
|
|
147
|
+
self.export_tabular_btn.setStyleSheet(self.button_select_all)
|
|
148
|
+
self.export_tabular_btn.setToolTip('Tabulate survival values.')
|
|
149
|
+
self.export_tabular_btn.setIconSize(QSize(20, 20))
|
|
150
|
+
plot_buttons_hbox.addWidget(self.export_tabular_btn, 5, alignment=Qt.AlignRight)
|
|
151
|
+
self.export_tabular_btn.hide()
|
|
152
|
+
|
|
139
153
|
self.ax.set_prop_cycle('color',[self.cmap(i) for i in np.linspace(0, 1, len(self.parent_window.well_indices))])
|
|
140
154
|
|
|
141
155
|
self.fig.set_facecolor('none') # or 'None'
|
|
@@ -671,7 +685,9 @@ class SurvivalPlotWidget(GenericSignalPlotWidget):
|
|
|
671
685
|
self.class_selection_widget.hide()
|
|
672
686
|
self.rescale_widget.hide()
|
|
673
687
|
self.cell_lines_alpha_wdg.hide()
|
|
674
|
-
|
|
688
|
+
self.export_tabular_btn.show()
|
|
689
|
+
self.export_tabular_btn.clicked.connect(self.set_table_options)
|
|
690
|
+
|
|
675
691
|
def switch_to_log(self):
|
|
676
692
|
|
|
677
693
|
"""
|
|
@@ -790,4 +806,147 @@ class SurvivalPlotWidget(GenericSignalPlotWidget):
|
|
|
790
806
|
|
|
791
807
|
# Plot a signal
|
|
792
808
|
if line==line:
|
|
793
|
-
line.plot_survival_function(ci_show=ci_option, ax=self.ax, legend=legend, color=color, label=label, xlabel='timeline [min]')
|
|
809
|
+
line.plot_survival_function(ci_show=ci_option, ax=self.ax, legend=legend, color=color, label=label, xlabel='timeline [min]')
|
|
810
|
+
|
|
811
|
+
def set_table_options(self):
|
|
812
|
+
|
|
813
|
+
self.config_table_wg = QWidget()
|
|
814
|
+
self.config_table_wg.setMinimumWidth(480)
|
|
815
|
+
self.config_table_wg.setWindowTitle('Survival data')
|
|
816
|
+
|
|
817
|
+
layout = QVBoxLayout()
|
|
818
|
+
self.config_table_wg.setLayout(layout)
|
|
819
|
+
|
|
820
|
+
self.all_values_rb = QRadioButton('tabulate all values')
|
|
821
|
+
self.single_timepoint_rb = QRadioButton('survival at single timepoint [min]: ')
|
|
822
|
+
self.ec_rb = QRadioButton(r'EC N% survival: ')
|
|
823
|
+
self.all_values_rb.toggled.connect(self.activate_sliders)
|
|
824
|
+
self.single_timepoint_rb.toggled.connect(self.activate_sliders)
|
|
825
|
+
|
|
826
|
+
self.single_timepoint_slider = QLabeledSlider()
|
|
827
|
+
self.single_timepoint_slider.setRange(0, int(self.df['FRAME'].max()*self.parent_window.FrameToMin))
|
|
828
|
+
self.single_timepoint_slider.setValue(int(self.df['FRAME'].max()*self.parent_window.FrameToMin))
|
|
829
|
+
|
|
830
|
+
self.ec_slider = QLabeledSlider()
|
|
831
|
+
self.ec_slider.setRange(0, 100)
|
|
832
|
+
self.ec_slider.setValue(50)
|
|
833
|
+
|
|
834
|
+
self.ec_rb.toggled.connect(self.activate_sliders)
|
|
835
|
+
self.all_values_rb.click()
|
|
836
|
+
|
|
837
|
+
self.set_btn = QPushButton('Set')
|
|
838
|
+
self.set_btn.setStyleSheet(self.button_style_sheet)
|
|
839
|
+
self.set_btn.clicked.connect(self.assemble_survival_data)
|
|
840
|
+
|
|
841
|
+
layout.addWidget(self.all_values_rb)
|
|
842
|
+
|
|
843
|
+
single_tp_layout = QHBoxLayout()
|
|
844
|
+
single_tp_layout.addWidget(self.single_timepoint_rb, 33)
|
|
845
|
+
single_tp_layout.addWidget(self.single_timepoint_slider, 66)
|
|
846
|
+
layout.addLayout(single_tp_layout)
|
|
847
|
+
|
|
848
|
+
ec_layout = QHBoxLayout()
|
|
849
|
+
ec_layout.addWidget(self.ec_rb, 33)
|
|
850
|
+
ec_layout.addWidget(self.ec_slider, 66)
|
|
851
|
+
layout.addLayout(ec_layout)
|
|
852
|
+
|
|
853
|
+
layout.addWidget(self.set_btn)
|
|
854
|
+
center_window(self.config_table_wg)
|
|
855
|
+
self.config_table_wg.show()
|
|
856
|
+
|
|
857
|
+
def activate_sliders(self):
|
|
858
|
+
if self.all_values_rb.isChecked():
|
|
859
|
+
self.single_timepoint_slider.setEnabled(False)
|
|
860
|
+
self.ec_slider.setEnabled(False)
|
|
861
|
+
elif self.single_timepoint_rb.isChecked():
|
|
862
|
+
self.single_timepoint_slider.setEnabled(True)
|
|
863
|
+
self.ec_slider.setEnabled(False)
|
|
864
|
+
elif self.ec_rb.isChecked():
|
|
865
|
+
self.ec_slider.setEnabled(True)
|
|
866
|
+
self.single_timepoint_slider.setEnabled(False)
|
|
867
|
+
|
|
868
|
+
def assemble_survival_data(self):
|
|
869
|
+
|
|
870
|
+
if self.plot_options[0].isChecked():
|
|
871
|
+
data = self.df_well_info
|
|
872
|
+
groupby = ['well_path']
|
|
873
|
+
if self.plot_options[1].isChecked():
|
|
874
|
+
data = self.df_pos_info
|
|
875
|
+
groupby = ['pos_path']
|
|
876
|
+
if self.plot_options[2].isChecked():
|
|
877
|
+
print('Not implemented yet... Please select "well" or "position" as grouping...')
|
|
878
|
+
return None
|
|
879
|
+
|
|
880
|
+
if self.all_values_rb.isChecked():
|
|
881
|
+
|
|
882
|
+
survival_table = []
|
|
883
|
+
tid=0
|
|
884
|
+
for name,group in data.groupby(groupby):
|
|
885
|
+
print(name)
|
|
886
|
+
if groupby[0]=="pos_path":
|
|
887
|
+
metadata = collect_experiment_metadata(pos_path=name[0])
|
|
888
|
+
elif groupby[0]=="well_path":
|
|
889
|
+
metadata = collect_experiment_metadata(well_path=name[0])
|
|
890
|
+
ks_estimator = group['survival_fit'].values[0]
|
|
891
|
+
if ks_estimator!=ks_estimator:
|
|
892
|
+
continue
|
|
893
|
+
timeline = list(ks_estimator.survival_function_.index)
|
|
894
|
+
survival = ks_estimator.survival_function_['KM_estimate'].values
|
|
895
|
+
lower_error = ks_estimator.confidence_interval_['KM_estimate_lower_0.95'].values
|
|
896
|
+
upper_error = ks_estimator.confidence_interval_['KM_estimate_upper_0.95'].values
|
|
897
|
+
for k in range(len(timeline)):
|
|
898
|
+
dico = metadata.copy()
|
|
899
|
+
dico.update({'TRACK_ID': tid,'FRAME': int(timeline[k] / self.parent_window.FrameToMin),'timeline': timeline[k], 'survival': survival[k], "event_fraction": 1-survival[k], 'KM_estimate_lower_0.95': lower_error[k], 'KM_estimate_upper_0.95': upper_error[k]})
|
|
900
|
+
survival_table.append(dico)
|
|
901
|
+
tid+=1
|
|
902
|
+
|
|
903
|
+
survival_table = pd.DataFrame(survival_table)
|
|
904
|
+
self.table = TableUI(survival_table, f"Survival data", plot_mode="plot_track_signals")
|
|
905
|
+
self.table.show()
|
|
906
|
+
|
|
907
|
+
elif self.single_timepoint_rb.isChecked():
|
|
908
|
+
|
|
909
|
+
survival_table = []
|
|
910
|
+
tid=0
|
|
911
|
+
for name,group in data.groupby(groupby):
|
|
912
|
+
print(name)
|
|
913
|
+
if groupby[0]=="pos_path":
|
|
914
|
+
metadata = collect_experiment_metadata(pos_path=name[0])
|
|
915
|
+
elif groupby[0]=="well_path":
|
|
916
|
+
metadata = collect_experiment_metadata(well_path=name[0])
|
|
917
|
+
ks_estimator = group['survival_fit'].values[0]
|
|
918
|
+
if ks_estimator!=ks_estimator:
|
|
919
|
+
continue
|
|
920
|
+
survival = ks_estimator.survival_function_at_times(self.single_timepoint_slider.value()).values[0]
|
|
921
|
+
dico = metadata.copy()
|
|
922
|
+
dico.update({'timepoint': self.single_timepoint_slider.value(), 'survival': survival, 'event_fraction': 1 - survival})
|
|
923
|
+
survival_table.append(dico)
|
|
924
|
+
tid+=1
|
|
925
|
+
|
|
926
|
+
survival_table = pd.DataFrame(survival_table)
|
|
927
|
+
self.table = TableUI(survival_table, f"Survival data", plot_mode="static")
|
|
928
|
+
self.table.show()
|
|
929
|
+
|
|
930
|
+
elif self.ec_rb.isChecked():
|
|
931
|
+
|
|
932
|
+
survival_table = []
|
|
933
|
+
tid=0
|
|
934
|
+
for name,group in data.groupby(groupby):
|
|
935
|
+
print(name)
|
|
936
|
+
if groupby[0]=="pos_path":
|
|
937
|
+
metadata = collect_experiment_metadata(pos_path=name[0])
|
|
938
|
+
elif groupby[0]=="well_path":
|
|
939
|
+
metadata = collect_experiment_metadata(well_path=name[0])
|
|
940
|
+
ks_estimator = group['survival_fit'].values[0]
|
|
941
|
+
if ks_estimator!=ks_estimator:
|
|
942
|
+
continue
|
|
943
|
+
survival = ks_estimator.survival_function_
|
|
944
|
+
ecN = qth_survival_times(float(self.ec_slider.value())/100.0, survival)
|
|
945
|
+
dico = metadata.copy()
|
|
946
|
+
dico.update({"qth": int(self.ec_slider.value()), f'EC{int(self.ec_slider.value())}% [min]': ecN})
|
|
947
|
+
survival_table.append(dico)
|
|
948
|
+
tid+=1
|
|
949
|
+
|
|
950
|
+
survival_table = pd.DataFrame(survival_table)
|
|
951
|
+
self.table = TableUI(survival_table, f"Survival data", plot_mode="static")
|
|
952
|
+
self.table.show()
|
celldetective/gui/gui_utils.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
from PyQt5.QtWidgets import QApplication, QMessageBox, QFrame, QSizePolicy, QWidget, QLineEdit, QListWidget, QVBoxLayout, QComboBox, \
|
|
3
3
|
QPushButton, QLabel, QHBoxLayout, QCheckBox, QFileDialog
|
|
4
|
-
from PyQt5.QtCore import Qt, QSize
|
|
4
|
+
from PyQt5.QtCore import Qt, QSize, QAbstractTableModel
|
|
5
5
|
from PyQt5.QtGui import QDoubleValidator, QIntValidator
|
|
6
6
|
|
|
7
7
|
from celldetective.gui import Styles
|
|
@@ -16,6 +16,95 @@ from inspect import getmembers, isfunction
|
|
|
16
16
|
from celldetective.filters import *
|
|
17
17
|
from os import sep
|
|
18
18
|
|
|
19
|
+
class PandasModel(QAbstractTableModel):
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
from https://stackoverflow.com/questions/31475965/fastest-way-to-populate-qtableview-from-pandas-data-frame
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, data):
|
|
26
|
+
QAbstractTableModel.__init__(self)
|
|
27
|
+
self._data = data
|
|
28
|
+
self.colors = dict()
|
|
29
|
+
|
|
30
|
+
def rowCount(self, parent=None):
|
|
31
|
+
return self._data.shape[0]
|
|
32
|
+
|
|
33
|
+
def columnCount(self, parent=None):
|
|
34
|
+
return self._data.shape[1]
|
|
35
|
+
|
|
36
|
+
def data(self, index, role=Qt.DisplayRole):
|
|
37
|
+
if index.isValid():
|
|
38
|
+
if role == Qt.DisplayRole:
|
|
39
|
+
return str(self._data.iloc[index.row(), index.column()])
|
|
40
|
+
if role == Qt.BackgroundRole:
|
|
41
|
+
color = self.colors.get((index.row(), index.column()))
|
|
42
|
+
if color is not None:
|
|
43
|
+
return color
|
|
44
|
+
return None
|
|
45
|
+
|
|
46
|
+
def headerData(self, rowcol, orientation, role):
|
|
47
|
+
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
|
48
|
+
return self._data.columns[rowcol]
|
|
49
|
+
if orientation == Qt.Vertical and role == Qt.DisplayRole:
|
|
50
|
+
return self._data.index[rowcol]
|
|
51
|
+
return None
|
|
52
|
+
|
|
53
|
+
def change_color(self, row, column, color):
|
|
54
|
+
ix = self.index(row, column)
|
|
55
|
+
self.colors[(row, column)] = color
|
|
56
|
+
self.dataChanged.emit(ix, ix, (Qt.BackgroundRole,))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class GenericOpColWidget(QWidget, Styles):
|
|
60
|
+
|
|
61
|
+
def __init__(self, parent_window, column=None, title=''):
|
|
62
|
+
|
|
63
|
+
super().__init__()
|
|
64
|
+
|
|
65
|
+
self.parent_window = parent_window
|
|
66
|
+
self.column = column
|
|
67
|
+
self.title = title
|
|
68
|
+
|
|
69
|
+
self.setWindowTitle(self.title)
|
|
70
|
+
# Create the QComboBox and add some items
|
|
71
|
+
|
|
72
|
+
self.layout = QVBoxLayout(self)
|
|
73
|
+
self.layout.setContentsMargins(30,30,30,30)
|
|
74
|
+
|
|
75
|
+
self.sublayout = QVBoxLayout()
|
|
76
|
+
|
|
77
|
+
self.measurements_cb = QComboBox()
|
|
78
|
+
self.measurements_cb.addItems(list(self.parent_window.data.columns))
|
|
79
|
+
if self.column is not None:
|
|
80
|
+
idx = self.measurements_cb.findText(self.column)
|
|
81
|
+
self.measurements_cb.setCurrentIndex(idx)
|
|
82
|
+
|
|
83
|
+
measurement_layout = QHBoxLayout()
|
|
84
|
+
measurement_layout.addWidget(QLabel('measurements: '), 25)
|
|
85
|
+
measurement_layout.addWidget(self.measurements_cb, 75)
|
|
86
|
+
self.sublayout.addLayout(measurement_layout)
|
|
87
|
+
|
|
88
|
+
self.layout.addLayout(self.sublayout)
|
|
89
|
+
|
|
90
|
+
self.submit_btn = QPushButton('Compute')
|
|
91
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
92
|
+
self.submit_btn.clicked.connect(self.launch_operation)
|
|
93
|
+
self.layout.addWidget(self.submit_btn, 30)
|
|
94
|
+
|
|
95
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
96
|
+
center_window(self)
|
|
97
|
+
|
|
98
|
+
def launch_operation(self):
|
|
99
|
+
|
|
100
|
+
self.compute()
|
|
101
|
+
self.parent_window.model = PandasModel(self.parent_window.data)
|
|
102
|
+
self.parent_window.table_view.setModel(self.parent_window.model)
|
|
103
|
+
self.close()
|
|
104
|
+
|
|
105
|
+
def compute(self):
|
|
106
|
+
pass
|
|
107
|
+
|
|
19
108
|
|
|
20
109
|
class QuickSliderLayout(QHBoxLayout):
|
|
21
110
|
|
|
@@ -10,7 +10,7 @@ from superqt import QLabeledDoubleSlider
|
|
|
10
10
|
from superqt.fonticon import icon
|
|
11
11
|
from fonticon_mdi6 import MDI6
|
|
12
12
|
|
|
13
|
-
from celldetective.gui.thresholds_gui import ThresholdSpot
|
|
13
|
+
#from celldetective.gui.thresholds_gui import ThresholdSpot
|
|
14
14
|
from celldetective.utils import extract_experiment_channels, get_software_location
|
|
15
15
|
from celldetective.io import load_frames, auto_load_number_of_frames
|
|
16
16
|
from celldetective.measure import compute_haralick_features
|
|
@@ -26,7 +26,7 @@ from tifffile import imread
|
|
|
26
26
|
from pathlib import Path
|
|
27
27
|
import gc
|
|
28
28
|
|
|
29
|
-
from celldetective.gui.viewers import CellEdgeVisualizer
|
|
29
|
+
from celldetective.gui.viewers import CellEdgeVisualizer, SpotDetectionVisualizer
|
|
30
30
|
from celldetective.gui.layouts import ProtocolDesignerLayout, BackgroundFitCorrectionLayout, LocalCorrectionLayout
|
|
31
31
|
from celldetective.gui.gui_utils import ThresholdLineEdit
|
|
32
32
|
from celldetective.gui import Styles
|
|
@@ -951,51 +951,54 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
951
951
|
|
|
952
952
|
layout.addWidget(self.threshold_lbl, 4, 0)
|
|
953
953
|
layout.addWidget(self.threshold_value, 4, 1)
|
|
954
|
-
self.preview_spot = QPushButton('Preview')
|
|
955
|
-
self.preview_spot.clicked.connect(self.spot_preview)
|
|
956
|
-
self.preview_spot.setStyleSheet(self.button_style_sheet_2)
|
|
957
|
-
layout.addWidget(self.preview_spot, 5, 0, 1, 2)
|
|
958
|
-
self.spot_channel.setEnabled(False)
|
|
959
|
-
self.spot_channel_lbl.setEnabled(False)
|
|
960
|
-
self.diameter_value.setEnabled(False)
|
|
961
|
-
self.diameter_lbl.setEnabled(False)
|
|
962
|
-
self.threshold_value.setEnabled(False)
|
|
963
|
-
self.threshold_lbl.setEnabled(False)
|
|
964
|
-
self.preview_spot.setEnabled(False)
|
|
965
954
|
|
|
955
|
+
self.spot_viewer_btn = QPushButton()
|
|
956
|
+
self.spot_viewer_btn.clicked.connect(self.spot_preview)
|
|
957
|
+
self.spot_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
958
|
+
self.spot_viewer_btn.setStyleSheet(self.button_select_all)
|
|
959
|
+
self.spot_viewer_btn.setToolTip('Set detection parameters visually.')
|
|
960
|
+
layout.addWidget(self.spot_viewer_btn, 1, 1, 1, 1, alignment=Qt.AlignRight)
|
|
961
|
+
|
|
962
|
+
self.spot_detection_widgets = [self.spot_channel, self.spot_channel_lbl, self.diameter_value, self.diameter_lbl, self.threshold_value, self.threshold_lbl, self.spot_viewer_btn]
|
|
963
|
+
for wg in self.spot_detection_widgets:
|
|
964
|
+
wg.setEnabled(False)
|
|
966
965
|
|
|
967
966
|
def enable_spot_preview(self):
|
|
968
967
|
|
|
969
968
|
diam = self.diameter_value.text().replace(',','').replace('.','')
|
|
970
969
|
thresh = self.threshold_value.text().replace(',','').replace('.','')
|
|
971
970
|
if diam.isnumeric() and thresh.isnumeric():
|
|
972
|
-
self.
|
|
971
|
+
self.spot_viewer_btn.setEnabled(True)
|
|
973
972
|
else:
|
|
974
|
-
self.
|
|
973
|
+
self.spot_viewer_btn.setEnabled(False)
|
|
975
974
|
|
|
976
975
|
def spot_preview(self):
|
|
977
976
|
self.locate_image()
|
|
978
977
|
if self.test_frame is not None:
|
|
979
978
|
self.locate_mask()
|
|
980
979
|
if self.test_mask is not None:
|
|
981
|
-
self.spot_visual =
|
|
982
|
-
|
|
980
|
+
self.spot_visual = SpotDetectionVisualizer(frame_slider=True,
|
|
981
|
+
contrast_slider=True,
|
|
982
|
+
cell_type=self.mode,
|
|
983
|
+
channel_cb=True,
|
|
984
|
+
channel_names = self.channel_names,
|
|
985
|
+
stack_path=self.current_stack,
|
|
986
|
+
n_channels=len(self.channel_names),
|
|
987
|
+
target_channel=self.spot_channel.currentIndex(),
|
|
988
|
+
window_title='Detect spots',
|
|
989
|
+
parent_channel_cb=self.spot_channel,
|
|
990
|
+
parent_diameter_le=self.diameter_value,
|
|
991
|
+
parent_threshold_le=self.threshold_value,
|
|
992
|
+
PxToUm = 1,)
|
|
993
|
+
self.spot_visual.show()
|
|
994
|
+
#self.spot_visual = ThresholdSpot(current_channel=self.spot_channel.currentIndex(), img=self.test_frame,
|
|
995
|
+
# mask=self.test_mask, parent_window=self)
|
|
983
996
|
|
|
984
997
|
def enable_spot_detection(self):
|
|
985
|
-
if self.spot_check.isChecked():
|
|
986
|
-
self.spot_channel.setEnabled(True)
|
|
987
|
-
self.spot_channel_lbl.setEnabled(True)
|
|
988
|
-
self.diameter_value.setEnabled(True)
|
|
989
|
-
self.diameter_lbl.setEnabled(True)
|
|
990
|
-
self.threshold_value.setEnabled(True)
|
|
991
|
-
self.threshold_lbl.setEnabled(True)
|
|
992
|
-
self.preview_spot.setEnabled(True)
|
|
993
998
|
|
|
999
|
+
if self.spot_check.isChecked():
|
|
1000
|
+
for wg in self.spot_detection_widgets:
|
|
1001
|
+
wg.setEnabled(True)
|
|
994
1002
|
else:
|
|
995
|
-
self.
|
|
996
|
-
|
|
997
|
-
self.diameter_value.setEnabled(False)
|
|
998
|
-
self.diameter_lbl.setEnabled(False)
|
|
999
|
-
self.threshold_value.setEnabled(False)
|
|
1000
|
-
self.threshold_lbl.setEnabled(False)
|
|
1001
|
-
self.preview_spot.setEnabled(False)
|
|
1003
|
+
for wg in self.spot_detection_widgets:
|
|
1004
|
+
wg.setEnabled(False)
|
|
@@ -96,7 +96,7 @@ class ConfigSignalPlot(QWidget, Styles):
|
|
|
96
96
|
main_layout.addWidget(panel_title, alignment=Qt.AlignCenter)
|
|
97
97
|
|
|
98
98
|
labels = [QLabel('population: '), QLabel('class: '), QLabel('time of\ninterest: '), QLabel('cmap: ')]
|
|
99
|
-
self.cb_options = [['targets','effectors'],[
|
|
99
|
+
self.cb_options = [['targets','effectors'],[], [], []]
|
|
100
100
|
self.cbs = [QComboBox() for i in range(len(labels))]
|
|
101
101
|
self.cbs[-1] = QColormapComboBox()
|
|
102
102
|
|
|
@@ -183,6 +183,11 @@ class ConfigSignalPlot(QWidget, Styles):
|
|
|
183
183
|
self.auto_close = True
|
|
184
184
|
return None
|
|
185
185
|
|
|
186
|
+
if 'class' in self.all_columns:
|
|
187
|
+
class_columns.append("class")
|
|
188
|
+
if 't0' in self.all_columns:
|
|
189
|
+
time_columns.append('t0')
|
|
190
|
+
|
|
186
191
|
self.cbs[2].clear()
|
|
187
192
|
self.cbs[2].addItems(np.unique(self.cb_options[2]+time_columns))
|
|
188
193
|
|
|
@@ -339,7 +344,7 @@ class ConfigSignalPlot(QWidget, Styles):
|
|
|
339
344
|
|
|
340
345
|
for block,movie_group in self.df.groupby(['well','position']):
|
|
341
346
|
|
|
342
|
-
well_signal_mean, well_std_mean, timeline_all, matrix_all = mean_signal(movie_group, self.feature_selected, class_col, time_col=time_col, class_value=
|
|
347
|
+
well_signal_mean, well_std_mean, timeline_all, matrix_all = mean_signal(movie_group, self.feature_selected, class_col, time_col=time_col, class_value=None, return_matrix=True, forced_max_duration=max_time)
|
|
343
348
|
well_signal_event, well_std_event, timeline_event, matrix_event = mean_signal(movie_group, self.feature_selected, class_col, time_col=time_col, class_value=[0], return_matrix=True, forced_max_duration=max_time)
|
|
344
349
|
well_signal_no_event, well_std_no_event, timeline_no_event, matrix_no_event = mean_signal(movie_group, self.feature_selected, class_col, time_col=time_col, class_value=[1], return_matrix=True, forced_max_duration=max_time)
|
|
345
350
|
self.mean_plots_timeline = timeline_all
|
|
@@ -354,7 +359,7 @@ class ConfigSignalPlot(QWidget, Styles):
|
|
|
354
359
|
# Per well
|
|
355
360
|
for well,well_group in self.df.groupby('well'):
|
|
356
361
|
|
|
357
|
-
well_signal_mean, well_std_mean, timeline_all, matrix_all = mean_signal(well_group, self.feature_selected, class_col, time_col=time_col, class_value=
|
|
362
|
+
well_signal_mean, well_std_mean, timeline_all, matrix_all = mean_signal(well_group, self.feature_selected, class_col, time_col=time_col, class_value=None, return_matrix=True, forced_max_duration=max_time)
|
|
358
363
|
well_signal_event, well_std_event, timeline_event, matrix_event = mean_signal(well_group, self.feature_selected, class_col, time_col=time_col, class_value=[0], return_matrix=True, forced_max_duration=max_time)
|
|
359
364
|
well_signal_no_event, well_std_no_event, timeline_no_event, matrix_no_event = mean_signal(well_group, self.feature_selected, class_col, time_col=time_col, class_value=[1], return_matrix=True, forced_max_duration=max_time)
|
|
360
365
|
|