celldetective 1.1.0__py3-none-any.whl → 1.1.1.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celldetective/__main__.py +5 -19
- celldetective/extra_properties.py +63 -53
- celldetective/filters.py +39 -11
- celldetective/gui/classifier_widget.py +56 -7
- celldetective/gui/control_panel.py +5 -0
- celldetective/gui/layouts.py +3 -2
- celldetective/gui/measurement_options.py +13 -109
- celldetective/gui/plot_signals_ui.py +1 -0
- celldetective/gui/process_block.py +1 -1
- celldetective/gui/survival_ui.py +7 -1
- celldetective/gui/tableUI.py +294 -28
- celldetective/gui/thresholds_gui.py +51 -10
- celldetective/gui/viewers.py +169 -22
- celldetective/io.py +41 -17
- celldetective/measure.py +13 -238
- celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
- celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
- celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
- celldetective/neighborhood.py +4 -1
- celldetective/preprocessing.py +483 -143
- celldetective/scripts/segment_cells.py +26 -7
- celldetective/scripts/train_segmentation_model.py +35 -34
- celldetective/segmentation.py +29 -20
- celldetective/signals.py +13 -231
- celldetective/tracking.py +2 -1
- celldetective/utils.py +440 -26
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/METADATA +1 -1
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/RECORD +34 -30
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/WHEEL +1 -1
- tests/test_preprocessing.py +37 -0
- tests/test_utils.py +48 -1
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/top_level.txt +0 -0
celldetective/gui/tableUI.py
CHANGED
|
@@ -1,16 +1,20 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import QMainWindow, QTableView, QAction, QMenu,QFileDialog, QLineEdit, QHBoxLayout, QWidget, QPushButton, QVBoxLayout, QComboBox, QLabel, QCheckBox, QMessageBox
|
|
1
|
+
from PyQt5.QtWidgets import QRadioButton, QButtonGroup, QMainWindow, QTableView, QAction, QMenu,QFileDialog, QLineEdit, QHBoxLayout, QWidget, QPushButton, QVBoxLayout, QComboBox, QLabel, QCheckBox, QMessageBox
|
|
2
2
|
from PyQt5.QtCore import Qt, QAbstractTableModel
|
|
3
3
|
import pandas as pd
|
|
4
4
|
import matplotlib.pyplot as plt
|
|
5
5
|
from matplotlib.cm import viridis
|
|
6
6
|
plt.rcParams['svg.fonttype'] = 'none'
|
|
7
7
|
from celldetective.gui.gui_utils import FigureCanvas, center_window
|
|
8
|
+
from celldetective.utils import differentiate_per_track
|
|
8
9
|
import numpy as np
|
|
9
10
|
import seaborn as sns
|
|
10
11
|
import matplotlib.cm as mcm
|
|
11
12
|
import os
|
|
12
13
|
from celldetective.gui import Styles
|
|
13
|
-
from superqt import QColormapComboBox
|
|
14
|
+
from superqt import QColormapComboBox, QLabeledSlider, QSearchableComboBox
|
|
15
|
+
from math import floor
|
|
16
|
+
|
|
17
|
+
from matplotlib import colormaps
|
|
14
18
|
|
|
15
19
|
class PandasModel(QAbstractTableModel):
|
|
16
20
|
|
|
@@ -55,6 +59,7 @@ class QueryWidget(QWidget):
|
|
|
55
59
|
self.submit_btn = QPushButton('submit')
|
|
56
60
|
self.submit_btn.clicked.connect(self.filter_table)
|
|
57
61
|
layout.addWidget(self.submit_btn, 30)
|
|
62
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
58
63
|
|
|
59
64
|
def filter_table(self):
|
|
60
65
|
try:
|
|
@@ -67,6 +72,84 @@ class QueryWidget(QWidget):
|
|
|
67
72
|
print(e)
|
|
68
73
|
return None
|
|
69
74
|
|
|
75
|
+
class DifferentiateColWidget(QWidget, Styles):
|
|
76
|
+
|
|
77
|
+
def __init__(self, parent_window, column=None):
|
|
78
|
+
|
|
79
|
+
super().__init__()
|
|
80
|
+
self.parent_window = parent_window
|
|
81
|
+
self.column = column
|
|
82
|
+
|
|
83
|
+
self.setWindowTitle("d/dt")
|
|
84
|
+
# Create the QComboBox and add some items
|
|
85
|
+
center_window(self)
|
|
86
|
+
|
|
87
|
+
layout = QVBoxLayout(self)
|
|
88
|
+
layout.setContentsMargins(30,30,30,30)
|
|
89
|
+
|
|
90
|
+
self.measurements_cb = QComboBox()
|
|
91
|
+
self.measurements_cb.addItems(list(self.parent_window.data.columns))
|
|
92
|
+
if self.column is not None:
|
|
93
|
+
idx = self.measurements_cb.findText(self.column)
|
|
94
|
+
self.measurements_cb.setCurrentIndex(idx)
|
|
95
|
+
|
|
96
|
+
measurement_layout = QHBoxLayout()
|
|
97
|
+
measurement_layout.addWidget(QLabel('measurements: '), 25)
|
|
98
|
+
measurement_layout.addWidget(self.measurements_cb, 75)
|
|
99
|
+
layout.addLayout(measurement_layout)
|
|
100
|
+
|
|
101
|
+
self.window_size_slider = QLabeledSlider()
|
|
102
|
+
self.window_size_slider.setRange(1,np.nanmax(self.parent_window.data.FRAME.to_numpy()))
|
|
103
|
+
self.window_size_slider.setValue(3)
|
|
104
|
+
window_layout = QHBoxLayout()
|
|
105
|
+
window_layout.addWidget(QLabel('window size: '), 25)
|
|
106
|
+
window_layout.addWidget(self.window_size_slider, 75)
|
|
107
|
+
layout.addLayout(window_layout)
|
|
108
|
+
|
|
109
|
+
self.backward_btn = QRadioButton('backward')
|
|
110
|
+
self.bi_btn = QRadioButton('bi')
|
|
111
|
+
self.bi_btn.click()
|
|
112
|
+
self.forward_btn = QRadioButton('forward')
|
|
113
|
+
self.mode_btn_group = QButtonGroup()
|
|
114
|
+
self.mode_btn_group.addButton(self.backward_btn)
|
|
115
|
+
self.mode_btn_group.addButton(self.bi_btn)
|
|
116
|
+
self.mode_btn_group.addButton(self.forward_btn)
|
|
117
|
+
|
|
118
|
+
mode_layout = QHBoxLayout()
|
|
119
|
+
mode_layout.addWidget(QLabel('mode: '),25)
|
|
120
|
+
mode_sublayout = QHBoxLayout()
|
|
121
|
+
mode_sublayout.addWidget(self.backward_btn, 33, alignment=Qt.AlignCenter)
|
|
122
|
+
mode_sublayout.addWidget(self.bi_btn, 33, alignment=Qt.AlignCenter)
|
|
123
|
+
mode_sublayout.addWidget(self.forward_btn, 33, alignment=Qt.AlignCenter)
|
|
124
|
+
mode_layout.addLayout(mode_sublayout, 75)
|
|
125
|
+
layout.addLayout(mode_layout)
|
|
126
|
+
|
|
127
|
+
self.submit_btn = QPushButton('Compute')
|
|
128
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
129
|
+
self.submit_btn.clicked.connect(self.compute_derivative_and_add_new_column)
|
|
130
|
+
layout.addWidget(self.submit_btn, 30)
|
|
131
|
+
|
|
132
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def compute_derivative_and_add_new_column(self):
|
|
136
|
+
|
|
137
|
+
if self.bi_btn.isChecked():
|
|
138
|
+
mode = 'bi'
|
|
139
|
+
elif self.forward_btn.isChecked():
|
|
140
|
+
mode = 'forward'
|
|
141
|
+
elif self.backward_btn.isChecked():
|
|
142
|
+
mode = 'backward'
|
|
143
|
+
self.parent_window.data = differentiate_per_track(self.parent_window.data,
|
|
144
|
+
self.measurements_cb.currentText(),
|
|
145
|
+
window_size=self.window_size_slider.value(),
|
|
146
|
+
mode=mode)
|
|
147
|
+
self.parent_window.model = PandasModel(self.parent_window.data)
|
|
148
|
+
self.parent_window.table_view.setModel(self.parent_window.model)
|
|
149
|
+
self.close()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
|
|
70
153
|
class RenameColWidget(QWidget):
|
|
71
154
|
|
|
72
155
|
def __init__(self, parent_window, column=None):
|
|
@@ -90,6 +173,7 @@ class RenameColWidget(QWidget):
|
|
|
90
173
|
self.submit_btn = QPushButton('rename')
|
|
91
174
|
self.submit_btn.clicked.connect(self.rename_col)
|
|
92
175
|
layout.addWidget(self.submit_btn, 30)
|
|
176
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
93
177
|
|
|
94
178
|
def rename_col(self):
|
|
95
179
|
|
|
@@ -128,6 +212,8 @@ class TableUI(QMainWindow, Styles):
|
|
|
128
212
|
self.model = PandasModel(data)
|
|
129
213
|
self.table_view.setModel(self.model)
|
|
130
214
|
self.table_view.resizeColumnsToContents()
|
|
215
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
216
|
+
|
|
131
217
|
|
|
132
218
|
def _createActions(self):
|
|
133
219
|
|
|
@@ -146,6 +232,11 @@ class TableUI(QMainWindow, Styles):
|
|
|
146
232
|
self.plot_action.setShortcut("Ctrl+p")
|
|
147
233
|
self.fileMenu.addAction(self.plot_action)
|
|
148
234
|
|
|
235
|
+
self.plot_inst_action = QAction("&Plot instantaneous...", self)
|
|
236
|
+
self.plot_inst_action.triggered.connect(self.plot_instantaneous)
|
|
237
|
+
self.plot_inst_action.setShortcut("Ctrl+i")
|
|
238
|
+
self.fileMenu.addAction(self.plot_inst_action)
|
|
239
|
+
|
|
149
240
|
self.groupby_action = QAction("&Group by tracks...", self)
|
|
150
241
|
self.groupby_action.triggered.connect(self.set_projection_mode_tracks)
|
|
151
242
|
self.groupby_action.setShortcut("Ctrl+g")
|
|
@@ -231,8 +322,17 @@ class TableUI(QMainWindow, Styles):
|
|
|
231
322
|
# check only one col selected and assert is numerical
|
|
232
323
|
# open widget to select window parameters, directionality
|
|
233
324
|
# create new col
|
|
234
|
-
|
|
235
|
-
|
|
325
|
+
|
|
326
|
+
x = self.table_view.selectedIndexes()
|
|
327
|
+
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
328
|
+
if col_idx!=0:
|
|
329
|
+
cols = np.array(list(self.data.columns))
|
|
330
|
+
selected_col = str(cols[col_idx][0])
|
|
331
|
+
else:
|
|
332
|
+
selected_col = None
|
|
333
|
+
|
|
334
|
+
self.diffWidget = DifferentiateColWidget(self, selected_col)
|
|
335
|
+
self.diffWidget.show()
|
|
236
336
|
|
|
237
337
|
|
|
238
338
|
def groupby_time_table(self):
|
|
@@ -270,25 +370,102 @@ class TableUI(QMainWindow, Styles):
|
|
|
270
370
|
def set_projection_mode_tracks(self):
|
|
271
371
|
|
|
272
372
|
self.projectionWidget = QWidget()
|
|
373
|
+
self.projectionWidget.setMinimumWidth(500)
|
|
273
374
|
self.projectionWidget.setWindowTitle('Set projection mode')
|
|
274
375
|
|
|
275
376
|
layout = QVBoxLayout()
|
|
276
377
|
self.projectionWidget.setLayout(layout)
|
|
378
|
+
|
|
379
|
+
self.projection_option = QRadioButton('global operation: ')
|
|
380
|
+
self.projection_option.setToolTip('Collapse the cell track measurements with an operation over each track.')
|
|
381
|
+
self.projection_option.toggled.connect(self.enable_projection_options)
|
|
277
382
|
self.projection_op_cb = QComboBox()
|
|
278
383
|
self.projection_op_cb.addItems(['mean','median','min','max', 'prod', 'sum'])
|
|
279
|
-
hbox = QHBoxLayout()
|
|
280
|
-
hbox.addWidget(QLabel('operation: '), 33)
|
|
281
|
-
hbox.addWidget(self.projection_op_cb, 66)
|
|
282
|
-
layout.addLayout(hbox)
|
|
283
384
|
|
|
284
|
-
|
|
385
|
+
projection_layout = QHBoxLayout()
|
|
386
|
+
projection_layout.addWidget(self.projection_option, 33)
|
|
387
|
+
projection_layout.addWidget(self.projection_op_cb, 66)
|
|
388
|
+
layout.addLayout(projection_layout)
|
|
389
|
+
|
|
390
|
+
self.event_time_option = QRadioButton('@event time: ')
|
|
391
|
+
self.event_time_option.setToolTip('Pick the measurements at a specific event time.')
|
|
392
|
+
self.event_time_option.toggled.connect(self.enable_projection_options)
|
|
393
|
+
self.event_times_cb = QComboBox()
|
|
394
|
+
cols = np.array(self.data.columns)
|
|
395
|
+
time_cols = np.array([c.startswith('t_') for c in cols])
|
|
396
|
+
time_cols = list(cols[time_cols])
|
|
397
|
+
if 't0' in list(self.data.columns):
|
|
398
|
+
time_cols.append('t0')
|
|
399
|
+
self.event_times_cb.addItems(time_cols)
|
|
400
|
+
self.event_times_cb.setEnabled(False)
|
|
401
|
+
|
|
402
|
+
event_time_layout = QHBoxLayout()
|
|
403
|
+
event_time_layout.addWidget(self.event_time_option, 33)
|
|
404
|
+
event_time_layout.addWidget(self.event_times_cb, 66)
|
|
405
|
+
layout.addLayout(event_time_layout)
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
self.per_status_option = QRadioButton('per status: ')
|
|
409
|
+
self.per_status_option.setToolTip('Collapse the cell track measurements independently for each of the cell state.')
|
|
410
|
+
self.per_status_option.toggled.connect(self.enable_projection_options)
|
|
411
|
+
self.per_status_cb = QComboBox()
|
|
412
|
+
self.status_operation = QComboBox()
|
|
413
|
+
self.status_operation.setEnabled(False)
|
|
414
|
+
self.status_operation.addItems(['mean','median','min','max', 'prod', 'sum'])
|
|
415
|
+
|
|
416
|
+
status_cols = np.array([c.startswith('status_') or c.startswith('group_') for c in cols])
|
|
417
|
+
status_cols = list(cols[status_cols])
|
|
418
|
+
if 'status' in list(self.data.columns):
|
|
419
|
+
status_cols.append('status')
|
|
420
|
+
self.per_status_cb.addItems(status_cols)
|
|
421
|
+
self.per_status_cb.setEnabled(False)
|
|
422
|
+
|
|
423
|
+
per_status_layout = QHBoxLayout()
|
|
424
|
+
per_status_layout.addWidget(self.per_status_option, 33)
|
|
425
|
+
per_status_layout.addWidget(self.per_status_cb, 66)
|
|
426
|
+
layout.addLayout(per_status_layout)
|
|
427
|
+
|
|
428
|
+
status_operation_layout = QHBoxLayout()
|
|
429
|
+
status_operation_layout.addWidget(QLabel('operation: '), 33, alignment=Qt.AlignRight)
|
|
430
|
+
status_operation_layout.addWidget(self.status_operation, 66)
|
|
431
|
+
layout.addLayout(status_operation_layout)
|
|
432
|
+
|
|
433
|
+
self.btn_projection_group = QButtonGroup()
|
|
434
|
+
self.btn_projection_group.addButton(self.projection_option)
|
|
435
|
+
self.btn_projection_group.addButton(self.event_time_option)
|
|
436
|
+
self.btn_projection_group.addButton(self.per_status_option)
|
|
437
|
+
|
|
438
|
+
apply_layout = QHBoxLayout()
|
|
439
|
+
|
|
440
|
+
self.set_projection_btn = QPushButton('Apply')
|
|
285
441
|
self.set_projection_btn.setStyleSheet(self.button_style_sheet)
|
|
286
442
|
self.set_projection_btn.clicked.connect(self.set_proj_mode)
|
|
287
|
-
|
|
443
|
+
apply_layout.addWidget(QLabel(''), 33)
|
|
444
|
+
apply_layout.addWidget(self.set_projection_btn,33)
|
|
445
|
+
apply_layout.addWidget(QLabel(''),33)
|
|
446
|
+
layout.addLayout(apply_layout)
|
|
288
447
|
|
|
289
448
|
self.projectionWidget.show()
|
|
290
449
|
center_window(self.projectionWidget)
|
|
291
450
|
|
|
451
|
+
def enable_projection_options(self):
|
|
452
|
+
|
|
453
|
+
if self.projection_option.isChecked():
|
|
454
|
+
self.projection_op_cb.setEnabled(True)
|
|
455
|
+
self.event_times_cb.setEnabled(False)
|
|
456
|
+
self.per_status_cb.setEnabled(False)
|
|
457
|
+
self.status_operation.setEnabled(False)
|
|
458
|
+
elif self.event_time_option.isChecked():
|
|
459
|
+
self.projection_op_cb.setEnabled(False)
|
|
460
|
+
self.event_times_cb.setEnabled(True)
|
|
461
|
+
self.per_status_cb.setEnabled(False)
|
|
462
|
+
self.status_operation.setEnabled(False)
|
|
463
|
+
elif self.per_status_option.isChecked():
|
|
464
|
+
self.projection_op_cb.setEnabled(False)
|
|
465
|
+
self.event_times_cb.setEnabled(False)
|
|
466
|
+
self.per_status_cb.setEnabled(True)
|
|
467
|
+
self.status_operation.setEnabled(True)
|
|
468
|
+
|
|
292
469
|
def set_1D_plot_params(self):
|
|
293
470
|
|
|
294
471
|
self.plot1Dparams = QWidget()
|
|
@@ -316,12 +493,12 @@ class TableUI(QMainWindow, Styles):
|
|
|
316
493
|
layout.addWidget(self.box_check)
|
|
317
494
|
layout.addWidget(self.boxenplot_check)
|
|
318
495
|
|
|
319
|
-
self.x_cb =
|
|
496
|
+
self.x_cb = QSearchableComboBox()
|
|
320
497
|
self.x_cb.addItems(['--']+list(self.data.columns))
|
|
321
498
|
|
|
322
|
-
self.hue_cb =
|
|
323
|
-
self.hue_cb.addItems(list(self.data.columns))
|
|
324
|
-
idx = self.hue_cb.findText('
|
|
499
|
+
self.hue_cb = QSearchableComboBox()
|
|
500
|
+
self.hue_cb.addItems(['--']+list(self.data.columns))
|
|
501
|
+
idx = self.hue_cb.findText('--')
|
|
325
502
|
|
|
326
503
|
self.x_cb.findText('--')
|
|
327
504
|
hbox = QHBoxLayout()
|
|
@@ -335,7 +512,12 @@ class TableUI(QMainWindow, Styles):
|
|
|
335
512
|
layout.addLayout(hbox)
|
|
336
513
|
|
|
337
514
|
self.cmap_cb = QColormapComboBox()
|
|
338
|
-
|
|
515
|
+
for cm in list(colormaps):
|
|
516
|
+
try:
|
|
517
|
+
self.cmap_cb.addColormap(cm)
|
|
518
|
+
except:
|
|
519
|
+
pass
|
|
520
|
+
|
|
339
521
|
hbox = QHBoxLayout()
|
|
340
522
|
hbox.addWidget(QLabel('colormap: '), 33)
|
|
341
523
|
hbox.addWidget(self.cmap_cb, 66)
|
|
@@ -370,9 +552,16 @@ class TableUI(QMainWindow, Styles):
|
|
|
370
552
|
y = self.data.iloc[row_idx_i, unique_cols]
|
|
371
553
|
|
|
372
554
|
cmap = getattr(mcm, self.cmap_cb.currentText())
|
|
373
|
-
hue_variable = self.hue_cb.currentText()
|
|
374
555
|
|
|
375
|
-
|
|
556
|
+
try:
|
|
557
|
+
hue_variable = self.hue_cb.currentText()
|
|
558
|
+
colors = [cmap(i / len(self.data[hue_variable].unique())) for i in range(len(self.data[hue_variable].unique()))]
|
|
559
|
+
except:
|
|
560
|
+
colors = None
|
|
561
|
+
|
|
562
|
+
if self.hue_cb.currentText()=='--':
|
|
563
|
+
hue_variable = None
|
|
564
|
+
|
|
376
565
|
#for w,well_group in self.data.groupby('well_index'):
|
|
377
566
|
|
|
378
567
|
legend=True
|
|
@@ -435,18 +624,86 @@ class TableUI(QMainWindow, Styles):
|
|
|
435
624
|
|
|
436
625
|
|
|
437
626
|
def set_proj_mode(self):
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
627
|
+
|
|
628
|
+
self.static_columns = ['well_index', 'well_name', 'pos_name', 'position', 'well', 'status', 't0', 'class','cell_type','concentration', 'antibody', 'pharmaceutical_agent','TRACK_ID','position']
|
|
629
|
+
|
|
630
|
+
if self.projection_option.isChecked():
|
|
631
|
+
|
|
632
|
+
self.projection_mode = self.projection_op_cb.currentText()
|
|
633
|
+
op = getattr(self.data.groupby(['position', 'TRACK_ID']), self.projection_mode)
|
|
634
|
+
group_table = op(self.data.groupby(['position', 'TRACK_ID']))
|
|
635
|
+
|
|
636
|
+
for c in self.static_columns:
|
|
637
|
+
try:
|
|
638
|
+
group_table[c] = self.data.groupby(['position','TRACK_ID'])[c].apply(lambda x: x.unique()[0])
|
|
639
|
+
except Exception as e:
|
|
640
|
+
print(e)
|
|
641
|
+
pass
|
|
642
|
+
|
|
643
|
+
for col in ['TRACK_ID']:
|
|
644
|
+
first_column = group_table.pop(col)
|
|
645
|
+
group_table.insert(0, col, first_column)
|
|
646
|
+
group_table.pop('FRAME')
|
|
647
|
+
|
|
648
|
+
|
|
649
|
+
elif self.event_time_option.isChecked():
|
|
650
|
+
time_of_interest = self.event_times_cb.currentText()
|
|
651
|
+
self.projection_mode = f"measurements at {time_of_interest}"
|
|
652
|
+
new_table = []
|
|
653
|
+
for tid,group in self.data.groupby(['position','TRACK_ID']):
|
|
654
|
+
time = group[time_of_interest].values[0]
|
|
655
|
+
if time==time:
|
|
656
|
+
time = floor(time) # floor for onset
|
|
657
|
+
else:
|
|
658
|
+
continue
|
|
659
|
+
frames = group['FRAME'].values
|
|
660
|
+
values = group.loc[group['FRAME']==time,:].to_numpy()
|
|
661
|
+
if len(values)>0:
|
|
662
|
+
values = dict(zip(list(self.data.columns), values[0]))
|
|
663
|
+
values.update({'TRACK_ID': tid[1]})
|
|
664
|
+
values.update({'position': tid[0]})
|
|
665
|
+
new_table.append(values)
|
|
666
|
+
|
|
667
|
+
group_table = pd.DataFrame(new_table)
|
|
668
|
+
for col in ['TRACK_ID']:
|
|
669
|
+
first_column = group_table.pop(col)
|
|
670
|
+
group_table.insert(0, col, first_column)
|
|
671
|
+
|
|
672
|
+
group_table = group_table.sort_values(by=['position','TRACK_ID','FRAME'],ignore_index=True)
|
|
673
|
+
group_table = group_table.reset_index(drop=True)
|
|
674
|
+
|
|
675
|
+
|
|
676
|
+
elif self.per_status_option.isChecked():
|
|
677
|
+
|
|
678
|
+
status_of_interest = self.per_status_cb.currentText()
|
|
679
|
+
self.projection_mode = f'{self.status_operation.currentText()} per {status_of_interest}'
|
|
680
|
+
self.data = self.data.dropna(subset=status_of_interest,ignore_index=True)
|
|
681
|
+
unique_statuses = np.unique(self.data[status_of_interest].to_numpy())
|
|
682
|
+
|
|
683
|
+
df_sections = []
|
|
684
|
+
for s in unique_statuses:
|
|
685
|
+
subtab = self.data.loc[self.data[status_of_interest]==s,:]
|
|
686
|
+
op = getattr(subtab.groupby(['position', 'TRACK_ID']), self.status_operation.currentText())
|
|
687
|
+
subtab_projected = op(subtab.groupby(['position', 'TRACK_ID']))
|
|
688
|
+
frame_duration = subtab.groupby(['position','TRACK_ID']).size().to_numpy()
|
|
689
|
+
for c in self.static_columns:
|
|
690
|
+
try:
|
|
691
|
+
subtab_projected[c] = subtab.groupby(['position', 'TRACK_ID'])[c].apply(lambda x: x.unique()[0])
|
|
692
|
+
except Exception as e:
|
|
693
|
+
print(e)
|
|
694
|
+
pass
|
|
695
|
+
subtab_projected['duration_in_state'] = frame_duration
|
|
696
|
+
df_sections.append(subtab_projected)
|
|
697
|
+
|
|
698
|
+
group_table = pd.concat(df_sections,axis=0,ignore_index=True)
|
|
699
|
+
for col in ['duration_in_state',status_of_interest,'TRACK_ID']:
|
|
700
|
+
first_column = group_table.pop(col)
|
|
701
|
+
group_table.insert(0, col, first_column)
|
|
702
|
+
group_table.pop('FRAME')
|
|
703
|
+
group_table = group_table.sort_values(by=['position','TRACK_ID',status_of_interest],ignore_index=True)
|
|
704
|
+
group_table = group_table.reset_index(drop=True)
|
|
705
|
+
|
|
442
706
|
|
|
443
|
-
self.static_columns = ['well_index', 'well_name', 'pos_name', 'position', 'well', 'status', 't0', 'class', 'concentration', 'antibody', 'pharmaceutical_agent']
|
|
444
|
-
for c in self.static_columns:
|
|
445
|
-
try:
|
|
446
|
-
group_table[c] = self.data.groupby(['position','TRACK_ID'])[c].apply(lambda x: x.unique()[0])
|
|
447
|
-
except Exception as e:
|
|
448
|
-
print(e)
|
|
449
|
-
pass
|
|
450
707
|
self.subtable = TableUI(group_table,f"Group by tracks: {self.projection_mode}", plot_mode="static")
|
|
451
708
|
self.subtable.show()
|
|
452
709
|
|
|
@@ -487,6 +744,15 @@ class TableUI(QMainWindow, Styles):
|
|
|
487
744
|
else:
|
|
488
745
|
return array
|
|
489
746
|
|
|
747
|
+
def plot_instantaneous(self):
|
|
748
|
+
|
|
749
|
+
if self.plot_mode=='plot_track_signals':
|
|
750
|
+
self.plot_mode = 'static'
|
|
751
|
+
self.plot()
|
|
752
|
+
self.plot_mode = 'plot_track_signals'
|
|
753
|
+
elif self.plot_mode=="static":
|
|
754
|
+
self.plot()
|
|
755
|
+
|
|
490
756
|
def plot(self):
|
|
491
757
|
if self.plot_mode == "static":
|
|
492
758
|
|
|
@@ -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
|
|
@@ -12,11 +12,12 @@ from skimage.morphology import disk
|
|
|
12
12
|
|
|
13
13
|
from celldetective.filters import std_filter, gauss_filter
|
|
14
14
|
from celldetective.gui.gui_utils import center_window, FigureCanvas, ListWidget, FilterChoice, color_from_class
|
|
15
|
-
from celldetective.utils import get_software_location, extract_experiment_channels, rename_intensity_column
|
|
15
|
+
from celldetective.utils import get_software_location, extract_experiment_channels, rename_intensity_column, estimate_unreliable_edge
|
|
16
16
|
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
|
|
@@ -61,6 +62,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
61
62
|
self.onlyFloat = QDoubleValidator()
|
|
62
63
|
self.onlyInt = QIntValidator()
|
|
63
64
|
self.cell_properties = ['centroid', 'area', 'perimeter', 'eccentricity', 'intensity_mean', 'solidity']
|
|
65
|
+
self.edge = None
|
|
64
66
|
|
|
65
67
|
if self.mode == "targets":
|
|
66
68
|
self.config_out_name = "threshold_targets.json"
|
|
@@ -262,10 +264,20 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
262
264
|
marker_box = QVBoxLayout()
|
|
263
265
|
marker_box.setContentsMargins(30, 30, 30, 30)
|
|
264
266
|
|
|
265
|
-
marker_lbl = QLabel('
|
|
267
|
+
marker_lbl = QLabel('Objects')
|
|
266
268
|
marker_lbl.setStyleSheet("font-weight: bold;")
|
|
267
269
|
marker_box.addWidget(marker_lbl, alignment=Qt.AlignCenter)
|
|
268
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
|
+
|
|
269
281
|
hbox_footprint = QHBoxLayout()
|
|
270
282
|
hbox_footprint.addWidget(QLabel('Footprint: '), 20)
|
|
271
283
|
self.footprint_slider = QLabeledSlider()
|
|
@@ -274,7 +286,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
274
286
|
self.footprint_slider.setRange(1, self.binary.shape[0] // 4)
|
|
275
287
|
self.footprint_slider.setValue(self.footprint)
|
|
276
288
|
self.footprint_slider.valueChanged.connect(self.set_footprint)
|
|
277
|
-
hbox_footprint.addWidget(self.footprint_slider,
|
|
289
|
+
hbox_footprint.addWidget(self.footprint_slider, 30)
|
|
290
|
+
hbox_footprint.addWidget(QLabel(''), 50)
|
|
278
291
|
marker_box.addLayout(hbox_footprint)
|
|
279
292
|
|
|
280
293
|
hbox_distance = QHBoxLayout()
|
|
@@ -285,7 +298,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
285
298
|
self.min_dist_slider.setRange(0, self.binary.shape[0] // 4)
|
|
286
299
|
self.min_dist_slider.setValue(self.min_dist)
|
|
287
300
|
self.min_dist_slider.valueChanged.connect(self.set_min_dist)
|
|
288
|
-
hbox_distance.addWidget(self.min_dist_slider,
|
|
301
|
+
hbox_distance.addWidget(self.min_dist_slider, 30)
|
|
302
|
+
hbox_distance.addWidget(QLabel(''), 50)
|
|
289
303
|
marker_box.addLayout(hbox_distance)
|
|
290
304
|
|
|
291
305
|
hbox_marker_btns = QHBoxLayout()
|
|
@@ -304,8 +318,23 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
304
318
|
hbox_marker_btns.addWidget(self.watershed_btn)
|
|
305
319
|
marker_box.addLayout(hbox_marker_btns)
|
|
306
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
|
+
|
|
307
325
|
self.left_panel.addLayout(marker_box)
|
|
308
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
|
+
|
|
309
338
|
def generate_props_contents(self):
|
|
310
339
|
|
|
311
340
|
properties_box = QVBoxLayout()
|
|
@@ -437,7 +466,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
437
466
|
self.im = self.ax.imshow(self.img, cmap='gray')
|
|
438
467
|
|
|
439
468
|
self.binary = threshold_image(self.img, self.threshold_slider.value()[0], self.threshold_slider.value()[1],
|
|
440
|
-
foreground_value=
|
|
469
|
+
foreground_value=1., fill_holes=True, edge_exclusion=None)
|
|
441
470
|
self.thresholded_image = np.ma.masked_where(self.binary == 0., self.binary)
|
|
442
471
|
self.image_thresholded = self.ax.imshow(self.thresholded_image, cmap="viridis", alpha=0.5, interpolation='none')
|
|
443
472
|
|
|
@@ -591,6 +620,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
591
620
|
|
|
592
621
|
self.reload_frame()
|
|
593
622
|
filters = self.filters_qlist.items
|
|
623
|
+
self.edge = estimate_unreliable_edge(filters)
|
|
594
624
|
self.img = filter_image(self.img, filters)
|
|
595
625
|
self.refresh_imshow()
|
|
596
626
|
self.update_histogram()
|
|
@@ -637,7 +667,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
637
667
|
"""
|
|
638
668
|
|
|
639
669
|
self.binary = threshold_image(self.img, self.threshold_slider.value()[0], self.threshold_slider.value()[1],
|
|
640
|
-
foreground_value=
|
|
670
|
+
foreground_value=1., fill_holes=True, edge_exclusion=self.edge)
|
|
641
671
|
self.thresholded_image = np.ma.masked_where(self.binary == 0., self.binary)
|
|
642
672
|
self.image_thresholded.set_data(self.thresholded_image)
|
|
643
673
|
self.fcanvas.canvas.draw_idle()
|
|
@@ -658,7 +688,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
658
688
|
|
|
659
689
|
if self.binary.ndim == 3:
|
|
660
690
|
self.binary = np.squeeze(self.binary)
|
|
661
|
-
self.binary = binary_fill_holes(self.binary)
|
|
691
|
+
#self.binary = binary_fill_holes(self.binary)
|
|
662
692
|
self.coords, self.edt_map = identify_markers_from_binary(self.binary, self.min_dist,
|
|
663
693
|
footprint_size=self.footprint, footprint=None,
|
|
664
694
|
return_edt=True)
|
|
@@ -673,7 +703,10 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
673
703
|
|
|
674
704
|
def apply_watershed_to_selection(self):
|
|
675
705
|
|
|
676
|
-
|
|
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))
|
|
677
710
|
|
|
678
711
|
self.current_channel = self.channels_cb.currentIndex()
|
|
679
712
|
t = int(self.frame_slider.value())
|
|
@@ -785,7 +818,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
785
818
|
self.property_query_le.setText('')
|
|
786
819
|
|
|
787
820
|
self.binary = threshold_image(self.img, self.threshold_slider.value()[0], self.threshold_slider.value()[1],
|
|
788
|
-
foreground_value=
|
|
821
|
+
foreground_value=1., fill_holes=True, edge_exclusion=self.edge)
|
|
789
822
|
self.thresholded_image = np.ma.masked_where(self.binary == 0., self.binary)
|
|
790
823
|
|
|
791
824
|
self.scat_markers.set_color('tab:red')
|
|
@@ -804,6 +837,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
804
837
|
"marker_footprint_size": self.footprint,
|
|
805
838
|
"feature_queries": [self.property_query_le.text()],
|
|
806
839
|
"equalize_reference": [self.equalize_option, self.frame_slider.value()],
|
|
840
|
+
"do_watershed": self.marker_option.isChecked(),
|
|
807
841
|
}
|
|
808
842
|
|
|
809
843
|
print('The following instructions will be written: ', instructions)
|
|
@@ -871,6 +905,13 @@ class ThresholdConfigWizard(QMainWindow, Styles):
|
|
|
871
905
|
self.property_query_le.setText(feature_queries[0])
|
|
872
906
|
self.submit_query_btn.click()
|
|
873
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
|
+
|
|
874
915
|
|
|
875
916
|
class ThresholdNormalisation(ThresholdConfigWizard):
|
|
876
917
|
def __init__(self, min_threshold, current_channel, parent_window=None):
|