celldetective 1.1.1__py3-none-any.whl → 1.1.1.post3__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/extra_properties.py +1 -1
- celldetective/filters.py +39 -11
- celldetective/gui/classifier_widget.py +56 -7
- celldetective/gui/layouts.py +246 -3
- celldetective/gui/retrain_segmentation_model_options.py +66 -164
- celldetective/gui/retrain_signal_model_options.py +18 -164
- celldetective/gui/tableUI.py +175 -61
- celldetective/gui/thresholds_gui.py +44 -5
- celldetective/gui/viewers.py +1 -1
- celldetective/io.py +29 -14
- celldetective/preprocessing.py +23 -11
- celldetective/scripts/segment_cells.py +13 -4
- celldetective/scripts/train_segmentation_model.py +11 -22
- celldetective/segmentation.py +16 -12
- celldetective/utils.py +6 -2
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post3.dist-info}/METADATA +1 -1
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post3.dist-info}/RECORD +21 -21
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post3.dist-info}/WHEEL +1 -1
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post3.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post3.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.dist-info → celldetective-1.1.1.post3.dist-info}/top_level.txt +0 -0
celldetective/gui/tableUI.py
CHANGED
|
@@ -12,6 +12,8 @@ import matplotlib.cm as mcm
|
|
|
12
12
|
import os
|
|
13
13
|
from celldetective.gui import Styles
|
|
14
14
|
from superqt import QColormapComboBox, QLabeledSlider, QSearchableComboBox
|
|
15
|
+
from superqt.fonticon import icon
|
|
16
|
+
from fonticon_mdi6 import MDI6
|
|
15
17
|
from math import floor
|
|
16
18
|
|
|
17
19
|
from matplotlib import colormaps
|
|
@@ -72,6 +74,92 @@ class QueryWidget(QWidget):
|
|
|
72
74
|
print(e)
|
|
73
75
|
return None
|
|
74
76
|
|
|
77
|
+
|
|
78
|
+
class MergeOneHotWidget(QWidget, Styles):
|
|
79
|
+
|
|
80
|
+
def __init__(self, parent_window, selected_columns=None):
|
|
81
|
+
|
|
82
|
+
super().__init__()
|
|
83
|
+
self.parent_window = parent_window
|
|
84
|
+
self.selected_columns = selected_columns
|
|
85
|
+
|
|
86
|
+
self.setWindowTitle("Merge one-hot encoded columns...")
|
|
87
|
+
# Create the QComboBox and add some items
|
|
88
|
+
center_window(self)
|
|
89
|
+
|
|
90
|
+
self.layout = QVBoxLayout(self)
|
|
91
|
+
self.layout.setContentsMargins(30,30,30,30)
|
|
92
|
+
|
|
93
|
+
if self.selected_columns is not None:
|
|
94
|
+
n_cols = len(self.selected_columns)
|
|
95
|
+
else:
|
|
96
|
+
n_cols = 2
|
|
97
|
+
|
|
98
|
+
name_hbox = QHBoxLayout()
|
|
99
|
+
name_hbox.addWidget(QLabel('New categorical column: '), 33)
|
|
100
|
+
self.new_col_le = QLineEdit()
|
|
101
|
+
self.new_col_le.setText('categorical_')
|
|
102
|
+
self.new_col_le.textChanged.connect(self.allow_merge)
|
|
103
|
+
name_hbox.addWidget(self.new_col_le, 66)
|
|
104
|
+
self.layout.addLayout(name_hbox)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
self.layout.addWidget(QLabel('Source columns: '))
|
|
108
|
+
|
|
109
|
+
self.cbs = [QSearchableComboBox() for i in range(n_cols)]
|
|
110
|
+
self.cbs_layout = QVBoxLayout()
|
|
111
|
+
|
|
112
|
+
for i in range(n_cols):
|
|
113
|
+
lay = QHBoxLayout()
|
|
114
|
+
lay.addWidget(QLabel(f'column {i}: '), 33)
|
|
115
|
+
self.cbs[i].addItems(['--']+list(self.parent_window.data.columns))
|
|
116
|
+
if self.selected_columns is not None:
|
|
117
|
+
self.cbs[i].setCurrentText(self.selected_columns[i])
|
|
118
|
+
lay.addWidget(self.cbs[i], 66)
|
|
119
|
+
self.cbs_layout.addLayout(lay)
|
|
120
|
+
|
|
121
|
+
self.layout.addLayout(self.cbs_layout)
|
|
122
|
+
|
|
123
|
+
hbox = QHBoxLayout()
|
|
124
|
+
self.add_col_btn = QPushButton('Add column')
|
|
125
|
+
self.add_col_btn.clicked.connect(self.add_col)
|
|
126
|
+
self.add_col_btn.setStyleSheet(self.button_add)
|
|
127
|
+
self.add_col_btn.setIcon(icon(MDI6.plus,color="black"))
|
|
128
|
+
|
|
129
|
+
hbox.addWidget(QLabel(''), 50)
|
|
130
|
+
hbox.addWidget(self.add_col_btn, 50, alignment=Qt.AlignRight)
|
|
131
|
+
self.layout.addLayout(hbox)
|
|
132
|
+
|
|
133
|
+
self.submit_btn = QPushButton('Merge')
|
|
134
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
135
|
+
self.submit_btn.clicked.connect(self.merge_cols)
|
|
136
|
+
self.layout.addWidget(self.submit_btn, 30)
|
|
137
|
+
|
|
138
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
139
|
+
|
|
140
|
+
def add_col(self):
|
|
141
|
+
self.cbs.append(QSearchableComboBox())
|
|
142
|
+
self.cbs[-1].addItems(['--']+list(self.parent_window.data.columns))
|
|
143
|
+
lay = QHBoxLayout()
|
|
144
|
+
lay.addWidget(QLabel(f'column {len(self.cbs)-1}: '), 33)
|
|
145
|
+
lay.addWidget(self.cbs[-1], 66)
|
|
146
|
+
self.cbs_layout.addLayout(lay)
|
|
147
|
+
|
|
148
|
+
def merge_cols(self):
|
|
149
|
+
|
|
150
|
+
self.parent_window.data[self.new_col_le.text()] = self.parent_window.data.loc[:,list(self.selected_columns)].idxmax(axis=1)
|
|
151
|
+
self.parent_window.model = PandasModel(self.parent_window.data)
|
|
152
|
+
self.parent_window.table_view.setModel(self.parent_window.model)
|
|
153
|
+
self.close()
|
|
154
|
+
|
|
155
|
+
def allow_merge(self):
|
|
156
|
+
|
|
157
|
+
if self.new_col_le.text()=='':
|
|
158
|
+
self.submit_btn.setEnabled(False)
|
|
159
|
+
else:
|
|
160
|
+
self.submit_btn.setEnabled(True)
|
|
161
|
+
|
|
162
|
+
|
|
75
163
|
class DifferentiateColWidget(QWidget, Styles):
|
|
76
164
|
|
|
77
165
|
def __init__(self, parent_window, column=None):
|
|
@@ -232,6 +320,11 @@ class TableUI(QMainWindow, Styles):
|
|
|
232
320
|
self.plot_action.setShortcut("Ctrl+p")
|
|
233
321
|
self.fileMenu.addAction(self.plot_action)
|
|
234
322
|
|
|
323
|
+
self.plot_inst_action = QAction("&Plot instantaneous...", self)
|
|
324
|
+
self.plot_inst_action.triggered.connect(self.plot_instantaneous)
|
|
325
|
+
self.plot_inst_action.setShortcut("Ctrl+i")
|
|
326
|
+
self.fileMenu.addAction(self.plot_inst_action)
|
|
327
|
+
|
|
235
328
|
self.groupby_action = QAction("&Group by tracks...", self)
|
|
236
329
|
self.groupby_action.triggered.connect(self.set_projection_mode_tracks)
|
|
237
330
|
self.groupby_action.setShortcut("Ctrl+g")
|
|
@@ -259,7 +352,12 @@ class TableUI(QMainWindow, Styles):
|
|
|
259
352
|
self.derivative_action = QAction('&Differentiate...', self)
|
|
260
353
|
self.derivative_action.triggered.connect(self.differenciate_selected_feature)
|
|
261
354
|
self.derivative_action.setShortcut("Ctrl+D")
|
|
262
|
-
self.mathMenu.addAction(self.derivative_action)
|
|
355
|
+
self.mathMenu.addAction(self.derivative_action)
|
|
356
|
+
|
|
357
|
+
self.onehot_action = QAction('&One hot to categorical...', self)
|
|
358
|
+
self.onehot_action.triggered.connect(self.transform_one_hot_cols_to_categorical)
|
|
359
|
+
#self.onehot_action.setShortcut("Ctrl+D")
|
|
360
|
+
self.mathMenu.addAction(self.onehot_action)
|
|
263
361
|
|
|
264
362
|
def delete_columns(self):
|
|
265
363
|
|
|
@@ -329,6 +427,19 @@ class TableUI(QMainWindow, Styles):
|
|
|
329
427
|
self.diffWidget = DifferentiateColWidget(self, selected_col)
|
|
330
428
|
self.diffWidget.show()
|
|
331
429
|
|
|
430
|
+
def transform_one_hot_cols_to_categorical(self):
|
|
431
|
+
|
|
432
|
+
x = self.table_view.selectedIndexes()
|
|
433
|
+
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
434
|
+
if list(col_idx):
|
|
435
|
+
cols = np.array(list(self.data.columns))
|
|
436
|
+
selected_cols = cols[col_idx]
|
|
437
|
+
else:
|
|
438
|
+
selected_cols = None
|
|
439
|
+
|
|
440
|
+
self.mergewidget = MergeOneHotWidget(self, selected_columns=selected_cols)
|
|
441
|
+
self.mergewidget.show()
|
|
442
|
+
|
|
332
443
|
|
|
333
444
|
def groupby_time_table(self):
|
|
334
445
|
|
|
@@ -408,7 +519,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
408
519
|
self.status_operation.setEnabled(False)
|
|
409
520
|
self.status_operation.addItems(['mean','median','min','max', 'prod', 'sum'])
|
|
410
521
|
|
|
411
|
-
status_cols = np.array([c.startswith('status_') for c in cols])
|
|
522
|
+
status_cols = np.array([c.startswith('status_') or c.startswith('group_') for c in cols])
|
|
412
523
|
status_cols = list(cols[status_cols])
|
|
413
524
|
if 'status' in list(self.data.columns):
|
|
414
525
|
status_cols.append('status')
|
|
@@ -472,6 +583,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
472
583
|
layout.addWidget(QLabel('Representations: '))
|
|
473
584
|
self.hist_check = QCheckBox('histogram')
|
|
474
585
|
self.kde_check = QCheckBox('KDE plot')
|
|
586
|
+
self.count_check = QCheckBox('Countplot')
|
|
475
587
|
self.ecdf_check = QCheckBox('ECDF plot')
|
|
476
588
|
self.swarm_check = QCheckBox('swarm')
|
|
477
589
|
self.violin_check = QCheckBox('violin')
|
|
@@ -481,6 +593,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
481
593
|
|
|
482
594
|
layout.addWidget(self.hist_check)
|
|
483
595
|
layout.addWidget(self.kde_check)
|
|
596
|
+
layout.addWidget(self.count_check)
|
|
484
597
|
layout.addWidget(self.ecdf_check)
|
|
485
598
|
layout.addWidget(self.swarm_check)
|
|
486
599
|
layout.addWidget(self.violin_check)
|
|
@@ -491,16 +604,38 @@ class TableUI(QMainWindow, Styles):
|
|
|
491
604
|
self.x_cb = QSearchableComboBox()
|
|
492
605
|
self.x_cb.addItems(['--']+list(self.data.columns))
|
|
493
606
|
|
|
607
|
+
self.y_cb = QSearchableComboBox()
|
|
608
|
+
self.y_cb.addItems(['--']+list(self.data.columns))
|
|
609
|
+
|
|
494
610
|
self.hue_cb = QSearchableComboBox()
|
|
495
611
|
self.hue_cb.addItems(['--']+list(self.data.columns))
|
|
496
612
|
idx = self.hue_cb.findText('--')
|
|
613
|
+
self.hue_cb.setCurrentIndex(idx)
|
|
614
|
+
|
|
615
|
+
# Set selected column
|
|
616
|
+
|
|
617
|
+
try:
|
|
618
|
+
x = self.table_view.selectedIndexes()
|
|
619
|
+
col_idx = np.array([l.column() for l in x])
|
|
620
|
+
row_idx = np.array([l.row() for l in x])
|
|
621
|
+
column_names = self.data.columns
|
|
622
|
+
unique_cols = np.unique(col_idx)[0]
|
|
623
|
+
y = column_names[unique_cols]
|
|
624
|
+
idx = self.y_cb.findText(y)
|
|
625
|
+
self.y_cb.setCurrentIndex(idx)
|
|
626
|
+
except:
|
|
627
|
+
pass
|
|
497
628
|
|
|
498
|
-
self.x_cb.findText('--')
|
|
499
629
|
hbox = QHBoxLayout()
|
|
500
630
|
hbox.addWidget(QLabel('x: '), 33)
|
|
501
631
|
hbox.addWidget(self.x_cb, 66)
|
|
502
632
|
layout.addLayout(hbox)
|
|
503
633
|
|
|
634
|
+
hbox = QHBoxLayout()
|
|
635
|
+
hbox.addWidget(QLabel('y: '), 33)
|
|
636
|
+
hbox.addWidget(self.y_cb, 66)
|
|
637
|
+
layout.addLayout(hbox)
|
|
638
|
+
|
|
504
639
|
hbox = QHBoxLayout()
|
|
505
640
|
hbox.addWidget(QLabel('hue: '), 33)
|
|
506
641
|
hbox.addWidget(self.hue_cb, 66)
|
|
@@ -534,18 +669,10 @@ class TableUI(QMainWindow, Styles):
|
|
|
534
669
|
self.x_option = True
|
|
535
670
|
self.x = self.x_cb.currentText()
|
|
536
671
|
|
|
537
|
-
x = self.table_view.selectedIndexes()
|
|
538
|
-
col_idx = np.array([l.column() for l in x])
|
|
539
|
-
row_idx = np.array([l.row() for l in x])
|
|
540
|
-
column_names = self.data.columns
|
|
541
|
-
unique_cols = np.unique(col_idx)[0]
|
|
542
|
-
|
|
543
672
|
self.fig, self.ax = plt.subplots(1,1,figsize=(4,3))
|
|
544
673
|
self.plot1dWindow = FigureCanvas(self.fig, title="scatter")
|
|
545
674
|
self.ax.clear()
|
|
546
|
-
|
|
547
|
-
y = self.data.iloc[row_idx_i, unique_cols]
|
|
548
|
-
|
|
675
|
+
|
|
549
676
|
cmap = getattr(mcm, self.cmap_cb.currentText())
|
|
550
677
|
|
|
551
678
|
try:
|
|
@@ -557,58 +684,68 @@ class TableUI(QMainWindow, Styles):
|
|
|
557
684
|
if self.hue_cb.currentText()=='--':
|
|
558
685
|
hue_variable = None
|
|
559
686
|
|
|
560
|
-
|
|
687
|
+
if self.y_cb.currentText()=='--':
|
|
688
|
+
self.y = None
|
|
689
|
+
else:
|
|
690
|
+
self.y = self.y_cb.currentText()
|
|
691
|
+
|
|
692
|
+
if self.x_cb.currentText()=='--':
|
|
693
|
+
self.x = None
|
|
694
|
+
else:
|
|
695
|
+
self.x = self.x_cb.currentText()
|
|
561
696
|
|
|
562
697
|
legend=True
|
|
563
698
|
if self.hist_check.isChecked():
|
|
564
|
-
sns.histplot(data=self.data, x=
|
|
699
|
+
sns.histplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors, kde=True, common_norm=False, stat='density')
|
|
565
700
|
legend = False
|
|
566
701
|
if self.kde_check.isChecked():
|
|
567
|
-
sns.kdeplot(data=self.data, x=
|
|
702
|
+
sns.kdeplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors, cut=0)
|
|
703
|
+
legend = False
|
|
704
|
+
if self.count_check.isChecked():
|
|
705
|
+
sns.countplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors)
|
|
568
706
|
legend = False
|
|
569
|
-
|
|
570
707
|
if self.ecdf_check.isChecked():
|
|
571
|
-
sns.ecdfplot(data=self.data, x=
|
|
708
|
+
sns.ecdfplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors)
|
|
572
709
|
legend = False
|
|
573
710
|
|
|
574
711
|
if self.swarm_check.isChecked():
|
|
575
712
|
if self.x_option:
|
|
576
|
-
sns.swarmplot(data=self.data, x=self.x,y=
|
|
713
|
+
sns.swarmplot(data=self.data, x=self.x,y=self.y,dodge=True, hue=hue_variable,legend=legend, ax=self.ax, palette=colors)
|
|
577
714
|
legend = False
|
|
578
715
|
else:
|
|
579
|
-
sns.swarmplot(data=self.data, y=
|
|
716
|
+
sns.swarmplot(data=self.data, y=self.y,dodge=True, hue=hue_variable,legend=legend, ax=self.ax, palette=colors)
|
|
580
717
|
legend = False
|
|
581
718
|
|
|
582
719
|
if self.violin_check.isChecked():
|
|
583
720
|
if self.x_option:
|
|
584
|
-
sns.stripplot(data=self.data,x=self.x, y=
|
|
721
|
+
sns.stripplot(data=self.data,x=self.x, y=self.y,dodge=True, ax=self.ax, hue=hue_variable, legend=legend, palette=colors)
|
|
585
722
|
legend = False
|
|
586
723
|
else:
|
|
587
|
-
sns.violinplot(data=self.data, y=
|
|
724
|
+
sns.violinplot(data=self.data, y=self.y,dodge=True, hue=hue_variable,legend=legend, ax=self.ax, palette=colors, cut=0)
|
|
588
725
|
legend = False
|
|
589
726
|
|
|
590
727
|
if self.box_check.isChecked():
|
|
591
728
|
if self.x_option:
|
|
592
|
-
sns.boxplot(data=self.data, x=self.x, y=
|
|
729
|
+
sns.boxplot(data=self.data, x=self.x, y=self.y,dodge=True, hue=hue_variable,legend=legend, ax=self.ax, fill=False,palette=colors, linewidth=2,)
|
|
593
730
|
legend = False
|
|
594
731
|
else:
|
|
595
|
-
sns.boxplot(data=self.data, y=
|
|
732
|
+
sns.boxplot(data=self.data, y=self.y,dodge=True, hue=hue_variable,legend=legend, ax=self.ax, fill=False,palette=colors, linewidth=2,)
|
|
596
733
|
legend = False
|
|
597
734
|
|
|
598
735
|
if self.boxenplot_check.isChecked():
|
|
599
736
|
if self.x_option:
|
|
600
|
-
sns.boxenplot(data=self.data, x
|
|
737
|
+
sns.boxenplot(data=self.data, x=self.x, y=self.y,dodge=True, hue=hue_variable,legend=legend, ax=self.ax, fill=False,palette=colors, linewidth=2,)
|
|
601
738
|
legend = False
|
|
602
739
|
else:
|
|
603
|
-
sns.boxenplot(data=self.data, y=
|
|
740
|
+
sns.boxenplot(data=self.data, y=self.y,dodge=True, hue=hue_variable,legend=legend, ax=self.ax, fill=False,palette=colors, linewidth=2,)
|
|
604
741
|
legend = False
|
|
605
742
|
|
|
606
743
|
if self.strip_check.isChecked():
|
|
607
744
|
if self.x_option:
|
|
608
|
-
sns.stripplot(data=self.data, x = self.x, y=
|
|
745
|
+
sns.stripplot(data=self.data, x = self.x, y=self.y,dodge=True, ax=self.ax, hue=hue_variable, legend=legend, palette=colors)
|
|
609
746
|
legend = False
|
|
610
747
|
else:
|
|
611
|
-
sns.stripplot(data=self.data, y=
|
|
748
|
+
sns.stripplot(data=self.data, y=self.y,dodge=True, ax=self.ax, hue=hue_variable, legend=legend, palette=colors)
|
|
612
749
|
legend = False
|
|
613
750
|
|
|
614
751
|
plt.tight_layout()
|
|
@@ -739,6 +876,15 @@ class TableUI(QMainWindow, Styles):
|
|
|
739
876
|
else:
|
|
740
877
|
return array
|
|
741
878
|
|
|
879
|
+
def plot_instantaneous(self):
|
|
880
|
+
|
|
881
|
+
if self.plot_mode=='plot_track_signals':
|
|
882
|
+
self.plot_mode = 'static'
|
|
883
|
+
self.plot()
|
|
884
|
+
self.plot_mode = 'plot_track_signals'
|
|
885
|
+
elif self.plot_mode=="static":
|
|
886
|
+
self.plot()
|
|
887
|
+
|
|
742
888
|
def plot(self):
|
|
743
889
|
if self.plot_mode == "static":
|
|
744
890
|
|
|
@@ -748,42 +894,10 @@ class TableUI(QMainWindow, Styles):
|
|
|
748
894
|
column_names = self.data.columns
|
|
749
895
|
unique_cols = np.unique(col_idx)
|
|
750
896
|
|
|
751
|
-
if len(unique_cols)==1:
|
|
752
|
-
# 1D plot
|
|
753
|
-
# Open widget to set 1D data representations
|
|
897
|
+
if len(unique_cols)==1 or len(unique_cols)==0:
|
|
754
898
|
self.set_1D_plot_params()
|
|
755
899
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
# x = self.table_view.selectedIndexes()
|
|
759
|
-
# col_idx = np.array([l.column() for l in x])
|
|
760
|
-
# row_idx = np.array([l.row() for l in x])
|
|
761
|
-
# column_names = self.data.columns
|
|
762
|
-
# unique_cols = np.unique(col_idx)[0]
|
|
763
|
-
|
|
764
|
-
# self.fig, self.ax = plt.subplots(1,1,figsize=(4,3))
|
|
765
|
-
# self.histogram_window = FigureCanvas(self.fig, title="scatter")
|
|
766
|
-
# self.ax.clear()
|
|
767
|
-
# row_idx_i = row_idx[np.where(col_idx==unique_cols)[0]]
|
|
768
|
-
# y = self.data.iloc[row_idx_i, unique_cols]
|
|
769
|
-
|
|
770
|
-
# colors = [viridis(i / len(self.data['well_index'].unique())) for i in range(len(self.data['well_index'].unique()))]
|
|
771
|
-
# #for w,well_group in self.data.groupby('well_index'):
|
|
772
|
-
# sns.boxplot(data=self.data, y=column_names[unique_cols],dodge=True, hue='well_index',legend=False, ax=self.ax, fill=False,palette=colors, linewidth=2,)
|
|
773
|
-
# sns.stripplot(data=self.data, y=column_names[unique_cols],dodge=True, ax=self.ax, hue='well_index', legend=False, palette=colors)
|
|
774
|
-
# # sns.kdeplot(data=self.data, x=column_names[unique_cols], hue='well_index', ax=self.ax, fill=False,common_norm=False, palette=colors, alpha=.5, linewidth=2,)
|
|
775
|
-
# # for k,(w,well_group) in enumerate(self.data.groupby('well_index')):
|
|
776
|
-
# # self.ax.hist(well_group[column_names[unique_cols]],label=w, density=True, alpha=0.5, color=colors[k])
|
|
777
|
-
# #self.ax.legend()
|
|
778
|
-
# self.ax.set_xlabel(column_names[unique_cols])
|
|
779
|
-
# plt.tight_layout()
|
|
780
|
-
# self.fig.set_facecolor('none') # or 'None'
|
|
781
|
-
# self.fig.canvas.setStyleSheet("background-color: transparent;")
|
|
782
|
-
# self.histogram_window.canvas.draw()
|
|
783
|
-
# self.histogram_window.show()
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
elif len(unique_cols) == 2:
|
|
900
|
+
if len(unique_cols) == 2:
|
|
787
901
|
|
|
788
902
|
print("two columns, plot mode")
|
|
789
903
|
x1 = self.test_bool(self.data.iloc[row_idx, unique_cols[0]])
|
|
@@ -2,7 +2,7 @@ import math
|
|
|
2
2
|
|
|
3
3
|
import skimage
|
|
4
4
|
from PyQt5.QtWidgets import QAction, QMenu, QMainWindow, QMessageBox, QLabel, QWidget, QFileDialog, QHBoxLayout, \
|
|
5
|
-
QGridLayout, QLineEdit, QScrollArea, QVBoxLayout, QComboBox, QPushButton, QApplication, QPushButton
|
|
5
|
+
QGridLayout, QLineEdit, QScrollArea, QVBoxLayout, QComboBox, QPushButton, QApplication, QPushButton, QRadioButton, QButtonGroup
|
|
6
6
|
from PyQt5.QtGui import QDoubleValidator, QIntValidator
|
|
7
7
|
from matplotlib.backends.backend_qt import NavigationToolbar2QT
|
|
8
8
|
from matplotlib.patches import Circle
|
|
@@ -17,6 +17,7 @@ from celldetective.io import auto_load_number_of_frames, load_frames
|
|
|
17
17
|
from celldetective.segmentation import threshold_image, identify_markers_from_binary, apply_watershed, \
|
|
18
18
|
segment_frame_from_thresholds
|
|
19
19
|
from scipy.ndimage import binary_fill_holes
|
|
20
|
+
import scipy.ndimage as ndi
|
|
20
21
|
from PyQt5.QtCore import Qt, QSize
|
|
21
22
|
from glob import glob
|
|
22
23
|
from superqt.fonticon import icon
|
|
@@ -263,10 +264,20 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
263
264
|
marker_box = QVBoxLayout()
|
|
264
265
|
marker_box.setContentsMargins(30, 30, 30, 30)
|
|
265
266
|
|
|
266
|
-
marker_lbl = QLabel('
|
|
267
|
+
marker_lbl = QLabel('Objects')
|
|
267
268
|
marker_lbl.setStyleSheet("font-weight: bold;")
|
|
268
269
|
marker_box.addWidget(marker_lbl, alignment=Qt.AlignCenter)
|
|
269
270
|
|
|
271
|
+
object_option_hbox = QHBoxLayout()
|
|
272
|
+
self.marker_option = QRadioButton('markers')
|
|
273
|
+
self.all_objects_option = QRadioButton('all non-contiguous objects')
|
|
274
|
+
self.marker_option_group = QButtonGroup()
|
|
275
|
+
self.marker_option_group.addButton(self.marker_option)
|
|
276
|
+
self.marker_option_group.addButton(self.all_objects_option)
|
|
277
|
+
object_option_hbox.addWidget(self.marker_option, 50, alignment=Qt.AlignCenter)
|
|
278
|
+
object_option_hbox.addWidget(self.all_objects_option, 50, alignment=Qt.AlignCenter)
|
|
279
|
+
marker_box.addLayout(object_option_hbox)
|
|
280
|
+
|
|
270
281
|
hbox_footprint = QHBoxLayout()
|
|
271
282
|
hbox_footprint.addWidget(QLabel('Footprint: '), 20)
|
|
272
283
|
self.footprint_slider = QLabeledSlider()
|
|
@@ -275,7 +286,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
275
286
|
self.footprint_slider.setRange(1, self.binary.shape[0] // 4)
|
|
276
287
|
self.footprint_slider.setValue(self.footprint)
|
|
277
288
|
self.footprint_slider.valueChanged.connect(self.set_footprint)
|
|
278
|
-
hbox_footprint.addWidget(self.footprint_slider,
|
|
289
|
+
hbox_footprint.addWidget(self.footprint_slider, 30)
|
|
290
|
+
hbox_footprint.addWidget(QLabel(''), 50)
|
|
279
291
|
marker_box.addLayout(hbox_footprint)
|
|
280
292
|
|
|
281
293
|
hbox_distance = QHBoxLayout()
|
|
@@ -286,7 +298,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
286
298
|
self.min_dist_slider.setRange(0, self.binary.shape[0] // 4)
|
|
287
299
|
self.min_dist_slider.setValue(self.min_dist)
|
|
288
300
|
self.min_dist_slider.valueChanged.connect(self.set_min_dist)
|
|
289
|
-
hbox_distance.addWidget(self.min_dist_slider,
|
|
301
|
+
hbox_distance.addWidget(self.min_dist_slider, 30)
|
|
302
|
+
hbox_distance.addWidget(QLabel(''), 50)
|
|
290
303
|
marker_box.addLayout(hbox_distance)
|
|
291
304
|
|
|
292
305
|
hbox_marker_btns = QHBoxLayout()
|
|
@@ -305,8 +318,23 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
305
318
|
hbox_marker_btns.addWidget(self.watershed_btn)
|
|
306
319
|
marker_box.addLayout(hbox_marker_btns)
|
|
307
320
|
|
|
321
|
+
self.marker_option.clicked.connect(self.enable_marker_options)
|
|
322
|
+
self.all_objects_option.clicked.connect(self.enable_marker_options)
|
|
323
|
+
self.marker_option.click()
|
|
324
|
+
|
|
308
325
|
self.left_panel.addLayout(marker_box)
|
|
309
326
|
|
|
327
|
+
def enable_marker_options(self):
|
|
328
|
+
if self.marker_option.isChecked():
|
|
329
|
+
self.footprint_slider.setEnabled(True)
|
|
330
|
+
self.min_dist_slider.setEnabled(True)
|
|
331
|
+
self.markers_btn.setEnabled(True)
|
|
332
|
+
else:
|
|
333
|
+
self.footprint_slider.setEnabled(False)
|
|
334
|
+
self.min_dist_slider.setEnabled(False)
|
|
335
|
+
self.markers_btn.setEnabled(False)
|
|
336
|
+
self.watershed_btn.setEnabled(True)
|
|
337
|
+
|
|
310
338
|
def generate_props_contents(self):
|
|
311
339
|
|
|
312
340
|
properties_box = QVBoxLayout()
|
|
@@ -675,7 +703,10 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
675
703
|
|
|
676
704
|
def apply_watershed_to_selection(self):
|
|
677
705
|
|
|
678
|
-
|
|
706
|
+
if self.marker_option.isChecked():
|
|
707
|
+
self.labels = apply_watershed(self.binary, self.coords, self.edt_map)
|
|
708
|
+
else:
|
|
709
|
+
self.labels,_ = ndi.label(self.binary.astype(int))
|
|
679
710
|
|
|
680
711
|
self.current_channel = self.channels_cb.currentIndex()
|
|
681
712
|
t = int(self.frame_slider.value())
|
|
@@ -806,6 +837,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
806
837
|
"marker_footprint_size": self.footprint,
|
|
807
838
|
"feature_queries": [self.property_query_le.text()],
|
|
808
839
|
"equalize_reference": [self.equalize_option, self.frame_slider.value()],
|
|
840
|
+
"do_watershed": self.marker_option.isChecked(),
|
|
809
841
|
}
|
|
810
842
|
|
|
811
843
|
print('The following instructions will be written: ', instructions)
|
|
@@ -873,6 +905,13 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
873
905
|
self.property_query_le.setText(feature_queries[0])
|
|
874
906
|
self.submit_query_btn.click()
|
|
875
907
|
|
|
908
|
+
if 'do_watershed' in threshold_instructions:
|
|
909
|
+
do_watershed = threshold_instructions['do_watershed']
|
|
910
|
+
if do_watershed:
|
|
911
|
+
self.marker_option.click()
|
|
912
|
+
else:
|
|
913
|
+
self.all_objects_option.click()
|
|
914
|
+
|
|
876
915
|
|
|
877
916
|
class ThresholdNormalisation(ThresholdConfigWizard):
|
|
878
917
|
def __init__(self, min_threshold, current_channel, parent_window=None):
|
celldetective/gui/viewers.py
CHANGED
|
@@ -396,7 +396,7 @@ class ThresholdedStackVisualizer(StackVisualizer):
|
|
|
396
396
|
# Compute the mask based on the threshold value
|
|
397
397
|
self.preprocess_image()
|
|
398
398
|
edge = estimate_unreliable_edge(self.preprocessing)
|
|
399
|
-
self.mask = threshold_image(self.processed_image, threshold_value,
|
|
399
|
+
self.mask = threshold_image(self.processed_image, threshold_value, np.inf, foreground_value=1, edge_exclusion=edge).astype(int)
|
|
400
400
|
|
|
401
401
|
def preprocess_image(self):
|
|
402
402
|
# Preprocess the image before thresholding
|
celldetective/io.py
CHANGED
|
@@ -1238,12 +1238,23 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
|
|
|
1238
1238
|
"""
|
|
1239
1239
|
position = position.replace('\\','/')
|
|
1240
1240
|
if population.lower()=="target" or population.lower()=="targets":
|
|
1241
|
-
|
|
1241
|
+
if os.path.exists(position+os.sep.join(['output','tables','napari_target_trajectories.npy'])):
|
|
1242
|
+
napari_data = np.load(position+os.sep.join(['output','tables','napari_target_trajectories.npy']), allow_pickle=True)
|
|
1243
|
+
else:
|
|
1244
|
+
napari_data = None
|
|
1242
1245
|
elif population.lower()=="effector" or population.lower()=="effectors":
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1246
|
+
if os.path.exists(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy'])):
|
|
1247
|
+
napari_data = np.load(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy']), allow_pickle=True)
|
|
1248
|
+
else:
|
|
1249
|
+
napari_data = None
|
|
1250
|
+
if napari_data is not None:
|
|
1251
|
+
data = napari_data.item()['data']
|
|
1252
|
+
properties = napari_data.item()['properties']
|
|
1253
|
+
graph = napari_data.item()['graph']
|
|
1254
|
+
else:
|
|
1255
|
+
data = None
|
|
1256
|
+
properties = None
|
|
1257
|
+
graph = None
|
|
1247
1258
|
if return_stack:
|
|
1248
1259
|
stack,labels = locate_stack_and_labels(position, prefix=prefix, population=population)
|
|
1249
1260
|
else:
|
|
@@ -1968,15 +1979,19 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
|
|
|
1968
1979
|
v = values[c]
|
|
1969
1980
|
else:
|
|
1970
1981
|
v = None
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1982
|
+
|
|
1983
|
+
if np.all(mf[:,:,c]==0.):
|
|
1984
|
+
mf_new.append(mf[:,:,c].copy())
|
|
1985
|
+
else:
|
|
1986
|
+
norm = normalize(mf[:,:,c].copy(),
|
|
1987
|
+
percentiles=percentiles[c],
|
|
1988
|
+
values=v,
|
|
1989
|
+
ignore_gray_value=ignore_gray_value,
|
|
1990
|
+
clip=clip,
|
|
1991
|
+
amplification=amplification,
|
|
1992
|
+
dtype=dtype,
|
|
1993
|
+
)
|
|
1994
|
+
mf_new.append(norm)
|
|
1980
1995
|
|
|
1981
1996
|
return np.moveaxis(mf_new,0,-1)
|
|
1982
1997
|
|
celldetective/preprocessing.py
CHANGED
|
@@ -15,6 +15,8 @@ from gc import collect
|
|
|
15
15
|
from lmfit import Parameters, Model, models
|
|
16
16
|
import tifffile.tifffile as tiff
|
|
17
17
|
|
|
18
|
+
from tifffile import imwrite
|
|
19
|
+
|
|
18
20
|
def estimate_background_per_condition(experiment, threshold_on_std=1, well_option='*', target_channel="channel_name", frame_range=[0,5], mode="timeseries", activation_protocol=[['gauss',2],['std',4]], show_progress_per_pos=False, show_progress_per_well=True):
|
|
19
21
|
|
|
20
22
|
"""
|
|
@@ -69,6 +71,7 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
|
|
|
69
71
|
... print(bg["well"], bg["bg"].shape)
|
|
70
72
|
"""
|
|
71
73
|
|
|
74
|
+
|
|
72
75
|
config = get_config(experiment)
|
|
73
76
|
wells = get_experiment_wells(experiment)
|
|
74
77
|
len_movie = float(ConfigSectionMap(config,"MovieSettings")["len_movie"])
|
|
@@ -79,7 +82,7 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
|
|
|
79
82
|
channel_indices = _extract_channel_indices_from_config(config, [target_channel])
|
|
80
83
|
nbr_channels = _extract_nbr_channels_from_config(config)
|
|
81
84
|
img_num_channels = _get_img_num_per_channel(channel_indices, int(len_movie), nbr_channels)
|
|
82
|
-
|
|
85
|
+
|
|
83
86
|
backgrounds = []
|
|
84
87
|
|
|
85
88
|
for k, well_path in enumerate(tqdm(wells[well_indices], disable=not show_progress_per_well)):
|
|
@@ -110,7 +113,7 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
|
|
|
110
113
|
frame = frame_mean.copy().astype(float)
|
|
111
114
|
std_frame = filter_image(frame.copy(),filters=activation_protocol)
|
|
112
115
|
edge = estimate_unreliable_edge(activation_protocol)
|
|
113
|
-
mask = threshold_image(std_frame, threshold_on_std,
|
|
116
|
+
mask = threshold_image(std_frame, threshold_on_std, np.inf, foreground_value=1, edge_exclusion=edge)
|
|
114
117
|
frame[np.where(mask.astype(int)==1)] = np.nan
|
|
115
118
|
|
|
116
119
|
elif mode=="tiles":
|
|
@@ -118,21 +121,23 @@ def estimate_background_per_condition(experiment, threshold_on_std=1, well_optio
|
|
|
118
121
|
frames = load_frames(img_num_channels[0,:], stack_path, normalize_input=False).astype(float)
|
|
119
122
|
frames = np.moveaxis(frames, -1, 0).astype(float)
|
|
120
123
|
|
|
124
|
+
new_frames = []
|
|
121
125
|
for i in range(len(frames)):
|
|
122
126
|
|
|
123
127
|
if np.all(frames[i].flatten()==0):
|
|
124
|
-
|
|
128
|
+
empty_frame = np.zeros_like(frames[i])
|
|
129
|
+
empty_frame[:,:] = np.nan
|
|
130
|
+
new_frames.append(empty_frame)
|
|
125
131
|
continue
|
|
126
132
|
|
|
127
133
|
f = frames[i].copy()
|
|
128
134
|
std_frame = filter_image(f.copy(),filters=activation_protocol)
|
|
129
135
|
edge = estimate_unreliable_edge(activation_protocol)
|
|
130
|
-
mask = threshold_image(std_frame, threshold_on_std,
|
|
136
|
+
mask = threshold_image(std_frame, threshold_on_std, np.inf, foreground_value=1, edge_exclusion=edge)
|
|
131
137
|
f[np.where(mask.astype(int)==1)] = np.nan
|
|
132
|
-
|
|
133
|
-
frames[i,:,:] = f
|
|
138
|
+
new_frames.append(f.copy())
|
|
134
139
|
|
|
135
|
-
frame = np.nanmedian(
|
|
140
|
+
frame = np.nanmedian(new_frames, axis=0)
|
|
136
141
|
|
|
137
142
|
# store
|
|
138
143
|
frame_mean_per_position.append(frame)
|
|
@@ -371,18 +376,25 @@ def apply_background_to_stack(stack_path, background, target_channel_index=0, nb
|
|
|
371
376
|
|
|
372
377
|
std_frame = filter_image(target_copy.copy(),filters=activation_protocol)
|
|
373
378
|
edge = estimate_unreliable_edge(activation_protocol)
|
|
374
|
-
mask = threshold_image(std_frame, threshold_on_std,
|
|
379
|
+
mask = threshold_image(std_frame, threshold_on_std, np.inf, foreground_value=1, edge_exclusion=edge)
|
|
375
380
|
target_copy[np.where(mask.astype(int)==1)] = np.nan
|
|
376
381
|
|
|
377
382
|
loss = []
|
|
378
383
|
|
|
379
384
|
# brute-force regression, could do gradient descent instead
|
|
380
385
|
for c in coefficients:
|
|
386
|
+
|
|
381
387
|
target_crop = unpad(target_copy,edge)
|
|
382
388
|
bg_crop = unpad(background, edge)
|
|
383
|
-
|
|
384
|
-
|
|
389
|
+
|
|
390
|
+
roi = np.zeros_like(target_crop).astype(int)
|
|
391
|
+
roi[target_crop!=target_crop] = 1
|
|
392
|
+
roi[bg_crop!=bg_crop] = 1
|
|
393
|
+
|
|
394
|
+
diff = np.subtract(target_crop, c*bg_crop, where=roi==0)
|
|
395
|
+
s = np.sum(np.abs(diff, where=roi==0), where=roi==0)
|
|
385
396
|
loss.append(s)
|
|
397
|
+
|
|
386
398
|
c = coefficients[np.argmin(loss)]
|
|
387
399
|
print(f"Frame: {i}; optimal coefficient: {c}...")
|
|
388
400
|
# if c==min(coefficients) or c==max(coefficients):
|
|
@@ -954,7 +966,7 @@ def field_correction(img, threshold_on_std=1, operation='divide', model='parabol
|
|
|
954
966
|
|
|
955
967
|
std_frame = filter_image(target_copy,filters=activation_protocol)
|
|
956
968
|
edge = estimate_unreliable_edge(activation_protocol)
|
|
957
|
-
mask = threshold_image(std_frame, threshold_on_std,
|
|
969
|
+
mask = threshold_image(std_frame, threshold_on_std, np.inf, foreground_value=1, edge_exclusion=edge).astype(int)
|
|
958
970
|
background = fit_background_model(img, cell_masks=mask, model=model, edge_exclusion=edge)
|
|
959
971
|
|
|
960
972
|
if operation=="divide":
|