celldetective 1.4.2__py3-none-any.whl → 1.5.0b0__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/__init__.py +25 -0
- celldetective/__main__.py +62 -43
- celldetective/_version.py +1 -1
- celldetective/extra_properties.py +477 -399
- celldetective/filters.py +192 -97
- celldetective/gui/InitWindow.py +541 -411
- celldetective/gui/__init__.py +0 -15
- celldetective/gui/about.py +44 -39
- celldetective/gui/analyze_block.py +120 -84
- celldetective/gui/base/__init__.py +0 -0
- celldetective/gui/base/channel_norm_generator.py +335 -0
- celldetective/gui/base/components.py +249 -0
- celldetective/gui/base/feature_choice.py +92 -0
- celldetective/gui/base/figure_canvas.py +52 -0
- celldetective/gui/base/list_widget.py +133 -0
- celldetective/gui/{styles.py → base/styles.py} +92 -36
- celldetective/gui/base/utils.py +33 -0
- celldetective/gui/base_annotator.py +900 -767
- celldetective/gui/classifier_widget.py +6 -22
- celldetective/gui/configure_new_exp.py +777 -671
- celldetective/gui/control_panel.py +635 -524
- celldetective/gui/dynamic_progress.py +449 -0
- celldetective/gui/event_annotator.py +2023 -1662
- celldetective/gui/generic_signal_plot.py +1292 -944
- celldetective/gui/gui_utils.py +899 -1289
- celldetective/gui/interactions_block.py +658 -0
- celldetective/gui/interactive_timeseries_viewer.py +447 -0
- celldetective/gui/json_readers.py +48 -15
- celldetective/gui/layouts/__init__.py +5 -0
- celldetective/gui/layouts/background_model_free_layout.py +537 -0
- celldetective/gui/layouts/channel_offset_layout.py +134 -0
- celldetective/gui/layouts/local_correction_layout.py +91 -0
- celldetective/gui/layouts/model_fit_layout.py +372 -0
- celldetective/gui/layouts/operation_layout.py +68 -0
- celldetective/gui/layouts/protocol_designer_layout.py +96 -0
- celldetective/gui/pair_event_annotator.py +3130 -2435
- celldetective/gui/plot_measurements.py +586 -267
- celldetective/gui/plot_signals_ui.py +724 -506
- celldetective/gui/preprocessing_block.py +395 -0
- celldetective/gui/process_block.py +1678 -1831
- celldetective/gui/seg_model_loader.py +580 -473
- celldetective/gui/settings/__init__.py +0 -7
- celldetective/gui/settings/_cellpose_model_params.py +181 -0
- celldetective/gui/settings/_event_detection_model_params.py +95 -0
- celldetective/gui/settings/_segmentation_model_params.py +159 -0
- celldetective/gui/settings/_settings_base.py +77 -65
- celldetective/gui/settings/_settings_event_model_training.py +752 -526
- celldetective/gui/settings/_settings_measurements.py +1133 -964
- celldetective/gui/settings/_settings_neighborhood.py +574 -488
- celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
- celldetective/gui/settings/_settings_signal_annotator.py +329 -305
- celldetective/gui/settings/_settings_tracking.py +1304 -1094
- celldetective/gui/settings/_stardist_model_params.py +98 -0
- celldetective/gui/survival_ui.py +422 -312
- celldetective/gui/tableUI.py +1665 -1701
- celldetective/gui/table_ops/_maths.py +295 -0
- celldetective/gui/table_ops/_merge_groups.py +140 -0
- celldetective/gui/table_ops/_merge_one_hot.py +95 -0
- celldetective/gui/table_ops/_query_table.py +43 -0
- celldetective/gui/table_ops/_rename_col.py +44 -0
- celldetective/gui/thresholds_gui.py +382 -179
- celldetective/gui/viewers/__init__.py +0 -0
- celldetective/gui/viewers/base_viewer.py +700 -0
- celldetective/gui/viewers/channel_offset_viewer.py +331 -0
- celldetective/gui/viewers/contour_viewer.py +394 -0
- celldetective/gui/viewers/size_viewer.py +153 -0
- celldetective/gui/viewers/spot_detection_viewer.py +341 -0
- celldetective/gui/viewers/threshold_viewer.py +309 -0
- celldetective/gui/workers.py +304 -126
- celldetective/log_manager.py +92 -0
- celldetective/measure.py +1895 -1478
- celldetective/napari/__init__.py +0 -0
- celldetective/napari/utils.py +1025 -0
- celldetective/neighborhood.py +1914 -1448
- celldetective/preprocessing.py +1620 -1220
- celldetective/processes/__init__.py +0 -0
- celldetective/processes/background_correction.py +271 -0
- celldetective/processes/compute_neighborhood.py +894 -0
- celldetective/processes/detect_events.py +246 -0
- celldetective/processes/measure_cells.py +565 -0
- celldetective/processes/segment_cells.py +760 -0
- celldetective/processes/track_cells.py +435 -0
- celldetective/processes/train_segmentation_model.py +694 -0
- celldetective/processes/train_signal_model.py +265 -0
- celldetective/processes/unified_process.py +292 -0
- celldetective/regionprops/_regionprops.py +358 -317
- celldetective/relative_measurements.py +987 -710
- celldetective/scripts/measure_cells.py +313 -212
- celldetective/scripts/measure_relative.py +90 -46
- celldetective/scripts/segment_cells.py +165 -104
- celldetective/scripts/segment_cells_thresholds.py +96 -68
- celldetective/scripts/track_cells.py +198 -149
- celldetective/scripts/train_segmentation_model.py +324 -201
- celldetective/scripts/train_signal_model.py +87 -45
- celldetective/segmentation.py +844 -749
- celldetective/signals.py +3514 -2861
- celldetective/tracking.py +30 -15
- celldetective/utils/__init__.py +0 -0
- celldetective/utils/cellpose_utils/__init__.py +133 -0
- celldetective/utils/color_mappings.py +42 -0
- celldetective/utils/data_cleaning.py +630 -0
- celldetective/utils/data_loaders.py +450 -0
- celldetective/utils/dataset_helpers.py +207 -0
- celldetective/utils/downloaders.py +197 -0
- celldetective/utils/event_detection/__init__.py +8 -0
- celldetective/utils/experiment.py +1782 -0
- celldetective/utils/image_augmenters.py +308 -0
- celldetective/utils/image_cleaning.py +74 -0
- celldetective/utils/image_loaders.py +926 -0
- celldetective/utils/image_transforms.py +335 -0
- celldetective/utils/io.py +62 -0
- celldetective/utils/mask_cleaning.py +348 -0
- celldetective/utils/mask_transforms.py +5 -0
- celldetective/utils/masks.py +184 -0
- celldetective/utils/maths.py +351 -0
- celldetective/utils/model_getters.py +325 -0
- celldetective/utils/model_loaders.py +296 -0
- celldetective/utils/normalization.py +380 -0
- celldetective/utils/parsing.py +465 -0
- celldetective/utils/plots/__init__.py +0 -0
- celldetective/utils/plots/regression.py +53 -0
- celldetective/utils/resources.py +34 -0
- celldetective/utils/stardist_utils/__init__.py +104 -0
- celldetective/utils/stats.py +90 -0
- celldetective/utils/types.py +21 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
- celldetective-1.5.0b0.dist-info/RECORD +187 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
- tests/gui/test_new_project.py +129 -117
- tests/gui/test_project.py +127 -79
- tests/test_filters.py +39 -15
- tests/test_notebooks.py +8 -0
- tests/test_tracking.py +232 -13
- tests/test_utils.py +123 -77
- celldetective/gui/base_components.py +0 -23
- celldetective/gui/layouts.py +0 -1602
- celldetective/gui/processes/compute_neighborhood.py +0 -594
- celldetective/gui/processes/measure_cells.py +0 -360
- celldetective/gui/processes/segment_cells.py +0 -499
- celldetective/gui/processes/track_cells.py +0 -303
- celldetective/gui/processes/train_segmentation_model.py +0 -270
- celldetective/gui/processes/train_signal_model.py +0 -108
- celldetective/gui/table_ops/merge_groups.py +0 -118
- celldetective/gui/viewers.py +0 -1354
- celldetective/io.py +0 -3663
- celldetective/utils.py +0 -3108
- celldetective-1.4.2.dist-info/RECORD +0 -123
- /celldetective/{gui/processes → processes}/downloader.py +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/top_level.txt +0 -0
celldetective/gui/gui_utils.py
CHANGED
|
@@ -1,1373 +1,983 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from PyQt5.QtWidgets import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
from PyQt5.QtWidgets import (
|
|
4
|
+
QGridLayout,
|
|
5
|
+
QMessageBox,
|
|
6
|
+
QLineEdit,
|
|
7
|
+
QVBoxLayout,
|
|
8
|
+
QComboBox,
|
|
9
|
+
QPushButton,
|
|
10
|
+
QLabel,
|
|
11
|
+
QHBoxLayout,
|
|
12
|
+
QCheckBox,
|
|
13
|
+
QFileDialog,
|
|
14
|
+
)
|
|
15
|
+
from PyQt5.QtCore import Qt, QSize, QAbstractTableModel
|
|
16
|
+
from PyQt5.QtGui import QDoubleValidator, QIntValidator
|
|
17
|
+
|
|
18
|
+
from celldetective.gui.base.list_widget import ListWidget
|
|
19
|
+
from celldetective.gui.base.styles import Styles
|
|
20
|
+
from celldetective.gui.base.components import CelldetectiveWidget
|
|
9
21
|
from superqt.fonticon import icon
|
|
10
22
|
from fonticon_mdi6 import MDI6
|
|
11
23
|
|
|
12
|
-
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
|
13
|
-
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT
|
|
14
|
-
import matplotlib.pyplot as plt
|
|
15
|
-
|
|
16
|
-
from celldetective.utils import get_software_location
|
|
17
24
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
extra_props = True
|
|
21
|
-
except Exception as e:
|
|
22
|
-
print(f"The module extra_properties seems corrupted: {e}... Skip...")
|
|
23
|
-
extra_props = False
|
|
25
|
+
from celldetective.gui.base.utils import center_window
|
|
26
|
+
from celldetective import get_software_location
|
|
24
27
|
|
|
25
|
-
from inspect import getmembers, isfunction
|
|
26
28
|
from celldetective.filters import *
|
|
27
29
|
from os import sep
|
|
28
30
|
import json
|
|
29
31
|
|
|
30
32
|
|
|
31
|
-
def generic_message(message, msg_type="warning"):
|
|
32
|
-
|
|
33
|
-
print(message)
|
|
34
|
-
message_box = QMessageBox()
|
|
35
|
-
if msg_type=="warning":
|
|
36
|
-
message_box.setIcon(QMessageBox.Warning)
|
|
37
|
-
elif msg_type=="info":
|
|
38
|
-
message_box.setIcon(QMessageBox.Information)
|
|
39
|
-
elif msg_type=="critical":
|
|
40
|
-
message_box.setIcon(QMessageBox.Critical)
|
|
41
|
-
message_box.setText(message)
|
|
42
|
-
message_box.setWindowTitle(msg_type)
|
|
43
|
-
message_box.setStandardButtons(QMessageBox.Ok)
|
|
44
|
-
_ = message_box.exec()
|
|
45
|
-
|
|
46
33
|
class PreprocessingLayout(QVBoxLayout, Styles):
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
34
|
+
"""
|
|
35
|
+
A widget that allows user to choose preprocessing filters for an image
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def __init__(self, parent_window=None, apply_btn_option=True, *args, **kwargs):
|
|
39
|
+
super().__init__(*args, **kwargs)
|
|
40
|
+
|
|
41
|
+
self.parent_window = parent_window
|
|
42
|
+
self.apply_btn_option = apply_btn_option
|
|
43
|
+
self.generate_components()
|
|
44
|
+
self.add_to_layout()
|
|
45
|
+
|
|
46
|
+
def add_to_layout(self):
|
|
47
|
+
|
|
48
|
+
self.setContentsMargins(20, 20, 20, 20)
|
|
49
|
+
|
|
50
|
+
button_layout = QHBoxLayout()
|
|
51
|
+
button_layout.addWidget(self.preprocess_lbl, 85, alignment=Qt.AlignLeft)
|
|
52
|
+
button_layout.addWidget(self.delete_filter_btn, 5)
|
|
53
|
+
button_layout.addWidget(self.add_filter_btn, 5)
|
|
54
|
+
button_layout.addWidget(self.help_prefilter_btn, 5)
|
|
55
|
+
self.addLayout(button_layout, 25)
|
|
56
|
+
|
|
57
|
+
self.addWidget(self.list, 70)
|
|
58
|
+
if self.apply_btn_option:
|
|
59
|
+
self.addWidget(self.apply_btn, 5)
|
|
60
|
+
|
|
61
|
+
def generate_components(self):
|
|
62
|
+
|
|
63
|
+
self.list = ListWidget(FilterChoice, [])
|
|
64
|
+
|
|
65
|
+
self.preprocess_lbl = QLabel("Preprocessing")
|
|
66
|
+
self.preprocess_lbl.setStyleSheet("font-weight: bold;")
|
|
67
|
+
|
|
68
|
+
self.delete_filter_btn = QPushButton()
|
|
69
|
+
self.delete_filter_btn.setStyleSheet(self.button_select_all)
|
|
70
|
+
self.delete_filter_btn.setIcon(icon(MDI6.trash_can, color="black"))
|
|
71
|
+
self.delete_filter_btn.setToolTip("Remove filter")
|
|
72
|
+
self.delete_filter_btn.setIconSize(QSize(20, 20))
|
|
73
|
+
self.delete_filter_btn.clicked.connect(self.list.removeSel)
|
|
74
|
+
|
|
75
|
+
self.add_filter_btn = QPushButton()
|
|
76
|
+
self.add_filter_btn.setStyleSheet(self.button_select_all)
|
|
77
|
+
self.add_filter_btn.setIcon(icon(MDI6.filter_plus, color="black"))
|
|
78
|
+
self.add_filter_btn.setToolTip("Add filter")
|
|
79
|
+
self.add_filter_btn.setIconSize(QSize(20, 20))
|
|
80
|
+
self.add_filter_btn.clicked.connect(self.list.addItem)
|
|
81
|
+
|
|
82
|
+
self.help_prefilter_btn = QPushButton()
|
|
83
|
+
self.help_prefilter_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
|
|
84
|
+
self.help_prefilter_btn.setIconSize(QSize(20, 20))
|
|
85
|
+
self.help_prefilter_btn.clicked.connect(self.help_prefilter)
|
|
86
|
+
self.help_prefilter_btn.setStyleSheet(self.button_select_all)
|
|
87
|
+
self.help_prefilter_btn.setToolTip("Help.")
|
|
88
|
+
|
|
89
|
+
if self.apply_btn_option:
|
|
90
|
+
self.apply_btn = QPushButton("Apply")
|
|
91
|
+
self.apply_btn.setIcon(icon(MDI6.filter_cog_outline, color="white"))
|
|
92
|
+
self.apply_btn.setIconSize(QSize(20, 20))
|
|
93
|
+
self.apply_btn.setStyleSheet(self.button_style_sheet)
|
|
94
|
+
self.apply_btn.clicked.connect(self.parent_window.preprocess_image)
|
|
95
|
+
|
|
96
|
+
def help_prefilter(self):
|
|
97
|
+
"""
|
|
98
|
+
Helper for prefiltering strategy
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
dict_path = os.sep.join(
|
|
102
|
+
[
|
|
103
|
+
get_software_location(),
|
|
104
|
+
"celldetective",
|
|
105
|
+
"gui",
|
|
106
|
+
"help",
|
|
107
|
+
"prefilter-for-segmentation.json",
|
|
108
|
+
]
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
with open(dict_path) as f:
|
|
112
|
+
d = json.load(f)
|
|
113
|
+
|
|
114
|
+
suggestion = help_generic(d)
|
|
115
|
+
if isinstance(suggestion, str):
|
|
116
|
+
print(f"{suggestion=}")
|
|
117
|
+
msgBox = QMessageBox()
|
|
118
|
+
msgBox.setIcon(QMessageBox.Information)
|
|
119
|
+
msgBox.setTextFormat(Qt.RichText)
|
|
120
|
+
msgBox.setText(
|
|
121
|
+
f"The suggested technique is to {suggestion}.\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>."
|
|
122
|
+
)
|
|
123
|
+
msgBox.setWindowTitle("Info")
|
|
124
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
125
|
+
returnValue = msgBox.exec()
|
|
126
|
+
if returnValue == QMessageBox.Ok:
|
|
127
|
+
return None
|
|
133
128
|
|
|
134
129
|
|
|
135
130
|
class PreprocessingLayout2(PreprocessingLayout):
|
|
136
131
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
self.fraction = fraction
|
|
140
|
-
super().__init__(apply_btn_option=False, *args, **kwargs)
|
|
141
|
-
self.preprocess_lbl.setText('Preprocessing: ')
|
|
142
|
-
self.preprocess_lbl.setStyleSheet("")
|
|
143
|
-
self.setContentsMargins(0,0,0,0)
|
|
144
|
-
|
|
145
|
-
def add_to_layout(self):
|
|
146
|
-
|
|
147
|
-
main_layout = QHBoxLayout()
|
|
148
|
-
main_layout.setContentsMargins(0,0,0,0)
|
|
149
|
-
main_layout.setSpacing(5)
|
|
150
|
-
main_layout.addWidget(self.preprocess_lbl, self.fraction, alignment=Qt.AlignTop)
|
|
151
|
-
|
|
152
|
-
list_grid = QGridLayout()
|
|
153
|
-
list_grid.addWidget(self.list, 0, 0, 2, 2)
|
|
154
|
-
list_grid.addWidget(self.add_filter_btn, 0, 2, 1, 1)
|
|
155
|
-
list_grid.addWidget(self.delete_filter_btn, 1, 2, 1, 1)
|
|
156
|
-
main_layout.addLayout(list_grid, 100-self.fraction)
|
|
157
|
-
self.add_filter_btn.setFixedWidth(35) # Ensure the button width is fixed
|
|
158
|
-
self.delete_filter_btn.setFixedWidth(35)
|
|
159
|
-
list_grid.setColumnStretch(2, 0)
|
|
160
|
-
|
|
161
|
-
self.addLayout(main_layout)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
class QCheckableComboBox(QComboBox):
|
|
165
|
-
|
|
166
|
-
"""
|
|
167
|
-
adapted from https://stackoverflow.com/questions/22775095/pyqt-how-to-set-combobox-items-be-checkable
|
|
168
|
-
"""
|
|
169
|
-
|
|
170
|
-
activated = pyqtSignal(str)
|
|
171
|
-
|
|
172
|
-
def __init__(self, obj='', parent_window=None, *args, **kwargs):
|
|
173
|
-
|
|
174
|
-
super().__init__(parent_window, *args, **kwargs)
|
|
175
|
-
|
|
176
|
-
self.setTitle('')
|
|
177
|
-
self.setModel(QStandardItemModel(self))
|
|
178
|
-
self.obj = obj
|
|
179
|
-
self.toolButton = QToolButton(parent_window)
|
|
180
|
-
self.toolButton.setText('')
|
|
181
|
-
self.toolMenu = QMenu(parent_window)
|
|
182
|
-
self.toolButton.setMenu(self.toolMenu)
|
|
183
|
-
self.toolButton.setPopupMode(QToolButton.InstantPopup)
|
|
184
|
-
self.anySelected = False
|
|
185
|
-
|
|
186
|
-
self.view().viewport().installEventFilter(self)
|
|
187
|
-
self.view().pressed.connect(self.handleItemPressed)
|
|
188
|
-
|
|
189
|
-
def clear(self):
|
|
190
|
-
|
|
191
|
-
self.unselectAll()
|
|
192
|
-
self.toolMenu.clear()
|
|
193
|
-
super().clear()
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
def handleItemPressed(self, index):
|
|
197
|
-
|
|
198
|
-
idx = index.row()
|
|
199
|
-
actions = self.toolMenu.actions()
|
|
200
|
-
|
|
201
|
-
item = self.model().itemFromIndex(index)
|
|
202
|
-
if item.checkState() == Qt.Checked:
|
|
203
|
-
item.setCheckState(Qt.Unchecked)
|
|
204
|
-
actions[idx].setChecked(False)
|
|
205
|
-
else:
|
|
206
|
-
item.setCheckState(Qt.Checked)
|
|
207
|
-
actions[idx].setChecked(True)
|
|
208
|
-
self.anySelected = True
|
|
209
|
-
|
|
210
|
-
options_checked = np.array([a.isChecked() for a in actions])
|
|
211
|
-
if len(options_checked[options_checked]) > 1:
|
|
212
|
-
self.setTitle(f'Multiple {self.obj+"s"} selected...')
|
|
213
|
-
elif len(options_checked[options_checked])==1:
|
|
214
|
-
idx_selected = np.where(options_checked)[0][0]
|
|
215
|
-
if idx_selected!=idx:
|
|
216
|
-
item = self.model().item(idx_selected)
|
|
217
|
-
self.setTitle(item.text())
|
|
218
|
-
elif len(options_checked[options_checked])==0:
|
|
219
|
-
self.setTitle(f"No {self.obj} selected...")
|
|
220
|
-
self.anySelected = False
|
|
221
|
-
|
|
222
|
-
self.activated.emit(self.title())
|
|
223
|
-
|
|
224
|
-
def setCurrentIndex(self, index):
|
|
225
|
-
|
|
226
|
-
super().setCurrentIndex(index)
|
|
227
|
-
|
|
228
|
-
item = self.model().item(index)
|
|
229
|
-
modelIndex = self.model().indexFromItem(item)
|
|
230
|
-
|
|
231
|
-
self.handleItemPressed(modelIndex)
|
|
232
|
-
|
|
233
|
-
def selectAll(self):
|
|
234
|
-
|
|
235
|
-
actions = self.toolMenu.actions()
|
|
236
|
-
for i,a in enumerate(actions):
|
|
237
|
-
if not a.isChecked():
|
|
238
|
-
self.setCurrentIndex(i)
|
|
239
|
-
self.anySelected = True
|
|
240
|
-
|
|
241
|
-
def unselectAll(self):
|
|
242
|
-
|
|
243
|
-
actions = self.toolMenu.actions()
|
|
244
|
-
for i,a in enumerate(actions):
|
|
245
|
-
if a.isChecked():
|
|
246
|
-
self.setCurrentIndex(i)
|
|
247
|
-
self.anySelected = False
|
|
248
|
-
|
|
249
|
-
def title(self):
|
|
250
|
-
return self._title
|
|
251
|
-
|
|
252
|
-
def setTitle(self, title):
|
|
253
|
-
self._title = title
|
|
254
|
-
self.update()
|
|
255
|
-
self.repaint()
|
|
256
|
-
|
|
257
|
-
def paintEvent(self, event):
|
|
258
|
-
|
|
259
|
-
painter = QStylePainter(self)
|
|
260
|
-
painter.setPen(self.palette().color(QPalette.Text))
|
|
261
|
-
opt = QStyleOptionComboBox()
|
|
262
|
-
self.initStyleOption(opt)
|
|
263
|
-
opt.currentText = self._title
|
|
264
|
-
painter.drawComplexControl(QStyle.CC_ComboBox, opt)
|
|
265
|
-
painter.drawControl(QStyle.CE_ComboBoxLabel, opt)
|
|
266
|
-
|
|
267
|
-
def addItem(self, item, tooltip=None):
|
|
268
|
-
|
|
269
|
-
super().addItem(item)
|
|
270
|
-
idx = self.findText(item)
|
|
271
|
-
if tooltip is not None:
|
|
272
|
-
self.setItemData(idx, tooltip, Qt.ToolTipRole)
|
|
273
|
-
item2 = self.model().item(idx, 0)
|
|
274
|
-
item2.setCheckState(Qt.Unchecked)
|
|
275
|
-
action = self.toolMenu.addAction(item)
|
|
276
|
-
action.setCheckable(True)
|
|
277
|
-
|
|
278
|
-
def addItems(self, items):
|
|
279
|
-
|
|
280
|
-
super().addItems(items)
|
|
281
|
-
|
|
282
|
-
for item in items:
|
|
283
|
-
|
|
284
|
-
idx = self.findText(item)
|
|
285
|
-
item2 = self.model().item(idx, 0)
|
|
286
|
-
item2.setCheckState(Qt.Unchecked)
|
|
287
|
-
action = self.toolMenu.addAction(item)
|
|
288
|
-
action.setCheckable(True)
|
|
289
|
-
|
|
290
|
-
def getSelectedIndices(self):
|
|
291
|
-
|
|
292
|
-
actions = self.toolMenu.actions()
|
|
293
|
-
options_checked = np.array([a.isChecked() for a in actions])
|
|
294
|
-
idx_selected = np.where(options_checked)[0]
|
|
295
|
-
|
|
296
|
-
return list(idx_selected)
|
|
297
|
-
|
|
298
|
-
def currentText(self):
|
|
299
|
-
return self.title()
|
|
300
|
-
|
|
301
|
-
def isMultipleSelection(self):
|
|
302
|
-
return self.currentText().startswith('Multiple')
|
|
303
|
-
|
|
304
|
-
def isSingleSelection(self):
|
|
305
|
-
return not self.currentText().startswith('Multiple') and not self.title().startswith('No')
|
|
306
|
-
|
|
307
|
-
def isAnySelected(self):
|
|
308
|
-
return not self.title().startswith('No')
|
|
309
|
-
|
|
310
|
-
def eventFilter(self, source, event):
|
|
311
|
-
if source is self.view().viewport():
|
|
312
|
-
if event.type() == QEvent.MouseButtonRelease:
|
|
313
|
-
return True # Prevent the popup from closing
|
|
314
|
-
return super().eventFilter(source, event)
|
|
132
|
+
def __init__(self, fraction=75, *args, **kwargs):
|
|
315
133
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
134
|
+
self.fraction = fraction
|
|
135
|
+
super().__init__(apply_btn_option=False, *args, **kwargs)
|
|
136
|
+
self.preprocess_lbl.setText("Preprocessing: ")
|
|
137
|
+
self.preprocess_lbl.setStyleSheet("")
|
|
138
|
+
self.setContentsMargins(0, 0, 0, 0)
|
|
321
139
|
|
|
322
|
-
|
|
323
|
-
QAbstractTableModel.__init__(self)
|
|
324
|
-
self._data = data
|
|
325
|
-
self.colors = dict()
|
|
140
|
+
def add_to_layout(self):
|
|
326
141
|
|
|
327
|
-
|
|
328
|
-
|
|
142
|
+
main_layout = QHBoxLayout()
|
|
143
|
+
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
144
|
+
main_layout.setSpacing(5)
|
|
145
|
+
main_layout.addWidget(self.preprocess_lbl, self.fraction, alignment=Qt.AlignTop)
|
|
329
146
|
|
|
330
|
-
|
|
331
|
-
|
|
147
|
+
list_grid = QGridLayout()
|
|
148
|
+
list_grid.addWidget(self.list, 0, 0, 2, 2)
|
|
149
|
+
list_grid.addWidget(self.add_filter_btn, 0, 2, 1, 1)
|
|
150
|
+
list_grid.addWidget(self.delete_filter_btn, 1, 2, 1, 1)
|
|
151
|
+
main_layout.addLayout(list_grid, 100 - self.fraction)
|
|
152
|
+
self.add_filter_btn.setFixedWidth(35) # Ensure the button width is fixed
|
|
153
|
+
self.delete_filter_btn.setFixedWidth(35)
|
|
154
|
+
list_grid.setColumnStretch(2, 0)
|
|
332
155
|
|
|
333
|
-
|
|
334
|
-
if index.isValid():
|
|
335
|
-
if role == Qt.DisplayRole:
|
|
336
|
-
return str(self._data.iloc[index.row(), index.column()])
|
|
337
|
-
if role == Qt.BackgroundRole:
|
|
338
|
-
color = self.colors.get((index.row(), index.column()))
|
|
339
|
-
if color is not None:
|
|
340
|
-
return color
|
|
341
|
-
return None
|
|
156
|
+
self.addLayout(main_layout)
|
|
342
157
|
|
|
343
|
-
def headerData(self, rowcol, orientation, role):
|
|
344
|
-
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
|
345
|
-
return self._data.columns[rowcol]
|
|
346
|
-
if orientation == Qt.Vertical and role == Qt.DisplayRole:
|
|
347
|
-
return self._data.index[rowcol]
|
|
348
|
-
return None
|
|
349
158
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
159
|
+
class PandasModel(QAbstractTableModel):
|
|
160
|
+
"""
|
|
161
|
+
from https://stackoverflow.com/questions/31475965/fastest-way-to-populate-qtableview-from-pandas-data-frame
|
|
162
|
+
"""
|
|
163
|
+
|
|
164
|
+
def __init__(self, data):
|
|
165
|
+
QAbstractTableModel.__init__(self)
|
|
166
|
+
self._data = data
|
|
167
|
+
self.colors = dict()
|
|
168
|
+
|
|
169
|
+
def rowCount(self, parent=None):
|
|
170
|
+
return self._data.shape[0]
|
|
171
|
+
|
|
172
|
+
def columnCount(self, parent=None):
|
|
173
|
+
return self._data.shape[1]
|
|
174
|
+
|
|
175
|
+
def data(self, index, role=Qt.DisplayRole):
|
|
176
|
+
if index.isValid():
|
|
177
|
+
if role == Qt.DisplayRole:
|
|
178
|
+
return str(self._data.iloc[index.row(), index.column()])
|
|
179
|
+
if role == Qt.BackgroundRole:
|
|
180
|
+
color = self.colors.get((index.row(), index.column()))
|
|
181
|
+
if color is not None:
|
|
182
|
+
return color
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
def headerData(self, rowcol, orientation, role):
|
|
186
|
+
if orientation == Qt.Horizontal and role == Qt.DisplayRole:
|
|
187
|
+
return self._data.columns[rowcol]
|
|
188
|
+
if orientation == Qt.Vertical and role == Qt.DisplayRole:
|
|
189
|
+
return self._data.index[rowcol]
|
|
190
|
+
return None
|
|
191
|
+
|
|
192
|
+
def change_color(self, row, column, color):
|
|
193
|
+
ix = self.index(row, column)
|
|
194
|
+
self.colors[(row, column)] = color
|
|
195
|
+
self.dataChanged.emit(ix, ix, (Qt.BackgroundRole,))
|
|
354
196
|
|
|
355
197
|
|
|
356
198
|
class GenericOpColWidget(CelldetectiveWidget):
|
|
357
199
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
super().__init__()
|
|
361
|
-
|
|
362
|
-
self.parent_window = parent_window
|
|
363
|
-
self.column = column
|
|
364
|
-
self.title = title
|
|
200
|
+
def __init__(self, parent_window, column=None, title=""):
|
|
365
201
|
|
|
366
|
-
|
|
367
|
-
# Create the QComboBox and add some items
|
|
368
|
-
|
|
369
|
-
self.layout = QVBoxLayout(self)
|
|
370
|
-
self.layout.setContentsMargins(30,30,30,30)
|
|
202
|
+
super().__init__()
|
|
371
203
|
|
|
372
|
-
|
|
204
|
+
self.parent_window = parent_window
|
|
205
|
+
self.column = column
|
|
206
|
+
self.title = title
|
|
373
207
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
if self.column is not None:
|
|
377
|
-
idx = self.measurements_cb.findText(self.column)
|
|
378
|
-
self.measurements_cb.setCurrentIndex(idx)
|
|
208
|
+
self.setWindowTitle(self.title)
|
|
209
|
+
# Create the QComboBox and add some items
|
|
379
210
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
measurement_layout.addWidget(self.measurements_cb, 75)
|
|
383
|
-
self.sublayout.addLayout(measurement_layout)
|
|
211
|
+
self.layout = QVBoxLayout(self)
|
|
212
|
+
self.layout.setContentsMargins(30, 30, 30, 30)
|
|
384
213
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
self.submit_btn = QPushButton('Compute')
|
|
388
|
-
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
389
|
-
self.submit_btn.clicked.connect(self.launch_operation)
|
|
390
|
-
self.layout.addWidget(self.submit_btn, 30)
|
|
214
|
+
self.sublayout = QVBoxLayout()
|
|
391
215
|
|
|
392
|
-
|
|
393
|
-
|
|
216
|
+
self.measurements_cb = QComboBox()
|
|
217
|
+
self.measurements_cb.addItems(list(self.parent_window.data.columns))
|
|
218
|
+
if self.column is not None:
|
|
219
|
+
idx = self.measurements_cb.findText(self.column)
|
|
220
|
+
self.measurements_cb.setCurrentIndex(idx)
|
|
394
221
|
|
|
395
|
-
|
|
222
|
+
measurement_layout = QHBoxLayout()
|
|
223
|
+
measurement_layout.addWidget(QLabel("measurements: "), 25)
|
|
224
|
+
measurement_layout.addWidget(self.measurements_cb, 75)
|
|
225
|
+
self.sublayout.addLayout(measurement_layout)
|
|
396
226
|
|
|
397
|
-
|
|
398
|
-
self.parent_window.model = PandasModel(self.parent_window.data)
|
|
399
|
-
self.parent_window.table_view.setModel(self.parent_window.model)
|
|
400
|
-
self.close()
|
|
227
|
+
self.layout.addLayout(self.sublayout)
|
|
401
228
|
|
|
402
|
-
|
|
403
|
-
|
|
229
|
+
self.submit_btn = QPushButton("Compute")
|
|
230
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
231
|
+
self.submit_btn.clicked.connect(self.launch_operation)
|
|
232
|
+
self.layout.addWidget(self.submit_btn, 30)
|
|
404
233
|
|
|
234
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
235
|
+
center_window(self)
|
|
405
236
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
"""
|
|
409
|
-
A layout class that combines a QLabel and a QSlider in a horizontal box layout.
|
|
410
|
-
|
|
411
|
-
This layout provides a convenient way to include a slider with an optional label and configurable
|
|
412
|
-
parameters such as range, precision, and step size. It allows for both integer and decimal step values,
|
|
413
|
-
making it versatile for different types of input adjustments.
|
|
414
|
-
|
|
415
|
-
Parameters
|
|
416
|
-
----------
|
|
417
|
-
label : str, optional
|
|
418
|
-
The label to be displayed next to the slider (default is None).
|
|
419
|
-
slider : QSlider
|
|
420
|
-
The slider widget to be added to the layout.
|
|
421
|
-
layout_ratio : tuple of float, optional
|
|
422
|
-
Defines the width ratio between the label and the slider in the layout. The first element is the
|
|
423
|
-
ratio for the label, and the second is for the slider (default is (0.25, 0.75)).
|
|
424
|
-
slider_initial_value : int or float, optional
|
|
425
|
-
The initial value to set for the slider (default is 1).
|
|
426
|
-
slider_range : tuple of int or float, optional
|
|
427
|
-
A tuple specifying the minimum and maximum values for the slider (default is (0, 1)).
|
|
428
|
-
slider_tooltip : str, optional
|
|
429
|
-
Tooltip text to display when hovering over the slider (default is None).
|
|
430
|
-
decimal_option : bool, optional
|
|
431
|
-
If True, the slider allows decimal values with a specified precision (default is True).
|
|
432
|
-
precision : float, optional
|
|
433
|
-
The step size for the slider when `decimal_option` is enabled (default is 1.0E-03).
|
|
434
|
-
|
|
435
|
-
Attributes
|
|
436
|
-
----------
|
|
437
|
-
qlabel : QLabel
|
|
438
|
-
The label widget that displays the provided label text (only if `label` is provided).
|
|
439
|
-
slider : QSlider
|
|
440
|
-
The slider widget that allows the user to select a value.
|
|
441
|
-
"""
|
|
442
|
-
|
|
443
|
-
def __init__(self, label=None, slider=None, layout_ratio=(0.25,0.75), slider_initial_value=1, slider_range=(0,1), slider_tooltip=None, decimal_option=True, precision=3, *args):
|
|
444
|
-
super().__init__(*args)
|
|
445
|
-
|
|
446
|
-
if label is not None and isinstance(label,str):
|
|
447
|
-
self.qlabel = QLabel(label)
|
|
448
|
-
self.addWidget(self.qlabel, int(100*layout_ratio[0]))
|
|
449
|
-
|
|
450
|
-
self.slider = slider
|
|
451
|
-
self.slider.setOrientation(Qt.Horizontal)
|
|
452
|
-
if decimal_option:
|
|
453
|
-
self.slider.setSingleStep(1.0*(10**(-precision)))
|
|
454
|
-
self.slider.setTickInterval(1.0*(10**(-precision)))
|
|
455
|
-
self.slider.setDecimals(precision)
|
|
456
|
-
else:
|
|
457
|
-
self.slider.setSingleStep(1)
|
|
458
|
-
self.slider.setTickInterval(1)
|
|
459
|
-
|
|
460
|
-
self.slider.setRange(*slider_range)
|
|
461
|
-
self.slider.setValue(slider_initial_value)
|
|
462
|
-
if isinstance(slider_tooltip,str):
|
|
463
|
-
self.slider.setToolTip(slider_tooltip)
|
|
464
|
-
|
|
465
|
-
self.addWidget(self.slider, int(100*layout_ratio[1]))
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
def center_window(window):
|
|
469
|
-
|
|
470
|
-
"""
|
|
471
|
-
Centers the given window in the middle of the screen.
|
|
472
|
-
|
|
473
|
-
This function calculates the current screen's geometry and moves the
|
|
474
|
-
specified window to the center of the screen. It works by retrieving the
|
|
475
|
-
frame geometry of the window, identifying the screen where the cursor is
|
|
476
|
-
currently located, and adjusting the window's position to be centrally
|
|
477
|
-
aligned on that screen.
|
|
478
|
-
|
|
479
|
-
Parameters
|
|
480
|
-
----------
|
|
481
|
-
window : QMainWindow or QWidget
|
|
482
|
-
The window or widget to be centered on the screen.
|
|
483
|
-
"""
|
|
484
|
-
|
|
485
|
-
frameGm = window.frameGeometry()
|
|
486
|
-
screen = QApplication.desktop().screenNumber(QApplication.desktop().cursor().pos())
|
|
487
|
-
centerPoint = QApplication.desktop().screenGeometry(screen).center()
|
|
488
|
-
frameGm.moveCenter(centerPoint)
|
|
489
|
-
window.move(frameGm.topLeft())
|
|
490
|
-
|
|
491
|
-
class ExportPlotBtn(QPushButton, Styles):
|
|
492
|
-
|
|
493
|
-
"""
|
|
494
|
-
A custom QPushButton widget for exporting a matplotlib figure.
|
|
495
|
-
|
|
496
|
-
This class combines a QPushButton with functionality to export a given matplotlib
|
|
497
|
-
figure (`fig`) to an image file. The button includes an icon and a tooltip for easy
|
|
498
|
-
user interaction. When clicked, a file dialog is opened allowing the user to specify
|
|
499
|
-
the location and file format to save the plot.
|
|
500
|
-
|
|
501
|
-
Parameters
|
|
502
|
-
----------
|
|
503
|
-
fig : matplotlib.figure.Figure
|
|
504
|
-
The matplotlib figure object to be exported.
|
|
505
|
-
export_dir : str, optional
|
|
506
|
-
The default directory where the file will be saved. If not provided, the current
|
|
507
|
-
working directory will be used.
|
|
508
|
-
*args : tuple
|
|
509
|
-
Additional positional arguments passed to the parent `QPushButton` constructor.
|
|
510
|
-
**kwargs : dict
|
|
511
|
-
Additional keyword arguments passed to the parent `QPushButton` constructor.
|
|
512
|
-
|
|
513
|
-
Attributes
|
|
514
|
-
----------
|
|
515
|
-
fig : matplotlib.figure.Figure
|
|
516
|
-
The figure that will be saved when the button is clicked.
|
|
517
|
-
export_dir : str or None
|
|
518
|
-
The default directory where the file dialog will initially point when saving the image.
|
|
519
|
-
|
|
520
|
-
Methods
|
|
521
|
-
-------
|
|
522
|
-
save_plot():
|
|
523
|
-
Opens a file dialog to choose the file name and location for saving the figure.
|
|
524
|
-
The figure is then saved in the specified format and location.
|
|
525
|
-
"""
|
|
526
|
-
|
|
527
|
-
def __init__(self, fig, export_dir=None, *args, **kwargs):
|
|
528
|
-
|
|
529
|
-
super().__init__()
|
|
530
|
-
|
|
531
|
-
self.export_dir = export_dir
|
|
532
|
-
self.fig = fig
|
|
533
|
-
|
|
534
|
-
self.setText('')
|
|
535
|
-
self.setIcon(icon(MDI6.content_save,color="black"))
|
|
536
|
-
self.setStyleSheet(self.button_select_all)
|
|
537
|
-
self.setToolTip('Export figure.')
|
|
538
|
-
self.setIconSize(QSize(20, 20))
|
|
539
|
-
self.clicked.connect(self.save_plot)
|
|
540
|
-
|
|
541
|
-
def save_plot(self):
|
|
542
|
-
|
|
543
|
-
"""
|
|
544
|
-
Opens a file dialog for the user to specify the location and name to save the plot.
|
|
545
|
-
|
|
546
|
-
If the user selects a file, the figure is saved with tight layout and 300 DPI resolution.
|
|
547
|
-
Supported formats include PNG, JPG, SVG, and XPM.
|
|
548
|
-
"""
|
|
549
|
-
|
|
550
|
-
if self.export_dir is not None:
|
|
551
|
-
guess_dir = self.export_dir+sep+'plot.png'
|
|
552
|
-
else:
|
|
553
|
-
guess_dir = 'plot.png'
|
|
554
|
-
fileName, _ = QFileDialog.getSaveFileName(self,
|
|
555
|
-
"Save Image", guess_dir, "Images (*.png *.xpm *.jpg *.svg)") #, options=options
|
|
556
|
-
if fileName:
|
|
557
|
-
self.fig.tight_layout()
|
|
558
|
-
self.fig.savefig(fileName, bbox_inches='tight', dpi=300)
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
class QHSeperationLine(QFrame):
|
|
562
|
-
'''
|
|
563
|
-
a horizontal seperation line\n
|
|
564
|
-
'''
|
|
565
|
-
|
|
566
|
-
def __init__(self):
|
|
567
|
-
super().__init__()
|
|
568
|
-
self.setMinimumWidth(1)
|
|
569
|
-
self.setFixedHeight(20)
|
|
570
|
-
self.setFrameShape(QFrame.HLine)
|
|
571
|
-
self.setFrameShadow(QFrame.Sunken)
|
|
572
|
-
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
class FeatureChoice(CelldetectiveWidget):
|
|
576
|
-
|
|
577
|
-
def __init__(self, parent_window):
|
|
578
|
-
super().__init__()
|
|
579
|
-
self.parent_window = parent_window
|
|
580
|
-
self.setWindowTitle("Add feature")
|
|
581
|
-
# Create the QComboBox and add some items
|
|
582
|
-
self.combo_box = QComboBox(self)
|
|
583
|
-
|
|
584
|
-
standard_measurements = ["area",
|
|
585
|
-
"area_bbox",
|
|
586
|
-
"area_convex",
|
|
587
|
-
"area_filled",
|
|
588
|
-
"major_axis_length",
|
|
589
|
-
"minor_axis_length",
|
|
590
|
-
"eccentricity",
|
|
591
|
-
"equivalent_diameter_area",
|
|
592
|
-
"euler_number",
|
|
593
|
-
"extent",
|
|
594
|
-
"feret_diameter_max",
|
|
595
|
-
"orientation",
|
|
596
|
-
"perimeter",
|
|
597
|
-
"perimeter_crofton",
|
|
598
|
-
"solidity",
|
|
599
|
-
"intensity_mean",
|
|
600
|
-
"intensity_max",
|
|
601
|
-
"intensity_min",
|
|
602
|
-
]
|
|
603
|
-
|
|
604
|
-
if extra_props:
|
|
605
|
-
members = getmembers(extra_properties, isfunction)
|
|
606
|
-
for o in members:
|
|
607
|
-
if isfunction(o[1]) and o[1].__module__=="celldetective.extra_properties":
|
|
608
|
-
standard_measurements.append(o[0])
|
|
609
|
-
|
|
610
|
-
self.combo_box.addItems(standard_measurements)
|
|
611
|
-
|
|
612
|
-
self.add_btn = QPushButton("Add")
|
|
613
|
-
self.add_btn.setStyleSheet(self.button_style_sheet)
|
|
614
|
-
self.add_btn.clicked.connect(self.add_current_feature)
|
|
615
|
-
|
|
616
|
-
# Create the layout
|
|
617
|
-
layout = QVBoxLayout(self)
|
|
618
|
-
layout.addWidget(self.combo_box)
|
|
619
|
-
layout.addWidget(self.add_btn)
|
|
620
|
-
center_window(self)
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
def add_current_feature(self):
|
|
624
|
-
filtername = self.combo_box.currentText()
|
|
625
|
-
self.parent_window.list_widget.addItems([filtername])
|
|
626
|
-
self.close()
|
|
627
|
-
|
|
237
|
+
def launch_operation(self):
|
|
628
238
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
super().__init__()
|
|
634
|
-
self.parent_window = parent_window
|
|
635
|
-
self.setWindowTitle("Add filter")
|
|
636
|
-
# Create the QComboBox and add some items
|
|
637
|
-
center_window(self)
|
|
638
|
-
|
|
639
|
-
self.default_params = {
|
|
640
|
-
'gauss_filter': {'sigma': 2},
|
|
641
|
-
'median_filter': {'size': 4},
|
|
642
|
-
'maximum_filter': {'size': 4},
|
|
643
|
-
'minimum_filter': {'size': 4},
|
|
644
|
-
'percentile_filter': {'percentile': 99, 'size': 4},
|
|
645
|
-
'variance_filter': {'size': 4},
|
|
646
|
-
'std_filter': {'size': 4},
|
|
647
|
-
'laplace_filter': None,
|
|
648
|
-
'abs_filter': None,
|
|
649
|
-
'ln_filter': None,
|
|
650
|
-
'invert_filter': {'value': 65535},
|
|
651
|
-
'subtract_filter': {'value': 1},
|
|
652
|
-
'dog_filter': {'blob_size': 30},
|
|
653
|
-
'log_filter': {'blob_size': 30},
|
|
654
|
-
'tophat_filter': {'size': 4, 'connectivity': 4},
|
|
655
|
-
'otsu_filter': None,
|
|
656
|
-
'multiotsu_filter': {'classes': 3},
|
|
657
|
-
'local_filter': {'block_size': 73, 'method': 'mean', 'offset': 0},
|
|
658
|
-
'niblack_filter': {'window_size': 15, 'k': 0.2},
|
|
659
|
-
# 'sauvola_filter': {'window_size': 15, 'k': 0.2}
|
|
660
|
-
}
|
|
661
|
-
|
|
662
|
-
layout = QVBoxLayout(self)
|
|
663
|
-
self.combo_box = QComboBox(self)
|
|
664
|
-
self.combo_box.addItems(list(self.default_params.keys()))
|
|
665
|
-
self.combo_box.currentTextChanged.connect(self.update_arguments)
|
|
666
|
-
layout.addWidget(self.combo_box)
|
|
667
|
-
|
|
668
|
-
self.floatValidator = QDoubleValidator()
|
|
669
|
-
self.arguments_le = [QLineEdit() for i in range(3)]
|
|
670
|
-
for i in range(3):
|
|
671
|
-
self.arguments_le[i].setValidator(self.floatValidator)
|
|
672
|
-
|
|
673
|
-
self.arguments_labels = [QLabel('') for i in range(3)]
|
|
674
|
-
for i in range(2):
|
|
675
|
-
hbox = QHBoxLayout()
|
|
676
|
-
hbox.addWidget(self.arguments_labels[i], 40)
|
|
677
|
-
hbox.addWidget(self.arguments_le[i], 60)
|
|
678
|
-
layout.addLayout(hbox)
|
|
679
|
-
|
|
680
|
-
self.add_btn = QPushButton("Add")
|
|
681
|
-
self.add_btn.setStyleSheet(self.button_style_sheet)
|
|
682
|
-
self.add_btn.clicked.connect(self.add_current_feature)
|
|
683
|
-
layout.addWidget(self.add_btn)
|
|
684
|
-
|
|
685
|
-
self.combo_box.setCurrentIndex(0)
|
|
686
|
-
self.update_arguments()
|
|
687
|
-
center_window(self)
|
|
688
|
-
|
|
689
|
-
def add_current_feature(self):
|
|
690
|
-
|
|
691
|
-
filtername = self.combo_box.currentText()
|
|
692
|
-
self.parent_window.list_widget.addItems([filtername])
|
|
693
|
-
|
|
694
|
-
filter_instructions = [filtername.split('_')[0]]
|
|
695
|
-
for a in self.arguments_le:
|
|
696
|
-
|
|
697
|
-
arg = a.text().replace(',','.')
|
|
698
|
-
arg_num = arg
|
|
699
|
-
|
|
700
|
-
if (arg != '') and arg_num.replace('.', '').replace(',', '').isnumeric():
|
|
701
|
-
num = float(arg)
|
|
702
|
-
if num.is_integer():
|
|
703
|
-
num = int(num)
|
|
704
|
-
filter_instructions.append(num)
|
|
705
|
-
elif arg != '':
|
|
706
|
-
filter_instructions.append(arg)
|
|
707
|
-
|
|
708
|
-
print(f'You added filter {filter_instructions}.')
|
|
709
|
-
|
|
710
|
-
self.parent_window.items.append(filter_instructions)
|
|
711
|
-
self.close()
|
|
712
|
-
|
|
713
|
-
def update_arguments(self):
|
|
714
|
-
|
|
715
|
-
selected_filter = self.combo_box.currentText()
|
|
716
|
-
arguments = self.default_params[selected_filter]
|
|
717
|
-
if arguments is not None:
|
|
718
|
-
args = list(arguments.keys())
|
|
719
|
-
for i in range(len(args)):
|
|
720
|
-
self.arguments_labels[i].setEnabled(True)
|
|
721
|
-
self.arguments_le[i].setEnabled(True)
|
|
722
|
-
|
|
723
|
-
self.arguments_labels[i].setText(args[i])
|
|
724
|
-
self.arguments_le[i].setText(str(arguments[args[i]]))
|
|
725
|
-
|
|
726
|
-
if len(args) < 2:
|
|
727
|
-
for i in range(len(args), 2):
|
|
728
|
-
self.arguments_labels[i].setEnabled(False)
|
|
729
|
-
self.arguments_labels[i].setText('')
|
|
730
|
-
self.arguments_le[i].setEnabled(False)
|
|
731
|
-
else:
|
|
732
|
-
for i in range(2):
|
|
733
|
-
self.arguments_labels[i].setEnabled(False)
|
|
734
|
-
self.arguments_le[i].setEnabled(False)
|
|
735
|
-
self.arguments_labels[i].setText('')
|
|
239
|
+
self.compute()
|
|
240
|
+
self.parent_window.model = PandasModel(self.parent_window.data)
|
|
241
|
+
self.parent_window.table_view.setModel(self.parent_window.model)
|
|
242
|
+
self.close()
|
|
736
243
|
|
|
244
|
+
def compute(self):
|
|
245
|
+
pass
|
|
737
246
|
|
|
738
|
-
class OperationChoice(CelldetectiveWidget):
|
|
739
|
-
"""
|
|
740
|
-
Mini window to select an operation from numpy to apply on the ROI.
|
|
741
|
-
|
|
742
|
-
"""
|
|
743
247
|
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
248
|
+
class QuickSliderLayout(QHBoxLayout):
|
|
249
|
+
"""
|
|
250
|
+
A layout class that combines a QLabel and a QSlider in a horizontal box layout.
|
|
251
|
+
|
|
252
|
+
This layout provides a convenient way to include a slider with an optional label and configurable
|
|
253
|
+
parameters such as range, precision, and step size. It allows for both integer and decimal step values,
|
|
254
|
+
making it versatile for different types of input adjustments.
|
|
255
|
+
|
|
256
|
+
Parameters
|
|
257
|
+
----------
|
|
258
|
+
label : str, optional
|
|
259
|
+
The label to be displayed next to the slider (default is None).
|
|
260
|
+
slider : QSlider
|
|
261
|
+
The slider widget to be added to the layout.
|
|
262
|
+
layout_ratio : tuple of float, optional
|
|
263
|
+
Defines the width ratio between the label and the slider in the layout. The first element is the
|
|
264
|
+
ratio for the label, and the second is for the slider (default is (0.25, 0.75)).
|
|
265
|
+
slider_initial_value : int or float, optional
|
|
266
|
+
The initial value to set for the slider (default is 1).
|
|
267
|
+
slider_range : tuple of int or float, optional
|
|
268
|
+
A tuple specifying the minimum and maximum values for the slider (default is (0, 1)).
|
|
269
|
+
slider_tooltip : str, optional
|
|
270
|
+
Tooltip text to display when hovering over the slider (default is None).
|
|
271
|
+
decimal_option : bool, optional
|
|
272
|
+
If True, the slider allows decimal values with a specified precision (default is True).
|
|
273
|
+
precision : float, optional
|
|
274
|
+
The step size for the slider when `decimal_option` is enabled (default is 1.0E-03).
|
|
275
|
+
|
|
276
|
+
Attributes
|
|
277
|
+
----------
|
|
278
|
+
qlabel : QLabel
|
|
279
|
+
The label widget that displays the provided label text (only if `label` is provided).
|
|
280
|
+
slider : QSlider
|
|
281
|
+
The slider widget that allows the user to select a value.
|
|
282
|
+
"""
|
|
283
|
+
|
|
284
|
+
def __init__(
|
|
285
|
+
self,
|
|
286
|
+
label=None,
|
|
287
|
+
slider=None,
|
|
288
|
+
layout_ratio=(0.25, 0.75),
|
|
289
|
+
slider_initial_value=1,
|
|
290
|
+
slider_range=(0, 1),
|
|
291
|
+
slider_tooltip=None,
|
|
292
|
+
decimal_option=True,
|
|
293
|
+
precision=3,
|
|
294
|
+
*args,
|
|
295
|
+
):
|
|
296
|
+
super().__init__(*args)
|
|
297
|
+
|
|
298
|
+
if label is not None and isinstance(label, str):
|
|
299
|
+
self.qlabel = QLabel(label)
|
|
300
|
+
self.addWidget(self.qlabel, int(100 * layout_ratio[0]))
|
|
301
|
+
|
|
302
|
+
self.slider = slider
|
|
303
|
+
self.slider.setOrientation(Qt.Horizontal)
|
|
304
|
+
if decimal_option:
|
|
305
|
+
self.slider.setSingleStep(1.0 * (10 ** (-precision)))
|
|
306
|
+
self.slider.setTickInterval(1.0 * (10 ** (-precision)))
|
|
307
|
+
self.slider.setDecimals(precision)
|
|
308
|
+
else:
|
|
309
|
+
self.slider.setSingleStep(1)
|
|
310
|
+
self.slider.setTickInterval(1)
|
|
311
|
+
|
|
312
|
+
self.slider.setRange(*slider_range)
|
|
313
|
+
self.slider.setValue(slider_initial_value)
|
|
314
|
+
if isinstance(slider_tooltip, str):
|
|
315
|
+
self.slider.setToolTip(slider_tooltip)
|
|
316
|
+
|
|
317
|
+
self.addWidget(self.slider, int(100 * layout_ratio[1]))
|
|
770
318
|
|
|
771
|
-
def __init__(self, parent_window):
|
|
772
319
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
320
|
+
class ExportPlotBtn(QPushButton, Styles):
|
|
321
|
+
"""
|
|
322
|
+
A custom QPushButton widget for exporting a matplotlib figure.
|
|
323
|
+
|
|
324
|
+
This class combines a QPushButton with functionality to export a given matplotlib
|
|
325
|
+
figure (`fig`) to an image file. The button includes an icon and a tooltip for easy
|
|
326
|
+
user interaction. When clicked, a file dialog is opened allowing the user to specify
|
|
327
|
+
the location and file format to save the plot.
|
|
328
|
+
|
|
329
|
+
Parameters
|
|
330
|
+
----------
|
|
331
|
+
fig : matplotlib.figure.Figure
|
|
332
|
+
The matplotlib figure object to be exported.
|
|
333
|
+
export_dir : str, optional
|
|
334
|
+
The default directory where the file will be saved. If not provided, the current
|
|
335
|
+
working directory will be used.
|
|
336
|
+
*args : tuple
|
|
337
|
+
Additional positional arguments passed to the parent `QPushButton` constructor.
|
|
338
|
+
**kwargs : dict
|
|
339
|
+
Additional keyword arguments passed to the parent `QPushButton` constructor.
|
|
340
|
+
|
|
341
|
+
Attributes
|
|
342
|
+
----------
|
|
343
|
+
fig : matplotlib.figure.Figure
|
|
344
|
+
The figure that will be saved when the button is clicked.
|
|
345
|
+
export_dir : str or None
|
|
346
|
+
The default directory where the file dialog will initially point when saving the image.
|
|
347
|
+
|
|
348
|
+
Methods
|
|
349
|
+
-------
|
|
350
|
+
save_plot():
|
|
351
|
+
Opens a file dialog to choose the file name and location for saving the figure.
|
|
352
|
+
The figure is then saved in the specified format and location.
|
|
353
|
+
"""
|
|
354
|
+
|
|
355
|
+
def __init__(self, fig, export_dir=None, *args, **kwargs):
|
|
356
|
+
|
|
357
|
+
super().__init__()
|
|
358
|
+
|
|
359
|
+
self.export_dir = export_dir
|
|
360
|
+
self.fig = fig
|
|
361
|
+
|
|
362
|
+
self.setText("")
|
|
363
|
+
self.setIcon(icon(MDI6.content_save, color="black"))
|
|
364
|
+
self.setStyleSheet(self.button_select_all)
|
|
365
|
+
self.setToolTip("Export figure.")
|
|
366
|
+
self.setIconSize(QSize(20, 20))
|
|
367
|
+
self.clicked.connect(self.save_plot)
|
|
368
|
+
|
|
369
|
+
def save_plot(self):
|
|
370
|
+
"""
|
|
371
|
+
Opens a file dialog for the user to specify the location and name to save the plot.
|
|
372
|
+
|
|
373
|
+
If the user selects a file, the figure is saved with tight layout and 300 DPI resolution.
|
|
374
|
+
Supported formats include PNG, JPG, SVG, and XPM.
|
|
375
|
+
"""
|
|
376
|
+
|
|
377
|
+
if self.export_dir is not None:
|
|
378
|
+
guess_dir = self.export_dir + sep + "plot.png"
|
|
379
|
+
else:
|
|
380
|
+
guess_dir = "plot.png"
|
|
381
|
+
fileName, _ = QFileDialog.getSaveFileName(
|
|
382
|
+
self, "Save Image", guess_dir, "Images (*.png *.xpm *.jpg *.svg)"
|
|
383
|
+
) # , options=options
|
|
384
|
+
if fileName:
|
|
385
|
+
self.fig.tight_layout()
|
|
386
|
+
self.fig.savefig(fileName, bbox_inches="tight", dpi=300)
|
|
777
387
|
|
|
778
|
-
# Create the QComboBox and add some items
|
|
779
388
|
|
|
780
|
-
|
|
781
|
-
self.dist_le = QLineEdit('10')
|
|
389
|
+
class FilterChoice(CelldetectiveWidget):
|
|
782
390
|
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
391
|
+
def __init__(self, parent_window):
|
|
392
|
+
|
|
393
|
+
super().__init__()
|
|
394
|
+
self.parent_window = parent_window
|
|
395
|
+
self.setWindowTitle("Add filter")
|
|
396
|
+
# Create the QComboBox and add some items
|
|
397
|
+
center_window(self)
|
|
398
|
+
|
|
399
|
+
self.default_params = {
|
|
400
|
+
"gauss_filter": {"sigma": 2},
|
|
401
|
+
"median_filter": {"size": 4},
|
|
402
|
+
"maximum_filter": {"size": 4},
|
|
403
|
+
"minimum_filter": {"size": 4},
|
|
404
|
+
"percentile_filter": {"percentile": 99, "size": 4},
|
|
405
|
+
"variance_filter": {"size": 4},
|
|
406
|
+
"std_filter": {"size": 4},
|
|
407
|
+
"laplace_filter": None,
|
|
408
|
+
"abs_filter": None,
|
|
409
|
+
"ln_filter": None,
|
|
410
|
+
"invert_filter": {"value": 65535},
|
|
411
|
+
"subtract_filter": {"value": 1},
|
|
412
|
+
"dog_filter": {"blob_size": 30},
|
|
413
|
+
"log_filter": {"blob_size": 30},
|
|
414
|
+
"tophat_filter": {"size": 4, "connectivity": 4},
|
|
415
|
+
"otsu_filter": None,
|
|
416
|
+
"multiotsu_filter": {"classes": 3},
|
|
417
|
+
"local_filter": {"block_size": 73, "method": "mean", "offset": 0},
|
|
418
|
+
"niblack_filter": {"window_size": 15, "k": 0.2},
|
|
419
|
+
# 'sauvola_filter': {'window_size': 15, 'k': 0.2}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
layout = QVBoxLayout(self)
|
|
423
|
+
self.combo_box = QComboBox(self)
|
|
424
|
+
self.combo_box.addItems(list(self.default_params.keys()))
|
|
425
|
+
self.combo_box.currentTextChanged.connect(self.update_arguments)
|
|
426
|
+
layout.addWidget(self.combo_box)
|
|
427
|
+
|
|
428
|
+
self.floatValidator = QDoubleValidator()
|
|
429
|
+
self.arguments_le = [QLineEdit() for i in range(3)]
|
|
430
|
+
for i in range(3):
|
|
431
|
+
self.arguments_le[i].setValidator(self.floatValidator)
|
|
432
|
+
|
|
433
|
+
self.arguments_labels = [QLabel("") for i in range(3)]
|
|
434
|
+
for i in range(2):
|
|
435
|
+
hbox = QHBoxLayout()
|
|
436
|
+
hbox.addWidget(self.arguments_labels[i], 40)
|
|
437
|
+
hbox.addWidget(self.arguments_le[i], 60)
|
|
438
|
+
layout.addLayout(hbox)
|
|
439
|
+
|
|
440
|
+
self.add_btn = QPushButton("Add")
|
|
441
|
+
self.add_btn.setStyleSheet(self.button_style_sheet)
|
|
442
|
+
self.add_btn.clicked.connect(self.add_current_feature)
|
|
443
|
+
layout.addWidget(self.add_btn)
|
|
444
|
+
|
|
445
|
+
self.combo_box.setCurrentIndex(0)
|
|
446
|
+
self.update_arguments()
|
|
447
|
+
center_window(self)
|
|
448
|
+
|
|
449
|
+
def add_current_feature(self):
|
|
450
|
+
|
|
451
|
+
filtername = self.combo_box.currentText()
|
|
452
|
+
self.parent_window.list_widget.addItems([filtername])
|
|
453
|
+
|
|
454
|
+
filter_instructions = [filtername.split("_")[0]]
|
|
455
|
+
for a in self.arguments_le:
|
|
456
|
+
|
|
457
|
+
arg = a.text().replace(",", ".")
|
|
458
|
+
arg_num = arg
|
|
459
|
+
|
|
460
|
+
if (arg != "") and arg_num.replace(".", "").replace(",", "").isnumeric():
|
|
461
|
+
num = float(arg)
|
|
462
|
+
if num.is_integer():
|
|
463
|
+
num = int(num)
|
|
464
|
+
filter_instructions.append(num)
|
|
465
|
+
elif arg != "":
|
|
466
|
+
filter_instructions.append(arg)
|
|
467
|
+
|
|
468
|
+
print(f"You added filter {filter_instructions}.")
|
|
469
|
+
|
|
470
|
+
self.parent_window.items.append(filter_instructions)
|
|
471
|
+
self.close()
|
|
472
|
+
|
|
473
|
+
def update_arguments(self):
|
|
474
|
+
|
|
475
|
+
selected_filter = self.combo_box.currentText()
|
|
476
|
+
arguments = self.default_params[selected_filter]
|
|
477
|
+
if arguments is not None:
|
|
478
|
+
args = list(arguments.keys())
|
|
479
|
+
for i in range(len(args)):
|
|
480
|
+
self.arguments_labels[i].setEnabled(True)
|
|
481
|
+
self.arguments_le[i].setEnabled(True)
|
|
482
|
+
|
|
483
|
+
self.arguments_labels[i].setText(args[i])
|
|
484
|
+
self.arguments_le[i].setText(str(arguments[args[i]]))
|
|
485
|
+
|
|
486
|
+
if len(args) < 2:
|
|
487
|
+
for i in range(len(args), 2):
|
|
488
|
+
self.arguments_labels[i].setEnabled(False)
|
|
489
|
+
self.arguments_labels[i].setText("")
|
|
490
|
+
self.arguments_le[i].setEnabled(False)
|
|
491
|
+
else:
|
|
492
|
+
for i in range(2):
|
|
493
|
+
self.arguments_labels[i].setEnabled(False)
|
|
494
|
+
self.arguments_le[i].setEnabled(False)
|
|
495
|
+
self.arguments_labels[i].setText("")
|
|
786
496
|
|
|
787
|
-
self.outer_btn = QCheckBox('outer distance')
|
|
788
|
-
self.outer_btn.clicked.connect(self.activate_outer_value)
|
|
789
497
|
|
|
790
|
-
|
|
791
|
-
|
|
498
|
+
class OperationChoice(CelldetectiveWidget):
|
|
499
|
+
"""
|
|
500
|
+
Mini window to select an operation from numpy to apply on the ROI.
|
|
501
|
+
|
|
502
|
+
"""
|
|
503
|
+
|
|
504
|
+
def __init__(self, parent_window):
|
|
505
|
+
super().__init__()
|
|
506
|
+
self.parent_window = parent_window
|
|
507
|
+
self.setWindowTitle("Add feature")
|
|
508
|
+
# Create the QComboBox and add some items
|
|
509
|
+
self.combo_box = QComboBox(self)
|
|
510
|
+
center_window(self)
|
|
511
|
+
|
|
512
|
+
self.combo_box.addItems(
|
|
513
|
+
[
|
|
514
|
+
"mean",
|
|
515
|
+
"median",
|
|
516
|
+
"average",
|
|
517
|
+
"std",
|
|
518
|
+
"var",
|
|
519
|
+
"nanmedian",
|
|
520
|
+
"nanmean",
|
|
521
|
+
"nanstd",
|
|
522
|
+
"nanvar",
|
|
523
|
+
]
|
|
524
|
+
)
|
|
525
|
+
|
|
526
|
+
self.add_btn = QPushButton("Add")
|
|
527
|
+
self.add_btn.clicked.connect(self.add_current_feature)
|
|
528
|
+
|
|
529
|
+
# Create the layout
|
|
530
|
+
layout = QVBoxLayout(self)
|
|
531
|
+
layout.addWidget(self.combo_box)
|
|
532
|
+
layout.addWidget(self.add_btn)
|
|
533
|
+
|
|
534
|
+
def add_current_feature(self):
|
|
535
|
+
filtername = self.combo_box.currentText()
|
|
536
|
+
self.parent_window.list_widget.addItems([filtername])
|
|
537
|
+
self.close()
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
class GeometryChoice(CelldetectiveWidget, Styles):
|
|
541
|
+
|
|
542
|
+
def __init__(self, parent_window):
|
|
543
|
+
|
|
544
|
+
super().__init__()
|
|
545
|
+
self.parent_window = parent_window
|
|
546
|
+
self.setWindowTitle("Set distances")
|
|
547
|
+
center_window(self)
|
|
548
|
+
|
|
549
|
+
# Create the QComboBox and add some items
|
|
550
|
+
|
|
551
|
+
self.dist_label = QLabel("Distance [px]: ")
|
|
552
|
+
self.dist_le = QLineEdit("10")
|
|
553
|
+
|
|
554
|
+
self.dist_outer_label = QLabel("Max distance [px]")
|
|
555
|
+
self.dist_outer_le = QLineEdit("100")
|
|
556
|
+
self.outer_to_hide = [self.dist_outer_le, self.dist_outer_label]
|
|
557
|
+
|
|
558
|
+
self.outer_btn = QCheckBox("outer distance")
|
|
559
|
+
self.outer_btn.clicked.connect(self.activate_outer_value)
|
|
560
|
+
|
|
561
|
+
self.add_btn = QPushButton("Add")
|
|
562
|
+
self.add_btn.clicked.connect(self.add_current_feature)
|
|
563
|
+
self.add_btn.setStyleSheet(self.button_style_sheet)
|
|
564
|
+
|
|
565
|
+
# Create the layout
|
|
566
|
+
layout = QVBoxLayout(self)
|
|
567
|
+
dist_layout = QHBoxLayout()
|
|
568
|
+
dist_layout.addWidget(self.dist_label, 30)
|
|
569
|
+
dist_layout.addWidget(self.dist_le, 70)
|
|
570
|
+
|
|
571
|
+
self.dist_outer_layout = QHBoxLayout()
|
|
572
|
+
self.dist_outer_layout.addWidget(self.dist_outer_label, 30)
|
|
573
|
+
self.dist_outer_layout.addWidget(self.dist_outer_le, 70)
|
|
574
|
+
|
|
575
|
+
layout.addLayout(dist_layout)
|
|
576
|
+
layout.addLayout(self.dist_outer_layout)
|
|
577
|
+
layout.addWidget(self.outer_btn)
|
|
578
|
+
layout.addWidget(self.add_btn)
|
|
579
|
+
|
|
580
|
+
for el in self.outer_to_hide:
|
|
581
|
+
el.hide()
|
|
582
|
+
|
|
583
|
+
def activate_outer_value(self):
|
|
584
|
+
if self.outer_btn.isChecked():
|
|
585
|
+
self.dist_label.setText("Min distance [px]: ")
|
|
586
|
+
for el in self.outer_to_hide:
|
|
587
|
+
el.show()
|
|
588
|
+
else:
|
|
589
|
+
self.dist_label.setText("Distance [px]: ")
|
|
590
|
+
for el in self.outer_to_hide:
|
|
591
|
+
el.hide()
|
|
592
|
+
|
|
593
|
+
def add_current_feature(self):
|
|
594
|
+
|
|
595
|
+
value = self.dist_le.text()
|
|
596
|
+
if self.outer_btn.isChecked():
|
|
597
|
+
value2 = self.dist_outer_le.text()
|
|
598
|
+
values = [value + "-" + value2]
|
|
599
|
+
else:
|
|
600
|
+
values = [value]
|
|
601
|
+
self.parent_window.list_widget.addItems(values)
|
|
602
|
+
self.close()
|
|
792
603
|
|
|
793
|
-
# Create the layout
|
|
794
|
-
layout = QVBoxLayout(self)
|
|
795
|
-
dist_layout = QHBoxLayout()
|
|
796
|
-
dist_layout.addWidget(self.dist_label, 30)
|
|
797
|
-
dist_layout.addWidget(self.dist_le, 70)
|
|
798
604
|
|
|
799
|
-
|
|
800
|
-
self.dist_outer_layout.addWidget(self.dist_outer_label, 30)
|
|
801
|
-
self.dist_outer_layout.addWidget(self.dist_outer_le, 70)
|
|
605
|
+
class DistanceChoice(CelldetectiveWidget):
|
|
802
606
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
607
|
+
def __init__(self, parent_window):
|
|
608
|
+
super().__init__()
|
|
609
|
+
self.parent_window = parent_window
|
|
610
|
+
self.setWindowTitle("Set distances")
|
|
611
|
+
self.floatValidator = QDoubleValidator()
|
|
612
|
+
center_window(self)
|
|
807
613
|
|
|
808
|
-
|
|
809
|
-
el.hide()
|
|
614
|
+
# Create the QComboBox and add some items
|
|
810
615
|
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
for el in self.outer_to_hide:
|
|
815
|
-
el.show()
|
|
816
|
-
else:
|
|
817
|
-
self.dist_label.setText('Distance [px]: ')
|
|
818
|
-
for el in self.outer_to_hide:
|
|
819
|
-
el.hide()
|
|
616
|
+
self.dist_label = QLabel("Distance [px]: ")
|
|
617
|
+
self.dist_le = QLineEdit("10")
|
|
618
|
+
self.dist_le.setValidator(self.floatValidator)
|
|
820
619
|
|
|
821
|
-
|
|
620
|
+
self.add_btn = QPushButton("Add")
|
|
621
|
+
self.add_btn.clicked.connect(self.add_current_feature)
|
|
822
622
|
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
values = [value]
|
|
829
|
-
self.parent_window.list_widget.addItems(values)
|
|
830
|
-
self.close()
|
|
623
|
+
# Create the layout
|
|
624
|
+
layout = QVBoxLayout(self)
|
|
625
|
+
dist_layout = QHBoxLayout()
|
|
626
|
+
dist_layout.addWidget(self.dist_label, 30)
|
|
627
|
+
dist_layout.addWidget(self.dist_le, 70)
|
|
831
628
|
|
|
629
|
+
layout.addLayout(dist_layout)
|
|
630
|
+
layout.addWidget(self.add_btn)
|
|
832
631
|
|
|
833
|
-
|
|
632
|
+
def add_current_feature(self):
|
|
633
|
+
value = self.dist_le.text().replace(",", ".")
|
|
634
|
+
values = [value]
|
|
635
|
+
self.parent_window.list_widget.addItems(values)
|
|
636
|
+
self.close()
|
|
834
637
|
|
|
835
|
-
def __init__(self, parent_window):
|
|
836
|
-
super().__init__()
|
|
837
|
-
self.parent_window = parent_window
|
|
838
|
-
self.setWindowTitle("Set distances")
|
|
839
|
-
self.floatValidator = QDoubleValidator()
|
|
840
|
-
center_window(self)
|
|
841
|
-
|
|
842
|
-
# Create the QComboBox and add some items
|
|
843
|
-
|
|
844
|
-
self.dist_label = QLabel('Distance [px]: ')
|
|
845
|
-
self.dist_le = QLineEdit('10')
|
|
846
|
-
self.dist_le.setValidator(self.floatValidator)
|
|
847
|
-
|
|
848
|
-
self.add_btn = QPushButton("Add")
|
|
849
|
-
self.add_btn.clicked.connect(self.add_current_feature)
|
|
850
|
-
|
|
851
|
-
# Create the layout
|
|
852
|
-
layout = QVBoxLayout(self)
|
|
853
|
-
dist_layout = QHBoxLayout()
|
|
854
|
-
dist_layout.addWidget(self.dist_label, 30)
|
|
855
|
-
dist_layout.addWidget(self.dist_le, 70)
|
|
856
|
-
|
|
857
|
-
layout.addLayout(dist_layout)
|
|
858
|
-
layout.addWidget(self.add_btn)
|
|
859
|
-
|
|
860
|
-
def add_current_feature(self):
|
|
861
|
-
value = self.dist_le.text().replace(',','.')
|
|
862
|
-
values = [value]
|
|
863
|
-
self.parent_window.list_widget.addItems(values)
|
|
864
|
-
self.close()
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
class ListWidget(CelldetectiveWidget):
|
|
868
|
-
|
|
869
|
-
"""
|
|
870
|
-
A customizable widget for displaying and managing a list of items, with the
|
|
871
|
-
ability to add and remove items interactively.
|
|
872
|
-
|
|
873
|
-
This widget is built around a `QListWidget` and allows for initialization with
|
|
874
|
-
a set of features. It also provides options to retrieve the items, add new items
|
|
875
|
-
using a custom widget, and remove selected items. The items can be parsed and
|
|
876
|
-
returned as a list, with support for various data types and formatted input (e.g.,
|
|
877
|
-
ranges specified with a dash).
|
|
878
|
-
|
|
879
|
-
Parameters
|
|
880
|
-
----------
|
|
881
|
-
choiceWidget : QWidget
|
|
882
|
-
A custom widget that is used to add new items to the list.
|
|
883
|
-
initial_features : list
|
|
884
|
-
A list of initial items to populate the list widget.
|
|
885
|
-
dtype : type, optional
|
|
886
|
-
The data type to cast the list items to. Default is `str`.
|
|
887
|
-
|
|
888
|
-
Attributes
|
|
889
|
-
----------
|
|
890
|
-
initial_features : list
|
|
891
|
-
The initial set of features or items displayed in the list.
|
|
892
|
-
choiceWidget : QWidget
|
|
893
|
-
The widget used to prompt the user to add new items.
|
|
894
|
-
dtype : type
|
|
895
|
-
The data type to convert items into when retrieved from the list.
|
|
896
|
-
items : list
|
|
897
|
-
A list to store the current items in the list widget.
|
|
898
|
-
list_widget : QListWidget
|
|
899
|
-
The core Qt widget that displays the list of items.
|
|
900
|
-
|
|
901
|
-
Methods
|
|
902
|
-
-------
|
|
903
|
-
addItem()
|
|
904
|
-
Opens a new window to add an item to the list using the custom `choiceWidget`.
|
|
905
|
-
getItems()
|
|
906
|
-
Retrieves the items from the list widget, parsing ranges (e.g., 'min-max')
|
|
907
|
-
into two values, and converts them to the specified `dtype`.
|
|
908
|
-
removeSel()
|
|
909
|
-
Removes the currently selected item(s) from the list widget and updates the
|
|
910
|
-
internal `items` list accordingly.
|
|
911
|
-
"""
|
|
912
|
-
|
|
913
|
-
def __init__(self, choiceWidget, initial_features, dtype=str, *args, **kwargs):
|
|
914
|
-
|
|
915
|
-
super().__init__()
|
|
916
|
-
self.initial_features = initial_features
|
|
917
|
-
self.choiceWidget = choiceWidget
|
|
918
|
-
self.dtype = dtype
|
|
919
|
-
self.items = []
|
|
920
|
-
|
|
921
|
-
self.setFixedHeight(80)
|
|
922
|
-
|
|
923
|
-
# Initialize list widget
|
|
924
|
-
self.list_widget = QListWidget()
|
|
925
|
-
self.list_widget.addItems(initial_features)
|
|
926
|
-
|
|
927
|
-
# Set up layout
|
|
928
|
-
main_layout = QVBoxLayout()
|
|
929
|
-
main_layout.addWidget(self.list_widget)
|
|
930
|
-
self.setLayout(main_layout)
|
|
931
|
-
center_window(self)
|
|
932
|
-
|
|
933
|
-
def addItem(self):
|
|
934
|
-
|
|
935
|
-
"""
|
|
936
|
-
Opens the custom choiceWidget to add a new item to the list.
|
|
937
|
-
"""
|
|
938
|
-
|
|
939
|
-
self.addItemWindow = self.choiceWidget(self)
|
|
940
|
-
self.addItemWindow.show()
|
|
941
|
-
|
|
942
|
-
def addItemToList(self, item):
|
|
943
|
-
self.list_widget.addItems([item])
|
|
944
|
-
|
|
945
|
-
def getItems(self):
|
|
946
|
-
|
|
947
|
-
"""
|
|
948
|
-
Retrieves and returns the items from the list widget.
|
|
949
|
-
|
|
950
|
-
This method parses any items that contain a range (formatted as 'min-max')
|
|
951
|
-
into a list of two values, and casts all items to the specified `dtype`.
|
|
952
|
-
|
|
953
|
-
Returns
|
|
954
|
-
-------
|
|
955
|
-
list
|
|
956
|
-
A list of the items in the list widget, with ranges split into two values.
|
|
957
|
-
"""
|
|
958
|
-
|
|
959
|
-
items = []
|
|
960
|
-
for x in range(self.list_widget.count()):
|
|
961
|
-
if len(self.list_widget.item(x).text().split('-')) == 2:
|
|
962
|
-
if self.list_widget.item(x).text()[0] == '-':
|
|
963
|
-
items.append(self.dtype(self.list_widget.item(x).text()))
|
|
964
|
-
else:
|
|
965
|
-
minn, maxx = self.list_widget.item(x).text().split('-')
|
|
966
|
-
to_add = [self.dtype(minn), self.dtype(maxx)]
|
|
967
|
-
items.append(to_add)
|
|
968
|
-
else:
|
|
969
|
-
items.append(self.dtype(self.list_widget.item(x).text()))
|
|
970
|
-
return items
|
|
971
|
-
|
|
972
|
-
def clear(self):
|
|
973
|
-
self.items = []
|
|
974
|
-
self.list_widget.clear()
|
|
975
|
-
|
|
976
|
-
def removeSel(self):
|
|
977
|
-
|
|
978
|
-
"""
|
|
979
|
-
Removes the selected item(s) from the list widget.
|
|
980
|
-
|
|
981
|
-
If there are any selected items, they are removed both from the visual list
|
|
982
|
-
and the internal `items` list that tracks the current state of the widget.
|
|
983
|
-
"""
|
|
984
|
-
|
|
985
|
-
listItems = self.list_widget.selectedItems()
|
|
986
|
-
if not listItems: return
|
|
987
|
-
for item in listItems:
|
|
988
|
-
idx = self.list_widget.row(item)
|
|
989
|
-
self.list_widget.takeItem(idx)
|
|
990
|
-
if self.items:
|
|
991
|
-
del self.items[idx]
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
class FigureCanvas(CelldetectiveWidget):
|
|
995
|
-
"""
|
|
996
|
-
Generic figure canvas.
|
|
997
|
-
"""
|
|
998
|
-
|
|
999
|
-
def __init__(self, fig, title="", interactive=True):
|
|
1000
|
-
super().__init__()
|
|
1001
|
-
self.fig = fig
|
|
1002
|
-
self.setWindowTitle(title)
|
|
1003
|
-
self.canvas = FigureCanvasQTAgg(self.fig)
|
|
1004
|
-
self.canvas.setStyleSheet("background-color: transparent;")
|
|
1005
|
-
if interactive:
|
|
1006
|
-
self.toolbar = NavigationToolbar2QT(self.canvas)
|
|
1007
|
-
self.layout = QVBoxLayout(self)
|
|
1008
|
-
self.layout.addWidget(self.canvas,90)
|
|
1009
|
-
if interactive:
|
|
1010
|
-
self.layout.addWidget(self.toolbar)
|
|
1011
|
-
|
|
1012
|
-
center_window(self)
|
|
1013
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
1014
|
-
|
|
1015
|
-
def resizeEvent(self, event):
|
|
1016
|
-
|
|
1017
|
-
super().resizeEvent(event)
|
|
1018
|
-
try:
|
|
1019
|
-
self.fig.tight_layout()
|
|
1020
|
-
except:
|
|
1021
|
-
pass
|
|
1022
|
-
|
|
1023
|
-
def draw(self):
|
|
1024
|
-
self.canvas.draw()
|
|
1025
|
-
|
|
1026
|
-
def closeEvent(self, event):
|
|
1027
|
-
""" Delete figure on closing window. """
|
|
1028
|
-
# self.canvas.ax.cla() # ****
|
|
1029
|
-
self.fig.clf() # ****
|
|
1030
|
-
plt.close(self.fig)
|
|
1031
|
-
super(FigureCanvas, self).closeEvent(event)
|
|
1032
638
|
|
|
1033
639
|
class ThresholdLineEdit(QLineEdit):
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
640
|
+
"""
|
|
641
|
+
A custom QLineEdit widget to manage and validate threshold values.
|
|
642
|
+
|
|
643
|
+
This class extends QLineEdit to input and manage threshold values (either float or int),
|
|
644
|
+
with optional validation and interaction with connected QPushButtons. The widget can
|
|
645
|
+
validate the input and enable/disable buttons based on whether a valid threshold is set.
|
|
646
|
+
|
|
647
|
+
Parameters
|
|
648
|
+
----------
|
|
649
|
+
init_value : float or int, optional
|
|
650
|
+
The initial threshold value to display in the input field (default is 2.0).
|
|
651
|
+
connected_buttons : QPushButton or list of QPushButton, optional
|
|
652
|
+
QPushButton(s) that should be enabled/disabled based on the validity of the threshold
|
|
653
|
+
value (default is None).
|
|
654
|
+
placeholder : str, optional
|
|
655
|
+
Placeholder text to show when no value is entered in the input field
|
|
656
|
+
(default is 'px > thresh are masked').
|
|
657
|
+
value_type : str, optional
|
|
658
|
+
Specifies the type of threshold value, either 'float' or 'int' (default is 'float').
|
|
659
|
+
|
|
660
|
+
Methods
|
|
661
|
+
-------
|
|
662
|
+
enable_btn():
|
|
663
|
+
Enables or disables connected QPushButtons based on the validity of the threshold value.
|
|
664
|
+
set_threshold(value):
|
|
665
|
+
Sets the input field to the given threshold value.
|
|
666
|
+
get_threshold(show_warning=True):
|
|
667
|
+
Retrieves the current threshold value from the input field, returning it as a float or int.
|
|
668
|
+
If invalid, optionally displays a warning dialog.
|
|
669
|
+
|
|
670
|
+
Example
|
|
671
|
+
-------
|
|
672
|
+
>>> threshold_input = ThresholdLineEdit(init_value=5, value_type='int')
|
|
673
|
+
>>> print(threshold_input.get_threshold())
|
|
674
|
+
5
|
|
675
|
+
"""
|
|
676
|
+
|
|
677
|
+
def __init__(
|
|
678
|
+
self,
|
|
679
|
+
init_value=2.0,
|
|
680
|
+
connected_buttons=None,
|
|
681
|
+
placeholder="px > thresh are masked",
|
|
682
|
+
value_type="float",
|
|
683
|
+
*args,
|
|
684
|
+
):
|
|
685
|
+
super().__init__(*args)
|
|
686
|
+
|
|
687
|
+
self.init_value = init_value
|
|
688
|
+
self.value_type = value_type
|
|
689
|
+
self.connected_buttons = connected_buttons
|
|
690
|
+
self.setPlaceholderText(placeholder)
|
|
691
|
+
|
|
692
|
+
if self.value_type == "float":
|
|
693
|
+
self.setValidator(QDoubleValidator())
|
|
694
|
+
else:
|
|
695
|
+
self.init_value = int(self.init_value)
|
|
696
|
+
self.setValidator(QIntValidator())
|
|
697
|
+
|
|
698
|
+
if self.connected_buttons is not None:
|
|
699
|
+
self.textChanged.connect(self.enable_btn)
|
|
700
|
+
self.set_threshold(self.init_value)
|
|
701
|
+
|
|
702
|
+
def enable_btn(self):
|
|
703
|
+
"""
|
|
704
|
+
Enable or disable connected QPushButtons based on the threshold value.
|
|
705
|
+
|
|
706
|
+
If the current threshold value is valid, the connected buttons will be enabled.
|
|
707
|
+
If the value is invalid or empty, the buttons will be disabled.
|
|
708
|
+
"""
|
|
709
|
+
|
|
710
|
+
thresh = self.get_threshold(show_warning=False)
|
|
711
|
+
if isinstance(self.connected_buttons, QPushButton):
|
|
712
|
+
cbs = [self.connected_buttons]
|
|
713
|
+
else:
|
|
714
|
+
cbs = self.connected_buttons
|
|
715
|
+
|
|
716
|
+
if thresh is None:
|
|
717
|
+
for c in cbs:
|
|
718
|
+
c.setEnabled(False)
|
|
719
|
+
else:
|
|
720
|
+
for c in cbs:
|
|
721
|
+
c.setEnabled(True)
|
|
722
|
+
|
|
723
|
+
def set_threshold(self, value):
|
|
724
|
+
"""
|
|
725
|
+
Set the input field to the specified threshold value.
|
|
726
|
+
|
|
727
|
+
Parameters
|
|
728
|
+
----------
|
|
729
|
+
value : float or int
|
|
730
|
+
The value to set in the input field.
|
|
731
|
+
"""
|
|
732
|
+
|
|
733
|
+
try:
|
|
734
|
+
self.setText(str(value).replace(".", ","))
|
|
735
|
+
except:
|
|
736
|
+
print("Please provide a valid threshold value...")
|
|
737
|
+
|
|
738
|
+
def get_threshold(self, show_warning=True):
|
|
739
|
+
"""
|
|
740
|
+
Retrieve the current threshold value from the input field.
|
|
741
|
+
|
|
742
|
+
Converts the value to a float or int based on the `value_type` attribute. If the value
|
|
743
|
+
is invalid and `show_warning` is True, a warning dialog is shown.
|
|
744
|
+
|
|
745
|
+
Parameters
|
|
746
|
+
----------
|
|
747
|
+
show_warning : bool, optional
|
|
748
|
+
If True, show a warning dialog if the value is invalid (default is True).
|
|
749
|
+
|
|
750
|
+
Returns
|
|
751
|
+
-------
|
|
752
|
+
float or int or None
|
|
753
|
+
The threshold value as a float or int, or None if the value is invalid.
|
|
754
|
+
"""
|
|
755
|
+
|
|
756
|
+
try:
|
|
757
|
+
if self.value_type == "float":
|
|
758
|
+
thresh = float(self.text().replace(",", "."))
|
|
759
|
+
else:
|
|
760
|
+
thresh = int(self.text().replace(",", "."))
|
|
761
|
+
except ValueError:
|
|
762
|
+
if show_warning:
|
|
763
|
+
msgBox = QMessageBox()
|
|
764
|
+
msgBox.setWindowTitle("warning")
|
|
765
|
+
msgBox.setIcon(QMessageBox.Critical)
|
|
766
|
+
msgBox.setText("Please set a valid threshold value.")
|
|
767
|
+
msgBox.setWindowTitle("")
|
|
768
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
769
|
+
returnValue = msgBox.exec()
|
|
770
|
+
thresh = None
|
|
771
|
+
|
|
772
|
+
return thresh
|
|
1165
773
|
|
|
1166
774
|
|
|
1167
775
|
def color_from_status(status, recently_modified=False):
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
776
|
+
if not recently_modified:
|
|
777
|
+
if status == 0:
|
|
778
|
+
return "tab:blue"
|
|
779
|
+
elif status == 1:
|
|
780
|
+
return "tab:red"
|
|
781
|
+
elif status == 2:
|
|
782
|
+
return "yellow"
|
|
783
|
+
else:
|
|
784
|
+
return "k"
|
|
785
|
+
else:
|
|
786
|
+
if status == 0:
|
|
787
|
+
return "tab:cyan"
|
|
788
|
+
elif status == 1:
|
|
789
|
+
return "tab:orange"
|
|
790
|
+
elif status == 2:
|
|
791
|
+
return "tab:olive"
|
|
792
|
+
else:
|
|
793
|
+
return "k"
|
|
1186
794
|
|
|
1187
|
-
def color_from_state(state, recently_modified=False):
|
|
1188
795
|
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
796
|
+
def color_from_state(state, recently_modified=False):
|
|
797
|
+
"""
|
|
798
|
+
Generate a color map based on unique values in the provided state array.
|
|
799
|
+
|
|
800
|
+
This function creates a mapping between the unique values found in the `state` array
|
|
801
|
+
and colors from the `tab10` colormap in Matplotlib. A special condition is applied
|
|
802
|
+
to the value `99`, which is assigned the color black ('k').
|
|
803
|
+
|
|
804
|
+
Parameters
|
|
805
|
+
----------
|
|
806
|
+
state : array-like
|
|
807
|
+
An array or list of state values to be used for generating the color map.
|
|
808
|
+
|
|
809
|
+
Returns
|
|
810
|
+
-------
|
|
811
|
+
dict
|
|
812
|
+
A dictionary where the keys are the unique state values from the `state` array,
|
|
813
|
+
and the values are the corresponding colors from Matplotlib's `tab10` colormap.
|
|
814
|
+
The value `99` is mapped to the color black ('k').
|
|
815
|
+
|
|
816
|
+
Notes
|
|
817
|
+
-----
|
|
818
|
+
- Matplotlib's `tab10` colormap is used for values other than `99`.
|
|
819
|
+
"""
|
|
820
|
+
|
|
821
|
+
unique_values = np.unique(state)
|
|
822
|
+
color_map = {}
|
|
823
|
+
for value in unique_values:
|
|
824
|
+
|
|
825
|
+
if np.isnan(value):
|
|
826
|
+
value = "nan"
|
|
827
|
+
color_map[value] = "k"
|
|
828
|
+
elif value == 0:
|
|
829
|
+
color_map[value] = "tab:blue"
|
|
830
|
+
elif value == 1:
|
|
831
|
+
color_map[value] = "tab:red"
|
|
832
|
+
elif value == 99:
|
|
833
|
+
color_map[value] = "k"
|
|
834
|
+
else:
|
|
835
|
+
import matplotlib.pyplot as plt
|
|
836
|
+
|
|
837
|
+
color_map[value] = plt.cm.tab20(value / 20.0)
|
|
838
|
+
|
|
839
|
+
return color_map
|
|
1230
840
|
|
|
1231
841
|
|
|
1232
842
|
def color_from_class(cclass, recently_modified=False):
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
843
|
+
if not recently_modified:
|
|
844
|
+
if cclass == 0:
|
|
845
|
+
return "tab:red"
|
|
846
|
+
elif cclass == 1:
|
|
847
|
+
return "tab:blue"
|
|
848
|
+
elif cclass == 2:
|
|
849
|
+
return "yellow"
|
|
850
|
+
else:
|
|
851
|
+
return "k"
|
|
852
|
+
else:
|
|
853
|
+
if cclass == 0:
|
|
854
|
+
return "tab:orange"
|
|
855
|
+
elif cclass == 1:
|
|
856
|
+
return "tab:cyan"
|
|
857
|
+
elif cclass == 2:
|
|
858
|
+
return "tab:olive"
|
|
859
|
+
else:
|
|
860
|
+
return "k"
|
|
1251
861
|
|
|
1252
862
|
|
|
1253
863
|
class ChannelChoice(CelldetectiveWidget):
|
|
1254
864
|
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
865
|
+
def __init__(self, parent_window):
|
|
866
|
+
super().__init__()
|
|
867
|
+
self.parent_window = parent_window
|
|
868
|
+
# self.channel_names = channel_names
|
|
869
|
+
self.setWindowTitle("Choose target channel")
|
|
870
|
+
# Create the QComboBox and add some items
|
|
871
|
+
self.combo_box = QComboBox(self)
|
|
872
|
+
center_window(self)
|
|
873
|
+
|
|
874
|
+
channels = parent_window.channel_names
|
|
1263
875
|
|
|
1264
|
-
|
|
876
|
+
self.combo_box.addItems(channels)
|
|
1265
877
|
|
|
1266
|
-
|
|
878
|
+
self.add_btn = QPushButton("Add")
|
|
879
|
+
self.add_btn.clicked.connect(self.add_current_channel)
|
|
1267
880
|
|
|
1268
|
-
|
|
1269
|
-
|
|
881
|
+
# Create the layout
|
|
882
|
+
layout = QVBoxLayout(self)
|
|
883
|
+
layout.addWidget(self.combo_box)
|
|
884
|
+
layout.addWidget(self.add_btn)
|
|
1270
885
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
886
|
+
def add_current_channel(self):
|
|
887
|
+
filtername = self.combo_box.currentText()
|
|
888
|
+
self.parent_window.list_widget.addItems([filtername])
|
|
889
|
+
self.close()
|
|
1275
890
|
|
|
1276
|
-
def add_current_channel(self):
|
|
1277
|
-
filtername = self.combo_box.currentText()
|
|
1278
|
-
self.parent_window.list_widget.addItems([filtername])
|
|
1279
|
-
self.close()
|
|
1280
891
|
|
|
1281
892
|
def help_generic(tree):
|
|
893
|
+
"""
|
|
894
|
+
Interactively traverse a decision tree to provide user guidance based on a nested dictionary structure.
|
|
895
|
+
|
|
896
|
+
This function takes a nested dictionary representing a decision tree and guides the user through
|
|
897
|
+
it step-by-step by displaying messages for user input using the `generic_msg()` function.
|
|
898
|
+
At each step, the user selects a key that corresponds to a further step in the tree, until a
|
|
899
|
+
final suggestion (leaf node) is reached.
|
|
900
|
+
|
|
901
|
+
Parameters
|
|
902
|
+
----------
|
|
903
|
+
tree : dict
|
|
904
|
+
A dictionary where keys represent options and values represent either further steps (as dictionaries)
|
|
905
|
+
or a final suggestion (leaf nodes).
|
|
906
|
+
|
|
907
|
+
Returns
|
|
908
|
+
-------
|
|
909
|
+
any
|
|
910
|
+
The final suggestion or outcome after traversing the decision tree.
|
|
911
|
+
|
|
912
|
+
Example
|
|
913
|
+
-------
|
|
914
|
+
>>> decision_tree = {
|
|
915
|
+
... 'Start': {
|
|
916
|
+
... 'Option 1': {
|
|
917
|
+
... 'Sub-option 1': 'Final suggestion 1',
|
|
918
|
+
... 'Sub-option 2': 'Final suggestion 2'
|
|
919
|
+
... },
|
|
920
|
+
... 'Option 2': 'Final suggestion 3'
|
|
921
|
+
... }
|
|
922
|
+
... }
|
|
923
|
+
>>> result = help_generic(decision_tree)
|
|
924
|
+
# The function prompts the user to choose between "Option 1" or "Option 2",
|
|
925
|
+
# and then proceeds through the tree based on the user's choices.
|
|
926
|
+
"""
|
|
927
|
+
|
|
928
|
+
output = generic_msg(list(tree.keys())[0])
|
|
929
|
+
while output is not None:
|
|
930
|
+
tree = tree[list(tree.keys())[0]][output]
|
|
931
|
+
if isinstance(tree, dict):
|
|
932
|
+
output = generic_msg(list(tree.keys())[0])
|
|
933
|
+
else:
|
|
934
|
+
# return the final suggestion
|
|
935
|
+
output = None
|
|
936
|
+
return tree
|
|
1282
937
|
|
|
1283
|
-
"""
|
|
1284
|
-
Interactively traverse a decision tree to provide user guidance based on a nested dictionary structure.
|
|
1285
|
-
|
|
1286
|
-
This function takes a nested dictionary representing a decision tree and guides the user through
|
|
1287
|
-
it step-by-step by displaying messages for user input using the `generic_msg()` function.
|
|
1288
|
-
At each step, the user selects a key that corresponds to a further step in the tree, until a
|
|
1289
|
-
final suggestion (leaf node) is reached.
|
|
1290
|
-
|
|
1291
|
-
Parameters
|
|
1292
|
-
----------
|
|
1293
|
-
tree : dict
|
|
1294
|
-
A dictionary where keys represent options and values represent either further steps (as dictionaries)
|
|
1295
|
-
or a final suggestion (leaf nodes).
|
|
1296
|
-
|
|
1297
|
-
Returns
|
|
1298
|
-
-------
|
|
1299
|
-
any
|
|
1300
|
-
The final suggestion or outcome after traversing the decision tree.
|
|
1301
|
-
|
|
1302
|
-
Example
|
|
1303
|
-
-------
|
|
1304
|
-
>>> decision_tree = {
|
|
1305
|
-
... 'Start': {
|
|
1306
|
-
... 'Option 1': {
|
|
1307
|
-
... 'Sub-option 1': 'Final suggestion 1',
|
|
1308
|
-
... 'Sub-option 2': 'Final suggestion 2'
|
|
1309
|
-
... },
|
|
1310
|
-
... 'Option 2': 'Final suggestion 3'
|
|
1311
|
-
... }
|
|
1312
|
-
... }
|
|
1313
|
-
>>> result = help_generic(decision_tree)
|
|
1314
|
-
# The function prompts the user to choose between "Option 1" or "Option 2",
|
|
1315
|
-
# and then proceeds through the tree based on the user's choices.
|
|
1316
|
-
"""
|
|
1317
|
-
|
|
1318
|
-
output = generic_msg(list(tree.keys())[0])
|
|
1319
|
-
while output is not None:
|
|
1320
|
-
tree = tree[list(tree.keys())[0]][output]
|
|
1321
|
-
if isinstance(tree,dict):
|
|
1322
|
-
output = generic_msg(list(tree.keys())[0])
|
|
1323
|
-
else:
|
|
1324
|
-
# return the final suggestion
|
|
1325
|
-
output = None
|
|
1326
|
-
return tree
|
|
1327
938
|
|
|
1328
939
|
def generic_msg(text):
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
return None
|
|
940
|
+
"""
|
|
941
|
+
Display a message box with a question and capture the user's response.
|
|
942
|
+
|
|
943
|
+
This function creates a message box with a `Yes`, `No`, and `Cancel` option,
|
|
944
|
+
displaying the provided `text` as the question. It returns the user's selection as a string.
|
|
945
|
+
|
|
946
|
+
Parameters
|
|
947
|
+
----------
|
|
948
|
+
text : str
|
|
949
|
+
The message or question to display in the message box.
|
|
950
|
+
|
|
951
|
+
Returns
|
|
952
|
+
-------
|
|
953
|
+
str or None
|
|
954
|
+
The user's response: "yes" if Yes is selected, "no" if No is selected,
|
|
955
|
+
and `None` if Cancel is selected or the dialog is closed.
|
|
956
|
+
|
|
957
|
+
Example
|
|
958
|
+
-------
|
|
959
|
+
>>> response = generic_msg("Would you like to continue?")
|
|
960
|
+
>>> if response == "yes":
|
|
961
|
+
... print("User chose Yes")
|
|
962
|
+
... elif response == "no":
|
|
963
|
+
... print("User chose No")
|
|
964
|
+
... else:
|
|
965
|
+
... print("User cancelled the action")
|
|
966
|
+
|
|
967
|
+
Notes
|
|
968
|
+
-----
|
|
969
|
+
- The message box displays a window with three options: Yes, No, and Cancel.
|
|
970
|
+
"""
|
|
971
|
+
|
|
972
|
+
msgBox = QMessageBox()
|
|
973
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
974
|
+
msgBox.setText(text)
|
|
975
|
+
msgBox.setWindowTitle("Question")
|
|
976
|
+
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
|
|
977
|
+
returnValue = msgBox.exec()
|
|
978
|
+
if returnValue == QMessageBox.Yes:
|
|
979
|
+
return "yes"
|
|
980
|
+
elif returnValue == QMessageBox.No:
|
|
981
|
+
return "no"
|
|
982
|
+
else:
|
|
983
|
+
return None
|