celldetective 1.3.0.post1__py3-none-any.whl → 1.3.2__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 +88 -11
- celldetective/extra_properties.py +5 -1
- celldetective/gui/InitWindow.py +35 -9
- celldetective/gui/classifier_widget.py +99 -23
- celldetective/gui/control_panel.py +7 -1
- celldetective/gui/generic_signal_plot.py +161 -2
- celldetective/gui/gui_utils.py +90 -1
- celldetective/gui/layouts.py +128 -7
- celldetective/gui/measurement_options.py +3 -3
- celldetective/gui/plot_signals_ui.py +8 -3
- celldetective/gui/process_block.py +77 -32
- celldetective/gui/retrain_segmentation_model_options.py +24 -10
- celldetective/gui/signal_annotator.py +53 -26
- celldetective/gui/signal_annotator2.py +17 -30
- celldetective/gui/survival_ui.py +24 -3
- celldetective/gui/tableUI.py +300 -183
- celldetective/gui/viewers.py +263 -3
- celldetective/io.py +56 -3
- celldetective/links/zenodo.json +136 -123
- celldetective/measure.py +3 -0
- celldetective/models/tracking_configs/biased_motion.json +68 -0
- celldetective/models/tracking_configs/no_z_motion.json +202 -0
- celldetective/neighborhood.py +154 -69
- celldetective/preprocessing.py +172 -3
- celldetective/relative_measurements.py +128 -4
- celldetective/scripts/measure_cells.py +3 -3
- celldetective/signals.py +212 -215
- celldetective/tracking.py +7 -3
- celldetective/utils.py +22 -6
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/METADATA +3 -3
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/RECORD +36 -34
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/WHEEL +1 -1
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/LICENSE +0 -0
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.0.post1.dist-info → celldetective-1.3.2.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
celldetective/gui/layouts.py
CHANGED
|
@@ -8,7 +8,7 @@ from superqt import QLabeledRangeSlider, QLabeledDoubleSlider, QLabeledSlider, Q
|
|
|
8
8
|
from superqt.fonticon import icon
|
|
9
9
|
from fonticon_mdi6 import MDI6
|
|
10
10
|
from celldetective.utils import _extract_channel_indices_from_config
|
|
11
|
-
from celldetective.gui.viewers import ThresholdedStackVisualizer, CellEdgeVisualizer, StackVisualizer, CellSizeViewer
|
|
11
|
+
from celldetective.gui.viewers import ThresholdedStackVisualizer, CellEdgeVisualizer, StackVisualizer, CellSizeViewer, ChannelOffsetViewer
|
|
12
12
|
from celldetective.gui import Styles
|
|
13
13
|
from celldetective.preprocessing import correct_background_model, correct_background_model_free, estimate_background_per_condition
|
|
14
14
|
from functools import partial
|
|
@@ -535,7 +535,6 @@ class BackgroundFitCorrectionLayout(QGridLayout, Styles):
|
|
|
535
535
|
self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
|
|
536
536
|
self.threshold_viewer_btn.setToolTip('Set the threshold graphically.')
|
|
537
537
|
|
|
538
|
-
|
|
539
538
|
self.model_lbl = QLabel('Model: ')
|
|
540
539
|
self.model_lbl.setToolTip('2D model to fit the background with.')
|
|
541
540
|
self.models_cb = QComboBox()
|
|
@@ -560,6 +559,8 @@ class BackgroundFitCorrectionLayout(QGridLayout, Styles):
|
|
|
560
559
|
self.corrected_stack_viewer,
|
|
561
560
|
self.add_correction_btn
|
|
562
561
|
])
|
|
562
|
+
|
|
563
|
+
|
|
563
564
|
def add_to_layout(self):
|
|
564
565
|
|
|
565
566
|
channel_layout = QHBoxLayout()
|
|
@@ -572,6 +573,7 @@ class BackgroundFitCorrectionLayout(QGridLayout, Styles):
|
|
|
572
573
|
subthreshold_layout = QHBoxLayout()
|
|
573
574
|
subthreshold_layout.addWidget(self.threshold_le, 95)
|
|
574
575
|
subthreshold_layout.addWidget(self.threshold_viewer_btn, 5)
|
|
576
|
+
|
|
575
577
|
threshold_layout.addLayout(subthreshold_layout, 75)
|
|
576
578
|
self.addLayout(threshold_layout, 1, 0, 1, 3)
|
|
577
579
|
|
|
@@ -878,18 +880,26 @@ class ProtocolDesignerLayout(QVBoxLayout, Styles):
|
|
|
878
880
|
|
|
879
881
|
def generate_layout(self):
|
|
880
882
|
|
|
883
|
+
self.correction_layout = QVBoxLayout()
|
|
884
|
+
|
|
885
|
+
self.background_correction_layout = QVBoxLayout()
|
|
886
|
+
self.background_correction_layout.setContentsMargins(0,0,0,0)
|
|
881
887
|
self.title_layout = QHBoxLayout()
|
|
882
888
|
self.title_layout.addWidget(self.title_lbl, 100, alignment=Qt.AlignCenter)
|
|
889
|
+
self.background_correction_layout.addLayout(self.title_layout)
|
|
890
|
+
self.background_correction_layout.addWidget(self.tabs)
|
|
891
|
+
self.correction_layout.addLayout(self.background_correction_layout)
|
|
892
|
+
|
|
893
|
+
self.addLayout(self.correction_layout)
|
|
883
894
|
|
|
884
|
-
self.
|
|
885
|
-
self.addWidget(self.tabs)
|
|
886
|
-
|
|
895
|
+
self.list_layout = QVBoxLayout()
|
|
887
896
|
list_header_layout = QHBoxLayout()
|
|
888
897
|
list_header_layout.addWidget(self.protocol_list_lbl)
|
|
889
898
|
list_header_layout.addWidget(self.delete_protocol_btn, alignment=Qt.AlignRight)
|
|
890
|
-
self.addLayout(list_header_layout)
|
|
899
|
+
self.list_layout.addLayout(list_header_layout)
|
|
900
|
+
self.list_layout.addWidget(self.protocol_list)
|
|
891
901
|
|
|
892
|
-
self.
|
|
902
|
+
self.addLayout(self.list_layout)
|
|
893
903
|
|
|
894
904
|
|
|
895
905
|
def remove_protocol_from_list(self):
|
|
@@ -899,6 +909,117 @@ class ProtocolDesignerLayout(QVBoxLayout, Styles):
|
|
|
899
909
|
del self.protocols[current_item]
|
|
900
910
|
self.protocol_list.takeItem(current_item)
|
|
901
911
|
|
|
912
|
+
class ChannelOffsetOptionsLayout(QVBoxLayout, Styles):
|
|
913
|
+
|
|
914
|
+
def __init__(self, parent_window=None, *args, **kwargs):
|
|
915
|
+
|
|
916
|
+
super().__init__(*args, **kwargs)
|
|
917
|
+
|
|
918
|
+
self.parent_window = parent_window
|
|
919
|
+
if hasattr(self.parent_window.parent_window, 'exp_config'):
|
|
920
|
+
self.attr_parent = self.parent_window.parent_window
|
|
921
|
+
else:
|
|
922
|
+
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
923
|
+
|
|
924
|
+
self.channel_names = self.attr_parent.exp_channels
|
|
925
|
+
|
|
926
|
+
self.setContentsMargins(15,15,15,15)
|
|
927
|
+
self.generate_widgets()
|
|
928
|
+
self.add_to_layout()
|
|
929
|
+
|
|
930
|
+
def generate_widgets(self):
|
|
931
|
+
|
|
932
|
+
self.channel_lbl = QLabel('Channel: ')
|
|
933
|
+
self.channels_cb = QComboBox()
|
|
934
|
+
self.channels_cb.addItems(self.channel_names)
|
|
935
|
+
|
|
936
|
+
self.shift_lbl = QLabel('Shift: ')
|
|
937
|
+
self.shift_h_lbl = QLabel('(h): ')
|
|
938
|
+
self.shift_v_lbl = QLabel('(v): ')
|
|
939
|
+
|
|
940
|
+
self.set_shift_btn = QPushButton()
|
|
941
|
+
self.set_shift_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
942
|
+
self.set_shift_btn.setStyleSheet(self.button_select_all)
|
|
943
|
+
self.set_shift_btn.setToolTip('Set the channel shift.')
|
|
944
|
+
self.set_shift_btn.clicked.connect(self.open_offset_viewer)
|
|
945
|
+
|
|
946
|
+
self.add_correction_btn = QPushButton('Add correction')
|
|
947
|
+
self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
|
|
948
|
+
self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
|
|
949
|
+
self.add_correction_btn.setToolTip('Add correction.')
|
|
950
|
+
self.add_correction_btn.setIconSize(QSize(25, 25))
|
|
951
|
+
self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
|
|
952
|
+
|
|
953
|
+
self.vertical_shift_le = ThresholdLineEdit(init_value=0, connected_buttons=[self.add_correction_btn],placeholder='vertical shift [pixels]', value_type='float')
|
|
954
|
+
self.horizontal_shift_le = ThresholdLineEdit(init_value=0, connected_buttons=[self.add_correction_btn],placeholder='vertical shift [pixels]', value_type='float')
|
|
955
|
+
|
|
956
|
+
def add_to_layout(self):
|
|
957
|
+
|
|
958
|
+
channel_ch_hbox = QHBoxLayout()
|
|
959
|
+
channel_ch_hbox.addWidget(self.channel_lbl, 25)
|
|
960
|
+
channel_ch_hbox.addWidget(self.channels_cb, 75)
|
|
961
|
+
self.addLayout(channel_ch_hbox)
|
|
962
|
+
|
|
963
|
+
shift_hbox = QHBoxLayout()
|
|
964
|
+
shift_hbox.addWidget(self.shift_lbl, 25)
|
|
965
|
+
|
|
966
|
+
shift_subhbox = QHBoxLayout()
|
|
967
|
+
shift_subhbox.addWidget(self.shift_h_lbl, 10)
|
|
968
|
+
shift_subhbox.addWidget(self.horizontal_shift_le, 75//2)
|
|
969
|
+
shift_subhbox.addWidget(self.shift_v_lbl, 10)
|
|
970
|
+
shift_subhbox.addWidget(self.vertical_shift_le, 75//2)
|
|
971
|
+
shift_subhbox.addWidget(self.set_shift_btn, 5)
|
|
972
|
+
|
|
973
|
+
shift_hbox.addLayout(shift_subhbox, 75)
|
|
974
|
+
self.addLayout(shift_hbox)
|
|
975
|
+
|
|
976
|
+
btn_hbox = QHBoxLayout()
|
|
977
|
+
btn_hbox.addWidget(self.add_correction_btn, 95)
|
|
978
|
+
self.addLayout(btn_hbox)
|
|
979
|
+
|
|
980
|
+
def add_instructions_to_parent_list(self):
|
|
981
|
+
|
|
982
|
+
self.generate_instructions()
|
|
983
|
+
self.parent_window.protocol_layout.protocols.append(self.instructions)
|
|
984
|
+
correction_description = ""
|
|
985
|
+
for index, (key, value) in enumerate(self.instructions.items()):
|
|
986
|
+
if index > 0:
|
|
987
|
+
correction_description += ", "
|
|
988
|
+
correction_description += str(key) + " : " + str(value)
|
|
989
|
+
self.parent_window.protocol_layout.protocol_list.addItem(correction_description)
|
|
990
|
+
|
|
991
|
+
def generate_instructions(self):
|
|
992
|
+
|
|
993
|
+
self.instructions = {
|
|
994
|
+
"correction_type": "offset",
|
|
995
|
+
"target_channel": self.channels_cb.currentText(),
|
|
996
|
+
"correction_horizontal": self.horizontal_shift_le.get_threshold(),
|
|
997
|
+
"correction_vertical": self.vertical_shift_le.get_threshold(),
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
|
|
1001
|
+
def set_target_channel(self):
|
|
1002
|
+
|
|
1003
|
+
channel_indices = _extract_channel_indices_from_config(self.attr_parent.exp_config, [self.channels_cb.currentText()])
|
|
1004
|
+
self.target_channel = channel_indices[0]
|
|
1005
|
+
|
|
1006
|
+
def open_offset_viewer(self):
|
|
1007
|
+
|
|
1008
|
+
self.attr_parent.locate_image()
|
|
1009
|
+
self.set_target_channel()
|
|
1010
|
+
|
|
1011
|
+
if self.attr_parent.current_stack is not None:
|
|
1012
|
+
self.viewer = ChannelOffsetViewer(
|
|
1013
|
+
parent_window = self,
|
|
1014
|
+
stack_path=self.attr_parent.current_stack,
|
|
1015
|
+
channel_names=self.attr_parent.exp_channels,
|
|
1016
|
+
n_channels=len(self.channel_names),
|
|
1017
|
+
channel_cb=True,
|
|
1018
|
+
target_channel=self.target_channel,
|
|
1019
|
+
window_title='offset viewer',
|
|
1020
|
+
)
|
|
1021
|
+
self.viewer.show()
|
|
1022
|
+
|
|
902
1023
|
|
|
903
1024
|
class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
|
|
904
1025
|
|
|
@@ -154,7 +154,7 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
154
154
|
|
|
155
155
|
grid = QGridLayout(self.iso_frame)
|
|
156
156
|
|
|
157
|
-
self.iso_lbl = QLabel("
|
|
157
|
+
self.iso_lbl = QLabel("Position-based measurements".upper())
|
|
158
158
|
self.iso_lbl.setStyleSheet("""
|
|
159
159
|
font-weight: bold;
|
|
160
160
|
padding: 0px;
|
|
@@ -171,7 +171,7 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
171
171
|
|
|
172
172
|
grid = QGridLayout(self.features_frame)
|
|
173
173
|
|
|
174
|
-
self.feature_lbl = QLabel("
|
|
174
|
+
self.feature_lbl = QLabel("Mask-based measurements".upper())
|
|
175
175
|
self.feature_lbl.setStyleSheet("""
|
|
176
176
|
font-weight: bold;
|
|
177
177
|
padding: 0px;
|
|
@@ -262,7 +262,7 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
262
262
|
self.add_feature_btn.setToolTip("Add feature")
|
|
263
263
|
self.add_feature_btn.setIconSize(QSize(20, 20))
|
|
264
264
|
|
|
265
|
-
self.features_list = ListWidget(FeatureChoice, initial_features=['area', '
|
|
265
|
+
self.features_list = ListWidget(FeatureChoice, initial_features=['area', 'intensity_nanmean', ])
|
|
266
266
|
|
|
267
267
|
self.del_feature_btn.clicked.connect(self.features_list.removeSel)
|
|
268
268
|
self.add_feature_btn.clicked.connect(self.features_list.addItem)
|
|
@@ -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
|
|