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.
Files changed (152) 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 +403 -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/downloader.py +137 -0
  81. celldetective/processes/measure_cells.py +565 -0
  82. celldetective/processes/segment_cells.py +760 -0
  83. celldetective/processes/track_cells.py +435 -0
  84. celldetective/processes/train_segmentation_model.py +694 -0
  85. celldetective/processes/train_signal_model.py +265 -0
  86. celldetective/processes/unified_process.py +292 -0
  87. celldetective/regionprops/_regionprops.py +358 -317
  88. celldetective/relative_measurements.py +987 -710
  89. celldetective/scripts/measure_cells.py +313 -212
  90. celldetective/scripts/measure_relative.py +90 -46
  91. celldetective/scripts/segment_cells.py +165 -104
  92. celldetective/scripts/segment_cells_thresholds.py +96 -68
  93. celldetective/scripts/track_cells.py +198 -149
  94. celldetective/scripts/train_segmentation_model.py +324 -201
  95. celldetective/scripts/train_signal_model.py +87 -45
  96. celldetective/segmentation.py +844 -749
  97. celldetective/signals.py +3514 -2861
  98. celldetective/tracking.py +30 -15
  99. celldetective/utils/__init__.py +0 -0
  100. celldetective/utils/cellpose_utils/__init__.py +133 -0
  101. celldetective/utils/color_mappings.py +42 -0
  102. celldetective/utils/data_cleaning.py +630 -0
  103. celldetective/utils/data_loaders.py +450 -0
  104. celldetective/utils/dataset_helpers.py +207 -0
  105. celldetective/utils/downloaders.py +235 -0
  106. celldetective/utils/event_detection/__init__.py +8 -0
  107. celldetective/utils/experiment.py +1782 -0
  108. celldetective/utils/image_augmenters.py +308 -0
  109. celldetective/utils/image_cleaning.py +74 -0
  110. celldetective/utils/image_loaders.py +926 -0
  111. celldetective/utils/image_transforms.py +335 -0
  112. celldetective/utils/io.py +62 -0
  113. celldetective/utils/mask_cleaning.py +348 -0
  114. celldetective/utils/mask_transforms.py +5 -0
  115. celldetective/utils/masks.py +184 -0
  116. celldetective/utils/maths.py +351 -0
  117. celldetective/utils/model_getters.py +325 -0
  118. celldetective/utils/model_loaders.py +296 -0
  119. celldetective/utils/normalization.py +380 -0
  120. celldetective/utils/parsing.py +465 -0
  121. celldetective/utils/plots/__init__.py +0 -0
  122. celldetective/utils/plots/regression.py +53 -0
  123. celldetective/utils/resources.py +34 -0
  124. celldetective/utils/stardist_utils/__init__.py +104 -0
  125. celldetective/utils/stats.py +90 -0
  126. celldetective/utils/types.py +21 -0
  127. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/METADATA +1 -1
  128. celldetective-1.5.0b1.dist-info/RECORD +187 -0
  129. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/WHEEL +1 -1
  130. tests/gui/test_new_project.py +129 -117
  131. tests/gui/test_project.py +127 -79
  132. tests/test_filters.py +39 -15
  133. tests/test_notebooks.py +8 -0
  134. tests/test_tracking.py +232 -13
  135. tests/test_utils.py +123 -77
  136. celldetective/gui/base_components.py +0 -23
  137. celldetective/gui/layouts.py +0 -1602
  138. celldetective/gui/processes/compute_neighborhood.py +0 -594
  139. celldetective/gui/processes/downloader.py +0 -111
  140. celldetective/gui/processes/measure_cells.py +0 -360
  141. celldetective/gui/processes/segment_cells.py +0 -499
  142. celldetective/gui/processes/track_cells.py +0 -303
  143. celldetective/gui/processes/train_segmentation_model.py +0 -270
  144. celldetective/gui/processes/train_signal_model.py +0 -108
  145. celldetective/gui/table_ops/merge_groups.py +0 -118
  146. celldetective/gui/viewers.py +0 -1354
  147. celldetective/io.py +0 -3663
  148. celldetective/utils.py +0 -3108
  149. celldetective-1.4.2.dist-info/RECORD +0 -123
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/entry_points.txt +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
  152. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/top_level.txt +0 -0
