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,91 @@
1
+ from PyQt5.QtGui import QIntValidator
2
+
3
+ from celldetective.gui.layouts.model_fit_layout import BackgroundFitCorrectionLayout
4
+ from celldetective import get_logger
5
+
6
+ logger = get_logger(__name__)
7
+
8
+ class LocalCorrectionLayout(BackgroundFitCorrectionLayout):
9
+ """docstring for ClassName"""
10
+
11
+ def __init__(self, *args):
12
+
13
+ super().__init__(*args)
14
+
15
+ if hasattr(self.parent_window.parent_window, "locate_image"):
16
+ self.attr_parent = self.parent_window.parent_window
17
+ elif hasattr(self.parent_window.parent_window.parent_window, "locate_image"):
18
+ self.attr_parent = self.parent_window.parent_window.parent_window
19
+ else:
20
+ self.attr_parent = (
21
+ self.parent_window.parent_window.parent_window.parent_window
22
+ )
23
+
24
+ self.thresh_lbl.setText("Distance: ")
25
+ self.thresh_lbl.setToolTip(
26
+ "Distance from the cell mask over which to estimate local intensity."
27
+ )
28
+
29
+ self.models_cb.clear()
30
+ self.models_cb.addItems(["mean", "median"])
31
+
32
+ self.threshold_le.set_threshold(5)
33
+ self.threshold_le.connected_buttons = [
34
+ self.threshold_viewer_btn,
35
+ self.add_correction_btn,
36
+ ]
37
+ self.threshold_le.setValidator(QIntValidator())
38
+
39
+ self.threshold_viewer_btn.disconnect()
40
+ self.threshold_viewer_btn.clicked.connect(self.set_distance_graphically)
41
+
42
+ self.corrected_stack_viewer.hide()
43
+
44
+ def set_distance_graphically(self):
45
+ from celldetective.gui.viewers.contour_viewer import CellEdgeVisualizer
46
+
47
+ self.attr_parent.locate_image()
48
+ self.set_target_channel()
49
+ thresh = self.threshold_le.get_threshold()
50
+
51
+ if self.attr_parent.current_stack is not None and thresh is not None:
52
+
53
+ self.viewer = CellEdgeVisualizer(
54
+ cell_type=self.parent_window.parent_window.mode,
55
+ stack_path=self.attr_parent.current_stack,
56
+ parent_le=self.threshold_le,
57
+ n_channels=len(self.channel_names),
58
+ target_channel=self.channels_cb.currentIndex(),
59
+ edge_range=(0, 30),
60
+ initial_edge=int(thresh),
61
+ invert=True,
62
+ window_title="Set an edge distance to estimate local intensity",
63
+ channel_cb=False,
64
+ PxToUm=1,
65
+ )
66
+ self.viewer.show()
67
+
68
+ def generate_instructions(self):
69
+
70
+ if self.operation_layout.subtract_btn.isChecked():
71
+ operation = "subtract"
72
+ else:
73
+ operation = "divide"
74
+ clip = None
75
+
76
+ if (
77
+ self.operation_layout.clip_btn.isChecked()
78
+ and self.operation_layout.subtract_btn.isChecked()
79
+ ):
80
+ clip = True
81
+ else:
82
+ clip = False
83
+
84
+ self.instructions = {
85
+ "target_channel": self.channels_cb.currentText(),
86
+ "correction_type": "local",
87
+ "model": self.models_cb.currentText(),
88
+ "distance": int(self.threshold_le.get_threshold()),
89
+ "operation": operation,
90
+ "clip": clip,
91
+ }
@@ -0,0 +1,372 @@
1
+ import numpy as np
2
+ from PyQt5.QtCore import QSize, QThread, pyqtSignal
3
+ from PyQt5.QtGui import QIntValidator
4
+ from PyQt5.QtWidgets import (
5
+ QGridLayout,
6
+ QLabel,
7
+ QComboBox,
8
+ QPushButton,
9
+ QLineEdit,
10
+ QHBoxLayout,
11
+ QSpacerItem,
12
+ QSizePolicy,
13
+ QMessageBox,
14
+ )
15
+ from fonticon_mdi6 import MDI6
16
+ from superqt.fonticon import icon
17
+
18
+ from celldetective.gui.base.components import CelldetectiveProgressDialog
19
+ from celldetective.gui.base.styles import Styles
20
+ from celldetective.gui.gui_utils import ThresholdLineEdit
21
+ from celldetective.gui.layouts.operation_layout import OperationLayout
22
+ from celldetective.preprocessing import correct_background_model
23
+ from celldetective.processes.background_correction import BackgroundCorrectionProcess
24
+ from celldetective.utils.image_loaders import auto_load_number_of_frames
25
+ from celldetective.utils.parsing import _extract_channel_indices_from_config
26
+ from celldetective import get_logger
27
+ from celldetective.gui.viewers.base_viewer import StackVisualizer
28
+ from celldetective.gui.viewers.threshold_viewer import ThresholdedStackVisualizer
29
+
30
+ logger = get_logger(__name__)
31
+
32
+
33
+ class BackgroundFitCorrectionLayout(QGridLayout, Styles):
34
+ """docstring for ClassName"""
35
+
36
+ def __init__(self, parent_window=None, *args):
37
+ super().__init__(*args)
38
+
39
+ self.parent_window = parent_window
40
+
41
+ if hasattr(self.parent_window.parent_window, "locate_image"):
42
+ self.attr_parent = self.parent_window.parent_window
43
+ elif hasattr(self.parent_window.parent_window.parent_window, "locate_image"):
44
+ self.attr_parent = self.parent_window.parent_window.parent_window
45
+ else:
46
+ self.attr_parent = (
47
+ self.parent_window.parent_window.parent_window.parent_window
48
+ )
49
+
50
+ self.channel_names = self.attr_parent.exp_channels
51
+ self.setContentsMargins(15, 15, 15, 15)
52
+ self.generate_widgets()
53
+ self.add_to_layout()
54
+
55
+ def generate_widgets(self):
56
+
57
+ self.channel_lbl = QLabel("Channel: ")
58
+ self.channels_cb = QComboBox()
59
+ self.channels_cb.addItems(self.channel_names)
60
+
61
+ self.thresh_lbl = QLabel("Threshold: ")
62
+ self.thresh_lbl.setToolTip(
63
+ "Threshold on the STD-filtered image.\nPixel values above the threshold are\nconsidered as non-background and are\nmasked prior to background estimation."
64
+ )
65
+ self.threshold_viewer_btn = QPushButton()
66
+ self.threshold_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
67
+ self.threshold_viewer_btn.setStyleSheet(self.button_select_all)
68
+ self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
69
+ self.threshold_viewer_btn.setToolTip("Set the threshold graphically.")
70
+
71
+ self.model_lbl = QLabel("Model: ")
72
+ self.model_lbl.setToolTip("2D model to fit the background with.")
73
+ self.models_cb = QComboBox()
74
+ self.models_cb.addItems(["paraboloid", "plane"])
75
+ self.models_cb.setToolTip("2D model to fit the background with.")
76
+
77
+ self.corrected_stack_viewer = QPushButton("")
78
+ self.corrected_stack_viewer.setStyleSheet(self.button_select_all)
79
+ self.corrected_stack_viewer.setIcon(icon(MDI6.eye_outline, color="black"))
80
+ self.corrected_stack_viewer.setToolTip("View corrected image")
81
+ self.corrected_stack_viewer.clicked.connect(self.preview_correction)
82
+ self.corrected_stack_viewer.setIconSize(QSize(20, 20))
83
+
84
+ self.add_correction_btn = QPushButton("Add correction")
85
+ self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
86
+ self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
87
+ self.add_correction_btn.setToolTip("Add correction.")
88
+ self.add_correction_btn.setIconSize(QSize(25, 25))
89
+ self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
90
+
91
+ self.threshold_le = ThresholdLineEdit(
92
+ init_value=2,
93
+ connected_buttons=[
94
+ self.threshold_viewer_btn,
95
+ self.corrected_stack_viewer,
96
+ self.add_correction_btn,
97
+ ],
98
+ )
99
+
100
+ self.downsample_lbl = QLabel("Downsample: ")
101
+ self.downsample_lbl.setToolTip(
102
+ "Factor by which to downsample the image for fitting (for speed)."
103
+ )
104
+ self.downsample_le = QLineEdit("10")
105
+ self.downsample_le.setValidator(QIntValidator())
106
+
107
+ def add_to_layout(self):
108
+
109
+ channel_layout = QHBoxLayout()
110
+ channel_layout.addWidget(self.channel_lbl, 25)
111
+ channel_layout.addWidget(self.channels_cb, 75)
112
+ self.addLayout(channel_layout, 0, 0, 1, 3)
113
+
114
+ threshold_layout = QHBoxLayout()
115
+ threshold_layout.addWidget(self.thresh_lbl, 25)
116
+ subthreshold_layout = QHBoxLayout()
117
+ subthreshold_layout.addWidget(self.threshold_le, 95)
118
+ subthreshold_layout.addWidget(self.threshold_viewer_btn, 5)
119
+
120
+ threshold_layout.addLayout(subthreshold_layout, 75)
121
+ self.addLayout(threshold_layout, 1, 0, 1, 3)
122
+
123
+ model_layout = QHBoxLayout()
124
+ model_layout.addWidget(self.model_lbl, 25)
125
+ model_layout.addWidget(self.models_cb, 75)
126
+ self.addLayout(model_layout, 2, 0, 1, 3)
127
+
128
+ downsample_layout = QHBoxLayout()
129
+ downsample_layout.addWidget(self.downsample_lbl, 25)
130
+ downsample_layout.addWidget(self.downsample_le, 75)
131
+ self.addLayout(downsample_layout, 3, 0, 1, 3)
132
+
133
+ self.operation_layout = OperationLayout()
134
+ self.addLayout(self.operation_layout, 4, 0, 1, 3)
135
+
136
+ correction_layout = QHBoxLayout()
137
+ correction_layout.addWidget(self.add_correction_btn, 95)
138
+ correction_layout.addWidget(self.corrected_stack_viewer, 5)
139
+ self.addLayout(correction_layout, 5, 0, 1, 3)
140
+
141
+ verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
142
+ self.addItem(verticalSpacer, 6, 0, 1, 3)
143
+
144
+ def add_instructions_to_parent_list(self):
145
+
146
+ self.generate_instructions()
147
+ self.parent_window.protocols.append(self.instructions)
148
+ correction_description = ""
149
+ for index, (key, value) in enumerate(self.instructions.items()):
150
+ if index > 0:
151
+ correction_description += ", "
152
+ correction_description += str(key) + " : " + str(value)
153
+ self.parent_window.protocol_list.addItem(correction_description)
154
+
155
+ def generate_instructions(self):
156
+
157
+ if self.operation_layout.subtract_btn.isChecked():
158
+ operation = "subtract"
159
+ else:
160
+ operation = "divide"
161
+
162
+ if (
163
+ self.operation_layout.clip_btn.isChecked()
164
+ and self.operation_layout.subtract_btn.isChecked()
165
+ ):
166
+ clip = True
167
+ else:
168
+ clip = False
169
+
170
+ self.instructions = {
171
+ "target_channel": self.channels_cb.currentText(),
172
+ "correction_type": "fit",
173
+ "model": self.models_cb.currentText(),
174
+ "threshold_on_std": self.threshold_le.get_threshold(),
175
+ "operation": operation,
176
+ "clip": clip,
177
+ "downsample": int(self.downsample_le.text()),
178
+ }
179
+
180
+ def set_target_channel(self):
181
+
182
+ channel_indices = _extract_channel_indices_from_config(
183
+ self.attr_parent.exp_config, [self.channels_cb.currentText()]
184
+ )
185
+ self.target_channel = channel_indices[0]
186
+
187
+ def set_threshold_graphically(self):
188
+
189
+ self.attr_parent.locate_image()
190
+ self.set_target_channel()
191
+ thresh = self.threshold_le.get_threshold()
192
+
193
+ if self.attr_parent.current_stack is not None and thresh is not None:
194
+ self.viewer = ThresholdedStackVisualizer(
195
+ initial_threshold=thresh,
196
+ parent_le=self.threshold_le,
197
+ preprocessing=[["gauss", 2], ["std", 4]],
198
+ stack_path=self.attr_parent.current_stack,
199
+ n_channels=len(self.channel_names),
200
+ target_channel=self.target_channel,
201
+ window_title="Set the exclusion threshold",
202
+ )
203
+ self.viewer.show()
204
+
205
+ def preview_correction(self):
206
+
207
+ if (
208
+ self.attr_parent.well_list.isMultipleSelection()
209
+ or not self.attr_parent.well_list.isAnySelected()
210
+ or self.attr_parent.position_list.isMultipleSelection()
211
+ or not self.attr_parent.position_list.isAnySelected()
212
+ ):
213
+
214
+ msgBox = QMessageBox()
215
+ msgBox.setIcon(QMessageBox.Critical)
216
+ msgBox.setText("Please select a single position...")
217
+ msgBox.setWindowTitle("Critical")
218
+ msgBox.setStandardButtons(QMessageBox.Ok)
219
+ returnValue = msgBox.exec()
220
+ if returnValue == QMessageBox.Ok:
221
+ return None
222
+
223
+ if self.operation_layout.subtract_btn.isChecked():
224
+ operation = "subtract"
225
+ else:
226
+ operation = "divide"
227
+
228
+ if (
229
+ self.operation_layout.clip_btn.isChecked()
230
+ and self.operation_layout.subtract_btn.isChecked()
231
+ ):
232
+ clip = True
233
+ else:
234
+ clip = False
235
+
236
+ self.attr_parent.locate_image()
237
+ self.set_target_channel()
238
+
239
+ subset_indices = None
240
+ if (
241
+ hasattr(self.attr_parent, "current_stack")
242
+ and self.attr_parent.current_stack is not None
243
+ ):
244
+
245
+ n_frames = auto_load_number_of_frames(self.attr_parent.current_stack)
246
+ if n_frames is not None:
247
+ midpoint = int(n_frames // 2)
248
+ n_channels = len(self.attr_parent.exp_channels)
249
+ subset_indices = [midpoint * n_channels]
250
+
251
+ process_args = {
252
+ "exp_dir": self.attr_parent.exp_dir,
253
+ "well_option": self.attr_parent.well_list.getSelectedIndices(),
254
+ "position_option": self.attr_parent.position_list.getSelectedIndices(),
255
+ "target_channel": self.channels_cb.currentText(),
256
+ "model": self.models_cb.currentText(),
257
+ "threshold_on_std": self.threshold_le.get_threshold(),
258
+ "operation": operation,
259
+ "clip": clip,
260
+ "activation_protocol": [["gauss", 2], ["std", 4]],
261
+ "downsample": int(self.downsample_le.text()),
262
+ "subset_indices": subset_indices,
263
+ }
264
+
265
+ self.bg_progress = CelldetectiveProgressDialog(
266
+ "Correcting background (Preview)...",
267
+ "Cancel",
268
+ 0,
269
+ 0,
270
+ None,
271
+ window_title="Processing",
272
+ )
273
+ self.bg_progress.setRange(0, 0)
274
+
275
+ self.preview_worker = PreviewWorker(
276
+ BackgroundCorrectionProcess, process_args=process_args
277
+ )
278
+
279
+ def on_result(corrected_stack):
280
+
281
+ if corrected_stack is not None:
282
+ if subset_indices is not None and len(self.channel_names) > 0:
283
+ # Logic to extract the specific channel
284
+ if corrected_stack.ndim == 3 and corrected_stack.shape[0] == len(
285
+ self.channel_names
286
+ ):
287
+ # Shape likely (C, Y, X)
288
+ corrected_stack = corrected_stack[
289
+ self.channels_cb.currentIndex()
290
+ ]
291
+ elif corrected_stack.ndim == 4 and corrected_stack.shape[-1] == len(
292
+ self.channel_names
293
+ ):
294
+ # Shape likely (T, Y, X, C)
295
+ corrected_stack = corrected_stack[
296
+ ..., self.channels_cb.currentIndex()
297
+ ]
298
+
299
+ # Ensure (T=1, Y, X, C=1) for display or similar that StackVisualizer likes for single channel
300
+ if corrected_stack.ndim == 2:
301
+ corrected_stack = corrected_stack[np.newaxis, :, :, np.newaxis]
302
+ elif corrected_stack.ndim == 3:
303
+ # (1, Y, X)
304
+ corrected_stack = corrected_stack[:, :, :, np.newaxis]
305
+
306
+ self.viewer = StackVisualizer(
307
+ stack=corrected_stack,
308
+ window_title="Corrected channel",
309
+ target_channel=0,
310
+ frame_slider=True,
311
+ contrast_slider=True,
312
+ )
313
+ self.viewer.show()
314
+ else:
315
+ print("Corrected stack could not be generated...")
316
+
317
+ def on_finished():
318
+ self.bg_progress.close()
319
+
320
+ def on_error(msg):
321
+ self.bg_progress.close()
322
+ QMessageBox.critical(None, "Error", f"Correction failed: {msg}")
323
+
324
+ self.preview_worker.result_ready.connect(on_result)
325
+ self.preview_worker.finished.connect(on_finished)
326
+ self.preview_worker.error.connect(on_error)
327
+ self.bg_progress.canceled.connect(self.preview_worker.stop)
328
+
329
+ self.preview_worker.start()
330
+ self.bg_progress.exec_()
331
+
332
+
333
+ class PreviewWorker(QThread):
334
+ finished = pyqtSignal()
335
+ result_ready = pyqtSignal(object)
336
+ error = pyqtSignal(str)
337
+
338
+ def __init__(self, process_class, process_args):
339
+ super().__init__()
340
+ # process_class is unused now as we call function directly
341
+ self.process_args = process_args
342
+
343
+ def run(self):
344
+ try:
345
+ result = correct_background_model(
346
+ experiment=self.process_args["exp_dir"],
347
+ well_option=self.process_args["well_option"],
348
+ position_option=self.process_args["position_option"],
349
+ target_channel=self.process_args["target_channel"],
350
+ model=self.process_args["model"],
351
+ threshold_on_std=self.process_args["threshold_on_std"],
352
+ operation=self.process_args["operation"],
353
+ clip=self.process_args["clip"],
354
+ export=False,
355
+ return_stacks=True,
356
+ activation_protocol=self.process_args["activation_protocol"],
357
+ downsample=self.process_args["downsample"],
358
+ subset_indices=self.process_args["subset_indices"],
359
+ show_progress_per_well=False,
360
+ show_progress_per_pos=False,
361
+ )
362
+
363
+ if result is not None and len(result) > 0:
364
+ self.result_ready.emit(result[0])
365
+
366
+ except Exception as e:
367
+ self.error.emit(str(e))
368
+
369
+ self.finished.emit()
370
+
371
+ def stop(self):
372
+ self.quit()
@@ -0,0 +1,68 @@
1
+ from PyQt5.QtCore import Qt
2
+ from PyQt5.QtWidgets import QVBoxLayout, QLabel, QButtonGroup, QRadioButton, QHBoxLayout
3
+
4
+
5
+ class OperationLayout(QVBoxLayout):
6
+ """docstring for ClassName"""
7
+
8
+ def __init__(self, ratio=(0.25, 0.75), *args):
9
+
10
+ super().__init__(*args)
11
+
12
+ self.ratio = ratio
13
+ self.generate_widgets()
14
+ self.generate_layout()
15
+
16
+ def generate_widgets(self):
17
+
18
+ self.operation_lbl = QLabel("Operation: ")
19
+ self.operation_group = QButtonGroup()
20
+ self.subtract_btn = QRadioButton("Subtract")
21
+ self.divide_btn = QRadioButton("Divide")
22
+ self.subtract_btn.toggled.connect(self.activate_clipping_options)
23
+ self.divide_btn.toggled.connect(self.activate_clipping_options)
24
+
25
+ self.operation_group.addButton(self.subtract_btn)
26
+ self.operation_group.addButton(self.divide_btn)
27
+
28
+ self.clip_group = QButtonGroup()
29
+ self.clip_btn = QRadioButton("Clip")
30
+ self.clip_not_btn = QRadioButton("Do not clip")
31
+
32
+ self.clip_group.addButton(self.clip_btn)
33
+ self.clip_group.addButton(self.clip_not_btn)
34
+
35
+ def generate_layout(self):
36
+
37
+ operation_layout = QHBoxLayout()
38
+ operation_layout.addWidget(self.operation_lbl, 100 * int(self.ratio[0]))
39
+ operation_layout.addWidget(
40
+ self.subtract_btn, 100 * int(self.ratio[1]) // 2, alignment=Qt.AlignCenter
41
+ )
42
+ operation_layout.addWidget(
43
+ self.divide_btn, 100 * int(self.ratio[1]) // 2, alignment=Qt.AlignCenter
44
+ )
45
+ self.addLayout(operation_layout)
46
+
47
+ clip_layout = QHBoxLayout()
48
+ clip_layout.addWidget(QLabel(""), 100 * int(self.ratio[0]))
49
+ clip_layout.addWidget(
50
+ self.clip_btn, 100 * int(self.ratio[1]) // 4, alignment=Qt.AlignCenter
51
+ )
52
+ clip_layout.addWidget(
53
+ self.clip_not_btn, 100 * int(self.ratio[1]) // 4, alignment=Qt.AlignCenter
54
+ )
55
+ clip_layout.addWidget(QLabel(""), 100 * int(self.ratio[1]) // 2)
56
+ self.addLayout(clip_layout)
57
+
58
+ self.subtract_btn.click()
59
+ self.clip_not_btn.click()
60
+
61
+ def activate_clipping_options(self):
62
+
63
+ if self.subtract_btn.isChecked():
64
+ self.clip_btn.setEnabled(True)
65
+ self.clip_not_btn.setEnabled(True)
66
+ else:
67
+ self.clip_btn.setEnabled(False)
68
+ self.clip_not_btn.setEnabled(False)
@@ -0,0 +1,96 @@
1
+ from PyQt5.QtCore import QSize, Qt
2
+ from PyQt5.QtWidgets import QVBoxLayout, QLabel, QTabWidget, QSizePolicy, QListWidget, QPushButton, QHBoxLayout
3
+ from fonticon_mdi6 import MDI6
4
+ from superqt.fonticon import icon
5
+
6
+ from celldetective.gui.base.components import CelldetectiveWidget
7
+ from celldetective.gui.base.styles import Styles
8
+
9
+
10
+ class ProtocolDesignerLayout(QVBoxLayout, Styles):
11
+ """Multi tabs and list widget configuration for background correction
12
+ in preprocessing and measurements
13
+ """
14
+
15
+ def __init__(
16
+ self,
17
+ parent_window=None,
18
+ tab_layouts=[],
19
+ tab_names=[],
20
+ title="",
21
+ list_title="",
22
+ *args,
23
+ ):
24
+
25
+ super().__init__(*args)
26
+
27
+ self.title = title
28
+ self.parent_window = parent_window
29
+ self.channel_names = self.parent_window.channel_names
30
+ self.tab_layouts = tab_layouts
31
+ self.tab_names = tab_names
32
+ self.list_title = list_title
33
+ self.protocols = []
34
+ assert len(self.tab_layouts) == len(self.tab_names)
35
+
36
+ self.generate_widgets()
37
+ self.generate_layout()
38
+
39
+ def generate_widgets(self):
40
+
41
+ self.title_lbl = QLabel(self.title)
42
+ self.title_lbl.setStyleSheet(
43
+ """
44
+ font-weight: bold;
45
+ padding: 0px;
46
+ """
47
+ )
48
+
49
+ self.tabs = QTabWidget()
50
+ self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
51
+
52
+ for k in range(len(self.tab_layouts)):
53
+ wg = CelldetectiveWidget()
54
+ self.tab_layouts[k].parent_window = self
55
+ wg.setLayout(self.tab_layouts[k])
56
+ self.tabs.addTab(wg, self.tab_names[k])
57
+
58
+ self.protocol_list_lbl = QLabel(self.list_title)
59
+ self.protocol_list = QListWidget()
60
+
61
+ self.delete_protocol_btn = QPushButton("")
62
+ self.delete_protocol_btn.setStyleSheet(self.button_select_all)
63
+ self.delete_protocol_btn.setIcon(icon(MDI6.trash_can, color="black"))
64
+ self.delete_protocol_btn.setToolTip("Remove.")
65
+ self.delete_protocol_btn.setIconSize(QSize(20, 20))
66
+ self.delete_protocol_btn.clicked.connect(self.remove_protocol_from_list)
67
+
68
+ def generate_layout(self):
69
+
70
+ self.correction_layout = QVBoxLayout()
71
+
72
+ self.background_correction_layout = QVBoxLayout()
73
+ self.background_correction_layout.setContentsMargins(0, 0, 0, 0)
74
+ self.title_layout = QHBoxLayout()
75
+ self.title_layout.addWidget(self.title_lbl, 100, alignment=Qt.AlignCenter)
76
+ self.background_correction_layout.addLayout(self.title_layout)
77
+ self.background_correction_layout.addWidget(self.tabs)
78
+ self.correction_layout.addLayout(self.background_correction_layout)
79
+
80
+ self.addLayout(self.correction_layout)
81
+
82
+ self.list_layout = QVBoxLayout()
83
+ list_header_layout = QHBoxLayout()
84
+ list_header_layout.addWidget(self.protocol_list_lbl)
85
+ list_header_layout.addWidget(self.delete_protocol_btn, alignment=Qt.AlignRight)
86
+ self.list_layout.addLayout(list_header_layout)
87
+ self.list_layout.addWidget(self.protocol_list)
88
+
89
+ self.addLayout(self.list_layout)
90
+
91
+ def remove_protocol_from_list(self):
92
+
93
+ current_item = self.protocol_list.currentRow()
94
+ if current_item > -1:
95
+ del self.protocols[current_item]
96
+ self.protocol_list.takeItem(current_item)