celldetective 1.3.4.post1__py3-none-any.whl → 1.3.6.post1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celldetective/_version.py +1 -1
- celldetective/events.py +10 -5
- celldetective/filters.py +11 -0
- celldetective/gui/btrack_options.py +151 -1
- celldetective/gui/classifier_widget.py +44 -15
- celldetective/gui/configure_new_exp.py +13 -0
- celldetective/gui/control_panel.py +4 -2
- celldetective/gui/generic_signal_plot.py +2 -6
- celldetective/gui/gui_utils.py +170 -12
- celldetective/gui/measurement_options.py +85 -54
- celldetective/gui/neighborhood_options.py +1 -1
- celldetective/gui/plot_signals_ui.py +3 -4
- celldetective/gui/process_block.py +8 -6
- celldetective/gui/signal_annotator.py +10 -3
- celldetective/gui/signal_annotator2.py +146 -193
- celldetective/gui/survival_ui.py +121 -34
- celldetective/gui/tableUI.py +26 -12
- celldetective/gui/thresholds_gui.py +9 -52
- celldetective/gui/viewers.py +58 -21
- celldetective/io.py +1087 -161
- celldetective/measure.py +175 -102
- celldetective/preprocessing.py +2 -2
- celldetective/relative_measurements.py +6 -9
- celldetective/scripts/measure_cells.py +13 -3
- celldetective/scripts/segment_cells.py +0 -1
- celldetective/scripts/track_cells.py +25 -1
- celldetective/signals.py +9 -7
- celldetective/tracking.py +130 -81
- celldetective/utils.py +28 -7
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/METADATA +3 -2
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/RECORD +35 -35
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/WHEEL +0 -0
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/top_level.txt +0 -0
celldetective/gui/gui_utils.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import numpy as np
|
|
2
|
-
from PyQt5.QtWidgets import QApplication, QMessageBox, QFrame, QSizePolicy, QWidget, QLineEdit, QListWidget, QVBoxLayout, QComboBox, \
|
|
2
|
+
from PyQt5.QtWidgets import QGridLayout, QApplication, QMessageBox, QFrame, QSizePolicy, QWidget, QLineEdit, QListWidget, QVBoxLayout, QComboBox, \
|
|
3
3
|
QPushButton, QLabel, QHBoxLayout, QCheckBox, QFileDialog, QToolButton, QMenu, QStylePainter, QStyleOptionComboBox, QStyle
|
|
4
|
-
from PyQt5.QtCore import Qt, QSize, QAbstractTableModel
|
|
4
|
+
from PyQt5.QtCore import Qt, QSize, QAbstractTableModel, QEvent, pyqtSignal
|
|
5
5
|
from PyQt5.QtGui import QDoubleValidator, QIntValidator, QStandardItemModel, QPalette
|
|
6
6
|
|
|
7
7
|
from celldetective.gui import Styles
|
|
@@ -15,6 +15,125 @@ import celldetective.extra_properties as extra_properties
|
|
|
15
15
|
from inspect import getmembers, isfunction
|
|
16
16
|
from celldetective.filters import *
|
|
17
17
|
from os import sep
|
|
18
|
+
import json
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PreprocessingLayout(QVBoxLayout, Styles):
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
A widget that allows user to choose preprocessing filters for an image
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, parent_window=None, apply_btn_option=True, *args, **kwargs):
|
|
28
|
+
super().__init__(*args, **kwargs)
|
|
29
|
+
|
|
30
|
+
self.parent_window = parent_window
|
|
31
|
+
self.apply_btn_option = apply_btn_option
|
|
32
|
+
self.generate_components()
|
|
33
|
+
self.add_to_layout()
|
|
34
|
+
|
|
35
|
+
def add_to_layout(self):
|
|
36
|
+
|
|
37
|
+
self.setContentsMargins(20, 20, 20, 20)
|
|
38
|
+
|
|
39
|
+
button_layout = QHBoxLayout()
|
|
40
|
+
button_layout.addWidget(self.preprocess_lbl, 85, alignment=Qt.AlignLeft)
|
|
41
|
+
button_layout.addWidget(self.delete_filter_btn, 5)
|
|
42
|
+
button_layout.addWidget(self.add_filter_btn, 5)
|
|
43
|
+
button_layout.addWidget(self.help_prefilter_btn, 5)
|
|
44
|
+
self.addLayout(button_layout, 25)
|
|
45
|
+
|
|
46
|
+
self.addWidget(self.list, 70)
|
|
47
|
+
if self.apply_btn_option:
|
|
48
|
+
self.addWidget(self.apply_btn, 5)
|
|
49
|
+
|
|
50
|
+
def generate_components(self):
|
|
51
|
+
|
|
52
|
+
self.list = ListWidget(FilterChoice, [])
|
|
53
|
+
|
|
54
|
+
self.preprocess_lbl = QLabel('Preprocessing')
|
|
55
|
+
self.preprocess_lbl.setStyleSheet("font-weight: bold;")
|
|
56
|
+
|
|
57
|
+
self.delete_filter_btn = QPushButton()
|
|
58
|
+
self.delete_filter_btn.setStyleSheet(self.button_select_all)
|
|
59
|
+
self.delete_filter_btn.setIcon(icon(MDI6.trash_can, color="black"))
|
|
60
|
+
self.delete_filter_btn.setToolTip("Remove filter")
|
|
61
|
+
self.delete_filter_btn.setIconSize(QSize(20, 20))
|
|
62
|
+
self.delete_filter_btn.clicked.connect(self.list.removeSel)
|
|
63
|
+
|
|
64
|
+
self.add_filter_btn = QPushButton()
|
|
65
|
+
self.add_filter_btn.setStyleSheet(self.button_select_all)
|
|
66
|
+
self.add_filter_btn.setIcon(icon(MDI6.filter_plus, color="black"))
|
|
67
|
+
self.add_filter_btn.setToolTip("Add filter")
|
|
68
|
+
self.add_filter_btn.setIconSize(QSize(20, 20))
|
|
69
|
+
self.add_filter_btn.clicked.connect(self.list.addItem)
|
|
70
|
+
|
|
71
|
+
self.help_prefilter_btn = QPushButton()
|
|
72
|
+
self.help_prefilter_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
|
|
73
|
+
self.help_prefilter_btn.setIconSize(QSize(20, 20))
|
|
74
|
+
self.help_prefilter_btn.clicked.connect(self.help_prefilter)
|
|
75
|
+
self.help_prefilter_btn.setStyleSheet(self.button_select_all)
|
|
76
|
+
self.help_prefilter_btn.setToolTip("Help.")
|
|
77
|
+
|
|
78
|
+
if self.apply_btn_option:
|
|
79
|
+
self.apply_btn = QPushButton("Apply")
|
|
80
|
+
self.apply_btn.setIcon(icon(MDI6.filter_cog_outline, color="white"))
|
|
81
|
+
self.apply_btn.setIconSize(QSize(20, 20))
|
|
82
|
+
self.apply_btn.setStyleSheet(self.button_style_sheet)
|
|
83
|
+
self.apply_btn.clicked.connect(self.parent_window.preprocess_image)
|
|
84
|
+
|
|
85
|
+
def help_prefilter(self):
|
|
86
|
+
|
|
87
|
+
"""
|
|
88
|
+
Helper for prefiltering strategy
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','prefilter-for-segmentation.json'])
|
|
92
|
+
|
|
93
|
+
with open(dict_path) as f:
|
|
94
|
+
d = json.load(f)
|
|
95
|
+
|
|
96
|
+
suggestion = help_generic(d)
|
|
97
|
+
if isinstance(suggestion, str):
|
|
98
|
+
print(f"{suggestion=}")
|
|
99
|
+
msgBox = QMessageBox()
|
|
100
|
+
msgBox.setIcon(QMessageBox.Information)
|
|
101
|
+
msgBox.setTextFormat(Qt.RichText)
|
|
102
|
+
msgBox.setText(f"The suggested technique is to {suggestion}.\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>.")
|
|
103
|
+
msgBox.setWindowTitle("Info")
|
|
104
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
105
|
+
returnValue = msgBox.exec()
|
|
106
|
+
if returnValue == QMessageBox.Ok:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class PreprocessingLayout2(PreprocessingLayout):
|
|
111
|
+
|
|
112
|
+
def __init__(self, fraction=75, *args, **kwargs):
|
|
113
|
+
|
|
114
|
+
self.fraction = fraction
|
|
115
|
+
super().__init__(apply_btn_option=False, *args, **kwargs)
|
|
116
|
+
self.preprocess_lbl.setText('Preprocessing: ')
|
|
117
|
+
self.preprocess_lbl.setStyleSheet("")
|
|
118
|
+
self.setContentsMargins(0,0,0,0)
|
|
119
|
+
|
|
120
|
+
def add_to_layout(self):
|
|
121
|
+
|
|
122
|
+
main_layout = QHBoxLayout()
|
|
123
|
+
main_layout.setContentsMargins(0,0,0,0)
|
|
124
|
+
main_layout.setSpacing(5)
|
|
125
|
+
main_layout.addWidget(self.preprocess_lbl, self.fraction, alignment=Qt.AlignTop)
|
|
126
|
+
|
|
127
|
+
list_grid = QGridLayout()
|
|
128
|
+
list_grid.addWidget(self.list, 0, 0, 2, 2)
|
|
129
|
+
list_grid.addWidget(self.add_filter_btn, 0, 2, 1, 1)
|
|
130
|
+
list_grid.addWidget(self.delete_filter_btn, 1, 2, 1, 1)
|
|
131
|
+
main_layout.addLayout(list_grid, 100-self.fraction)
|
|
132
|
+
self.add_filter_btn.setFixedWidth(35) # Ensure the button width is fixed
|
|
133
|
+
self.delete_filter_btn.setFixedWidth(35)
|
|
134
|
+
list_grid.setColumnStretch(2, 0)
|
|
135
|
+
|
|
136
|
+
self.addLayout(main_layout)
|
|
18
137
|
|
|
19
138
|
|
|
20
139
|
class QCheckableComboBox(QComboBox):
|
|
@@ -23,12 +142,13 @@ class QCheckableComboBox(QComboBox):
|
|
|
23
142
|
adapted from https://stackoverflow.com/questions/22775095/pyqt-how-to-set-combobox-items-be-checkable
|
|
24
143
|
"""
|
|
25
144
|
|
|
145
|
+
activated = pyqtSignal(str)
|
|
146
|
+
|
|
26
147
|
def __init__(self, obj='', parent_window=None, *args, **kwargs):
|
|
27
148
|
|
|
28
149
|
super().__init__(parent_window, *args, **kwargs)
|
|
29
150
|
|
|
30
151
|
self.setTitle('')
|
|
31
|
-
self.view().pressed.connect(self.handleItemPressed)
|
|
32
152
|
self.setModel(QStandardItemModel(self))
|
|
33
153
|
self.obj = obj
|
|
34
154
|
self.toolButton = QToolButton(parent_window)
|
|
@@ -38,6 +158,9 @@ class QCheckableComboBox(QComboBox):
|
|
|
38
158
|
self.toolButton.setPopupMode(QToolButton.InstantPopup)
|
|
39
159
|
self.anySelected = False
|
|
40
160
|
|
|
161
|
+
self.view().viewport().installEventFilter(self)
|
|
162
|
+
self.view().pressed.connect(self.handleItemPressed)
|
|
163
|
+
|
|
41
164
|
def clear(self):
|
|
42
165
|
|
|
43
166
|
self.unselectAll()
|
|
@@ -70,6 +193,8 @@ class QCheckableComboBox(QComboBox):
|
|
|
70
193
|
elif len(options_checked[options_checked])==0:
|
|
71
194
|
self.setTitle(f"No {self.obj} selected...")
|
|
72
195
|
self.anySelected = False
|
|
196
|
+
|
|
197
|
+
self.activated.emit(self.title())
|
|
73
198
|
|
|
74
199
|
def setCurrentIndex(self, index):
|
|
75
200
|
|
|
@@ -101,6 +226,7 @@ class QCheckableComboBox(QComboBox):
|
|
|
101
226
|
|
|
102
227
|
def setTitle(self, title):
|
|
103
228
|
self._title = title
|
|
229
|
+
self.update()
|
|
104
230
|
self.repaint()
|
|
105
231
|
|
|
106
232
|
def paintEvent(self, event):
|
|
@@ -155,7 +281,12 @@ class QCheckableComboBox(QComboBox):
|
|
|
155
281
|
|
|
156
282
|
def isAnySelected(self):
|
|
157
283
|
return not self.title().startswith('No')
|
|
158
|
-
|
|
284
|
+
|
|
285
|
+
def eventFilter(self, source, event):
|
|
286
|
+
if source is self.view().viewport():
|
|
287
|
+
if event.type() == QEvent.MouseButtonRelease:
|
|
288
|
+
return True # Prevent the popup from closing
|
|
289
|
+
return super().eventFilter(source, event)
|
|
159
290
|
|
|
160
291
|
class PandasModel(QAbstractTableModel):
|
|
161
292
|
|
|
@@ -415,7 +546,7 @@ class QHSeperationLine(QFrame):
|
|
|
415
546
|
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
|
416
547
|
|
|
417
548
|
|
|
418
|
-
class FeatureChoice(QWidget):
|
|
549
|
+
class FeatureChoice(QWidget, Styles):
|
|
419
550
|
|
|
420
551
|
def __init__(self, parent_window):
|
|
421
552
|
super().__init__()
|
|
@@ -423,7 +554,6 @@ class FeatureChoice(QWidget):
|
|
|
423
554
|
self.setWindowTitle("Add feature")
|
|
424
555
|
# Create the QComboBox and add some items
|
|
425
556
|
self.combo_box = QComboBox(self)
|
|
426
|
-
center_window(self)
|
|
427
557
|
|
|
428
558
|
standard_measurements = ["area",
|
|
429
559
|
"area_bbox",
|
|
@@ -453,12 +583,15 @@ class FeatureChoice(QWidget):
|
|
|
453
583
|
self.combo_box.addItems(standard_measurements)
|
|
454
584
|
|
|
455
585
|
self.add_btn = QPushButton("Add")
|
|
586
|
+
self.add_btn.setStyleSheet(self.button_style_sheet)
|
|
456
587
|
self.add_btn.clicked.connect(self.add_current_feature)
|
|
457
588
|
|
|
458
589
|
# Create the layout
|
|
459
590
|
layout = QVBoxLayout(self)
|
|
460
591
|
layout.addWidget(self.combo_box)
|
|
461
592
|
layout.addWidget(self.add_btn)
|
|
593
|
+
center_window(self)
|
|
594
|
+
|
|
462
595
|
|
|
463
596
|
def add_current_feature(self):
|
|
464
597
|
filtername = self.combo_box.currentText()
|
|
@@ -466,7 +599,7 @@ class FeatureChoice(QWidget):
|
|
|
466
599
|
self.close()
|
|
467
600
|
|
|
468
601
|
|
|
469
|
-
class FilterChoice(QWidget):
|
|
602
|
+
class FilterChoice(QWidget, Styles):
|
|
470
603
|
|
|
471
604
|
def __init__(self, parent_window):
|
|
472
605
|
|
|
@@ -487,6 +620,7 @@ class FilterChoice(QWidget):
|
|
|
487
620
|
'laplace_filter': None,
|
|
488
621
|
'abs_filter': None,
|
|
489
622
|
'ln_filter': None,
|
|
623
|
+
'invert_filter': {'value': 65535},
|
|
490
624
|
'subtract_filter': {'value': 1},
|
|
491
625
|
'dog_filter': {'sigma_low': 0.8, 'sigma_high': 1.6},
|
|
492
626
|
'log_filter': {'sigma': 2},
|
|
@@ -503,20 +637,26 @@ class FilterChoice(QWidget):
|
|
|
503
637
|
self.combo_box.currentTextChanged.connect(self.update_arguments)
|
|
504
638
|
layout.addWidget(self.combo_box)
|
|
505
639
|
|
|
640
|
+
self.floatValidator = QDoubleValidator()
|
|
506
641
|
self.arguments_le = [QLineEdit() for i in range(3)]
|
|
642
|
+
for i in range(3):
|
|
643
|
+
self.arguments_le[i].setValidator(self.floatValidator)
|
|
644
|
+
|
|
507
645
|
self.arguments_labels = [QLabel('') for i in range(3)]
|
|
508
646
|
for i in range(2):
|
|
509
647
|
hbox = QHBoxLayout()
|
|
510
|
-
hbox.addWidget(self.arguments_labels[i],
|
|
511
|
-
hbox.addWidget(self.arguments_le[i],
|
|
648
|
+
hbox.addWidget(self.arguments_labels[i], 40)
|
|
649
|
+
hbox.addWidget(self.arguments_le[i], 60)
|
|
512
650
|
layout.addLayout(hbox)
|
|
513
651
|
|
|
514
652
|
self.add_btn = QPushButton("Add")
|
|
653
|
+
self.add_btn.setStyleSheet(self.button_style_sheet)
|
|
515
654
|
self.add_btn.clicked.connect(self.add_current_feature)
|
|
516
655
|
layout.addWidget(self.add_btn)
|
|
517
656
|
|
|
518
657
|
self.combo_box.setCurrentIndex(0)
|
|
519
658
|
self.update_arguments()
|
|
659
|
+
center_window(self)
|
|
520
660
|
|
|
521
661
|
def add_current_feature(self):
|
|
522
662
|
|
|
@@ -525,8 +665,10 @@ class FilterChoice(QWidget):
|
|
|
525
665
|
|
|
526
666
|
filter_instructions = [filtername.split('_')[0]]
|
|
527
667
|
for a in self.arguments_le:
|
|
528
|
-
|
|
668
|
+
|
|
669
|
+
arg = a.text().replace(',','.')
|
|
529
670
|
arg_num = arg
|
|
671
|
+
|
|
530
672
|
if (arg != '') and arg_num.replace('.', '').replace(',', '').isnumeric():
|
|
531
673
|
num = float(arg)
|
|
532
674
|
if num.is_integer():
|
|
@@ -666,12 +808,14 @@ class DistanceChoice(QWidget):
|
|
|
666
808
|
super().__init__()
|
|
667
809
|
self.parent_window = parent_window
|
|
668
810
|
self.setWindowTitle("Set distances")
|
|
811
|
+
self.floatValidator = QDoubleValidator()
|
|
669
812
|
center_window(self)
|
|
670
813
|
|
|
671
814
|
# Create the QComboBox and add some items
|
|
672
815
|
|
|
673
816
|
self.dist_label = QLabel('Distance [px]: ')
|
|
674
817
|
self.dist_le = QLineEdit('10')
|
|
818
|
+
self.dist_le.setValidator(self.floatValidator)
|
|
675
819
|
|
|
676
820
|
self.add_btn = QPushButton("Add")
|
|
677
821
|
self.add_btn.clicked.connect(self.add_current_feature)
|
|
@@ -686,7 +830,7 @@ class DistanceChoice(QWidget):
|
|
|
686
830
|
layout.addWidget(self.add_btn)
|
|
687
831
|
|
|
688
832
|
def add_current_feature(self):
|
|
689
|
-
value = self.dist_le.text()
|
|
833
|
+
value = self.dist_le.text().replace(',','.')
|
|
690
834
|
values = [value]
|
|
691
835
|
self.parent_window.list_widget.addItems(values)
|
|
692
836
|
self.close()
|
|
@@ -767,6 +911,9 @@ class ListWidget(QWidget):
|
|
|
767
911
|
self.addItemWindow = self.choiceWidget(self)
|
|
768
912
|
self.addItemWindow.show()
|
|
769
913
|
|
|
914
|
+
def addItemToList(self, item):
|
|
915
|
+
self.list_widget.addItems([item])
|
|
916
|
+
|
|
770
917
|
def getItems(self):
|
|
771
918
|
|
|
772
919
|
"""
|
|
@@ -794,6 +941,9 @@ class ListWidget(QWidget):
|
|
|
794
941
|
items.append(self.dtype(self.list_widget.item(x).text()))
|
|
795
942
|
return items
|
|
796
943
|
|
|
944
|
+
def clear(self):
|
|
945
|
+
self.items = []
|
|
946
|
+
self.list_widget.clear()
|
|
797
947
|
|
|
798
948
|
def removeSel(self):
|
|
799
949
|
|
|
@@ -827,13 +977,21 @@ class FigureCanvas(QWidget):
|
|
|
827
977
|
if interactive:
|
|
828
978
|
self.toolbar = NavigationToolbar2QT(self.canvas)
|
|
829
979
|
self.layout = QVBoxLayout(self)
|
|
830
|
-
self.layout.addWidget(self.canvas)
|
|
980
|
+
self.layout.addWidget(self.canvas,90)
|
|
831
981
|
if interactive:
|
|
832
982
|
self.layout.addWidget(self.toolbar)
|
|
833
983
|
|
|
834
984
|
center_window(self)
|
|
835
985
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
836
986
|
|
|
987
|
+
def resizeEvent(self, event):
|
|
988
|
+
|
|
989
|
+
super().resizeEvent(event)
|
|
990
|
+
try:
|
|
991
|
+
self.fig.tight_layout()
|
|
992
|
+
except:
|
|
993
|
+
pass
|
|
994
|
+
|
|
837
995
|
def draw(self):
|
|
838
996
|
self.canvas.draw()
|
|
839
997
|
|
|
@@ -27,9 +27,10 @@ from pathlib import Path
|
|
|
27
27
|
|
|
28
28
|
from celldetective.gui.viewers import CellEdgeVisualizer, SpotDetectionVisualizer
|
|
29
29
|
from celldetective.gui.layouts import ProtocolDesignerLayout, BackgroundFitCorrectionLayout, LocalCorrectionLayout
|
|
30
|
-
from celldetective.gui.gui_utils import ThresholdLineEdit
|
|
30
|
+
from celldetective.gui.gui_utils import ThresholdLineEdit, PreprocessingLayout2
|
|
31
31
|
from celldetective.gui import Styles
|
|
32
32
|
|
|
33
|
+
|
|
33
34
|
class ConfigMeasurements(QMainWindow, Styles):
|
|
34
35
|
"""
|
|
35
36
|
UI to set measurement instructions.
|
|
@@ -261,7 +262,7 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
261
262
|
self.add_feature_btn.setToolTip("Add feature")
|
|
262
263
|
self.add_feature_btn.setIconSize(QSize(20, 20))
|
|
263
264
|
|
|
264
|
-
self.features_list = ListWidget(FeatureChoice, initial_features=['area', '
|
|
265
|
+
self.features_list = ListWidget(FeatureChoice, initial_features=['area', 'intensity_mean', ])
|
|
265
266
|
|
|
266
267
|
self.del_feature_btn.clicked.connect(self.features_list.removeSel)
|
|
267
268
|
self.add_feature_btn.clicked.connect(self.features_list.addItem)
|
|
@@ -313,25 +314,6 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
313
314
|
|
|
314
315
|
self.feat_sep3 = QHSeperationLine()
|
|
315
316
|
layout.addWidget(self.feat_sep3)
|
|
316
|
-
# self.radial_intensity_btn = QCheckBox('Measure radial intensity distribution')
|
|
317
|
-
# layout.addWidget(self.radial_intensity_btn)
|
|
318
|
-
# self.radial_intensity_btn.clicked.connect(self.enable_step_size)
|
|
319
|
-
# self.channel_chechkboxes=[]
|
|
320
|
-
# for channel in self.channel_names:
|
|
321
|
-
# channel_checkbox=QCheckBox(channel)
|
|
322
|
-
# self.channel_chechkboxes.append(channel_checkbox)
|
|
323
|
-
# layout.addWidget(channel_checkbox)
|
|
324
|
-
# channel_checkbox.setEnabled(False)
|
|
325
|
-
# step_box=QHBoxLayout()
|
|
326
|
-
# self.step_lbl=QLabel("Step size (in px)")
|
|
327
|
-
# self.step_size=QLineEdit()
|
|
328
|
-
# self.step_lbl.setEnabled(False)
|
|
329
|
-
# self.step_size.setEnabled(False)
|
|
330
|
-
# step_box.addWidget(self.step_lbl)
|
|
331
|
-
# step_box.addWidget(self.step_size)
|
|
332
|
-
# layout.addLayout(step_box)
|
|
333
|
-
# self.feat_sep4 = QHSeperationLine()
|
|
334
|
-
# layout.addWidget(self.feat_sep4)
|
|
335
317
|
|
|
336
318
|
# Haralick features parameters
|
|
337
319
|
self.activate_haralick_btn = QCheckBox('Measure Haralick texture features')
|
|
@@ -496,6 +478,8 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
496
478
|
Write the selected options in a json file for later reading by the software.
|
|
497
479
|
"""
|
|
498
480
|
|
|
481
|
+
print(f"{self.spot_preprocessing.list.items=}")
|
|
482
|
+
|
|
499
483
|
print('Writing instructions...')
|
|
500
484
|
measurement_options = {}
|
|
501
485
|
background_correction = self.protocol_layout.protocols
|
|
@@ -511,16 +495,6 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
511
495
|
if not border_distances:
|
|
512
496
|
border_distances = None
|
|
513
497
|
measurement_options.update({'border_distances': border_distances})
|
|
514
|
-
# radial_intensity = {}
|
|
515
|
-
# radial_step = int(self.step_size.text())
|
|
516
|
-
# radial_channels = []
|
|
517
|
-
# for checkbox in self.channel_chechkboxes:
|
|
518
|
-
# if checkbox.isChecked():
|
|
519
|
-
# radial_channels.append(checkbox.text())
|
|
520
|
-
# radial_intensity={'radial_step': radial_step, 'radial_channels': radial_channels}
|
|
521
|
-
# if not self.radial_intensity_btn.isChecked():
|
|
522
|
-
# radial_intensity = None
|
|
523
|
-
# measurement_options.update({'radial_intensity' : radial_intensity})
|
|
524
498
|
|
|
525
499
|
self.extract_haralick_options()
|
|
526
500
|
measurement_options.update({'haralick_options': self.haralick_options})
|
|
@@ -537,8 +511,11 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
537
511
|
'isotropic_operations': isotropic_operations})
|
|
538
512
|
spot_detection = None
|
|
539
513
|
if self.spot_check.isChecked():
|
|
514
|
+
image_preprocessing = self.spot_preprocessing.list.items
|
|
515
|
+
if image_preprocessing==[]:
|
|
516
|
+
image_preprocessing = None
|
|
540
517
|
spot_detection = {'channel': self.spot_channel.currentText(), 'diameter': float(self.diameter_value.text().replace(',','.')),
|
|
541
|
-
'threshold': float(self.threshold_value.text().replace(',','.'))}
|
|
518
|
+
'threshold': float(self.threshold_value.text().replace(',','.')), 'image_preprocessing': image_preprocessing}
|
|
542
519
|
measurement_options.update({'spot_detection': spot_detection})
|
|
543
520
|
if self.clear_previous_btn.isChecked():
|
|
544
521
|
self.clear_previous = True
|
|
@@ -547,7 +524,6 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
547
524
|
measurement_options.update({'clear_previous': self.clear_previous})
|
|
548
525
|
|
|
549
526
|
|
|
550
|
-
|
|
551
527
|
print('Measurement instructions: ', measurement_options)
|
|
552
528
|
file_name = self.measure_instructions_path
|
|
553
529
|
with open(file_name, 'w') as f:
|
|
@@ -618,7 +594,13 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
618
594
|
self.spot_channel.setCurrentText(idx)
|
|
619
595
|
self.diameter_value.setText(str(spot_detection['diameter']))
|
|
620
596
|
self.threshold_value.setText(str(spot_detection['threshold']))
|
|
621
|
-
|
|
597
|
+
if 'image_preprocessing' in spot_detection:
|
|
598
|
+
items = spot_detection['image_preprocessing']
|
|
599
|
+
if items is not None:
|
|
600
|
+
items_for_list = [a[0] for a in items]
|
|
601
|
+
for it in items_for_list:
|
|
602
|
+
self.spot_preprocessing.list.addItemToList(it)
|
|
603
|
+
self.spot_preprocessing.list.items = items
|
|
622
604
|
|
|
623
605
|
if 'border_distances' in measurement_instructions:
|
|
624
606
|
border_distances = measurement_instructions['border_distances']
|
|
@@ -915,43 +897,81 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
915
897
|
|
|
916
898
|
def populate_spot_detection(self):
|
|
917
899
|
|
|
918
|
-
layout =
|
|
900
|
+
layout = QVBoxLayout(self.spot_detection_frame)
|
|
901
|
+
|
|
919
902
|
self.spot_detection_lbl = QLabel("SPOT DETECTION")
|
|
920
903
|
self.spot_detection_lbl.setStyleSheet("""font-weight: bold;padding: 0px;""")
|
|
921
|
-
layout.addWidget(self.spot_detection_lbl,
|
|
922
|
-
|
|
904
|
+
layout.addWidget(self.spot_detection_lbl, alignment=Qt.AlignCenter)
|
|
905
|
+
|
|
906
|
+
perform_hbox = QHBoxLayout()
|
|
907
|
+
self.spot_check = QCheckBox('Perform spot detection')
|
|
923
908
|
self.spot_check.toggled.connect(self.enable_spot_detection)
|
|
924
|
-
|
|
925
|
-
|
|
909
|
+
perform_hbox.addWidget(self.spot_check, 95)
|
|
910
|
+
|
|
911
|
+
self.spot_viewer_btn = QPushButton()
|
|
912
|
+
self.spot_viewer_btn.clicked.connect(self.spot_preview)
|
|
913
|
+
self.spot_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
914
|
+
self.spot_viewer_btn.setStyleSheet(self.button_select_all)
|
|
915
|
+
self.spot_viewer_btn.setToolTip('Set detection parameters visually.')
|
|
916
|
+
perform_hbox.addWidget(self.spot_viewer_btn, 5)
|
|
917
|
+
layout.addLayout(perform_hbox)
|
|
918
|
+
|
|
919
|
+
channel_hbox = QHBoxLayout()
|
|
920
|
+
self.spot_channel_lbl = QLabel("Channel: ")
|
|
926
921
|
self.spot_channel = QComboBox()
|
|
927
922
|
self.spot_channel.addItems(self.channel_names)
|
|
928
|
-
|
|
929
|
-
|
|
923
|
+
channel_hbox.addWidget(self.spot_channel_lbl, 30)
|
|
924
|
+
channel_hbox.addWidget(self.spot_channel, 70)
|
|
925
|
+
layout.addLayout(channel_hbox)
|
|
926
|
+
|
|
927
|
+
self.spot_preprocessing = PreprocessingLayout2(fraction=30, parent_window=self)
|
|
928
|
+
layout.addLayout(self.spot_preprocessing)
|
|
929
|
+
|
|
930
|
+
# continue switching to VBox + HBox down
|
|
931
|
+
diam_hbox = QHBoxLayout()
|
|
930
932
|
self.diameter_lbl = QLabel('Spot diameter: ')
|
|
931
933
|
self.diameter_value = QLineEdit()
|
|
932
934
|
self.diameter_value.setValidator(self.onlyFloat)
|
|
933
935
|
self.diameter_value.setText('7')
|
|
934
936
|
self.diameter_value.textChanged.connect(self.enable_spot_preview)
|
|
935
937
|
|
|
936
|
-
|
|
937
|
-
|
|
938
|
+
diam_hbox.addWidget(self.diameter_lbl,30)
|
|
939
|
+
diam_hbox.addWidget(self.diameter_value, 70)
|
|
940
|
+
layout.addLayout(diam_hbox)
|
|
941
|
+
|
|
942
|
+
thresh_hbox = QHBoxLayout()
|
|
938
943
|
self.threshold_lbl = QLabel('Spot threshold: ')
|
|
939
944
|
self.threshold_value = QLineEdit()
|
|
940
945
|
self.threshold_value.setValidator(self.onlyFloat)
|
|
941
946
|
self.threshold_value.setText('0')
|
|
942
947
|
self.threshold_value.textChanged.connect(self.enable_spot_preview)
|
|
943
948
|
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
self.
|
|
950
|
-
self.
|
|
951
|
-
self.
|
|
952
|
-
layout.addWidget(self.
|
|
953
|
-
|
|
954
|
-
|
|
949
|
+
thresh_hbox.addWidget(self.threshold_lbl, 30)
|
|
950
|
+
thresh_hbox.addWidget(self.threshold_value, 70)
|
|
951
|
+
layout.addLayout(thresh_hbox)
|
|
952
|
+
|
|
953
|
+
# #invert_layout = QHBoxLayout()
|
|
954
|
+
# self.invert_check = QCheckBox('invert')
|
|
955
|
+
# self.invert_value_le = QLineEdit('65535')
|
|
956
|
+
# self.invert_value_le.setValidator(self.onlyFloat)
|
|
957
|
+
# layout.addWidget(self.invert_check, 6, 0)
|
|
958
|
+
# layout.addWidget(self.invert_value_le, 6, 1)
|
|
959
|
+
# #layout.addLayout(invert_layout, 5, 1, 1, 1)
|
|
960
|
+
|
|
961
|
+
self.spot_detection_widgets = [self.spot_channel,
|
|
962
|
+
self.spot_channel_lbl,
|
|
963
|
+
self.diameter_value,
|
|
964
|
+
self.diameter_lbl,
|
|
965
|
+
self.threshold_value,
|
|
966
|
+
self.threshold_lbl,
|
|
967
|
+
self.spot_viewer_btn,
|
|
968
|
+
#self.invert_check,
|
|
969
|
+
#self.invert_value_le,
|
|
970
|
+
self.spot_preprocessing.list,
|
|
971
|
+
self.spot_preprocessing.add_filter_btn,
|
|
972
|
+
self.spot_preprocessing.delete_filter_btn,
|
|
973
|
+
self.spot_preprocessing.preprocess_lbl
|
|
974
|
+
]
|
|
955
975
|
for wg in self.spot_detection_widgets:
|
|
956
976
|
wg.setEnabled(False)
|
|
957
977
|
|
|
@@ -969,6 +989,13 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
969
989
|
if self.test_frame is not None:
|
|
970
990
|
self.locate_mask()
|
|
971
991
|
if self.test_mask is not None:
|
|
992
|
+
|
|
993
|
+
# invert_value = self.invert_value_le.text().replace(',','.')
|
|
994
|
+
# if invert_value != '':
|
|
995
|
+
# invert_value = float(invert_value)
|
|
996
|
+
# else:
|
|
997
|
+
# invert_value = None
|
|
998
|
+
|
|
972
999
|
self.spot_visual = SpotDetectionVisualizer(frame_slider=True,
|
|
973
1000
|
contrast_slider=True,
|
|
974
1001
|
cell_type=self.mode,
|
|
@@ -981,6 +1008,10 @@ class ConfigMeasurements(QMainWindow, Styles):
|
|
|
981
1008
|
parent_channel_cb=self.spot_channel,
|
|
982
1009
|
parent_diameter_le=self.diameter_value,
|
|
983
1010
|
parent_threshold_le=self.threshold_value,
|
|
1011
|
+
parent_preprocessing_list=self.spot_preprocessing.list,
|
|
1012
|
+
#parent_invert_check=self.invert_check,
|
|
1013
|
+
#invert = self.invert_check.isChecked(),
|
|
1014
|
+
#invert_value = self.invert_value_le.text().replace(',','.'),
|
|
984
1015
|
PxToUm = 1,)
|
|
985
1016
|
self.spot_visual.show()
|
|
986
1017
|
#self.spot_visual = ThresholdSpot(current_channel=self.spot_channel.currentIndex(), img=self.test_frame,
|
|
@@ -174,7 +174,7 @@ class ConfigNeighborhoods(QWidget, Styles):
|
|
|
174
174
|
self.attr_parent.locate_image()
|
|
175
175
|
if self.attr_parent.current_stack is not None:
|
|
176
176
|
self.viewer = CellEdgeVisualizer(
|
|
177
|
-
cell_type=
|
|
177
|
+
cell_type=self.reference_population_cb.currentText(),
|
|
178
178
|
edge_range=(1,30),
|
|
179
179
|
invert=True,
|
|
180
180
|
initial_edge=3,
|
|
@@ -19,6 +19,7 @@ import pandas as pd
|
|
|
19
19
|
import math
|
|
20
20
|
from celldetective.gui import Styles
|
|
21
21
|
from matplotlib import colormaps
|
|
22
|
+
import matplotlib.cm
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|
class ConfigSignalPlot(QWidget, Styles):
|
|
@@ -110,10 +111,8 @@ class ConfigSignalPlot(QWidget, Styles):
|
|
|
110
111
|
|
|
111
112
|
all_cms = list(colormaps)
|
|
112
113
|
for cm in all_cms:
|
|
113
|
-
|
|
114
|
-
self.cbs[-1].addColormap(cm)
|
|
115
|
-
except:
|
|
116
|
-
pass
|
|
114
|
+
if hasattr(matplotlib.cm, str(cm).lower()):
|
|
115
|
+
self.cbs[-1].addColormap(cm.lower())
|
|
117
116
|
|
|
118
117
|
self.cbs[0].setCurrentIndex(1)
|
|
119
118
|
self.cbs[0].setCurrentIndex(0)
|
|
@@ -9,7 +9,7 @@ from PyQt5.QtGui import QDoubleValidator, QIntValidator
|
|
|
9
9
|
from celldetective.gui.signal_annotator import MeasureAnnotator
|
|
10
10
|
from celldetective.gui.signal_annotator2 import SignalAnnotator2
|
|
11
11
|
from celldetective.io import get_segmentation_models_list, control_segmentation_napari, get_signal_models_list, \
|
|
12
|
-
|
|
12
|
+
control_tracks, load_experiment_tables, get_pair_signal_models_list
|
|
13
13
|
from celldetective.io import locate_segmentation_model, fix_missing_labels, auto_load_number_of_frames, load_frames, locate_signal_model
|
|
14
14
|
from celldetective.gui import SegmentationModelLoader, ClassifierWidget, ConfigNeighborhoods, ConfigSegmentationModelTraining, ConfigTracking, SignalAnnotator, ConfigSignalModelTraining, ConfigMeasurements, ConfigSignalAnnotator, TableUI
|
|
15
15
|
from celldetective.gui.gui_utils import QHSeperationLine
|
|
@@ -291,7 +291,7 @@ class ProcessPanel(QFrame, Styles):
|
|
|
291
291
|
self.track_action.setStyleSheet(self.menu_check_style)
|
|
292
292
|
self.track_action.setIcon(icon(MDI6.chart_timeline_variant,color="black"))
|
|
293
293
|
self.track_action.setIconSize(QSize(20, 20))
|
|
294
|
-
self.track_action.setToolTip("Track the
|
|
294
|
+
self.track_action.setToolTip(f"Track the {self.mode[:-1]} cells.")
|
|
295
295
|
grid_track.addWidget(self.track_action, 75)
|
|
296
296
|
|
|
297
297
|
self.delete_tracks_btn = QPushButton()
|
|
@@ -307,7 +307,7 @@ class ProcessPanel(QFrame, Styles):
|
|
|
307
307
|
self.check_tracking_result_btn = QPushButton()
|
|
308
308
|
self.check_tracking_result_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
|
|
309
309
|
self.check_tracking_result_btn.setIconSize(QSize(20, 20))
|
|
310
|
-
self.check_tracking_result_btn.setToolTip("View
|
|
310
|
+
self.check_tracking_result_btn.setToolTip("View tracking output in napari.")
|
|
311
311
|
self.check_tracking_result_btn.setStyleSheet(self.button_select_all)
|
|
312
312
|
self.check_tracking_result_btn.clicked.connect(self.open_napari_tracking)
|
|
313
313
|
self.check_tracking_result_btn.setEnabled(False)
|
|
@@ -845,14 +845,16 @@ class ProcessPanel(QFrame, Styles):
|
|
|
845
845
|
|
|
846
846
|
# self.stack = None
|
|
847
847
|
self.parent_window.update_position_options()
|
|
848
|
-
|
|
849
|
-
|
|
848
|
+
|
|
849
|
+
for action in [self.segment_action, self.track_action, self.measure_action, self.signal_analysis_action]:
|
|
850
|
+
if action.isChecked():
|
|
851
|
+
action.setChecked(False)
|
|
850
852
|
|
|
851
853
|
self.cellpose_calibrated = False
|
|
852
854
|
|
|
853
855
|
def open_napari_tracking(self):
|
|
854
856
|
print(f'View the tracks before post-processing for position {self.parent_window.pos} in napari...')
|
|
855
|
-
|
|
857
|
+
control_tracks(self.parent_window.pos, prefix=self.parent_window.movie_prefix, population=self.mode, threads=self.parent_window.parent_window.n_threads)
|
|
856
858
|
|
|
857
859
|
def view_table_ui(self):
|
|
858
860
|
|
|
@@ -7,7 +7,7 @@ from celldetective.gui.gui_utils import center_window, color_from_state
|
|
|
7
7
|
from superqt import QLabeledDoubleSlider, QLabeledDoubleRangeSlider, QSearchableComboBox
|
|
8
8
|
from celldetective.utils import extract_experiment_channels, get_software_location, _get_img_num_per_channel
|
|
9
9
|
from celldetective.io import auto_load_number_of_frames, load_frames, \
|
|
10
|
-
load_napari_data
|
|
10
|
+
load_napari_data, get_experiment_metadata
|
|
11
11
|
from celldetective.gui.gui_utils import FigureCanvas, color_from_status, color_from_class, ExportPlotBtn
|
|
12
12
|
import json
|
|
13
13
|
import numpy as np
|
|
@@ -457,8 +457,10 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
457
457
|
cols = np.array(self.df_tracks.columns)
|
|
458
458
|
self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
|
|
459
459
|
self.class_cols = list(cols[self.class_cols])
|
|
460
|
-
self.class_cols
|
|
461
|
-
|
|
460
|
+
if 'class_id' in self.class_cols:
|
|
461
|
+
self.class_cols.remove('class_id')
|
|
462
|
+
if 'class_color' in self.class_cols:
|
|
463
|
+
self.class_cols.remove('class_color')
|
|
462
464
|
self.class_choice_cb.addItems(self.class_cols)
|
|
463
465
|
idx = self.class_choice_cb.findText(self.target_class)
|
|
464
466
|
self.class_choice_cb.setCurrentIndex(idx)
|
|
@@ -779,6 +781,11 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
779
781
|
'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
|
|
780
782
|
'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
|
|
781
783
|
'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent'] + self.class_cols
|
|
784
|
+
meta = get_experiment_metadata(self.exp_dir)
|
|
785
|
+
if meta is not None:
|
|
786
|
+
keys = list(meta.keys())
|
|
787
|
+
cols_to_remove.extend(keys)
|
|
788
|
+
|
|
782
789
|
cols = np.array(list(self.df_tracks.columns))
|
|
783
790
|
time_cols = np.array([c.startswith('t_') for c in cols])
|
|
784
791
|
time_cols = list(cols[time_cols])
|