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.
Files changed (151) hide show
  1. celldetective/__init__.py +25 -0
  2. celldetective/__main__.py +62 -43
  3. celldetective/_version.py +1 -1
  4. celldetective/extra_properties.py +477 -399
  5. celldetective/filters.py +192 -97
  6. celldetective/gui/InitWindow.py +541 -411
  7. celldetective/gui/__init__.py +0 -15
  8. celldetective/gui/about.py +44 -39
  9. celldetective/gui/analyze_block.py +120 -84
  10. celldetective/gui/base/__init__.py +0 -0
  11. celldetective/gui/base/channel_norm_generator.py +335 -0
  12. celldetective/gui/base/components.py +249 -0
  13. celldetective/gui/base/feature_choice.py +92 -0
  14. celldetective/gui/base/figure_canvas.py +52 -0
  15. celldetective/gui/base/list_widget.py +133 -0
  16. celldetective/gui/{styles.py → base/styles.py} +92 -36
  17. celldetective/gui/base/utils.py +33 -0
  18. celldetective/gui/base_annotator.py +900 -767
  19. celldetective/gui/classifier_widget.py +6 -22
  20. celldetective/gui/configure_new_exp.py +777 -671
  21. celldetective/gui/control_panel.py +635 -524
  22. celldetective/gui/dynamic_progress.py +449 -0
  23. celldetective/gui/event_annotator.py +2023 -1662
  24. celldetective/gui/generic_signal_plot.py +1292 -944
  25. celldetective/gui/gui_utils.py +899 -1289
  26. celldetective/gui/interactions_block.py +658 -0
  27. celldetective/gui/interactive_timeseries_viewer.py +447 -0
  28. celldetective/gui/json_readers.py +48 -15
  29. celldetective/gui/layouts/__init__.py +5 -0
  30. celldetective/gui/layouts/background_model_free_layout.py +537 -0
  31. celldetective/gui/layouts/channel_offset_layout.py +134 -0
  32. celldetective/gui/layouts/local_correction_layout.py +91 -0
  33. celldetective/gui/layouts/model_fit_layout.py +372 -0
  34. celldetective/gui/layouts/operation_layout.py +68 -0
  35. celldetective/gui/layouts/protocol_designer_layout.py +96 -0
  36. celldetective/gui/pair_event_annotator.py +3130 -2435
  37. celldetective/gui/plot_measurements.py +586 -267
  38. celldetective/gui/plot_signals_ui.py +724 -506
  39. celldetective/gui/preprocessing_block.py +395 -0
  40. celldetective/gui/process_block.py +1678 -1831
  41. celldetective/gui/seg_model_loader.py +580 -473
  42. celldetective/gui/settings/__init__.py +0 -7
  43. celldetective/gui/settings/_cellpose_model_params.py +181 -0
  44. celldetective/gui/settings/_event_detection_model_params.py +95 -0
  45. celldetective/gui/settings/_segmentation_model_params.py +159 -0
  46. celldetective/gui/settings/_settings_base.py +77 -65
  47. celldetective/gui/settings/_settings_event_model_training.py +752 -526
  48. celldetective/gui/settings/_settings_measurements.py +1133 -964
  49. celldetective/gui/settings/_settings_neighborhood.py +574 -488
  50. celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
  51. celldetective/gui/settings/_settings_signal_annotator.py +329 -305
  52. celldetective/gui/settings/_settings_tracking.py +1304 -1094
  53. celldetective/gui/settings/_stardist_model_params.py +98 -0
  54. celldetective/gui/survival_ui.py +422 -312
  55. celldetective/gui/tableUI.py +1665 -1701
  56. celldetective/gui/table_ops/_maths.py +295 -0
  57. celldetective/gui/table_ops/_merge_groups.py +140 -0
  58. celldetective/gui/table_ops/_merge_one_hot.py +95 -0
  59. celldetective/gui/table_ops/_query_table.py +43 -0
  60. celldetective/gui/table_ops/_rename_col.py +44 -0
  61. celldetective/gui/thresholds_gui.py +382 -179
  62. celldetective/gui/viewers/__init__.py +0 -0
  63. celldetective/gui/viewers/base_viewer.py +700 -0
  64. celldetective/gui/viewers/channel_offset_viewer.py +331 -0
  65. celldetective/gui/viewers/contour_viewer.py +394 -0
  66. celldetective/gui/viewers/size_viewer.py +153 -0
  67. celldetective/gui/viewers/spot_detection_viewer.py +341 -0
  68. celldetective/gui/viewers/threshold_viewer.py +309 -0
  69. celldetective/gui/workers.py +304 -126
  70. celldetective/log_manager.py +92 -0
  71. celldetective/measure.py +1895 -1478
  72. celldetective/napari/__init__.py +0 -0
  73. celldetective/napari/utils.py +1025 -0
  74. celldetective/neighborhood.py +1914 -1448
  75. celldetective/preprocessing.py +1620 -1220
  76. celldetective/processes/__init__.py +0 -0
  77. celldetective/processes/background_correction.py +271 -0
  78. celldetective/processes/compute_neighborhood.py +894 -0
  79. celldetective/processes/detect_events.py +246 -0
  80. celldetective/processes/measure_cells.py +565 -0
  81. celldetective/processes/segment_cells.py +760 -0
  82. celldetective/processes/track_cells.py +435 -0
  83. celldetective/processes/train_segmentation_model.py +694 -0
  84. celldetective/processes/train_signal_model.py +265 -0
  85. celldetective/processes/unified_process.py +292 -0
  86. celldetective/regionprops/_regionprops.py +358 -317
  87. celldetective/relative_measurements.py +987 -710
  88. celldetective/scripts/measure_cells.py +313 -212
  89. celldetective/scripts/measure_relative.py +90 -46
  90. celldetective/scripts/segment_cells.py +165 -104
  91. celldetective/scripts/segment_cells_thresholds.py +96 -68
  92. celldetective/scripts/track_cells.py +198 -149
  93. celldetective/scripts/train_segmentation_model.py +324 -201
  94. celldetective/scripts/train_signal_model.py +87 -45
  95. celldetective/segmentation.py +844 -749
  96. celldetective/signals.py +3514 -2861
  97. celldetective/tracking.py +30 -15
  98. celldetective/utils/__init__.py +0 -0
  99. celldetective/utils/cellpose_utils/__init__.py +133 -0
  100. celldetective/utils/color_mappings.py +42 -0
  101. celldetective/utils/data_cleaning.py +630 -0
  102. celldetective/utils/data_loaders.py +450 -0
  103. celldetective/utils/dataset_helpers.py +207 -0
  104. celldetective/utils/downloaders.py +197 -0
  105. celldetective/utils/event_detection/__init__.py +8 -0
  106. celldetective/utils/experiment.py +1782 -0
  107. celldetective/utils/image_augmenters.py +308 -0
  108. celldetective/utils/image_cleaning.py +74 -0
  109. celldetective/utils/image_loaders.py +926 -0
  110. celldetective/utils/image_transforms.py +335 -0
  111. celldetective/utils/io.py +62 -0
  112. celldetective/utils/mask_cleaning.py +348 -0
  113. celldetective/utils/mask_transforms.py +5 -0
  114. celldetective/utils/masks.py +184 -0
  115. celldetective/utils/maths.py +351 -0
  116. celldetective/utils/model_getters.py +325 -0
  117. celldetective/utils/model_loaders.py +296 -0
  118. celldetective/utils/normalization.py +380 -0
  119. celldetective/utils/parsing.py +465 -0
  120. celldetective/utils/plots/__init__.py +0 -0
  121. celldetective/utils/plots/regression.py +53 -0
  122. celldetective/utils/resources.py +34 -0
  123. celldetective/utils/stardist_utils/__init__.py +104 -0
  124. celldetective/utils/stats.py +90 -0
  125. celldetective/utils/types.py +21 -0
  126. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
  127. celldetective-1.5.0b0.dist-info/RECORD +187 -0
  128. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
  129. tests/gui/test_new_project.py +129 -117
  130. tests/gui/test_project.py +127 -79
  131. tests/test_filters.py +39 -15
  132. tests/test_notebooks.py +8 -0
  133. tests/test_tracking.py +232 -13
  134. tests/test_utils.py +123 -77
  135. celldetective/gui/base_components.py +0 -23
  136. celldetective/gui/layouts.py +0 -1602
  137. celldetective/gui/processes/compute_neighborhood.py +0 -594
  138. celldetective/gui/processes/measure_cells.py +0 -360
  139. celldetective/gui/processes/segment_cells.py +0 -499
  140. celldetective/gui/processes/track_cells.py +0 -303
  141. celldetective/gui/processes/train_segmentation_model.py +0 -270
  142. celldetective/gui/processes/train_signal_model.py +0 -108
  143. celldetective/gui/table_ops/merge_groups.py +0 -118
  144. celldetective/gui/viewers.py +0 -1354
  145. celldetective/io.py +0 -3663
  146. celldetective/utils.py +0 -3108
  147. celldetective-1.4.2.dist-info/RECORD +0 -123
  148. /celldetective/{gui/processes → processes}/downloader.py +0 -0
  149. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.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]