@@ -1,1602 +0,0 @@
1
- from PyQt5.QtWidgets import QCheckBox, QLineEdit, QListWidget, QTabWidget, QHBoxLayout,QMessageBox, QPushButton, QVBoxLayout, QRadioButton, QLabel, QButtonGroup, QSizePolicy, QComboBox,QSpacerItem, QGridLayout
2
- from celldetective.gui.gui_utils import ThresholdLineEdit, QuickSliderLayout, center_window
3
- from PyQt5.QtCore import Qt, QSize
4
- from PyQt5.QtGui import QIntValidator, QDoubleValidator
5
-
6
- from superqt import QLabeledRangeSlider, QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider, QSearchableComboBox
7
-
8
- from superqt.fonticon import icon
9
- from fonticon_mdi6 import MDI6
10
- from celldetective.utils import _extract_channel_indices_from_config
11
- from celldetective.gui.viewers import ThresholdedStackVisualizer, CellEdgeVisualizer, StackVisualizer, CellSizeViewer, ChannelOffsetViewer
12
- from celldetective.gui import Styles, CelldetectiveWidget
13
- from celldetective.preprocessing import correct_background_model, correct_background_model_free, estimate_background_per_condition
14
- from functools import partial
15
- from glob import glob
16
- import os
17
- import pandas as pd
18
- import numpy as np
19
- from celldetective.io import locate_segmentation_model, locate_signal_model
20
- import json
21
-
22
-
23
- class SignalModelParamsWidget(CelldetectiveWidget):
24
-
25
- def __init__(self, parent_window=None, model_name=None, *args, **kwargs):
26
-
27
- super().__init__(*args)
28
- self.setWindowTitle('Signals')
29
- self.parent_window = parent_window
30
- self.model_name = model_name
31
- self.locate_model_path()
32
- self.required_channels = self.input_config["channels"]
33
- self.onlyFloat = QDoubleValidator()
34
-
35
- # Setting up references to parent window attributes
36
- if hasattr(self.parent_window.parent_window, 'locate_image'):
37
- self.attr_parent = self.parent_window.parent_window
38
- elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
39
- self.attr_parent = self.parent_window.parent_window.parent_window
40
- else:
41
- self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
42
-
43
- # Set up layout and widgets
44
- self.layout = QVBoxLayout()
45
- self.populate_widgets()
46
- self.setLayout(self.layout)
47
- center_window(self)
48
-
49
- def locate_model_path(self):
50
-
51
- self.model_complete_path = locate_signal_model(self.model_name)
52
- if self.model_complete_path is None:
53
- print('Model could not be found. Abort.')
54
- self.abort_process()
55
- else:
56
- print(f'Model path: {self.model_complete_path}...')
57
-
58
- if not os.path.exists(self.model_complete_path+"config_input.json"):
59
- print('The configuration for the inputs to the model could not be located. Abort.')
60
- self.abort_process()
61
-
62
- with open(self.model_complete_path+"config_input.json") as config_file:
63
- self.input_config = json.load(config_file)
64
-
65
- def populate_widgets(self):
66
-
67
- self.n_channels = len(self.required_channels)
68
- self.channel_cbs = [QComboBox() for i in range(self.n_channels)]
69
-
70
- self.parent_window.load_available_tables()
71
- available_channels = list(self.parent_window.signals)+['None']
72
- # Populate the comboboxes with available channels from the experiment
73
- for k in range(self.n_channels):
74
- hbox_channel = QHBoxLayout()
75
- hbox_channel.addWidget(QLabel(f'channel {k+1}: '), 33)
76
-
77
- ch_vbox = QVBoxLayout()
78
- ch_vbox.addWidget(QLabel(f'Req: {self.required_channels[k]}'), alignment=Qt.AlignLeft)
79
- ch_vbox.addWidget(self.channel_cbs[k])
80
-
81
- self.channel_cbs[k].addItems(available_channels) #Give none option for more than one channel input
82
- idx = self.channel_cbs[k].findText(self.required_channels[k])
83
-
84
- if idx>=0:
85
- self.channel_cbs[k].setCurrentIndex(idx)
86
- else:
87
- self.channel_cbs[k].setCurrentIndex(len(available_channels)-1)
88
-
89
- hbox_channel.addLayout(ch_vbox, 66)
90
- self.layout.addLayout(hbox_channel)
91
-
92
- # Button to apply the StarDist settings
93
- self.set_btn = QPushButton('set')
94
- self.set_btn.setStyleSheet(self.button_style_sheet)
95
- self.set_btn.clicked.connect(self.parent_window.set_selected_signals_for_event_detection)
96
- self.layout.addWidget(self.set_btn)
97
-
98
-
99
-
100
- class SegModelParamsWidget(CelldetectiveWidget):
101
-
102
- def __init__(self, parent_window=None, model_name='SD_versatile_fluo', *args, **kwargs):
103
-
104
- super().__init__(*args)
105
- self.setWindowTitle('Channels')
106
- self.parent_window = parent_window
107
- self.model_name = model_name
108
- self.locate_model_path()
109
- self.required_channels = self.input_config["channels"]
110
- self.onlyFloat = QDoubleValidator()
111
-
112
- # Setting up references to parent window attributes
113
- if hasattr(self.parent_window.parent_window, 'locate_image'):
114
- self.attr_parent = self.parent_window.parent_window
115
- elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
116
- self.attr_parent = self.parent_window.parent_window.parent_window
117
- else:
118
- self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
119
-
120
- # Set up layout and widgets
121
- self.layout = QVBoxLayout()
122
- self.populate_widgets()
123
- self.setLayout(self.layout)
124
- center_window(self)
125
-
126
- def locate_model_path(self):
127
-
128
- self.model_complete_path = locate_segmentation_model(self.model_name)
129
- if self.model_complete_path is None:
130
- print('Model could not be found. Abort.')
131
- self.abort_process()
132
- else:
133
- print(f'Model path: {self.model_complete_path}...')
134
-
135
- if not os.path.exists(self.model_complete_path+"config_input.json"):
136
- print('The configuration for the inputs to the model could not be located. Abort.')
137
- self.abort_process()
138
-
139
- with open(self.model_complete_path+"config_input.json") as config_file:
140
- self.input_config = json.load(config_file)
141
-
142
- def populate_widgets(self):
143
-
144
- self.n_channels = len(self.required_channels)
145
- self.channel_cbs = [QComboBox() for i in range(self.n_channels)]
146
-
147
- # Button to view the current stack with a scale bar
148
- self.view_diameter_btn = QPushButton()
149
- self.view_diameter_btn.setStyleSheet(self.button_select_all)
150
- self.view_diameter_btn.setIcon(icon(MDI6.image_check, color="black"))
151
- self.view_diameter_btn.setToolTip("View stack.")
152
- self.view_diameter_btn.setIconSize(QSize(20, 20))
153
- self.view_diameter_btn.clicked.connect(self.view_current_stack_with_scale_bar)
154
-
155
- # Line edit for entering cell diameter
156
- self.diameter_le = ThresholdLineEdit(init_value=40, connected_buttons=[self.view_diameter_btn],placeholder='cell diameter in µm', value_type='float')
157
-
158
- available_channels = list(self.attr_parent.exp_channels)+['None']
159
- # Populate the comboboxes with available channels from the experiment
160
- for k in range(self.n_channels):
161
- hbox_channel = QHBoxLayout()
162
- hbox_channel.addWidget(QLabel(f'channel {k+1}: '), 33)
163
-
164
- ch_vbox = QVBoxLayout()
165
- ch_vbox.addWidget(QLabel(f'Req: {self.required_channels[k]}'), alignment=Qt.AlignLeft)
166
- ch_vbox.addWidget(self.channel_cbs[k])
167
-
168
- self.channel_cbs[k].addItems(available_channels) #Give none option for more than one channel input
169
- idx = self.channel_cbs[k].findText(self.required_channels[k])
170
-
171
- if idx>=0:
172
- self.channel_cbs[k].setCurrentIndex(idx)
173
- else:
174
- self.channel_cbs[k].setCurrentIndex(len(available_channels)-1)
175
-
176
- hbox_channel.addLayout(ch_vbox, 66)
177
- self.layout.addLayout(hbox_channel)
178
-
179
- if 'cell_size_um' in self.input_config:
180
-
181
- # Layout for diameter input and button
182
- hbox = QHBoxLayout()
183
- hbox.addWidget(QLabel('cell size [µm]: '), 33)
184
- hbox.addWidget(self.diameter_le, 61)
185
- hbox.addWidget(self.view_diameter_btn)
186
- self.layout.addLayout(hbox)
187
-
188
- self.diameter_le.set_threshold(self.input_config['cell_size_um'])
189
-
190
- # size_hbox = QHBoxLayout()
191
- # size_hbox.addWidget(QLabel('cell size [µm]: '), 33)
192
- # self.size_le = QLineEdit(str(self.input_config['cell_size_um']).replace('.',','))
193
- # self.size_le.setValidator(self.onlyFloat)
194
- # size_hbox.addWidget(self.size_le, 66)
195
- # self.layout.addLayout(size_hbox)
196
-
197
- # Button to apply the StarDist settings
198
- self.set_btn = QPushButton('set')
199
- self.set_btn.setStyleSheet(self.button_style_sheet)
200
- self.set_btn.clicked.connect(self.parent_window.set_selected_channels_for_segmentation)
201
- self.layout.addWidget(self.set_btn)
202
-
203
- def view_current_stack_with_scale_bar(self):
204
-
205
- """
206
- Displays the current image stack with a scale bar, allowing users to visually estimate cell diameters.
207
- """
208
-
209
- self.attr_parent.locate_image()
210
- if self.attr_parent.current_stack is not None:
211
- max_size = np.amax([self.attr_parent.shape_x, self.attr_parent.shape_y])
212
- self.viewer = CellSizeViewer(
213
- initial_diameter = float(self.diameter_le.text().replace(',', '.')),
214
- parent_le = self.diameter_le,
215
- stack_path=self.attr_parent.current_stack,
216
- window_title=f'Position {self.attr_parent.position_list.currentText()}',
217
- diameter_slider_range=(0,max_size*self.attr_parent.PxToUm),
218
- frame_slider = True,
219
- contrast_slider = True,
220
- channel_cb = True,
221
- channel_names = self.attr_parent.exp_channels,
222
- n_channels = self.attr_parent.nbr_channels,
223
- PxToUm = self.attr_parent.PxToUm,
224
- )
225
- self.viewer.show()
226
-
227
-
228
- class StarDistParamsWidget(CelldetectiveWidget):
229
-
230
- """
231
- A widget to configure parameters for StarDist segmentation.
232
-
233
- This widget allows the user to select specific imaging channels for segmentation and adjust
234
- parameters for StarDist, a neural network-based image segmentation tool designed to segment
235
- star-convex shapes (typically nuclei).
236
-
237
- Parameters
238
- ----------
239
- parent_window : QWidget, optional
240
- The parent window hosting this widget (default is None).
241
- model_name : str, optional
242
- The name of the StarDist model being used, typically 'SD_versatile_fluo' for versatile
243
- fluorescence or 'SD_versatile_he' for H&E-stained images (default is 'SD_versatile_fluo').
244
- """
245
-
246
- def __init__(self, parent_window=None, model_name='SD_versatile_fluo', *args, **kwargs):
247
-
248
- super().__init__(*args)
249
- self.setWindowTitle('Channels')
250
- self.parent_window = parent_window
251
- self.model_name = model_name
252
-
253
- # Setting up references to parent window attributes
254
- if hasattr(self.parent_window.parent_window, 'locate_image'):
255
- self.attr_parent = self.parent_window.parent_window
256
- elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
257
- self.attr_parent = self.parent_window.parent_window.parent_window
258
- else:
259
- self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
260
-
261
- # Set up layout and widgets
262
- self.layout = QVBoxLayout()
263
- self.populate_widgets()
264
- self.setLayout(self.layout)
265
- center_window(self)
266
-
267
- def populate_widgets(self):
268
-
269
- """
270
- Populates the widget with channel selection comboboxes and a 'set' button to configure
271
- the StarDist segmentation settings. Handles different models by adjusting the number of
272
- available channels.
273
- """
274
-
275
- # Initialize comboboxes based on the selected model
276
- self.stardist_channel_cb = [QComboBox() for i in range(1)]
277
- self.stardist_channel_template = ['live_nuclei_channel']
278
- max_i = 1
279
-
280
- # If the H&E model is selected, update the combobox configuration
281
- if self.model_name=="SD_versatile_he":
282
- self.stardist_channel_template = ["H&E_1","H&E_2","H&E_3"]
283
- self.stardist_channel_cb = [QComboBox() for i in range(3)]
284
- max_i = 3
285
-
286
- # Populate the comboboxes with available channels from the experiment
287
- for k in range(max_i):
288
- hbox_channel = QHBoxLayout()
289
- hbox_channel.addWidget(QLabel(f'channel {k+1}: '))
290
- hbox_channel.addWidget(self.stardist_channel_cb[k])
291
- if k==1:
292
- self.stardist_channel_cb[k].addItems(list(self.attr_parent.exp_channels)+['None'])
293
- else:
294
- self.stardist_channel_cb[k].addItems(list(self.attr_parent.exp_channels))
295
-
296
- # Set the default channel based on the template or fallback to the first option
297
- idx = self.stardist_channel_cb[k].findText(self.stardist_channel_template[k])
298
- if idx>0:
299
- self.stardist_channel_cb[k].setCurrentIndex(idx)
300
- else:
301
- self.stardist_channel_cb[k].setCurrentIndex(0)
302
-
303
- self.layout.addLayout(hbox_channel)
304
-
305
- # Button to apply the StarDist settings
306
- self.set_stardist_scale_btn = QPushButton('set')
307
- self.set_stardist_scale_btn.setStyleSheet(self.button_style_sheet)
308
- self.set_stardist_scale_btn.clicked.connect(self.parent_window.set_stardist_scale)
309
- self.layout.addWidget(self.set_stardist_scale_btn)
310
-
311
-
312
- class CellposeParamsWidget(CelldetectiveWidget):
313
-
314
- """
315
- A widget to configure parameters for Cellpose segmentation, allowing users to set the cell diameter,
316
- select imaging channels, and adjust flow and cell probability thresholds for cell detection.
317
-
318
- This widget is designed for estimating cell diameters and configuring parameters for Cellpose,
319
- a deep learning-based segmentation tool. It also provides functionality to preview the image stack with a scale bar.
320
-
321
- Parameters
322
- ----------
323
- parent_window : QWidget, optional
324
- The parent window that hosts the widget (default is None).
325
- model_name : str, optional
326
- The name of the Cellpose model being used, typically 'CP_cyto2' for cytoplasm or 'CP_nuclei' for nuclei segmentation
327
- (default is 'CP_cyto2').
328
-
329
- Notes
330
- -----
331
- - This widget assumes that the parent window or one of its ancestor windows has access to the experiment channels
332
- and can locate the current image stack via `locate_image()`.
333
- - This class integrates sliders for flow and cell probability thresholds, as well as a channel selection for running
334
- Cellpose segmentation.
335
- - The `view_current_stack_with_scale_bar()` method opens a new window where the user can visually inspect the
336
- image stack with a superimposed scale bar, to better estimate the cell diameter.
337
-
338
- """
339
-
340
-
341
- def __init__(self, parent_window=None, model_name='CP_cyto2', *args, **kwargs):
342
-
343
- super().__init__(*args)
344
- self.setWindowTitle('Estimate diameter')
345
- self.parent_window = parent_window
346
- self.model_name = model_name
347
-
348
- # Setting up references to parent window attributes
349
- if hasattr(self.parent_window.parent_window, 'locate_image'):
350
- self.attr_parent = self.parent_window.parent_window
351
- elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
352
- self.attr_parent = self.parent_window.parent_window.parent_window
353
- else:
354
- self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
355
-
356
- # Layout and widgets setup
357
- self.layout = QVBoxLayout()
358
- self.populate_widgets()
359
- self.setLayout(self.layout)
360
- center_window(self)
361
-
362
- def populate_widgets(self):
363
-
364
- """
365
- Populates the widget with UI elements such as buttons, sliders, and comboboxes to allow configuration
366
- of Cellpose segmentation parameters.
367
- """
368
-
369
- # Button to view the current stack with a scale bar
370
- self.view_diameter_btn = QPushButton()
371
- self.view_diameter_btn.setStyleSheet(self.button_select_all)
372
- self.view_diameter_btn.setIcon(icon(MDI6.image_check, color="black"))
373
- self.view_diameter_btn.setToolTip("View stack.")
374
- self.view_diameter_btn.setIconSize(QSize(20, 20))
375
- self.view_diameter_btn.clicked.connect(self.view_current_stack_with_scale_bar)
376
-
377
- # Line edit for entering cell diameter
378
- self.diameter_le = ThresholdLineEdit(init_value=40, connected_buttons=[self.view_diameter_btn],placeholder='cell diameter in pixels', value_type='float')
379
-
380
- # Comboboxes for selecting imaging channels
381
- self.cellpose_channel_cb = [QComboBox() for i in range(2)]
382
- self.cellpose_channel_template = ['brightfield_channel', 'live_nuclei_channel']
383
- if self.model_name=="CP_nuclei":
384
- self.cellpose_channel_template = ['live_nuclei_channel', 'None']
385
-
386
- for k in range(2):
387
- hbox_channel = QHBoxLayout()
388
- hbox_channel.addWidget(QLabel(f'channel {k+1}: '))
389
- hbox_channel.addWidget(self.cellpose_channel_cb[k])
390
- if k==1:
391
- self.cellpose_channel_cb[k].addItems(list(self.attr_parent.exp_channels)+['None'])
392
- else:
393
- self.cellpose_channel_cb[k].addItems(list(self.attr_parent.exp_channels))
394
- idx = self.cellpose_channel_cb[k].findText(self.cellpose_channel_template[k])
395
- if idx>0:
396
- self.cellpose_channel_cb[k].setCurrentIndex(idx)
397
- else:
398
- self.cellpose_channel_cb[k].setCurrentIndex(0)
399
-
400
- if k==1:
401
- idx = self.cellpose_channel_cb[k].findText('None')
402
- self.cellpose_channel_cb[k].setCurrentIndex(idx)
403
-
404
- self.layout.addLayout(hbox_channel)
405
-
406
- # Layout for diameter input and button
407
- hbox = QHBoxLayout()
408
- hbox.addWidget(QLabel('diameter [px]: '), 33)
409
- hbox.addWidget(self.diameter_le, 61)
410
- hbox.addWidget(self.view_diameter_btn)
411
- self.layout.addLayout(hbox)
412
-
413
- # Flow threshold slider
414
- self.flow_slider = QLabeledDoubleSlider()
415
- self.flow_slider.setOrientation(Qt.Horizontal)
416
- self.flow_slider.setRange(-6,6)
417
- self.flow_slider.setValue(0.4)
418
- hbox = QHBoxLayout()
419
- hbox.addWidget(QLabel('flow threshold: '), 33)
420
- hbox.addWidget(self.flow_slider, 66)
421
- self.layout.addLayout(hbox)
422
-
423
- # Cell probability threshold slider
424
- self.cellprob_slider = QLabeledDoubleSlider()
425
- self.cellprob_slider.setOrientation(Qt.Horizontal)
426
- self.cellprob_slider.setRange(-6,6)
427
- self.cellprob_slider.setValue(0.)
428
- hbox = QHBoxLayout()
429
- hbox.addWidget(QLabel('cellprob threshold: '), 33)
430
- hbox.addWidget(self.cellprob_slider, 66)
431
- self.layout.addLayout(hbox)
432
-
433
- # Button to set the scale for Cellpose segmentation
434
- self.set_cellpose_scale_btn = QPushButton('set')
435
- self.set_cellpose_scale_btn.setStyleSheet(self.button_style_sheet)
436
- self.set_cellpose_scale_btn.clicked.connect(self.parent_window.set_cellpose_scale)
437
- self.layout.addWidget(self.set_cellpose_scale_btn)
438
-
439
- def view_current_stack_with_scale_bar(self):
440
-
441
- """
442
- Displays the current image stack with a scale bar, allowing users to visually estimate cell diameters.
443
- """
444
-
445
- self.attr_parent.locate_image()
446
- if self.attr_parent.current_stack is not None:
447
- max_size = np.amax([self.attr_parent.shape_x, self.attr_parent.shape_y])
448
- self.viewer = CellSizeViewer(
449
- initial_diameter = float(self.diameter_le.text().replace(',', '.')),
450
- parent_le = self.diameter_le,
451
- stack_path=self.attr_parent.current_stack,
452
- window_title=f'Position {self.attr_parent.position_list.currentText()}',
453
- diameter_slider_range=(0, max_size),
454
- frame_slider = True,
455
- contrast_slider = True,
456
- channel_cb = True,
457
- channel_names = self.attr_parent.exp_channels,
458
- n_channels = self.attr_parent.nbr_channels,
459
- PxToUm = 1,
460
- )
461
- self.viewer.show()
462
-
463
- class ChannelNormGenerator(QVBoxLayout, Styles):
464
-
465
- """Generator for list of channels"""
466
-
467
- def __init__(self, parent_window=None, init_n_channels=4, mode='signals', *args):
468
- super().__init__(*args)
469
-
470
- self.parent_window = parent_window
471
- self.mode = mode
472
- self.init_n_channels = init_n_channels
473
-
474
- if hasattr(self.parent_window.parent_window, 'locate_image'):
475
- self.attr_parent = self.parent_window.parent_window
476
- elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
477
- self.attr_parent = self.parent_window.parent_window.parent_window
478
- else:
479
- self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
480
-
481
- self.channel_names = self.attr_parent.exp_channels
482
- self.setContentsMargins(15,15,15,15)
483
- self.generate_widgets()
484
- self.add_to_layout()
485
-
486
- def generate_widgets(self):
487
-
488
- self.channel_cbs = [QSearchableComboBox() for i in range(self.init_n_channels)]
489
- self.channel_labels = [QLabel() for i in range(self.init_n_channels)]
490
-
491
- self.normalization_mode_btns = [QPushButton('') for i in range(self.init_n_channels)]
492
- self.normalization_mode = [True for i in range(self.init_n_channels)]
493
- self.normalization_clip_btns = [QPushButton('') for i in range(self.init_n_channels)]
494
- self.clip_option = [False for i in range(self.init_n_channels)]
495
-
496
- for i in range(self.init_n_channels):
497
- self.normalization_mode_btns[i].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
498
- self.normalization_mode_btns[i].setIconSize(QSize(20, 20))
499
- self.normalization_mode_btns[i].setStyleSheet(self.button_select_all)
500
- self.normalization_mode_btns[i].setToolTip("Switch to absolute normalization values.")
501
- self.normalization_mode_btns[i].clicked.connect(partial(self.switch_normalization_mode, i))
502
-
503
- self.normalization_clip_btns[i].setIcon(icon(MDI6.content_cut,color="black"))
504
- self.normalization_clip_btns[i].setIconSize(QSize(20, 20))
505
- self.normalization_clip_btns[i].setStyleSheet(self.button_select_all)
506
- self.normalization_clip_btns[i].clicked.connect(partial(self.switch_clipping_mode, i))
507
- self.normalization_clip_btns[i].setToolTip('clip')
508
-
509
- self.normalization_min_value_lbl = [QLabel('Min %: ') for i in range(self.init_n_channels)]
510
- self.normalization_min_value_le = [QLineEdit('0.1') for i in range(self.init_n_channels)]
511
- self.normalization_max_value_lbl = [QLabel('Max %: ') for i in range(self.init_n_channels)]
512
- self.normalization_max_value_le = [QLineEdit('99.99') for i in range(self.init_n_channels)]
513
-
514
- if self.mode=='signals':
515
- tables = glob(self.parent_window.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{self.parent_window.mode}.csv']))
516
- all_measurements = []
517
- for tab in tables:
518
- cols = pd.read_csv(tab, nrows=1).columns.tolist()
519
- all_measurements.extend(cols)
520
- all_measurements = np.unique(all_measurements)
521
-
522
- if self.mode=='signals':
523
- generic_measurements = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel',
524
- 'effector_fluo_channel', 'adhesion_channel', 'fluo_channel_1', 'fluo_channel_2',
525
- "area", "area_bbox","area_convex","area_filled","major_axis_length",
526
- "minor_axis_length",
527
- "eccentricity",
528
- "equivalent_diameter_area",
529
- "euler_number",
530
- "extent",
531
- "feret_diameter_max",
532
- "orientation",
533
- "perimeter",
534
- "perimeter_crofton",
535
- "solidity",
536
- "angular_second_moment",
537
- "contrast",
538
- "correlation",
539
- "sum_of_square_variance",
540
- "inverse_difference_moment",
541
- "sum_average",
542
- "sum_variance",
543
- "sum_entropy",
544
- "entropy",
545
- "difference_variance",
546
- "difference_entropy",
547
- "information_measure_of_correlation_1",
548
- "information_measure_of_correlation_2",
549
- "maximal_correlation_coefficient",
550
- "POSITION_X",
551
- "POSITION_Y",
552
- ]
553
- elif self.mode=='channels':
554
- generic_measurements = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel',
555
- 'effector_fluo_channel', 'adhesion_channel', 'fluo_channel_1', 'fluo_channel_2', 'None']
556
-
557
- if self.mode=='channels':
558
- all_measurements = []
559
- exp_ch = self.attr_parent.exp_channels
560
- for c in exp_ch:
561
- all_measurements.append(c)
562
-
563
- self.channel_items = np.unique(generic_measurements + list(all_measurements))
564
- self.channel_items = np.insert(self.channel_items, 0, '--')
565
-
566
- self.add_col_btn = QPushButton('Add channel')
567
- self.add_col_btn.clicked.connect(self.add_channel)
568
- self.add_col_btn.setStyleSheet(self.button_add)
569
- self.add_col_btn.setIcon(icon(MDI6.plus,color="black"))
570
-
571
- def add_channel(self):
572
-
573
- self.channel_cbs.append(QSearchableComboBox())
574
- self.channel_labels.append(QLabel())
575
- self.channel_cbs[-1].addItems(self.channel_items)
576
- self.channel_cbs[-1].currentIndexChanged.connect(self.check_valid_channels)
577
- self.channel_labels[-1].setText(f'channel {len(self.channel_cbs)-1}: ')
578
-
579
- self.normalization_mode_btns.append(QPushButton(''))
580
- self.normalization_mode.append(True)
581
- self.normalization_clip_btns.append(QPushButton(''))
582
- self.clip_option.append(False)
583
-
584
- self.normalization_mode_btns[-1].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
585
- self.normalization_mode_btns[-1].setIconSize(QSize(20, 20))
586
- self.normalization_mode_btns[-1].setStyleSheet(self.button_select_all)
587
- self.normalization_mode_btns[-1].setToolTip("Switch to absolute normalization values.")
588
- self.normalization_mode_btns[-1].clicked.connect(partial(self.switch_normalization_mode, len(self.channel_cbs)-1))
589
-
590
- self.normalization_clip_btns[-1].setIcon(icon(MDI6.content_cut,color="black"))
591
- self.normalization_clip_btns[-1].setIconSize(QSize(20, 20))
592
- self.normalization_clip_btns[-1].setStyleSheet(self.button_select_all)
593
- self.normalization_clip_btns[-1].clicked.connect(partial(self.switch_clipping_mode, len(self.channel_cbs)-1))
594
- self.normalization_clip_btns[-1].setToolTip('clip')
595
-
596
- self.normalization_min_value_lbl.append(QLabel('Min %: '))
597
- self.normalization_min_value_le.append(QLineEdit('0.1'))
598
- self.normalization_max_value_lbl.append(QLabel('Max %: '))
599
- self.normalization_max_value_le.append(QLineEdit('99.99'))
600
-
601
- ch_layout = QHBoxLayout()
602
- ch_layout.addWidget(self.channel_labels[-1], 30)
603
- ch_layout.addWidget(self.channel_cbs[-1], 70)
604
- self.channels_vb.addLayout(ch_layout)
605
-
606
- channel_norm_options_layout = QHBoxLayout()
607
- channel_norm_options_layout.addWidget(QLabel(''),30)
608
- ch_norm_sublayout = QHBoxLayout()
609
- ch_norm_sublayout.addWidget(self.normalization_min_value_lbl[-1])
610
- ch_norm_sublayout.addWidget(self.normalization_min_value_le[-1])
611
- ch_norm_sublayout.addWidget(self.normalization_max_value_lbl[-1])
612
- ch_norm_sublayout.addWidget(self.normalization_max_value_le[-1])
613
- ch_norm_sublayout.addWidget(self.normalization_clip_btns[-1])
614
- ch_norm_sublayout.addWidget(self.normalization_mode_btns[-1])
615
- channel_norm_options_layout.addLayout(ch_norm_sublayout, 70)
616
-
617
- self.channels_vb.addLayout(channel_norm_options_layout)
618
-
619
-
620
- def add_to_layout(self):
621
-
622
- self.channels_vb = QVBoxLayout()
623
- self.channel_option_layouts = []
624
- for i in range(len(self.channel_cbs)):
625
-
626
- ch_layout = QHBoxLayout()
627
- self.channel_labels[i].setText(f'channel {i}: ')
628
- ch_layout.addWidget(self.channel_labels[i], 30)
629
- self.channel_cbs[i].addItems(self.channel_items)
630
- self.channel_cbs[i].currentIndexChanged.connect(self.check_valid_channels)
631
- ch_layout.addWidget(self.channel_cbs[i], 70)
632
- self.channels_vb.addLayout(ch_layout)
633
-
634
- channel_norm_options_layout = QHBoxLayout()
635
- #channel_norm_options_layout.setContentsMargins(130,0,0,0)
636
- channel_norm_options_layout.addWidget(QLabel(''),30)
637
- ch_norm_sublayout = QHBoxLayout()
638
- ch_norm_sublayout.addWidget(self.normalization_min_value_lbl[i])
639
- ch_norm_sublayout.addWidget(self.normalization_min_value_le[i])
640
- ch_norm_sublayout.addWidget(self.normalization_max_value_lbl[i])
641
- ch_norm_sublayout.addWidget(self.normalization_max_value_le[i])
642
- ch_norm_sublayout.addWidget(self.normalization_clip_btns[i])
643
- ch_norm_sublayout.addWidget(self.normalization_mode_btns[i])
644
- channel_norm_options_layout.addLayout(ch_norm_sublayout, 70)
645
- self.channels_vb.addLayout(channel_norm_options_layout)
646
-
647
- self.addLayout(self.channels_vb)
648
-
649
- add_hbox = QHBoxLayout()
650
- add_hbox.addWidget(QLabel(''), 66)
651
- add_hbox.addWidget(self.add_col_btn, 33, alignment=Qt.AlignRight)
652
- self.addLayout(add_hbox)
653
-
654
- def switch_normalization_mode(self, index):
655
-
656
- """
657
- Use absolute or percentile values for the normalization of each individual channel.
658
-
659
- """
660
-
661
- currentNormMode = self.normalization_mode[index]
662
- self.normalization_mode[index] = not currentNormMode
663
-
664
- if self.normalization_mode[index]:
665
- self.normalization_mode_btns[index].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
666
- self.normalization_mode_btns[index].setIconSize(QSize(20, 20))
667
- self.normalization_mode_btns[index].setStyleSheet(self.button_select_all)
668
- self.normalization_mode_btns[index].setToolTip("Switch to absolute normalization values.")
669
- self.normalization_min_value_lbl[index].setText('Min %: ')
670
- self.normalization_max_value_lbl[index].setText('Max %: ')
671
- self.normalization_min_value_le[index].setText('0.1')
672
- self.normalization_max_value_le[index].setText('99.99')
673
-
674
- else:
675
- self.normalization_mode_btns[index].setIcon(icon(MDI6.percent_circle_outline,color="black"))
676
- self.normalization_mode_btns[index].setIconSize(QSize(20, 20))
677
- self.normalization_mode_btns[index].setStyleSheet(self.button_select_all)
678
- self.normalization_mode_btns[index].setToolTip("Switch to percentile normalization values.")
679
- self.normalization_min_value_lbl[index].setText('Min: ')
680
- self.normalization_min_value_le[index].setText('0')
681
- self.normalization_max_value_lbl[index].setText('Max: ')
682
- self.normalization_max_value_le[index].setText('1000')
683
-
684
- def switch_clipping_mode(self, index):
685
-
686
- currentClipMode = self.clip_option[index]
687
- self.clip_option[index] = not currentClipMode
688
-
689
- if self.clip_option[index]:
690
- self.normalization_clip_btns[index].setIcon(icon(MDI6.content_cut,color="#1565c0"))
691
- self.normalization_clip_btns[index].setIconSize(QSize(20, 20))
692
- self.normalization_clip_btns[index].setStyleSheet(self.button_select_all)
693
-
694
- else:
695
- self.normalization_clip_btns[index].setIcon(icon(MDI6.content_cut,color="black"))
696
- self.normalization_clip_btns[index].setIconSize(QSize(20, 20))
697
- self.normalization_clip_btns[index].setStyleSheet(self.button_select_all)
698
-
699
- def check_valid_channels(self):
700
-
701
- if hasattr(self.parent_window, "submit_btn"):
702
- if np.all([cb.currentText()=='--' for cb in self.channel_cbs]):
703
- self.parent_window.submit_btn.setEnabled(False)
704
-
705
- if hasattr(self.parent_window, "spatial_calib_le") and hasattr(self.parent_window, "submit_btn"):
706
- if self.parent_window.spatial_calib_le.text()!='--':
707
- self.parent_window.submit_btn.setEnabled(True)
708
- elif hasattr(self.parent_window, "submit_btn"):
709
- self.parent_window.submit_btn.setEnabled(True)
710
-
711
-
712
-
713
- class BackgroundFitCorrectionLayout(QGridLayout, Styles):
714
-
715
- """docstring for ClassName"""
716
-
717
- def __init__(self, parent_window=None, *args):
718
- super().__init__(*args)
719
-
720
- self.parent_window = parent_window
721
-
722
- if hasattr(self.parent_window.parent_window, 'locate_image'):
723
- self.attr_parent = self.parent_window.parent_window
724
- elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
725
- self.attr_parent = self.parent_window.parent_window.parent_window
726
- else:
727
- self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
728
-
729
- self.channel_names = self.attr_parent.exp_channels
730
- self.setContentsMargins(15,15,15,15)
731
- self.generate_widgets()
732
- self.add_to_layout()
733
-
734
- def generate_widgets(self):
735
-
736
- self.channel_lbl = QLabel('Channel: ')
737
- self.channels_cb = QComboBox()
738
- self.channels_cb.addItems(self.channel_names)
739
-
740
- self.thresh_lbl = QLabel('Threshold: ')
741
- self.thresh_lbl.setToolTip('Threshold on the STD-filtered image.\nPixel values above the threshold are\nconsidered as non-background and are\nmasked prior to background estimation.')
742
- self.threshold_viewer_btn = QPushButton()
743
- self.threshold_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
744
- self.threshold_viewer_btn.setStyleSheet(self.button_select_all)
745
- self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
746
- self.threshold_viewer_btn.setToolTip('Set the threshold graphically.')
747
-
748
- self.model_lbl = QLabel('Model: ')
749
- self.model_lbl.setToolTip('2D model to fit the background with.')
750
- self.models_cb = QComboBox()
751
- self.models_cb.addItems(['paraboloid', 'plane'])
752
- self.models_cb.setToolTip('2D model to fit the background with.')
753
-
754
- self.corrected_stack_viewer = QPushButton("")
755
- self.corrected_stack_viewer.setStyleSheet(self.button_select_all)
756
- self.corrected_stack_viewer.setIcon(icon(MDI6.eye_outline, color="black"))
757
- self.corrected_stack_viewer.setToolTip("View corrected image")
758
- self.corrected_stack_viewer.clicked.connect(self.preview_correction)
759
- self.corrected_stack_viewer.setIconSize(QSize(20, 20))
760
-
761
- self.add_correction_btn = QPushButton('Add correction')
762
- self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
763
- self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
764
- self.add_correction_btn.setToolTip('Add correction.')
765
- self.add_correction_btn.setIconSize(QSize(25, 25))
766
- self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
767
-
768
- self.threshold_le = ThresholdLineEdit(init_value=2, connected_buttons=[self.threshold_viewer_btn,
769
- self.corrected_stack_viewer,
770
- self.add_correction_btn
771
- ])
772
-
773
-
774
- def add_to_layout(self):
775
-
776
- channel_layout = QHBoxLayout()
777
- channel_layout.addWidget(self.channel_lbl, 25)
778
- channel_layout.addWidget(self.channels_cb, 75)
779
- self.addLayout(channel_layout, 0, 0, 1, 3)
780
-
781
- threshold_layout = QHBoxLayout()
782
- threshold_layout.addWidget(self.thresh_lbl, 25)
783
- subthreshold_layout = QHBoxLayout()
784
- subthreshold_layout.addWidget(self.threshold_le, 95)
785
- subthreshold_layout.addWidget(self.threshold_viewer_btn, 5)
786
-
787
- threshold_layout.addLayout(subthreshold_layout, 75)
788
- self.addLayout(threshold_layout, 1, 0, 1, 3)
789
-
790
- model_layout = QHBoxLayout()
791
- model_layout.addWidget(self.model_lbl, 25)
792
- model_layout.addWidget(self.models_cb, 75)
793
- self.addLayout(model_layout, 2, 0, 1, 3)
794
-
795
- self.operation_layout = OperationLayout()
796
- self.addLayout(self.operation_layout, 3, 0, 1, 3)
797
-
798
- correction_layout = QHBoxLayout()
799
- correction_layout.addWidget(self.add_correction_btn, 95)
800
- correction_layout.addWidget(self.corrected_stack_viewer, 5)
801
- self.addLayout(correction_layout, 4, 0, 1, 3)
802
-
803
- verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
804
- self.addItem(verticalSpacer, 5, 0, 1, 3)
805
-
806
- def add_instructions_to_parent_list(self):
807
-
808
- self.generate_instructions()
809
- self.parent_window.protocols.append(self.instructions)
810
- correction_description = ""
811
- for index, (key, value) in enumerate(self.instructions.items()):
812
- if index > 0:
813
- correction_description += ", "
814
- correction_description += str(key) + " : " + str(value)
815
- self.parent_window.protocol_list.addItem(correction_description)
816
-
817
- def generate_instructions(self):
818
-
819
- if self.operation_layout.subtract_btn.isChecked():
820
- operation = "subtract"
821
- else:
822
- operation = "divide"
823
- clip = None
824
-
825
- if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
826
- clip = True
827
- else:
828
- clip = False
829
-
830
- self.instructions = {
831
- "target_channel": self.channels_cb.currentText(),
832
- "correction_type": "fit",
833
- "model": self.models_cb.currentText(),
834
- "threshold_on_std": self.threshold_le.get_threshold(),
835
- "operation": operation,
836
- "clip": clip
837
- }
838
-
839
- def set_target_channel(self):
840
-
841
- channel_indices = _extract_channel_indices_from_config(self.attr_parent.exp_config, [self.channels_cb.currentText()])
842
- self.target_channel = channel_indices[0]
843
-
844
- def set_threshold_graphically(self):
845
-
846
- self.attr_parent.locate_image()
847
- self.set_target_channel()
848
- thresh = self.threshold_le.get_threshold()
849
-
850
- if self.attr_parent.current_stack is not None and thresh is not None:
851
- self.viewer = ThresholdedStackVisualizer(initial_threshold=thresh,
852
- parent_le = self.threshold_le,
853
- preprocessing=[['gauss',2],["std",4]],
854
- stack_path=self.attr_parent.current_stack,
855
- n_channels=len(self.channel_names),
856
- target_channel=self.target_channel,
857
- window_title='Set the exclusion threshold',
858
- )
859
- self.viewer.show()
860
-
861
- def preview_correction(self):
862
-
863
- if self.attr_parent.well_list.isMultipleSelection() or not self.attr_parent.well_list.isAnySelected() or self.attr_parent.position_list.isMultipleSelection() or not self.attr_parent.position_list.isAnySelected():
864
-
865
- msgBox = QMessageBox()
866
- msgBox.setIcon(QMessageBox.Critical)
867
- msgBox.setText("Please select a single position...")
868
- msgBox.setWindowTitle("Critical")
869
- msgBox.setStandardButtons(QMessageBox.Ok)
870
- returnValue = msgBox.exec()
871
- if returnValue == QMessageBox.Ok:
872
- return None
873
-
874
- if self.operation_layout.subtract_btn.isChecked():
875
- operation = "subtract"
876
- else:
877
- operation = "divide"
878
- clip = None
879
-
880
- if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
881
- clip = True
882
- else:
883
- clip = False
884
-
885
- corrected_stack = correct_background_model(self.attr_parent.exp_dir,
886
- well_option=self.attr_parent.well_list.getSelectedIndices(), #+1 ??
887
- position_option=self.attr_parent.position_list.getSelectedIndices(), #+1??
888
- target_channel=self.channels_cb.currentText(),
889
- model = self.models_cb.currentText(),
890
- threshold_on_std = self.threshold_le.get_threshold(),
891
- operation = operation,
892
- clip = clip,
893
- export= False,
894
- return_stacks=True,
895
- activation_protocol=[['gauss',2],['std',4]],
896
- show_progress_per_well = True,
897
- show_progress_per_pos = False,
898
- )
899
-
900
- if corrected_stack:
901
- self.viewer = StackVisualizer(
902
- stack=corrected_stack[0],
903
- window_title='Corrected channel',
904
- target_channel=self.channels_cb.currentIndex(),
905
- frame_slider = True,
906
- contrast_slider = True
907
- )
908
- self.viewer.show()
909
- else:
910
- print("Corrected stack could not be generated... No stack available...")
911
-
912
-
913
-
914
- class LocalCorrectionLayout(BackgroundFitCorrectionLayout):
915
-
916
- """docstring for ClassName"""
917
-
918
- def __init__(self, *args):
919
-
920
- super().__init__(*args)
921
-
922
- if hasattr(self.parent_window.parent_window, 'locate_image'):
923
- self.attr_parent = self.parent_window.parent_window
924
- elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
925
- self.attr_parent = self.parent_window.parent_window.parent_window
926
- else:
927
- self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
928
-
929
- self.thresh_lbl.setText('Distance: ')
930
- self.thresh_lbl.setToolTip('Distance from the cell mask over which to estimate local intensity.')
931
-
932
- self.models_cb.clear()
933
- self.models_cb.addItems(['mean','median'])
934
-
935
- self.threshold_le.set_threshold(5)
936
- self.threshold_le.connected_buttons = [self.threshold_viewer_btn,self.add_correction_btn]
937
- self.threshold_le.setValidator(QIntValidator())
938
-
939
- self.threshold_viewer_btn.disconnect()
940
- self.threshold_viewer_btn.clicked.connect(self.set_distance_graphically)
941
-
942
- self.corrected_stack_viewer.hide()
943
-
944
- def set_distance_graphically(self):
945
-
946
- self.attr_parent.locate_image()
947
- self.set_target_channel()
948
- thresh = self.threshold_le.get_threshold()
949
-
950
- if self.attr_parent.current_stack is not None and thresh is not None:
951
-
952
- self.viewer = CellEdgeVisualizer(cell_type=self.parent_window.parent_window.mode,
953
- stack_path=self.attr_parent.current_stack,
954
- parent_le = self.threshold_le,
955
- n_channels=len(self.channel_names),
956
- target_channel=self.channels_cb.currentIndex(),
957
- edge_range = (0,30),
958
- initial_edge=int(thresh),
959
- invert=True,
960
- window_title='Set an edge distance to estimate local intensity',
961
- channel_cb=False,
962
- PxToUm = 1,
963
- )
964
- self.viewer.show()
965
-
966
- def generate_instructions(self):
967
-
968
- if self.operation_layout.subtract_btn.isChecked():
969
- operation = "subtract"
970
- else:
971
- operation = "divide"
972
- clip = None
973
-
974
- if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
975
- clip = True
976
- else:
977
- clip = False
978
-
979
- self.instructions = {
980
- "target_channel": self.channels_cb.currentText(),
981
- "correction_type": "local",
982
- "model": self.models_cb.currentText(),
983
- "distance": int(self.threshold_le.get_threshold()),
984
- "operation": operation,
985
- "clip": clip,
986
- }
987
-
988
-
989
- class OperationLayout(QVBoxLayout):
990
-
991
- """docstring for ClassName"""
992
-
993
- def __init__(self, ratio=(0.25,0.75), *args):
994
-
995
- super().__init__(*args)
996
-
997
- self.ratio = ratio
998
- self.generate_widgets()
999
- self.generate_layout()
1000
-
1001
- def generate_widgets(self):
1002
-
1003
- self.operation_lbl = QLabel('Operation: ')
1004
- self.operation_group = QButtonGroup()
1005
- self.subtract_btn = QRadioButton('Subtract')
1006
- self.divide_btn = QRadioButton('Divide')
1007
- self.subtract_btn.toggled.connect(self.activate_clipping_options)
1008
- self.divide_btn.toggled.connect(self.activate_clipping_options)
1009
-
1010
- self.operation_group.addButton(self.subtract_btn)
1011
- self.operation_group.addButton(self.divide_btn)
1012
-
1013
- self.clip_group = QButtonGroup()
1014
- self.clip_btn = QRadioButton('Clip')
1015
- self.clip_not_btn = QRadioButton('Do not clip')
1016
-
1017
- self.clip_group.addButton(self.clip_btn)
1018
- self.clip_group.addButton(self.clip_not_btn)
1019
-
1020
- def generate_layout(self):
1021
-
1022
- operation_layout = QHBoxLayout()
1023
- operation_layout.addWidget(self.operation_lbl, 100*int(self.ratio[0]))
1024
- operation_layout.addWidget(self.subtract_btn, 100*int(self.ratio[1])//2, alignment=Qt.AlignCenter)
1025
- operation_layout.addWidget(self.divide_btn, 100*int(self.ratio[1])//2, alignment=Qt.AlignCenter)
1026
- self.addLayout(operation_layout)
1027
-
1028
- clip_layout = QHBoxLayout()
1029
- clip_layout.addWidget(QLabel(''), 100*int(self.ratio[0]))
1030
- clip_layout.addWidget(self.clip_btn, 100*int(self.ratio[1])//4, alignment=Qt.AlignCenter)
1031
- clip_layout.addWidget(self.clip_not_btn, 100*int(self.ratio[1])//4, alignment=Qt.AlignCenter)
1032
- clip_layout.addWidget(QLabel(''), 100*int(self.ratio[1])//2)
1033
- self.addLayout(clip_layout)
1034
-
1035
- self.subtract_btn.click()
1036
- self.clip_not_btn.click()
1037
-
1038
- def activate_clipping_options(self):
1039
-
1040
- if self.subtract_btn.isChecked():
1041
- self.clip_btn.setEnabled(True)
1042
- self.clip_not_btn.setEnabled(True)
1043
- else:
1044
- self.clip_btn.setEnabled(False)
1045
- self.clip_not_btn.setEnabled(False)
1046
-
1047
- class ProtocolDesignerLayout(QVBoxLayout, Styles):
1048
-
1049
- """Multi tabs and list widget configuration for background correction
1050
- in preprocessing and measurements
1051
- """
1052
-
1053
- def __init__(self, parent_window=None, tab_layouts=[], tab_names=[], title='',list_title='',*args):
1054
-
1055
- super().__init__(*args)
1056
-
1057
- self.title = title
1058
- self.parent_window = parent_window
1059
- self.channel_names = self.parent_window.channel_names
1060
- self.tab_layouts = tab_layouts
1061
- self.tab_names = tab_names
1062
- self.list_title = list_title
1063
- self.protocols = []
1064
- assert len(self.tab_layouts)==len(self.tab_names)
1065
-
1066
- self.generate_widgets()
1067
- self.generate_layout()
1068
-
1069
- def generate_widgets(self):
1070
-
1071
- self.title_lbl = QLabel(self.title)
1072
- self.title_lbl.setStyleSheet("""
1073
- font-weight: bold;
1074
- padding: 0px;
1075
- """)
1076
-
1077
- self.tabs = QTabWidget()
1078
- self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
1079
-
1080
- for k in range(len(self.tab_layouts)):
1081
- wg = CelldetectiveWidget()
1082
- self.tab_layouts[k].parent_window = self
1083
- wg.setLayout(self.tab_layouts[k])
1084
- self.tabs.addTab(wg, self.tab_names[k])
1085
-
1086
- self.protocol_list_lbl = QLabel(self.list_title)
1087
- self.protocol_list = QListWidget()
1088
-
1089
- self.delete_protocol_btn = QPushButton('')
1090
- self.delete_protocol_btn.setStyleSheet(self.button_select_all)
1091
- self.delete_protocol_btn.setIcon(icon(MDI6.trash_can, color="black"))
1092
- self.delete_protocol_btn.setToolTip("Remove.")
1093
- self.delete_protocol_btn.setIconSize(QSize(20, 20))
1094
- self.delete_protocol_btn.clicked.connect(self.remove_protocol_from_list)
1095
-
1096
- def generate_layout(self):
1097
-
1098
- self.correction_layout = QVBoxLayout()
1099
-
1100
- self.background_correction_layout = QVBoxLayout()
1101
- self.background_correction_layout.setContentsMargins(0,0,0,0)
1102
- self.title_layout = QHBoxLayout()
1103
- self.title_layout.addWidget(self.title_lbl, 100, alignment=Qt.AlignCenter)
1104
- self.background_correction_layout.addLayout(self.title_layout)
1105
- self.background_correction_layout.addWidget(self.tabs)
1106
- self.correction_layout.addLayout(self.background_correction_layout)
1107
-
1108
- self.addLayout(self.correction_layout)
1109
-
1110
- self.list_layout = QVBoxLayout()
1111
- list_header_layout = QHBoxLayout()
1112
- list_header_layout.addWidget(self.protocol_list_lbl)
1113
- list_header_layout.addWidget(self.delete_protocol_btn, alignment=Qt.AlignRight)
1114
- self.list_layout.addLayout(list_header_layout)
1115
- self.list_layout.addWidget(self.protocol_list)
1116
-
1117
- self.addLayout(self.list_layout)
1118
-
1119
-
1120
- def remove_protocol_from_list(self):
1121
-
1122
- current_item = self.protocol_list.currentRow()
1123
- if current_item > -1:
1124
- del self.protocols[current_item]
1125
- self.protocol_list.takeItem(current_item)
1126
-
1127
- class ChannelOffsetOptionsLayout(QVBoxLayout, Styles):
1128
-
1129
- def __init__(self, parent_window=None, *args, **kwargs):
1130
-
1131
- super().__init__(*args, **kwargs)
1132
-
1133
- self.parent_window = parent_window
1134
- if hasattr(self.parent_window.parent_window, 'exp_config'):
1135
- self.attr_parent = self.parent_window.parent_window
1136
- else:
1137
- self.attr_parent = self.parent_window.parent_window.parent_window
1138
-
1139
- self.channel_names = self.attr_parent.exp_channels
1140
-
1141
- self.setContentsMargins(15,15,15,15)
1142
- self.generate_widgets()
1143
- self.add_to_layout()
1144
-
1145
- def generate_widgets(self):
1146
-
1147
- self.channel_lbl = QLabel('Channel: ')
1148
- self.channels_cb = QComboBox()
1149
- self.channels_cb.addItems(self.channel_names)
1150
-
1151
- self.shift_lbl = QLabel('Shift: ')
1152
- self.shift_h_lbl = QLabel('(h): ')
1153
- self.shift_v_lbl = QLabel('(v): ')
1154
-
1155
- self.set_shift_btn = QPushButton()
1156
- self.set_shift_btn.setIcon(icon(MDI6.image_check, color="k"))
1157
- self.set_shift_btn.setStyleSheet(self.button_select_all)
1158
- self.set_shift_btn.setToolTip('Set the channel shift.')
1159
- self.set_shift_btn.clicked.connect(self.open_offset_viewer)
1160
-
1161
- self.add_correction_btn = QPushButton('Add correction')
1162
- self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
1163
- self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
1164
- self.add_correction_btn.setToolTip('Add correction.')
1165
- self.add_correction_btn.setIconSize(QSize(25, 25))
1166
- self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
1167
-
1168
- self.vertical_shift_le = ThresholdLineEdit(init_value=0, connected_buttons=[self.add_correction_btn],placeholder='vertical shift [pixels]', value_type='float')
1169
- self.horizontal_shift_le = ThresholdLineEdit(init_value=0, connected_buttons=[self.add_correction_btn],placeholder='vertical shift [pixels]', value_type='float')
1170
-
1171
- def add_to_layout(self):
1172
-
1173
- channel_ch_hbox = QHBoxLayout()
1174
- channel_ch_hbox.addWidget(self.channel_lbl, 25)
1175
- channel_ch_hbox.addWidget(self.channels_cb, 75)
1176
- self.addLayout(channel_ch_hbox)
1177
-
1178
- shift_hbox = QHBoxLayout()
1179
- shift_hbox.addWidget(self.shift_lbl, 25)
1180
-
1181
- shift_subhbox = QHBoxLayout()
1182
- shift_subhbox.addWidget(self.shift_h_lbl, 10)
1183
- shift_subhbox.addWidget(self.horizontal_shift_le, 75//2)
1184
- shift_subhbox.addWidget(self.shift_v_lbl, 10)
1185
- shift_subhbox.addWidget(self.vertical_shift_le, 75//2)
1186
- shift_subhbox.addWidget(self.set_shift_btn, 5)
1187
-
1188
- shift_hbox.addLayout(shift_subhbox, 75)
1189
- self.addLayout(shift_hbox)
1190
-
1191
- btn_hbox = QHBoxLayout()
1192
- btn_hbox.addWidget(self.add_correction_btn, 95)
1193
- self.addLayout(btn_hbox)
1194
-
1195
- def add_instructions_to_parent_list(self):
1196
-
1197
- self.generate_instructions()
1198
- self.parent_window.protocol_layout.protocols.append(self.instructions)
1199
- correction_description = ""
1200
- for index, (key, value) in enumerate(self.instructions.items()):
1201
- if index > 0:
1202
- correction_description += ", "
1203
- correction_description += str(key) + " : " + str(value)
1204
- self.parent_window.protocol_layout.protocol_list.addItem(correction_description)
1205
-
1206
- def generate_instructions(self):
1207
-
1208
- self.instructions = {
1209
- "correction_type": "offset",
1210
- "target_channel": self.channels_cb.currentText(),
1211
- "correction_horizontal": self.horizontal_shift_le.get_threshold(),
1212
- "correction_vertical": self.vertical_shift_le.get_threshold(),
1213
- }
1214
-
1215
-
1216
- def set_target_channel(self):
1217
-
1218
- channel_indices = _extract_channel_indices_from_config(self.attr_parent.exp_config, [self.channels_cb.currentText()])
1219
- self.target_channel = channel_indices[0]
1220
-
1221
- def open_offset_viewer(self):
1222
-
1223
- self.attr_parent.locate_image()
1224
- self.set_target_channel()
1225
-
1226
- if self.attr_parent.current_stack is not None:
1227
- self.viewer = ChannelOffsetViewer(
1228
- parent_window = self,
1229
- stack_path=self.attr_parent.current_stack,
1230
- channel_names=self.attr_parent.exp_channels,
1231
- n_channels=len(self.channel_names),
1232
- channel_cb=True,
1233
- target_channel=self.target_channel,
1234
- window_title='offset viewer',
1235
- )
1236
- self.viewer.show()
1237
-
1238
-
1239
- class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
1240
-
1241
- """docstring for ClassName"""
1242
-
1243
- def __init__(self, parent_window=None, *args):
1244
- super().__init__(*args)
1245
-
1246
- self.parent_window = parent_window
1247
-
1248
- if hasattr(self.parent_window.parent_window, 'exp_config'):
1249
- self.attr_parent = self.parent_window.parent_window
1250
- else:
1251
- self.attr_parent = self.parent_window.parent_window.parent_window
1252
-
1253
- self.channel_names = self.attr_parent.exp_channels
1254
-
1255
- self.setContentsMargins(15,15,15,15)
1256
- self.generate_widgets()
1257
- self.add_to_layout()
1258
-
1259
- def generate_widgets(self):
1260
-
1261
- self.channel_lbl = QLabel('Channel: ')
1262
- self.channels_cb = QComboBox()
1263
- self.channels_cb.addItems(self.channel_names)
1264
-
1265
- self.acquistion_lbl = QLabel('Stack mode: ')
1266
- self.acq_mode_group = QButtonGroup()
1267
- self.timeseries_rb = QRadioButton('timeseries')
1268
- self.timeseries_rb.setChecked(True)
1269
- self.tiles_rb = QRadioButton('tiles')
1270
- self.acq_mode_group.addButton(self.timeseries_rb, 0)
1271
- self.acq_mode_group.addButton(self.tiles_rb, 1)
1272
-
1273
- from PyQt5.QtWidgets import QSlider
1274
- from superqt import QRangeSlider
1275
- self.frame_range_slider = QLabeledRangeSlider(parent=None)
1276
-
1277
- self.timeseries_rb.toggled.connect(self.activate_time_range)
1278
- self.tiles_rb.toggled.connect(self.activate_time_range)
1279
-
1280
- self.thresh_lbl = QLabel('Threshold: ')
1281
- self.thresh_lbl.setToolTip('Threshold on the STD-filtered image.\nPixel values above the threshold are\nconsidered as non-background and are\nmasked prior to background estimation.')
1282
- self.threshold_viewer_btn = QPushButton()
1283
- self.threshold_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
1284
- self.threshold_viewer_btn.setStyleSheet(self.button_select_all)
1285
- self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
1286
-
1287
- self.background_viewer_btn = QPushButton()
1288
- self.background_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
1289
- self.background_viewer_btn.setStyleSheet(self.button_select_all)
1290
- self.background_viewer_btn.setToolTip('View reconstructed background.')
1291
-
1292
- self.corrected_stack_viewer_btn = QPushButton("")
1293
- self.corrected_stack_viewer_btn.setStyleSheet(self.button_select_all)
1294
- self.corrected_stack_viewer_btn.setIcon(icon(MDI6.eye_outline, color="black"))
1295
- self.corrected_stack_viewer_btn.setToolTip("View corrected image")
1296
- self.corrected_stack_viewer_btn.clicked.connect(self.preview_correction)
1297
- self.corrected_stack_viewer_btn.setIconSize(QSize(20, 20))
1298
-
1299
- self.add_correction_btn = QPushButton('Add correction')
1300
- self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
1301
- self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
1302
- self.add_correction_btn.setToolTip('Add correction.')
1303
- self.add_correction_btn.setIconSize(QSize(25, 25))
1304
- self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
1305
-
1306
- self.threshold_le = ThresholdLineEdit(init_value=2, connected_buttons=[self.threshold_viewer_btn,
1307
- self.background_viewer_btn, self.corrected_stack_viewer_btn, self.add_correction_btn])
1308
-
1309
- self.well_slider = QLabeledSlider(parent=None)
1310
-
1311
- self.background_viewer_btn.clicked.connect(self.estimate_bg)
1312
-
1313
- self.regress_cb = QCheckBox('Optimize for each frame?')
1314
- self.regress_cb.toggled.connect(self.activate_coef_options)
1315
- self.regress_cb.setChecked(False)
1316
-
1317
- self.coef_range_slider = QLabeledDoubleRangeSlider(parent=None)
1318
- self.coef_range_layout = QuickSliderLayout(label='Coef. range: ',
1319
- slider = self.coef_range_slider,
1320
- slider_initial_value=(0.95,1.05),
1321
- slider_range=(0.75,1.25),
1322
- slider_tooltip='Coefficient range to increase or decrease the background intensity level...',
1323
- )
1324
-
1325
- self.nbr_coefs_lbl = QLabel("Nbr of coefs: ")
1326
- self.nbr_coefs_lbl.setToolTip('Number of coefficients to be tested within range.\nThe more, the slower.')
1327
-
1328
- self.nbr_coef_le = QLineEdit()
1329
- self.nbr_coef_le.setText('100')
1330
- self.nbr_coef_le.setValidator(QIntValidator())
1331
- self.nbr_coef_le.setPlaceholderText('nbr of coefs')
1332
-
1333
- self.coef_widgets = [self.coef_range_layout.qlabel, self.coef_range_slider, self.nbr_coefs_lbl, self.nbr_coef_le]
1334
- for c in self.coef_widgets:
1335
- c.setEnabled(False)
1336
-
1337
- self.interpolate_check = QCheckBox("interpolate NaNs")
1338
-
1339
- def add_to_layout(self):
1340
-
1341
- channel_layout = QHBoxLayout()
1342
- channel_layout.addWidget(self.channel_lbl, 25)
1343
- channel_layout.addWidget(self.channels_cb, 75)
1344
- self.addLayout(channel_layout, 0, 0, 1, 3)
1345
-
1346
- acquisition_layout = QHBoxLayout()
1347
- acquisition_layout.addWidget(self.acquistion_lbl, 25)
1348
- acquisition_layout.addWidget(self.timeseries_rb, 75//2, alignment=Qt.AlignCenter)
1349
- acquisition_layout.addWidget(self.tiles_rb, 75//2, alignment=Qt.AlignCenter)
1350
- self.addLayout(acquisition_layout, 1, 0, 1, 3)
1351
-
1352
- frame_selection_layout = QuickSliderLayout(label='Time range: ',
1353
- slider = self.frame_range_slider,
1354
- slider_initial_value=(0,5),
1355
- slider_range=(0,self.attr_parent.len_movie),
1356
- slider_tooltip='frame [#]',
1357
- decimal_option = False,
1358
- )
1359
- frame_selection_layout.qlabel.setToolTip('Frame range for which the background\nis most likely to be observed.')
1360
- self.time_range_options = [self.frame_range_slider, frame_selection_layout.qlabel]
1361
- self.addLayout(frame_selection_layout, 2, 0, 1, 3)
1362
-
1363
-
1364
- threshold_layout = QHBoxLayout()
1365
- threshold_layout.addWidget(self.thresh_lbl, 25)
1366
- subthreshold_layout = QHBoxLayout()
1367
- subthreshold_layout.addWidget(self.threshold_le, 95)
1368
- subthreshold_layout.addWidget(self.threshold_viewer_btn, 5)
1369
- threshold_layout.addLayout(subthreshold_layout, 75)
1370
- self.addLayout(threshold_layout, 3, 0, 1, 3)
1371
-
1372
- background_layout = QuickSliderLayout(label='QC for well: ',
1373
- slider = self.well_slider,
1374
- slider_initial_value=1,
1375
- slider_range=(1,len(self.attr_parent.wells)),
1376
- slider_tooltip='well [#]',
1377
- decimal_option = False,
1378
- layout_ratio=(0.25,0.70)
1379
- )
1380
- background_layout.addWidget(self.background_viewer_btn, 5)
1381
- self.addLayout(background_layout, 4, 0, 1, 3)
1382
-
1383
- self.addWidget(self.regress_cb, 5, 0, 1, 3)
1384
-
1385
- self.addLayout(self.coef_range_layout, 6, 0, 1, 3)
1386
-
1387
- coef_nbr_layout = QHBoxLayout()
1388
- coef_nbr_layout.addWidget(self.nbr_coefs_lbl, 25)
1389
- coef_nbr_layout.addWidget(self.nbr_coef_le, 75)
1390
- self.addLayout(coef_nbr_layout, 7,0,1,3)
1391
-
1392
- offset_layout = QHBoxLayout()
1393
- offset_layout.addWidget(QLabel("Offset: "), 25)
1394
- self.camera_offset_le = QLineEdit("0")
1395
- self.camera_offset_le.setPlaceholderText('camera black level')
1396
- self.camera_offset_le.setValidator(QDoubleValidator())
1397
- offset_layout.addWidget(self.camera_offset_le, 75)
1398
- self.addLayout(offset_layout, 8, 0, 1, 3)
1399
-
1400
- self.operation_layout = OperationLayout()
1401
- self.addLayout(self.operation_layout, 9, 0, 1, 3)
1402
-
1403
- self.addWidget(self.interpolate_check, 10, 0, 1, 1)
1404
-
1405
- correction_layout = QHBoxLayout()
1406
- correction_layout.addWidget(self.add_correction_btn, 95)
1407
- correction_layout.addWidget(self.corrected_stack_viewer_btn, 5)
1408
- self.addLayout(correction_layout, 11, 0, 1, 3)
1409
-
1410
- # verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
1411
- # self.addItem(verticalSpacer, 5, 0, 1, 3)
1412
-
1413
- def add_instructions_to_parent_list(self):
1414
-
1415
- self.generate_instructions()
1416
- self.parent_window.protocols.append(self.instructions)
1417
- correction_description = ""
1418
- for index, (key, value) in enumerate(self.instructions.items()):
1419
- if index > 0:
1420
- correction_description += ", "
1421
- correction_description += str(key) + " : " + str(value)
1422
- self.parent_window.protocol_list.addItem(correction_description)
1423
-
1424
- def generate_instructions(self):
1425
-
1426
- if self.timeseries_rb.isChecked():
1427
- mode = "timeseries"
1428
- elif self.tiles_rb.isChecked():
1429
- mode = "tiles"
1430
-
1431
- if self.regress_cb.isChecked():
1432
- optimize_option = True
1433
- opt_coef_range = self.coef_range_slider.value()
1434
- opt_coef_nbr = int(self.nbr_coef_le.text())
1435
- else:
1436
- optimize_option = False
1437
- opt_coef_range = None
1438
- opt_coef_nbr = None
1439
-
1440
- if self.operation_layout.subtract_btn.isChecked():
1441
- operation = "subtract"
1442
- else:
1443
- operation = "divide"
1444
- clip = None
1445
-
1446
- if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
1447
- clip = True
1448
- else:
1449
- clip = False
1450
-
1451
- if self.camera_offset_le.text()=="":
1452
- offset = None
1453
- else:
1454
- offset = float(self.camera_offset_le.text().replace(",","."))
1455
-
1456
- self.instructions = {
1457
- "target_channel": self.channels_cb.currentText(),
1458
- "correction_type": "model-free",
1459
- "threshold_on_std": self.threshold_le.get_threshold(),
1460
- "frame_range": self.frame_range_slider.value(),
1461
- "mode": mode,
1462
- "optimize_option": optimize_option,
1463
- "opt_coef_range": opt_coef_range,
1464
- "opt_coef_nbr": opt_coef_nbr,
1465
- "operation": operation,
1466
- "clip": clip,
1467
- "offset": offset,
1468
- "fix_nan": self.interpolate_check.isChecked(),
1469
- }
1470
-
1471
- def set_target_channel(self):
1472
-
1473
- channel_indices = _extract_channel_indices_from_config(self.attr_parent.exp_config, [self.channels_cb.currentText()])
1474
- self.target_channel = channel_indices[0]
1475
-
1476
- def set_threshold_graphically(self):
1477
-
1478
- self.attr_parent.locate_image()
1479
- self.set_target_channel()
1480
- thresh = self.threshold_le.get_threshold()
1481
-
1482
- if self.attr_parent.current_stack is not None and thresh is not None:
1483
- self.viewer = ThresholdedStackVisualizer(initial_threshold=thresh,
1484
- parent_le = self.threshold_le,
1485
- preprocessing=[['gauss',2],["std",4]],
1486
- stack_path=self.attr_parent.current_stack,
1487
- n_channels=len(self.channel_names),
1488
- target_channel=self.target_channel,
1489
- window_title='Set the exclusion threshold',
1490
- )
1491
- self.viewer.show()
1492
-
1493
- def preview_correction(self):
1494
-
1495
- if self.attr_parent.well_list.isMultipleSelection() or not self.attr_parent.well_list.isAnySelected() or self.attr_parent.position_list.isMultipleSelection() or not self.attr_parent.position_list.isAnySelected():
1496
- msgBox = QMessageBox()
1497
- msgBox.setIcon(QMessageBox.Warning)
1498
- msgBox.setText("Please select a single position...")
1499
- msgBox.setWindowTitle("Warning")
1500
- msgBox.setStandardButtons(QMessageBox.Ok)
1501
- returnValue = msgBox.exec()
1502
- if returnValue == QMessageBox.Ok:
1503
- return None
1504
-
1505
- if self.timeseries_rb.isChecked():
1506
- mode = "timeseries"
1507
- elif self.tiles_rb.isChecked():
1508
- mode = "tiles"
1509
-
1510
- if self.regress_cb.isChecked():
1511
- optimize_option = True
1512
- opt_coef_range = self.coef_range_slider.value()
1513
- opt_coef_nbr = int(self.nbr_coef_le.text())
1514
- else:
1515
- optimize_option = False
1516
- opt_coef_range = None
1517
- opt_coef_nbr = None
1518
-
1519
- if self.operation_layout.subtract_btn.isChecked():
1520
- operation = "subtract"
1521
- else:
1522
- operation = "divide"
1523
- clip = None
1524
-
1525
- if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
1526
- clip = True
1527
- else:
1528
- clip = False
1529
-
1530
- corrected_stacks = correct_background_model_free(self.attr_parent.exp_dir,
1531
- well_option=self.attr_parent.well_list.getSelectedIndices(), #+1 ??
1532
- position_option=self.attr_parent.getSelectedIndices(), #+1??
1533
- target_channel=self.channels_cb.currentText(),
1534
- mode = mode,
1535
- threshold_on_std = self.threshold_le.get_threshold(),
1536
- frame_range = self.frame_range_slider.value(),
1537
- optimize_option = optimize_option,
1538
- opt_coef_range = opt_coef_range,
1539
- opt_coef_nbr = opt_coef_nbr,
1540
- operation = operation,
1541
- clip = clip,
1542
- export= False,
1543
- return_stacks=True,
1544
- fix_nan=self.interpolate_check.isChecked(),
1545
- show_progress_per_well = True,
1546
- show_progress_per_pos = False,
1547
- )
1548
-
1549
- self.viewer = StackVisualizer(
1550
- stack=corrected_stacks[0],
1551
- window_title='Corrected channel',
1552
- frame_slider = True,
1553
- contrast_slider = True,
1554
- target_channel=self.channels_cb.currentIndex(),
1555
- )
1556
- self.viewer.show()
1557
-
1558
- def activate_time_range(self):
1559
-
1560
- if self.timeseries_rb.isChecked():
1561
- for wg in self.time_range_options:
1562
- wg.setEnabled(True)
1563
- elif self.tiles_rb.isChecked():
1564
- for wg in self.time_range_options:
1565
- wg.setEnabled(False)
1566
-
1567
- def activate_coef_options(self):
1568
-
1569
- if self.regress_cb.isChecked():
1570
- for c in self.coef_widgets:
1571
- c.setEnabled(True)
1572
- else:
1573
- for c in self.coef_widgets:
1574
- c.setEnabled(False)
1575
-
1576
- def estimate_bg(self):
1577
-
1578
- if self.timeseries_rb.isChecked():
1579
- mode = "timeseries"
1580
- elif self.tiles_rb.isChecked():
1581
- mode = "tiles"
1582
-
1583
- bg = estimate_background_per_condition(
1584
- self.attr_parent.exp_dir,
1585
- well_option = self.well_slider.value() - 1,
1586
- frame_range = self.frame_range_slider.value(),
1587
- target_channel = self.channels_cb.currentText(),
1588
- show_progress_per_pos = True,
1589
- threshold_on_std = self.threshold_le.get_threshold(),
1590
- mode = mode,
1591
- )
1592
- bg = bg[0]
1593
- bg = bg['bg']
1594
- print(bg)
1595
- if len(bg)>0:
1596
-
1597
- self.viewer = StackVisualizer(
1598
- stack=[bg],
1599
- window_title='Reconstructed background',
1600
- frame_slider = False,
1601
- )
1602
- self.viewer.show()