celldetective 1.4.2__py3-none-any.whl → 1.5.0b1__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 +403 -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/downloader.py +137 -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 +235 -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.0b1.dist-info}/METADATA +1 -1
- celldetective-1.5.0b1.dist-info/RECORD +187 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.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/downloader.py +0 -111
- 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-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from PyQt5.QtGui import QStandardItemModel, QPalette, QFontMetrics
|
|
3
|
+
from PyQt5.QtWidgets import (
|
|
4
|
+
QMainWindow,
|
|
5
|
+
QWidget,
|
|
6
|
+
QDialog,
|
|
7
|
+
QMessageBox,
|
|
8
|
+
QComboBox,
|
|
9
|
+
QToolButton,
|
|
10
|
+
QMenu,
|
|
11
|
+
QStylePainter,
|
|
12
|
+
QStyleOptionComboBox,
|
|
13
|
+
QStyle,
|
|
14
|
+
QFrame,
|
|
15
|
+
QSizePolicy,
|
|
16
|
+
QProgressDialog,
|
|
17
|
+
)
|
|
18
|
+
from PyQt5.QtCore import Qt, pyqtSignal, QEvent
|
|
19
|
+
from celldetective.gui.base.styles import Styles
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CelldetectiveWidget(QWidget, Styles):
|
|
23
|
+
def __init__(self, *args, **kwargs):
|
|
24
|
+
super().__init__(*args, **kwargs)
|
|
25
|
+
self.setWindowIcon(self.celldetective_icon)
|
|
26
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CelldetectiveMainWindow(QMainWindow, Styles):
|
|
30
|
+
def __init__(self, *args, **kwargs):
|
|
31
|
+
super().__init__(*args, **kwargs)
|
|
32
|
+
self.setWindowIcon(self.celldetective_icon)
|
|
33
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class CelldetectiveDialog(QDialog, Styles):
|
|
37
|
+
def __init__(self, *args, **kwargs):
|
|
38
|
+
super().__init__(*args, **kwargs)
|
|
39
|
+
self.setWindowIcon(self.celldetective_icon)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class CelldetectiveProgressDialog(QProgressDialog, Styles):
|
|
43
|
+
def __init__(
|
|
44
|
+
self,
|
|
45
|
+
label_text,
|
|
46
|
+
cancel_button_text="Cancel",
|
|
47
|
+
minimum=0,
|
|
48
|
+
maximum=100,
|
|
49
|
+
parent=None,
|
|
50
|
+
window_title="Please wait",
|
|
51
|
+
):
|
|
52
|
+
super().__init__(label_text, cancel_button_text, minimum, maximum, parent)
|
|
53
|
+
self.setWindowIcon(self.celldetective_icon)
|
|
54
|
+
self.setWindowTitle(window_title)
|
|
55
|
+
self.setWindowModality(Qt.WindowModal)
|
|
56
|
+
self.setWindowFlags(
|
|
57
|
+
self.windowFlags()
|
|
58
|
+
& ~Qt.WindowContextHelpButtonHint
|
|
59
|
+
& ~Qt.WindowCloseButtonHint
|
|
60
|
+
)
|
|
61
|
+
self.setMinimumDuration(0)
|
|
62
|
+
self.setValue(0)
|
|
63
|
+
|
|
64
|
+
fm = QFontMetrics(self.font())
|
|
65
|
+
width = max(350, fm.horizontalAdvance(window_title) + 120)
|
|
66
|
+
self.setMinimumWidth(width)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def generic_message(message, msg_type="warning"):
|
|
70
|
+
|
|
71
|
+
print(message)
|
|
72
|
+
message_box = QMessageBox()
|
|
73
|
+
if msg_type == "warning":
|
|
74
|
+
message_box.setIcon(QMessageBox.Warning)
|
|
75
|
+
elif msg_type == "info":
|
|
76
|
+
message_box.setIcon(QMessageBox.Information)
|
|
77
|
+
elif msg_type == "critical":
|
|
78
|
+
message_box.setIcon(QMessageBox.Critical)
|
|
79
|
+
message_box.setText(message)
|
|
80
|
+
message_box.setWindowTitle(msg_type)
|
|
81
|
+
message_box.setStandardButtons(QMessageBox.Ok)
|
|
82
|
+
_ = message_box.exec()
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class QCheckableComboBox(QComboBox):
|
|
86
|
+
"""
|
|
87
|
+
adapted from https://stackoverflow.com/questions/22775095/pyqt-how-to-set-combobox-items-be-checkable
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
activated = pyqtSignal(str)
|
|
91
|
+
|
|
92
|
+
def __init__(self, obj="", parent_window=None, *args, **kwargs):
|
|
93
|
+
|
|
94
|
+
super().__init__(parent_window, *args, **kwargs)
|
|
95
|
+
|
|
96
|
+
self.setTitle("")
|
|
97
|
+
self.setModel(QStandardItemModel(self))
|
|
98
|
+
self.obj = obj
|
|
99
|
+
self.toolButton = QToolButton(parent_window)
|
|
100
|
+
self.toolButton.setText("")
|
|
101
|
+
self.toolMenu = QMenu(parent_window)
|
|
102
|
+
self.toolButton.setMenu(self.toolMenu)
|
|
103
|
+
self.toolButton.setPopupMode(QToolButton.InstantPopup)
|
|
104
|
+
self.anySelected = False
|
|
105
|
+
|
|
106
|
+
self.view().viewport().installEventFilter(self)
|
|
107
|
+
self.view().pressed.connect(self.handleItemPressed)
|
|
108
|
+
|
|
109
|
+
def clear(self):
|
|
110
|
+
|
|
111
|
+
self.unselectAll()
|
|
112
|
+
self.toolMenu.clear()
|
|
113
|
+
super().clear()
|
|
114
|
+
|
|
115
|
+
def handleItemPressed(self, index):
|
|
116
|
+
|
|
117
|
+
idx = index.row()
|
|
118
|
+
actions = self.toolMenu.actions()
|
|
119
|
+
|
|
120
|
+
item = self.model().itemFromIndex(index)
|
|
121
|
+
if item.checkState() == Qt.Checked:
|
|
122
|
+
item.setCheckState(Qt.Unchecked)
|
|
123
|
+
actions[idx].setChecked(False)
|
|
124
|
+
else:
|
|
125
|
+
item.setCheckState(Qt.Checked)
|
|
126
|
+
actions[idx].setChecked(True)
|
|
127
|
+
self.anySelected = True
|
|
128
|
+
|
|
129
|
+
options_checked = np.array([a.isChecked() for a in actions])
|
|
130
|
+
if len(options_checked[options_checked]) > 1:
|
|
131
|
+
self.setTitle(f'Multiple {self.obj+"s"} selected...')
|
|
132
|
+
elif len(options_checked[options_checked]) == 1:
|
|
133
|
+
idx_selected = np.where(options_checked)[0][0]
|
|
134
|
+
if idx_selected != idx:
|
|
135
|
+
item = self.model().item(idx_selected)
|
|
136
|
+
self.setTitle(item.text())
|
|
137
|
+
elif len(options_checked[options_checked]) == 0:
|
|
138
|
+
self.setTitle(f"No {self.obj} selected...")
|
|
139
|
+
self.anySelected = False
|
|
140
|
+
|
|
141
|
+
self.activated.emit(self.title())
|
|
142
|
+
|
|
143
|
+
def setCurrentIndex(self, index):
|
|
144
|
+
|
|
145
|
+
super().setCurrentIndex(index)
|
|
146
|
+
|
|
147
|
+
item = self.model().item(index)
|
|
148
|
+
modelIndex = self.model().indexFromItem(item)
|
|
149
|
+
|
|
150
|
+
self.handleItemPressed(modelIndex)
|
|
151
|
+
|
|
152
|
+
def selectAll(self):
|
|
153
|
+
|
|
154
|
+
actions = self.toolMenu.actions()
|
|
155
|
+
for i, a in enumerate(actions):
|
|
156
|
+
if not a.isChecked():
|
|
157
|
+
self.setCurrentIndex(i)
|
|
158
|
+
self.anySelected = True
|
|
159
|
+
|
|
160
|
+
def unselectAll(self):
|
|
161
|
+
|
|
162
|
+
actions = self.toolMenu.actions()
|
|
163
|
+
for i, a in enumerate(actions):
|
|
164
|
+
if a.isChecked():
|
|
165
|
+
self.setCurrentIndex(i)
|
|
166
|
+
self.anySelected = False
|
|
167
|
+
|
|
168
|
+
def title(self):
|
|
169
|
+
return self._title
|
|
170
|
+
|
|
171
|
+
def setTitle(self, title):
|
|
172
|
+
self._title = title
|
|
173
|
+
self.update()
|
|
174
|
+
self.repaint()
|
|
175
|
+
|
|
176
|
+
def paintEvent(self, event):
|
|
177
|
+
|
|
178
|
+
painter = QStylePainter(self)
|
|
179
|
+
painter.setPen(self.palette().color(QPalette.Text))
|
|
180
|
+
opt = QStyleOptionComboBox()
|
|
181
|
+
self.initStyleOption(opt)
|
|
182
|
+
opt.currentText = self._title
|
|
183
|
+
painter.drawComplexControl(QStyle.CC_ComboBox, opt)
|
|
184
|
+
painter.drawControl(QStyle.CE_ComboBoxLabel, opt)
|
|
185
|
+
|
|
186
|
+
def addItem(self, item, tooltip=None):
|
|
187
|
+
|
|
188
|
+
super().addItem(item)
|
|
189
|
+
idx = self.findText(item)
|
|
190
|
+
if tooltip is not None:
|
|
191
|
+
self.setItemData(idx, tooltip, Qt.ToolTipRole)
|
|
192
|
+
item2 = self.model().item(idx, 0)
|
|
193
|
+
item2.setCheckState(Qt.Unchecked)
|
|
194
|
+
action = self.toolMenu.addAction(item)
|
|
195
|
+
action.setCheckable(True)
|
|
196
|
+
|
|
197
|
+
def addItems(self, items):
|
|
198
|
+
|
|
199
|
+
super().addItems(items)
|
|
200
|
+
|
|
201
|
+
for item in items:
|
|
202
|
+
|
|
203
|
+
idx = self.findText(item)
|
|
204
|
+
item2 = self.model().item(idx, 0)
|
|
205
|
+
item2.setCheckState(Qt.Unchecked)
|
|
206
|
+
action = self.toolMenu.addAction(item)
|
|
207
|
+
action.setCheckable(True)
|
|
208
|
+
|
|
209
|
+
def getSelectedIndices(self):
|
|
210
|
+
|
|
211
|
+
actions = self.toolMenu.actions()
|
|
212
|
+
options_checked = np.array([a.isChecked() for a in actions])
|
|
213
|
+
idx_selected = np.where(options_checked)[0]
|
|
214
|
+
|
|
215
|
+
return list(idx_selected)
|
|
216
|
+
|
|
217
|
+
def currentText(self):
|
|
218
|
+
return self.title()
|
|
219
|
+
|
|
220
|
+
def isMultipleSelection(self):
|
|
221
|
+
return self.currentText().startswith("Multiple")
|
|
222
|
+
|
|
223
|
+
def isSingleSelection(self):
|
|
224
|
+
return not self.currentText().startswith(
|
|
225
|
+
"Multiple"
|
|
226
|
+
) and not self.title().startswith("No")
|
|
227
|
+
|
|
228
|
+
def isAnySelected(self):
|
|
229
|
+
return not self.title().startswith("No")
|
|
230
|
+
|
|
231
|
+
def eventFilter(self, source, event):
|
|
232
|
+
if source is self.view().viewport():
|
|
233
|
+
if event.type() == QEvent.MouseButtonRelease:
|
|
234
|
+
return True # Prevent the popup from closing
|
|
235
|
+
return super().eventFilter(source, event)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class QHSeperationLine(QFrame):
|
|
239
|
+
"""
|
|
240
|
+
a horizontal seperation line\n
|
|
241
|
+
"""
|
|
242
|
+
|
|
243
|
+
def __init__(self):
|
|
244
|
+
super().__init__()
|
|
245
|
+
self.setMinimumWidth(1)
|
|
246
|
+
self.setFixedHeight(20)
|
|
247
|
+
self.setFrameShape(QFrame.HLine)
|
|
248
|
+
self.setFrameShadow(QFrame.Sunken)
|
|
249
|
+
self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Minimum)
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import ast
|
|
2
|
+
import os
|
|
3
|
+
from celldetective import get_software_location
|
|
4
|
+
from PyQt5.QtWidgets import QComboBox, QPushButton, QVBoxLayout
|
|
5
|
+
|
|
6
|
+
from celldetective.gui.base.components import CelldetectiveWidget
|
|
7
|
+
from celldetective.gui.base.utils import center_window
|
|
8
|
+
|
|
9
|
+
CACHED_EXTRA_PROPERTIES = None
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_extra_properties_functions():
|
|
13
|
+
"""
|
|
14
|
+
Parses celldetective/extra_properties.py using AST to find function definitions
|
|
15
|
+
without importing the module (which would trigger heavy dependencies).
|
|
16
|
+
"""
|
|
17
|
+
try:
|
|
18
|
+
software_path = get_software_location()
|
|
19
|
+
extra_props_path = os.path.join(
|
|
20
|
+
software_path, "celldetective", "extra_properties.py"
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
if not os.path.exists(extra_props_path):
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
with open(extra_props_path, "r", encoding="utf-8") as f:
|
|
27
|
+
tree = ast.parse(f.read())
|
|
28
|
+
|
|
29
|
+
functions = []
|
|
30
|
+
for node in tree.body:
|
|
31
|
+
if isinstance(node, ast.FunctionDef):
|
|
32
|
+
functions.append(node.name)
|
|
33
|
+
|
|
34
|
+
return functions
|
|
35
|
+
except Exception as e:
|
|
36
|
+
print(f"Failed to parse extra_properties.py: {e}")
|
|
37
|
+
return []
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class FeatureChoice(CelldetectiveWidget):
|
|
41
|
+
|
|
42
|
+
def __init__(self, parent_window):
|
|
43
|
+
super().__init__()
|
|
44
|
+
self.parent_window = parent_window
|
|
45
|
+
self.setWindowTitle("Add feature")
|
|
46
|
+
# Create the QComboBox and add some items
|
|
47
|
+
self.combo_box = QComboBox(self)
|
|
48
|
+
|
|
49
|
+
standard_measurements = [
|
|
50
|
+
"area",
|
|
51
|
+
"area_bbox",
|
|
52
|
+
"area_convex",
|
|
53
|
+
"area_filled",
|
|
54
|
+
"major_axis_length",
|
|
55
|
+
"minor_axis_length",
|
|
56
|
+
"eccentricity",
|
|
57
|
+
"equivalent_diameter_area",
|
|
58
|
+
"euler_number",
|
|
59
|
+
"extent",
|
|
60
|
+
"feret_diameter_max",
|
|
61
|
+
"orientation",
|
|
62
|
+
"perimeter",
|
|
63
|
+
"perimeter_crofton",
|
|
64
|
+
"solidity",
|
|
65
|
+
"intensity_mean",
|
|
66
|
+
"intensity_max",
|
|
67
|
+
"intensity_min",
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
global CACHED_EXTRA_PROPERTIES
|
|
71
|
+
if CACHED_EXTRA_PROPERTIES is None:
|
|
72
|
+
CACHED_EXTRA_PROPERTIES = get_extra_properties_functions()
|
|
73
|
+
|
|
74
|
+
if CACHED_EXTRA_PROPERTIES:
|
|
75
|
+
standard_measurements.extend(CACHED_EXTRA_PROPERTIES)
|
|
76
|
+
|
|
77
|
+
self.combo_box.addItems(standard_measurements)
|
|
78
|
+
|
|
79
|
+
self.add_btn = QPushButton("Add")
|
|
80
|
+
self.add_btn.setStyleSheet(self.button_style_sheet)
|
|
81
|
+
self.add_btn.clicked.connect(self.add_current_feature)
|
|
82
|
+
|
|
83
|
+
# Create the layout
|
|
84
|
+
layout = QVBoxLayout(self)
|
|
85
|
+
layout.addWidget(self.combo_box)
|
|
86
|
+
layout.addWidget(self.add_btn)
|
|
87
|
+
center_window(self)
|
|
88
|
+
|
|
89
|
+
def add_current_feature(self):
|
|
90
|
+
filtername = self.combo_box.currentText()
|
|
91
|
+
self.parent_window.list_widget.addItems([filtername])
|
|
92
|
+
self.close()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from PyQt5.QtCore import Qt
|
|
2
|
+
from PyQt5.QtWidgets import QVBoxLayout
|
|
3
|
+
|
|
4
|
+
from celldetective.gui.base.components import CelldetectiveWidget
|
|
5
|
+
from celldetective.gui.base.utils import center_window
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FigureCanvas(CelldetectiveWidget):
|
|
9
|
+
"""
|
|
10
|
+
Generic figure canvas.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
def __init__(self, fig, title="", interactive=True):
|
|
14
|
+
super().__init__()
|
|
15
|
+
self.fig = fig
|
|
16
|
+
self.setWindowTitle(title)
|
|
17
|
+
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg
|
|
18
|
+
|
|
19
|
+
self.canvas = FigureCanvasQTAgg(self.fig)
|
|
20
|
+
self.canvas.setStyleSheet("background-color: transparent;")
|
|
21
|
+
if interactive:
|
|
22
|
+
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT
|
|
23
|
+
|
|
24
|
+
self.toolbar = NavigationToolbar2QT(self.canvas)
|
|
25
|
+
self.layout = QVBoxLayout(self)
|
|
26
|
+
self.layout.addWidget(self.canvas, 90)
|
|
27
|
+
if interactive:
|
|
28
|
+
self.layout.addWidget(self.toolbar)
|
|
29
|
+
|
|
30
|
+
#center_window(self)
|
|
31
|
+
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
32
|
+
|
|
33
|
+
def resizeEvent(self, event):
|
|
34
|
+
|
|
35
|
+
super().resizeEvent(event)
|
|
36
|
+
try:
|
|
37
|
+
self.fig.tight_layout()
|
|
38
|
+
except:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def draw(self):
|
|
42
|
+
self.canvas.draw()
|
|
43
|
+
|
|
44
|
+
def closeEvent(self, event):
|
|
45
|
+
"""Delete figure on closing window."""
|
|
46
|
+
# self.canvas.ax.cla() # ****
|
|
47
|
+
# self.canvas.ax.cla() # ****
|
|
48
|
+
self.fig.clf() # ****
|
|
49
|
+
import matplotlib.pyplot as plt
|
|
50
|
+
|
|
51
|
+
plt.close(self.fig)
|
|
52
|
+
super(FigureCanvas, self).closeEvent(event)
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
from PyQt5.QtCore import QTimer
|
|
2
|
+
from PyQt5.QtWidgets import QListWidget, QVBoxLayout
|
|
3
|
+
|
|
4
|
+
from celldetective.gui.base.components import CelldetectiveWidget
|
|
5
|
+
from celldetective.gui.base.utils import center_window
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class ListWidget(CelldetectiveWidget):
|
|
9
|
+
"""
|
|
10
|
+
A customizable widget for displaying and managing a list of items, with the
|
|
11
|
+
ability to add and remove items interactively.
|
|
12
|
+
|
|
13
|
+
This widget is built around a `QListWidget` and allows for initialization with
|
|
14
|
+
a set of features. It also provides options to retrieve the items, add new items
|
|
15
|
+
using a custom widget, and remove selected items. The items can be parsed and
|
|
16
|
+
returned as a list, with support for various data types and formatted input (e.g.,
|
|
17
|
+
ranges specified with a dash).
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
choiceWidget : QWidget
|
|
22
|
+
A custom widget that is used to add new items to the list.
|
|
23
|
+
initial_features : list
|
|
24
|
+
A list of initial items to populate the list widget.
|
|
25
|
+
dtype : type, optional
|
|
26
|
+
The data type to cast the list items to. Default is `str`.
|
|
27
|
+
|
|
28
|
+
Attributes
|
|
29
|
+
----------
|
|
30
|
+
initial_features : list
|
|
31
|
+
The initial set of features or items displayed in the list.
|
|
32
|
+
choiceWidget : QWidget
|
|
33
|
+
The widget used to prompt the user to add new items.
|
|
34
|
+
dtype : type
|
|
35
|
+
The data type to convert items into when retrieved from the list.
|
|
36
|
+
items : list
|
|
37
|
+
A list to store the current items in the list widget.
|
|
38
|
+
list_widget : QListWidget
|
|
39
|
+
The core Qt widget that displays the list of items.
|
|
40
|
+
|
|
41
|
+
Methods
|
|
42
|
+
-------
|
|
43
|
+
addItem()
|
|
44
|
+
Opens a new window to add an item to the list using the custom `choiceWidget`.
|
|
45
|
+
getItems()
|
|
46
|
+
Retrieves the items from the list widget, parsing ranges (e.g., 'min-max')
|
|
47
|
+
into two values, and converts them to the specified `dtype`.
|
|
48
|
+
removeSel()
|
|
49
|
+
Removes the currently selected item(s) from the list widget and updates the
|
|
50
|
+
internal `items` list accordingly.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(self, choiceWidget, initial_features, dtype=str, *args, **kwargs):
|
|
54
|
+
|
|
55
|
+
super().__init__()
|
|
56
|
+
self.initial_features = initial_features
|
|
57
|
+
self.choiceWidget = choiceWidget
|
|
58
|
+
self.dtype = dtype
|
|
59
|
+
self.items = []
|
|
60
|
+
|
|
61
|
+
self.setFixedHeight(80)
|
|
62
|
+
|
|
63
|
+
# Initialize list widget
|
|
64
|
+
self.list_widget = QListWidget()
|
|
65
|
+
self.list_widget.addItems(initial_features)
|
|
66
|
+
|
|
67
|
+
# Set up layout
|
|
68
|
+
main_layout = QVBoxLayout()
|
|
69
|
+
main_layout.addWidget(self.list_widget)
|
|
70
|
+
self.setLayout(main_layout)
|
|
71
|
+
center_window(self)
|
|
72
|
+
|
|
73
|
+
def addItem(self):
|
|
74
|
+
"""
|
|
75
|
+
Opens the custom choiceWidget to add a new item to the list.
|
|
76
|
+
"""
|
|
77
|
+
|
|
78
|
+
self.addItemWindow = self.choiceWidget(self)
|
|
79
|
+
self.addItemWindow.show()
|
|
80
|
+
try:
|
|
81
|
+
QTimer.singleShot(10, lambda: center_window(self.addItemWindow))
|
|
82
|
+
except Exception as e:
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
def addItemToList(self, item):
|
|
86
|
+
self.list_widget.addItems([item])
|
|
87
|
+
|
|
88
|
+
def getItems(self):
|
|
89
|
+
"""
|
|
90
|
+
Retrieves and returns the items from the list widget.
|
|
91
|
+
|
|
92
|
+
This method parses any items that contain a range (formatted as 'min-max')
|
|
93
|
+
into a list of two values, and casts all items to the specified `dtype`.
|
|
94
|
+
|
|
95
|
+
Returns
|
|
96
|
+
-------
|
|
97
|
+
list
|
|
98
|
+
A list of the items in the list widget, with ranges split into two values.
|
|
99
|
+
"""
|
|
100
|
+
|
|
101
|
+
items = []
|
|
102
|
+
for x in range(self.list_widget.count()):
|
|
103
|
+
if len(self.list_widget.item(x).text().split("-")) == 2:
|
|
104
|
+
if self.list_widget.item(x).text()[0] == "-":
|
|
105
|
+
items.append(self.dtype(self.list_widget.item(x).text()))
|
|
106
|
+
else:
|
|
107
|
+
minn, maxx = self.list_widget.item(x).text().split("-")
|
|
108
|
+
to_add = [self.dtype(minn), self.dtype(maxx)]
|
|
109
|
+
items.append(to_add)
|
|
110
|
+
else:
|
|
111
|
+
items.append(self.dtype(self.list_widget.item(x).text()))
|
|
112
|
+
return items
|
|
113
|
+
|
|
114
|
+
def clear(self):
|
|
115
|
+
self.items = []
|
|
116
|
+
self.list_widget.clear()
|
|
117
|
+
|
|
118
|
+
def removeSel(self):
|
|
119
|
+
"""
|
|
120
|
+
Removes the selected item(s) from the list widget.
|
|
121
|
+
|
|
122
|
+
If there are any selected items, they are removed both from the visual list
|
|
123
|
+
and the internal `items` list that tracks the current state of the widget.
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
listItems = self.list_widget.selectedItems()
|
|
127
|
+
if not listItems:
|
|
128
|
+
return
|
|
129
|
+
for item in listItems:
|
|
130
|
+
idx = self.list_widget.row(item)
|
|
131
|
+
self.list_widget.takeItem(idx)
|
|
132
|
+
if self.items:
|
|
133
|
+
del self.items[idx]
|