celldetective 1.1.1.post1__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/gui/layouts.py +244 -1
- celldetective/gui/retrain_segmentation_model_options.py +66 -164
- celldetective/gui/retrain_signal_model_options.py +18 -164
- celldetective/gui/tableUI.py +160 -60
- celldetective/gui/viewers.py +1 -1
- celldetective/preprocessing.py +23 -11
- celldetective/scripts/segment_cells.py +1 -0
- celldetective/scripts/train_segmentation_model.py +11 -22
- celldetective/utils.py +3 -2
- {celldetective-1.1.1.post1.dist-info → celldetective-1.1.1.post3.dist-info}/METADATA +1 -1
- {celldetective-1.1.1.post1.dist-info → celldetective-1.1.1.post3.dist-info}/RECORD +15 -15
- {celldetective-1.1.1.post1.dist-info → celldetective-1.1.1.post3.dist-info}/WHEEL +1 -1
- {celldetective-1.1.1.post1.dist-info → celldetective-1.1.1.post3.dist-info}/LICENSE +0 -0
- {celldetective-1.1.1.post1.dist-info → celldetective-1.1.1.post3.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.1.post1.dist-info → celldetective-1.1.1.post3.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@ from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QScrollArea,
|
|
|
2
2
|
from PyQt5.QtCore import Qt, QSize
|
|
3
3
|
from PyQt5.QtGui import QDoubleValidator, QIntValidator, QIcon
|
|
4
4
|
from celldetective.gui.gui_utils import center_window, FeatureChoice, ListWidget, QHSeperationLine, FigureCanvas, GeometryChoice, OperationChoice
|
|
5
|
+
from celldetective.gui.layouts import ChannelNormGenerator
|
|
5
6
|
from superqt import QLabeledDoubleRangeSlider, QLabeledDoubleSlider,QLabeledSlider
|
|
6
7
|
from superqt.fonticon import icon
|
|
7
8
|
from fonticon_mdi6 import MDI6
|
|
@@ -306,103 +307,9 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
306
307
|
recompile_layout.addWidget(self.recompile_option, 70)
|
|
307
308
|
layout.addLayout(recompile_layout)
|
|
308
309
|
|
|
309
|
-
#self.channel_cbs = [QComboBox() for i in range(4)]
|
|
310
|
-
|
|
311
310
|
self.max_nbr_channels = 5
|
|
312
|
-
self.
|
|
313
|
-
|
|
314
|
-
self.normalization_mode = [True for i in range(self.max_nbr_channels)]
|
|
315
|
-
|
|
316
|
-
self.normalization_clip_btns = [QPushButton('') for i in range(self.max_nbr_channels)]
|
|
317
|
-
self.clip_option = [False for i in range(self.max_nbr_channels)]
|
|
318
|
-
|
|
319
|
-
for i in range(self.max_nbr_channels):
|
|
320
|
-
|
|
321
|
-
self.normalization_mode_btns[i].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
|
|
322
|
-
self.normalization_mode_btns[i].setIconSize(QSize(20, 20))
|
|
323
|
-
self.normalization_mode_btns[i].setStyleSheet(self.button_select_all)
|
|
324
|
-
self.normalization_mode_btns[i].setToolTip("Switch to absolute normalization values.")
|
|
325
|
-
self.normalization_mode_btns[i].clicked.connect(partial(self.switch_normalization_mode, i))
|
|
326
|
-
|
|
327
|
-
self.normalization_clip_btns[i].setIcon(icon(MDI6.content_cut,color="black"))
|
|
328
|
-
self.normalization_clip_btns[i].setIconSize(QSize(20, 20))
|
|
329
|
-
self.normalization_clip_btns[i].setStyleSheet(self.button_select_all)
|
|
330
|
-
self.normalization_clip_btns[i].clicked.connect(partial(self.switch_clipping_mode, i))
|
|
331
|
-
self.normalization_clip_btns[i].setToolTip('clip')
|
|
332
|
-
|
|
333
|
-
self.normalization_min_value_lbl = [QLabel('Min %: ') for i in range(self.max_nbr_channels)]
|
|
334
|
-
self.normalization_min_value_le = [QLineEdit('0.1') for i in range(self.max_nbr_channels)]
|
|
335
|
-
|
|
336
|
-
self.normalization_max_value_lbl = [QLabel('Max %: ') for i in range(self.max_nbr_channels)]
|
|
337
|
-
self.normalization_max_value_le = [QLineEdit('99.99') for i in range(self.max_nbr_channels)]
|
|
338
|
-
|
|
339
|
-
tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{self.mode}.csv']))
|
|
340
|
-
print(tables)
|
|
341
|
-
all_measurements = []
|
|
342
|
-
for tab in tables:
|
|
343
|
-
cols = pd.read_csv(tab, nrows=1).columns.tolist()
|
|
344
|
-
all_measurements.extend(cols)
|
|
345
|
-
all_measurements = np.unique(all_measurements)
|
|
346
|
-
generic_measurements = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel',
|
|
347
|
-
'effector_fluo_channel', 'adhesion_channel', 'fluo_channel_1', 'fluo_channel_2',
|
|
348
|
-
"area", "area_bbox","area_convex","area_filled","major_axis_length",
|
|
349
|
-
"minor_axis_length",
|
|
350
|
-
"eccentricity",
|
|
351
|
-
"equivalent_diameter_area",
|
|
352
|
-
"euler_number",
|
|
353
|
-
"extent",
|
|
354
|
-
"feret_diameter_max",
|
|
355
|
-
"orientation",
|
|
356
|
-
"perimeter",
|
|
357
|
-
"perimeter_crofton",
|
|
358
|
-
"solidity",
|
|
359
|
-
"angular_second_moment",
|
|
360
|
-
"contrast",
|
|
361
|
-
"correlation",
|
|
362
|
-
"sum_of_square_variance",
|
|
363
|
-
"inverse_difference_moment",
|
|
364
|
-
"sum_average",
|
|
365
|
-
"sum_variance",
|
|
366
|
-
"sum_entropy",
|
|
367
|
-
"entropy",
|
|
368
|
-
"difference_variance",
|
|
369
|
-
"difference_entropy",
|
|
370
|
-
"information_measure_of_correlation_1",
|
|
371
|
-
"information_measure_of_correlation_2",
|
|
372
|
-
"maximal_correlation_coefficient",
|
|
373
|
-
"POSITION_X",
|
|
374
|
-
"POSITION_Y",
|
|
375
|
-
]
|
|
376
|
-
|
|
377
|
-
self.channel_items = np.unique(generic_measurements + list(all_measurements))
|
|
378
|
-
self.channel_items = np.insert(self.channel_items, 0, '--')
|
|
379
|
-
|
|
380
|
-
self.channel_option_layouts = []
|
|
381
|
-
for i in range(len(self.channel_cbs)):
|
|
382
|
-
ch_layout = QHBoxLayout()
|
|
383
|
-
ch_layout.addWidget(QLabel(f'channel {i}: '), 30)
|
|
384
|
-
self.channel_cbs[i].addItems(self.channel_items)
|
|
385
|
-
self.channel_cbs[i].currentIndexChanged.connect(self.check_valid_channels)
|
|
386
|
-
ch_layout.addWidget(self.channel_cbs[i], 70)
|
|
387
|
-
layout.addLayout(ch_layout)
|
|
388
|
-
|
|
389
|
-
channel_norm_options_layout = QHBoxLayout()
|
|
390
|
-
channel_norm_options_layout.setContentsMargins(130,0,0,0)
|
|
391
|
-
channel_norm_options_layout.addWidget(self.normalization_min_value_lbl[i])
|
|
392
|
-
channel_norm_options_layout.addWidget(self.normalization_min_value_le[i])
|
|
393
|
-
channel_norm_options_layout.addWidget(self.normalization_max_value_lbl[i])
|
|
394
|
-
channel_norm_options_layout.addWidget(self.normalization_max_value_le[i])
|
|
395
|
-
channel_norm_options_layout.addWidget(self.normalization_clip_btns[i])
|
|
396
|
-
channel_norm_options_layout.addWidget(self.normalization_mode_btns[i])
|
|
397
|
-
layout.addLayout(channel_norm_options_layout)
|
|
398
|
-
|
|
399
|
-
# for i in range(len(self.channel_cbs)):
|
|
400
|
-
# ch_layout = QHBoxLayout()
|
|
401
|
-
# ch_layout.addWidget(QLabel(f'channel {i}: '), 30)
|
|
402
|
-
# self.channel_cbs[i].addItems(self.channel_items)
|
|
403
|
-
# self.channel_cbs[i].currentIndexChanged.connect(self.check_valid_channels)
|
|
404
|
-
# ch_layout.addWidget(self.channel_cbs[i], 70)
|
|
405
|
-
# layout.addLayout(ch_layout)
|
|
311
|
+
self.ch_norm = ChannelNormGenerator(self, mode='signals')
|
|
312
|
+
layout.addLayout(self.ch_norm)
|
|
406
313
|
|
|
407
314
|
model_length_layout = QHBoxLayout()
|
|
408
315
|
model_length_layout.addWidget(QLabel('Max signal length: '), 30)
|
|
@@ -465,8 +372,9 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
465
372
|
|
|
466
373
|
self.pretrained_model = None
|
|
467
374
|
self.pretrained_lbl.setText('No folder chosen')
|
|
468
|
-
for cb in self.channel_cbs:
|
|
375
|
+
for cb in self.ch_norm.channel_cbs:
|
|
469
376
|
cb.setEnabled(True)
|
|
377
|
+
self.ch_norm.add_col_btn.setEnabled(True)
|
|
470
378
|
self.recompile_option.setEnabled(False)
|
|
471
379
|
self.cancel_pretrained.setVisible(False)
|
|
472
380
|
self.model_length_slider.setEnabled(True)
|
|
@@ -495,14 +403,15 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
495
403
|
self.model_length_slider.setValue(int(signal_length))
|
|
496
404
|
self.model_length_slider.setEnabled(False)
|
|
497
405
|
|
|
498
|
-
for c,cb in zip(channels, self.channel_cbs):
|
|
406
|
+
for c,cb in zip(channels, self.ch_norm.channel_cbs):
|
|
499
407
|
index = cb.findText(c)
|
|
500
408
|
cb.setCurrentIndex(index)
|
|
501
409
|
|
|
502
|
-
if len(channels)<len(self.channel_cbs):
|
|
503
|
-
for k in range(len(self.channel_cbs)-len(channels)):
|
|
504
|
-
self.channel_cbs[len(channels)+k].setCurrentIndex(0)
|
|
505
|
-
self.channel_cbs[len(channels)+k].setEnabled(False)
|
|
410
|
+
if len(channels)<len(self.ch_norm.channel_cbs):
|
|
411
|
+
for k in range(len(self.ch_norm.channel_cbs)-len(channels)):
|
|
412
|
+
self.ch_norm.channel_cbs[len(channels)+k].setCurrentIndex(0)
|
|
413
|
+
self.ch_norm.channel_cbs[len(channels)+k].setEnabled(False)
|
|
414
|
+
self.ch_norm.add_col_btn.setEnabled(False)
|
|
506
415
|
|
|
507
416
|
|
|
508
417
|
def adjustScrollArea(self):
|
|
@@ -524,23 +433,23 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
524
433
|
recompile_op = self.recompile_option.isChecked()
|
|
525
434
|
|
|
526
435
|
channels = []
|
|
527
|
-
for i in range(len(self.channel_cbs)):
|
|
528
|
-
channels.append(self.channel_cbs[i].currentText())
|
|
436
|
+
for i in range(len(self.ch_norm.channel_cbs)):
|
|
437
|
+
channels.append(self.ch_norm.channel_cbs[i].currentText())
|
|
529
438
|
|
|
530
439
|
slots_to_keep = np.where(np.array(channels)!='--')[0]
|
|
531
440
|
while '--' in channels:
|
|
532
441
|
channels.remove('--')
|
|
533
442
|
|
|
534
|
-
norm_values = np.array([[float(a.replace(',','.')),float(b.replace(',','.'))] for a,b in zip([l.text() for l in self.normalization_min_value_le],
|
|
535
|
-
[l.text() for l in self.normalization_max_value_le])])
|
|
443
|
+
norm_values = np.array([[float(a.replace(',','.')),float(b.replace(',','.'))] for a,b in zip([l.text() for l in self.ch_norm.normalization_min_value_le],
|
|
444
|
+
[l.text() for l in self.ch_norm.normalization_max_value_le])])
|
|
536
445
|
norm_values = norm_values[slots_to_keep]
|
|
537
446
|
norm_values = [list(v) for v in norm_values]
|
|
538
447
|
|
|
539
|
-
clip_values = np.array(self.clip_option)
|
|
448
|
+
clip_values = np.array(self.ch_norm.clip_option)
|
|
540
449
|
clip_values = list(clip_values[slots_to_keep])
|
|
541
450
|
clip_values = [bool(c) for c in clip_values]
|
|
542
451
|
|
|
543
|
-
normalization_mode = np.array(self.normalization_mode)
|
|
452
|
+
normalization_mode = np.array(self.ch_norm.normalization_mode)
|
|
544
453
|
normalization_mode = list(normalization_mode[slots_to_keep])
|
|
545
454
|
normalization_mode = [bool(m) for m in normalization_mode]
|
|
546
455
|
|
|
@@ -586,59 +495,4 @@ class ConfigSignalModelTraining(QMainWindow, Styles):
|
|
|
586
495
|
|
|
587
496
|
train_signal_model(model_folder+"training_instructions.json")
|
|
588
497
|
|
|
589
|
-
self.parent_window.refresh_signal_models()
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
def check_valid_channels(self):
|
|
593
|
-
|
|
594
|
-
if np.all([cb.currentText()=='--' for cb in self.channel_cbs]):
|
|
595
|
-
self.submit_btn.setEnabled(False)
|
|
596
|
-
else:
|
|
597
|
-
self.submit_btn.setEnabled(True)
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
def switch_normalization_mode(self, index):
|
|
601
|
-
|
|
602
|
-
"""
|
|
603
|
-
Use absolute or percentile values for the normalization of each individual channel.
|
|
604
|
-
|
|
605
|
-
"""
|
|
606
|
-
|
|
607
|
-
currentNormMode = self.normalization_mode[index]
|
|
608
|
-
self.normalization_mode[index] = not currentNormMode
|
|
609
|
-
|
|
610
|
-
if self.normalization_mode[index]:
|
|
611
|
-
self.normalization_mode_btns[index].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
|
|
612
|
-
self.normalization_mode_btns[index].setIconSize(QSize(20, 20))
|
|
613
|
-
self.normalization_mode_btns[index].setStyleSheet(self.button_select_all)
|
|
614
|
-
self.normalization_mode_btns[index].setToolTip("Switch to absolute normalization values.")
|
|
615
|
-
self.normalization_min_value_lbl[index].setText('Min %: ')
|
|
616
|
-
self.normalization_max_value_lbl[index].setText('Max %: ')
|
|
617
|
-
self.normalization_min_value_le[index].setText('0.1')
|
|
618
|
-
self.normalization_max_value_le[index].setText('99.99')
|
|
619
|
-
|
|
620
|
-
else:
|
|
621
|
-
self.normalization_mode_btns[index].setIcon(icon(MDI6.percent_circle_outline,color="black"))
|
|
622
|
-
self.normalization_mode_btns[index].setIconSize(QSize(20, 20))
|
|
623
|
-
self.normalization_mode_btns[index].setStyleSheet(self.button_select_all)
|
|
624
|
-
self.normalization_mode_btns[index].setToolTip("Switch to percentile normalization values.")
|
|
625
|
-
self.normalization_min_value_lbl[index].setText('Min: ')
|
|
626
|
-
self.normalization_min_value_le[index].setText('0')
|
|
627
|
-
self.normalization_max_value_lbl[index].setText('Max: ')
|
|
628
|
-
self.normalization_max_value_le[index].setText('1000')
|
|
629
|
-
|
|
630
|
-
def switch_clipping_mode(self, index):
|
|
631
|
-
|
|
632
|
-
currentClipMode = self.clip_option[index]
|
|
633
|
-
self.clip_option[index] = not currentClipMode
|
|
634
|
-
|
|
635
|
-
if self.clip_option[index]:
|
|
636
|
-
self.normalization_clip_btns[index].setIcon(icon(MDI6.content_cut,color="#1565c0"))
|
|
637
|
-
self.normalization_clip_btns[index].setIconSize(QSize(20, 20))
|
|
638
|
-
self.normalization_clip_btns[index].setStyleSheet(self.button_select_all)
|
|
639
|
-
|
|
640
|
-
else:
|
|
641
|
-
self.normalization_clip_btns[index].setIcon(icon(MDI6.content_cut,color="black"))
|
|
642
|
-
self.normalization_clip_btns[index].setIconSize(QSize(20, 20))
|
|
643
|
-
self.normalization_clip_btns[index].setStyleSheet(self.button_select_all)
|
|
644
|
-
|
|
498
|
+
self.parent_window.refresh_signal_models()
|
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):
|
|
@@ -264,7 +352,12 @@ class TableUI(QMainWindow, Styles):
|
|
|
264
352
|
self.derivative_action = QAction('&Differentiate...', self)
|
|
265
353
|
self.derivative_action.triggered.connect(self.differenciate_selected_feature)
|
|
266
354
|
self.derivative_action.setShortcut("Ctrl+D")
|
|
267
|
-
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)
|
|
268
361
|
|
|
269
362
|
def delete_columns(self):
|
|
270
363
|
|
|
@@ -334,6 +427,19 @@ class TableUI(QMainWindow, Styles):
|
|
|
334
427
|
self.diffWidget = DifferentiateColWidget(self, selected_col)
|
|
335
428
|
self.diffWidget.show()
|
|
336
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
|
+
|
|
337
443
|
|
|
338
444
|
def groupby_time_table(self):
|
|
339
445
|
|
|
@@ -477,6 +583,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
477
583
|
layout.addWidget(QLabel('Representations: '))
|
|
478
584
|
self.hist_check = QCheckBox('histogram')
|
|
479
585
|
self.kde_check = QCheckBox('KDE plot')
|
|
586
|
+
self.count_check = QCheckBox('Countplot')
|
|
480
587
|
self.ecdf_check = QCheckBox('ECDF plot')
|
|
481
588
|
self.swarm_check = QCheckBox('swarm')
|
|
482
589
|
self.violin_check = QCheckBox('violin')
|
|
@@ -486,6 +593,7 @@ class TableUI(QMainWindow, Styles):
|
|
|
486
593
|
|
|
487
594
|
layout.addWidget(self.hist_check)
|
|
488
595
|
layout.addWidget(self.kde_check)
|
|
596
|
+
layout.addWidget(self.count_check)
|
|
489
597
|
layout.addWidget(self.ecdf_check)
|
|
490
598
|
layout.addWidget(self.swarm_check)
|
|
491
599
|
layout.addWidget(self.violin_check)
|
|
@@ -496,16 +604,38 @@ class TableUI(QMainWindow, Styles):
|
|
|
496
604
|
self.x_cb = QSearchableComboBox()
|
|
497
605
|
self.x_cb.addItems(['--']+list(self.data.columns))
|
|
498
606
|
|
|
607
|
+
self.y_cb = QSearchableComboBox()
|
|
608
|
+
self.y_cb.addItems(['--']+list(self.data.columns))
|
|
609
|
+
|
|
499
610
|
self.hue_cb = QSearchableComboBox()
|
|
500
611
|
self.hue_cb.addItems(['--']+list(self.data.columns))
|
|
501
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
|
|
502
628
|
|
|
503
|
-
self.x_cb.findText('--')
|
|
504
629
|
hbox = QHBoxLayout()
|
|
505
630
|
hbox.addWidget(QLabel('x: '), 33)
|
|
506
631
|
hbox.addWidget(self.x_cb, 66)
|
|
507
632
|
layout.addLayout(hbox)
|
|
508
633
|
|
|
634
|
+
hbox = QHBoxLayout()
|
|
635
|
+
hbox.addWidget(QLabel('y: '), 33)
|
|
636
|
+
hbox.addWidget(self.y_cb, 66)
|
|
637
|
+
layout.addLayout(hbox)
|
|
638
|
+
|
|
509
639
|
hbox = QHBoxLayout()
|
|
510
640
|
hbox.addWidget(QLabel('hue: '), 33)
|
|
511
641
|
hbox.addWidget(self.hue_cb, 66)
|
|
@@ -539,18 +669,10 @@ class TableUI(QMainWindow, Styles):
|
|
|
539
669
|
self.x_option = True
|
|
540
670
|
self.x = self.x_cb.currentText()
|
|
541
671
|
|
|
542
|
-
x = self.table_view.selectedIndexes()
|
|
543
|
-
col_idx = np.array([l.column() for l in x])
|
|
544
|
-
row_idx = np.array([l.row() for l in x])
|
|
545
|
-
column_names = self.data.columns
|
|
546
|
-
unique_cols = np.unique(col_idx)[0]
|
|
547
|
-
|
|
548
672
|
self.fig, self.ax = plt.subplots(1,1,figsize=(4,3))
|
|
549
673
|
self.plot1dWindow = FigureCanvas(self.fig, title="scatter")
|
|
550
674
|
self.ax.clear()
|
|
551
|
-
|
|
552
|
-
y = self.data.iloc[row_idx_i, unique_cols]
|
|
553
|
-
|
|
675
|
+
|
|
554
676
|
cmap = getattr(mcm, self.cmap_cb.currentText())
|
|
555
677
|
|
|
556
678
|
try:
|
|
@@ -562,58 +684,68 @@ class TableUI(QMainWindow, Styles):
|
|
|
562
684
|
if self.hue_cb.currentText()=='--':
|
|
563
685
|
hue_variable = None
|
|
564
686
|
|
|
565
|
-
|
|
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()
|
|
566
696
|
|
|
567
697
|
legend=True
|
|
568
698
|
if self.hist_check.isChecked():
|
|
569
|
-
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')
|
|
570
700
|
legend = False
|
|
571
701
|
if self.kde_check.isChecked():
|
|
572
|
-
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)
|
|
573
706
|
legend = False
|
|
574
|
-
|
|
575
707
|
if self.ecdf_check.isChecked():
|
|
576
|
-
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)
|
|
577
709
|
legend = False
|
|
578
710
|
|
|
579
711
|
if self.swarm_check.isChecked():
|
|
580
712
|
if self.x_option:
|
|
581
|
-
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)
|
|
582
714
|
legend = False
|
|
583
715
|
else:
|
|
584
|
-
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)
|
|
585
717
|
legend = False
|
|
586
718
|
|
|
587
719
|
if self.violin_check.isChecked():
|
|
588
720
|
if self.x_option:
|
|
589
|
-
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)
|
|
590
722
|
legend = False
|
|
591
723
|
else:
|
|
592
|
-
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)
|
|
593
725
|
legend = False
|
|
594
726
|
|
|
595
727
|
if self.box_check.isChecked():
|
|
596
728
|
if self.x_option:
|
|
597
|
-
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,)
|
|
598
730
|
legend = False
|
|
599
731
|
else:
|
|
600
|
-
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,)
|
|
601
733
|
legend = False
|
|
602
734
|
|
|
603
735
|
if self.boxenplot_check.isChecked():
|
|
604
736
|
if self.x_option:
|
|
605
|
-
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,)
|
|
606
738
|
legend = False
|
|
607
739
|
else:
|
|
608
|
-
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,)
|
|
609
741
|
legend = False
|
|
610
742
|
|
|
611
743
|
if self.strip_check.isChecked():
|
|
612
744
|
if self.x_option:
|
|
613
|
-
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)
|
|
614
746
|
legend = False
|
|
615
747
|
else:
|
|
616
|
-
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)
|
|
617
749
|
legend = False
|
|
618
750
|
|
|
619
751
|
plt.tight_layout()
|
|
@@ -762,42 +894,10 @@ class TableUI(QMainWindow, Styles):
|
|
|
762
894
|
column_names = self.data.columns
|
|
763
895
|
unique_cols = np.unique(col_idx)
|
|
764
896
|
|
|
765
|
-
if len(unique_cols)==1:
|
|
766
|
-
# 1D plot
|
|
767
|
-
# Open widget to set 1D data representations
|
|
897
|
+
if len(unique_cols)==1 or len(unique_cols)==0:
|
|
768
898
|
self.set_1D_plot_params()
|
|
769
899
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
# x = self.table_view.selectedIndexes()
|
|
773
|
-
# col_idx = np.array([l.column() for l in x])
|
|
774
|
-
# row_idx = np.array([l.row() for l in x])
|
|
775
|
-
# column_names = self.data.columns
|
|
776
|
-
# unique_cols = np.unique(col_idx)[0]
|
|
777
|
-
|
|
778
|
-
# self.fig, self.ax = plt.subplots(1,1,figsize=(4,3))
|
|
779
|
-
# self.histogram_window = FigureCanvas(self.fig, title="scatter")
|
|
780
|
-
# self.ax.clear()
|
|
781
|
-
# row_idx_i = row_idx[np.where(col_idx==unique_cols)[0]]
|
|
782
|
-
# y = self.data.iloc[row_idx_i, unique_cols]
|
|
783
|
-
|
|
784
|
-
# colors = [viridis(i / len(self.data['well_index'].unique())) for i in range(len(self.data['well_index'].unique()))]
|
|
785
|
-
# #for w,well_group in self.data.groupby('well_index'):
|
|
786
|
-
# 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,)
|
|
787
|
-
# sns.stripplot(data=self.data, y=column_names[unique_cols],dodge=True, ax=self.ax, hue='well_index', legend=False, palette=colors)
|
|
788
|
-
# # 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,)
|
|
789
|
-
# # for k,(w,well_group) in enumerate(self.data.groupby('well_index')):
|
|
790
|
-
# # self.ax.hist(well_group[column_names[unique_cols]],label=w, density=True, alpha=0.5, color=colors[k])
|
|
791
|
-
# #self.ax.legend()
|
|
792
|
-
# self.ax.set_xlabel(column_names[unique_cols])
|
|
793
|
-
# plt.tight_layout()
|
|
794
|
-
# self.fig.set_facecolor('none') # or 'None'
|
|
795
|
-
# self.fig.canvas.setStyleSheet("background-color: transparent;")
|
|
796
|
-
# self.histogram_window.canvas.draw()
|
|
797
|
-
# self.histogram_window.show()
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
elif len(unique_cols) == 2:
|
|
900
|
+
if len(unique_cols) == 2:
|
|
801
901
|
|
|
802
902
|
print("two columns, plot mode")
|
|
803
903
|
x1 = self.test_bool(self.data.iloc[row_idx, unique_cols[0]])
|
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/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":
|