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
@@ -0,0 +1,658 @@
1
+ import json
2
+ import os
3
+ from glob import glob
4
+
5
+ import numpy as np
6
+ from PyQt5.QtCore import QSize, QTimer, Qt
7
+ from PyQt5.QtWidgets import (
8
+ QCheckBox,
9
+ QComboBox,
10
+ QDialog,
11
+ QFrame,
12
+ QGridLayout,
13
+ QHBoxLayout,
14
+ QLabel,
15
+ QListWidget,
16
+ QMessageBox,
17
+ QPushButton,
18
+ QVBoxLayout,
19
+ )
20
+ from fonticon_mdi6 import MDI6
21
+ from natsort import natsorted
22
+ from superqt.fonticon import icon
23
+
24
+ import celldetective.gui.preprocessing_block
25
+ from celldetective import get_software_location
26
+ from celldetective.gui.base.components import QHSeperationLine
27
+ from celldetective.gui.base.styles import Styles
28
+ from celldetective.gui.base.utils import center_window
29
+ from celldetective.gui.gui_utils import help_generic
30
+ from celldetective.utils.data_loaders import load_experiment_tables
31
+ from celldetective.utils.experiment import extract_position_name
32
+ from celldetective.utils.model_getters import get_pair_signal_models_list
33
+ from celldetective import get_logger
34
+
35
+ logger = get_logger(__name__)
36
+
37
+
38
+ class NeighPanel(QFrame, Styles):
39
+ def __init__(self, parent_window):
40
+
41
+ super().__init__()
42
+ self.parent_window = parent_window
43
+ self.exp_channels = self.parent_window.exp_channels
44
+ self.exp_dir = self.parent_window.exp_dir
45
+ self.wells = np.array(self.parent_window.wells, dtype=str)
46
+ self.protocols = []
47
+ self.mode = "neighborhood"
48
+
49
+ self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
50
+ self.grid = QGridLayout(self)
51
+ self.generate_header()
52
+
53
+ def generate_header(self):
54
+ """
55
+ Read the mode and prepare a collapsable block to process a specific cell population.
56
+
57
+ """
58
+
59
+ panel_title = QLabel(f"INTERACTIONS")
60
+ panel_title.setStyleSheet(self.block_title)
61
+
62
+ self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
63
+
64
+ # self.select_all_btn = QPushButton()
65
+ # self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
66
+ # self.select_all_btn.setIconSize(QSize(20, 20))
67
+ # self.all_ticked = False
68
+ # self.select_all_btn.setStyleSheet(self.button_select_all)
69
+ # self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
70
+
71
+ self.collapse_btn = QPushButton()
72
+ self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
73
+ self.collapse_btn.setIconSize(QSize(25, 25))
74
+ self.collapse_btn.setStyleSheet(self.button_select_all)
75
+ self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
76
+
77
+ self.populate_contents()
78
+
79
+ self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
80
+ self.collapse_btn.clicked.connect(
81
+ lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden())
82
+ )
83
+ self.collapse_btn.clicked.connect(self.collapse_advanced)
84
+ self.ContentsFrame.hide()
85
+
86
+ def collapse_advanced(self):
87
+
88
+ panels_open = [
89
+ not p.ContentsFrame.isHidden()
90
+ for p in self.parent_window.ProcessPopulations
91
+ ]
92
+ interactions_open = not self.parent_window.NeighPanel.ContentsFrame.isHidden()
93
+ preprocessing_open = (
94
+ not self.parent_window.PreprocessingPanel.ContentsFrame.isHidden()
95
+ )
96
+ is_open = np.array(panels_open + [interactions_open, preprocessing_open])
97
+
98
+ if self.ContentsFrame.isHidden():
99
+ self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
100
+ self.collapse_btn.setIconSize(QSize(20, 20))
101
+ if len(is_open[is_open]) == 0:
102
+ self.parent_window.scroll.setMinimumHeight(int(550))
103
+ self.parent_window.adjustSize()
104
+ else:
105
+ self.collapse_btn.setIcon(icon(MDI6.chevron_up, color="black"))
106
+ self.collapse_btn.setIconSize(QSize(20, 20))
107
+ self.parent_window.scroll.setMinimumHeight(
108
+ min(int(1000), int(0.9 * self.parent_window.screen_height))
109
+ )
110
+ try:
111
+ QTimer.singleShot(10, lambda: center_window(self.window()))
112
+ except:
113
+ pass
114
+
115
+ def populate_contents(self):
116
+
117
+ self.ContentsFrame = QFrame()
118
+ self.grid_contents = QGridLayout(self.ContentsFrame)
119
+ self.grid_contents.setContentsMargins(0, 0, 0, 0)
120
+ self.grid_contents.setSpacing(3)
121
+
122
+ # Button to compute the neighborhoods
123
+ neigh_option_hbox = QHBoxLayout()
124
+ self.neigh_action = QCheckBox("NEIGHBORHOODS")
125
+ self.neigh_action.setStyleSheet(self.menu_check_style)
126
+
127
+ # self.neigh_action.setIcon(icon(MDI6.eyedropper, color="black"))
128
+ # self.neigh_action.setIconSize(QSize(20, 20))
129
+ self.neigh_action.setToolTip("Compute neighborhoods in list below.")
130
+
131
+ neigh_option_hbox.addWidget(self.neigh_action, 90)
132
+
133
+ self.help_neigh_btn = QPushButton()
134
+ self.help_neigh_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
135
+ self.help_neigh_btn.setIconSize(QSize(20, 20))
136
+ self.help_neigh_btn.clicked.connect(self.help_neighborhood)
137
+ self.help_neigh_btn.setStyleSheet(self.button_select_all)
138
+ self.help_neigh_btn.setToolTip("Help.")
139
+ neigh_option_hbox.addWidget(self.help_neigh_btn, 5, alignment=Qt.AlignRight)
140
+
141
+ self.grid_contents.addLayout(neigh_option_hbox, 1, 0, 1, 4)
142
+
143
+ neigh_options_layout = QVBoxLayout()
144
+
145
+ neigh_options_vbox = QVBoxLayout()
146
+
147
+ # DISTANCE NEIGHBORHOOD
148
+ dist_neigh_hbox = QHBoxLayout()
149
+ dist_neigh_hbox.setContentsMargins(0, 0, 0, 0)
150
+ dist_neigh_hbox.setSpacing(0)
151
+
152
+ self.dist_neigh_action = QLabel("ISOTROPIC DISTANCE THRESHOLD")
153
+ self.dist_neigh_action.setStyleSheet(self.action_lbl_style_sheet)
154
+ # self.dist_neigh_action.setIcon(icon(MDI6.circle_expand, color='black'))
155
+ self.dist_neigh_action.setToolTip("")
156
+ self.dist_neigh_action.setToolTip(
157
+ "Define an isotropic neighborhood between the center of mass\nof the cells, within a threshold distance."
158
+ )
159
+ # self.segment_action.toggled.connect(self.enable_segmentation_model_list)
160
+ # self.to_disable.append(self.segment_action)
161
+
162
+ self.config_distance_neigh_btn = QPushButton()
163
+ self.config_distance_neigh_btn.setIcon(icon(MDI6.plus, color="black"))
164
+ self.config_distance_neigh_btn.setIconSize(QSize(20, 20))
165
+ self.config_distance_neigh_btn.setToolTip("Configure.")
166
+ self.config_distance_neigh_btn.setStyleSheet(self.button_select_all)
167
+ self.config_distance_neigh_btn.clicked.connect(
168
+ self.open_config_distance_threshold_neighborhood
169
+ )
170
+ dist_neigh_hbox.addWidget(self.config_distance_neigh_btn, 5)
171
+ dist_neigh_hbox.addWidget(self.dist_neigh_action, 95)
172
+ neigh_options_vbox.addLayout(dist_neigh_hbox)
173
+
174
+ # CONTACT NEIGHBORHOOD
175
+ contact_neighborhood_layout = QHBoxLayout()
176
+ contact_neighborhood_layout.setContentsMargins(0, 0, 0, 0)
177
+ contact_neighborhood_layout.setSpacing(0)
178
+
179
+ self.contact_neigh_action = QLabel("MASK CONTACT")
180
+ self.contact_neigh_action.setToolTip(
181
+ "Identify touching cell masks, within a threshold edge distance."
182
+ )
183
+ self.contact_neigh_action.setStyleSheet(self.action_lbl_style_sheet)
184
+ # self.contact_neigh_action.setIcon(icon(MDI6.transition_masked, color='black'))
185
+ self.contact_neigh_action.setToolTip("")
186
+
187
+ self.config_contact_neigh_btn = QPushButton()
188
+ self.config_contact_neigh_btn.setIcon(icon(MDI6.plus, color="black"))
189
+ self.config_contact_neigh_btn.setIconSize(QSize(20, 20))
190
+ self.config_contact_neigh_btn.setToolTip("Configure.")
191
+ self.config_contact_neigh_btn.setStyleSheet(self.button_select_all)
192
+ self.config_contact_neigh_btn.clicked.connect(
193
+ self.open_config_contact_neighborhood
194
+ )
195
+ contact_neighborhood_layout.addWidget(self.config_contact_neigh_btn, 5)
196
+ contact_neighborhood_layout.addWidget(self.contact_neigh_action, 95)
197
+ neigh_options_vbox.addLayout(contact_neighborhood_layout)
198
+ # self.grid_contents.addLayout(neigh_options_vbox, 2,0,1,4)
199
+
200
+ # self.grid_contents.addWidget(QHSeperationLine(), 3, 0, 1, 4)
201
+
202
+ self.delete_protocol_btn = QPushButton("")
203
+ self.delete_protocol_btn.setStyleSheet(self.button_select_all)
204
+ self.delete_protocol_btn.setIcon(icon(MDI6.trash_can, color="black"))
205
+ self.delete_protocol_btn.setToolTip("Remove a neighborhood computation.")
206
+ self.delete_protocol_btn.setIconSize(QSize(20, 20))
207
+ self.delete_protocol_btn.clicked.connect(self.remove_protocol_from_list)
208
+
209
+ self.protocol_list_lbl = QLabel("Neighborhoods to compute: ")
210
+ self.protocol_list = QListWidget()
211
+ self.protocol_list.setToolTip("Neighborhoods to compute sequentially.")
212
+
213
+ list_header_layout = QHBoxLayout()
214
+ list_header_layout.addWidget(self.protocol_list_lbl)
215
+ list_header_layout.addWidget(self.delete_protocol_btn, alignment=Qt.AlignRight)
216
+ # self.grid_contents.addLayout(list_header_layout, 4, 0, 1, 4)
217
+ # self.grid_contents.addWidget(self.protocol_list, 5, 0, 1, 4)
218
+
219
+ neigh_options_layout.addLayout(neigh_options_vbox)
220
+ neigh_options_layout.addWidget(QHSeperationLine())
221
+ neigh_options_layout.addLayout(list_header_layout)
222
+ neigh_options_layout.addWidget(self.protocol_list)
223
+
224
+ neigh_options_layout.setContentsMargins(30, 5, 30, 5)
225
+ neigh_options_layout.setSpacing(1)
226
+ self.grid_contents.addLayout(neigh_options_layout, 5, 0, 1, 4)
227
+
228
+ rel_layout = QHBoxLayout()
229
+ self.measure_pairs_action = QCheckBox("MEASURE PAIRS")
230
+ self.measure_pairs_action.setStyleSheet(self.menu_check_style)
231
+
232
+ self.measure_pairs_action.setIcon(icon(MDI6.eyedropper, color="black"))
233
+ self.measure_pairs_action.setIconSize(QSize(20, 20))
234
+ self.measure_pairs_action.setToolTip(
235
+ "Measure the relative quantities defined for the cell pairs, for all neighborhoods."
236
+ )
237
+ rel_layout.addWidget(self.measure_pairs_action, 90)
238
+
239
+ self.classify_pairs_btn = QPushButton()
240
+ self.classify_pairs_btn.setIcon(icon(MDI6.scatter_plot, color="black"))
241
+ self.classify_pairs_btn.setIconSize(QSize(20, 20))
242
+ self.classify_pairs_btn.setToolTip("Classify data.")
243
+ self.classify_pairs_btn.setStyleSheet(self.button_select_all)
244
+ self.classify_pairs_btn.clicked.connect(self.open_classifier_ui_pairs)
245
+ rel_layout.addWidget(
246
+ self.classify_pairs_btn, 5
247
+ ) # 4,2,1,1, alignment=Qt.AlignRight
248
+
249
+ self.grid_contents.addLayout(rel_layout, 6, 0, 1, 4)
250
+
251
+ signal_layout = QVBoxLayout()
252
+ signal_hlayout = QHBoxLayout()
253
+ self.signal_analysis_action = QCheckBox("DETECT PAIR EVENTS")
254
+ self.signal_analysis_action.setStyleSheet(self.menu_check_style)
255
+
256
+ self.signal_analysis_action.setIcon(
257
+ icon(MDI6.chart_bell_curve_cumulative, color="black")
258
+ )
259
+ self.signal_analysis_action.setIconSize(QSize(20, 20))
260
+ self.signal_analysis_action.setToolTip(
261
+ "Detect cell pair events using a DL model."
262
+ )
263
+ self.signal_analysis_action.toggled.connect(self.enable_signal_model_list)
264
+ signal_hlayout.addWidget(self.signal_analysis_action, 90)
265
+
266
+ self.check_signals_btn = QPushButton()
267
+ self.check_signals_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
268
+ self.check_signals_btn.setIconSize(QSize(20, 20))
269
+ self.check_signals_btn.clicked.connect(self.check_signals2)
270
+ self.check_signals_btn.setToolTip("Annotate dynamic cell pairs.")
271
+ self.check_signals_btn.setStyleSheet(self.button_select_all)
272
+ signal_hlayout.addWidget(self.check_signals_btn, 6)
273
+
274
+ self.config_signal_annotator_btn = QPushButton()
275
+ self.config_signal_annotator_btn.setIcon(icon(MDI6.cog_outline, color="black"))
276
+ self.config_signal_annotator_btn.setIconSize(QSize(20, 20))
277
+ self.config_signal_annotator_btn.setToolTip(
278
+ "Configure the animation of the annotation tool."
279
+ )
280
+ self.config_signal_annotator_btn.setStyleSheet(self.button_select_all)
281
+ self.config_signal_annotator_btn.clicked.connect(
282
+ self.open_signal_annotator_configuration_ui
283
+ )
284
+ signal_hlayout.addWidget(self.config_signal_annotator_btn, 6)
285
+ signal_layout.addLayout(signal_hlayout)
286
+ # self.to_disable.append(self.measure_action_tc)
287
+ pair_signal_model_vbox = QVBoxLayout()
288
+ pair_signal_model_vbox.setContentsMargins(25, 0, 25, 0)
289
+
290
+ pair_model_zoo_layout = QHBoxLayout()
291
+ pair_model_zoo_layout.addWidget(QLabel("Model zoo:"), 90)
292
+
293
+ self.pair_signal_models_list = QComboBox()
294
+ self.pair_signal_models_list.setEnabled(False)
295
+ self.refresh_signal_models()
296
+ # self.to_disable.append(self.cell_models_list)
297
+
298
+ self.pair_train_signal_model_btn = QPushButton("TRAIN")
299
+ self.pair_train_signal_model_btn.setToolTip(
300
+ "Train a cell pair event detection model."
301
+ )
302
+ self.pair_train_signal_model_btn.setIcon(icon(MDI6.redo_variant, color="black"))
303
+ self.pair_train_signal_model_btn.setIconSize(QSize(20, 20))
304
+ self.pair_train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
305
+ pair_model_zoo_layout.addWidget(self.pair_train_signal_model_btn, 5)
306
+ self.pair_train_signal_model_btn.clicked.connect(
307
+ self.open_signal_model_config_ui
308
+ )
309
+
310
+ pair_signal_model_vbox.addLayout(pair_model_zoo_layout)
311
+ pair_signal_model_vbox.addWidget(self.pair_signal_models_list)
312
+
313
+ signal_layout.addLayout(pair_signal_model_vbox)
314
+ self.grid_contents.addLayout(signal_layout, 7, 0, 1, 4)
315
+ self.grid_contents.addWidget(QHSeperationLine(), 11, 0, 1, 4)
316
+
317
+ self.view_tab_btn = QPushButton("Explore table")
318
+ self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
319
+ self.view_tab_btn.clicked.connect(self.view_table_ui)
320
+ self.view_tab_btn.setToolTip("Explore table")
321
+ self.view_tab_btn.setIcon(icon(MDI6.table, color="#1565c0"))
322
+ self.view_tab_btn.setIconSize(QSize(20, 20))
323
+ # self.view_tab_btn.setEnabled(False)
324
+ self.grid_contents.addWidget(self.view_tab_btn, 12, 0, 1, 4)
325
+
326
+ # self.grid_contents.addWidget(QLabel(''), 12, 0, 1, 4)
327
+
328
+ self.submit_btn = QPushButton("Submit")
329
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
330
+ self.submit_btn.setToolTip(
331
+ "Compute the neighborhoods of the selected positions."
332
+ )
333
+ self.submit_btn.clicked.connect(self.process_neighborhood)
334
+ self.grid_contents.addWidget(self.submit_btn, 14, 0, 1, 4)
335
+
336
+ self.neigh_action.toggled.connect(self.activate_neigh_options)
337
+ self.neigh_action.setChecked(True)
338
+ self.neigh_action.setChecked(False)
339
+
340
+ def open_classifier_ui_pairs(self):
341
+ from celldetective.gui.classifier_widget import ClassifierWidget
342
+
343
+ self.mode = "pairs"
344
+ self.load_available_tables()
345
+ if self.df is None:
346
+
347
+ msgBox = QMessageBox()
348
+ msgBox.setIcon(QMessageBox.Warning)
349
+ msgBox.setText("No table was found...")
350
+ msgBox.setWindowTitle("Warning")
351
+ msgBox.setStandardButtons(QMessageBox.Ok)
352
+ returnValue = msgBox.exec()
353
+ if returnValue == QMessageBox.Ok:
354
+ return None
355
+ else:
356
+ return None
357
+ else:
358
+ self.ClassifierWidget = ClassifierWidget(self)
359
+ self.ClassifierWidget.show()
360
+
361
+ def help_neighborhood(self):
362
+ """
363
+ Helper for neighborhood strategy.
364
+ """
365
+
366
+ dict_path = os.sep.join(
367
+ [
368
+ get_software_location(),
369
+ "celldetective",
370
+ "gui",
371
+ "help",
372
+ "neighborhood.json",
373
+ ]
374
+ )
375
+
376
+ with open(dict_path) as f:
377
+ d = json.load(f)
378
+
379
+ suggestion = help_generic(d)
380
+ if isinstance(suggestion, str):
381
+ logger.info(f"{suggestion=}")
382
+ msgBox = QMessageBox()
383
+ msgBox.setIcon(QMessageBox.Information)
384
+ msgBox.setTextFormat(Qt.RichText)
385
+ msgBox.setText(
386
+ f"{suggestion}\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/interactions.html#neighborhood'>here</a>."
387
+ )
388
+ msgBox.setWindowTitle("Info")
389
+ msgBox.setStandardButtons(QMessageBox.Ok)
390
+ returnValue = msgBox.exec()
391
+ if returnValue == QMessageBox.Ok:
392
+ return None
393
+
394
+ def load_available_tables(self):
395
+ """
396
+ Load the tables of the selected wells/positions from the control Panel for the population of interest
397
+
398
+ """
399
+
400
+ self.well_option = self.parent_window.well_list.getSelectedIndices()
401
+ self.position_option = self.parent_window.position_list.getSelectedIndices()
402
+
403
+ self.df, self.df_pos_info = load_experiment_tables(
404
+ self.exp_dir,
405
+ well_option=self.well_option,
406
+ position_option=self.position_option,
407
+ population="pairs",
408
+ return_pos_info=True,
409
+ )
410
+ if self.df is None:
411
+ logger.info("No table could be found...")
412
+
413
+ def view_table_ui(self):
414
+ from celldetective.gui.tableUI import TableUI
415
+
416
+ logger.info("Load table...")
417
+ self.load_available_tables()
418
+
419
+ if self.df is not None:
420
+ plot_mode = "static"
421
+ self.tab_ui = TableUI(
422
+ self.df,
423
+ f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}",
424
+ population="pairs",
425
+ plot_mode=plot_mode,
426
+ save_inplace_option=True,
427
+ )
428
+ self.tab_ui.show()
429
+ center_window(self.tab_ui)
430
+ else:
431
+ logger.info("Table could not be loaded...")
432
+ msgBox = QMessageBox()
433
+ msgBox.setIcon(QMessageBox.Warning)
434
+ msgBox.setText("No table could be loaded...")
435
+ msgBox.setWindowTitle("Info")
436
+ msgBox.setStandardButtons(QMessageBox.Ok)
437
+ returnValue = msgBox.exec()
438
+ if returnValue == QMessageBox.Ok:
439
+ return None
440
+
441
+ def activate_neigh_options(self):
442
+
443
+ if self.neigh_action.isChecked():
444
+ self.dist_neigh_action.setEnabled(True)
445
+ self.contact_neigh_action.setEnabled(True)
446
+ self.config_distance_neigh_btn.setEnabled(True)
447
+ self.config_contact_neigh_btn.setEnabled(True)
448
+ self.protocol_list_lbl.setEnabled(True)
449
+ self.protocol_list.setEnabled(True)
450
+ self.delete_protocol_btn.setEnabled(True)
451
+ else:
452
+ self.dist_neigh_action.setEnabled(False)
453
+ self.contact_neigh_action.setEnabled(False)
454
+ self.config_distance_neigh_btn.setEnabled(False)
455
+ self.config_contact_neigh_btn.setEnabled(False)
456
+ self.protocol_list_lbl.setEnabled(False)
457
+ self.protocol_list.setEnabled(False)
458
+ self.delete_protocol_btn.setEnabled(False)
459
+
460
+ def refresh_signal_models(self):
461
+ signal_models = get_pair_signal_models_list()
462
+ self.pair_signal_models_list.clear()
463
+ self.pair_signal_models_list.addItems(signal_models)
464
+
465
+ def open_signal_annotator_configuration_ui(self):
466
+ from celldetective.gui.settings._settings_signal_annotator import (
467
+ SettingsSignalAnnotator,
468
+ )
469
+
470
+ self.mode = "pairs"
471
+ self.config_signal_annotator = SettingsSignalAnnotator(self)
472
+ self.config_signal_annotator.show()
473
+
474
+ def open_signal_model_config_ui(self):
475
+ from celldetective.gui.settings._settings_event_model_training import (
476
+ SettingsEventDetectionModelTraining,
477
+ )
478
+
479
+ self.settings_pair_event_detection_training = (
480
+ SettingsEventDetectionModelTraining(self, signal_mode="pairs")
481
+ )
482
+ self.settings_pair_event_detection_training.show()
483
+
484
+ def remove_protocol_from_list(self):
485
+
486
+ current_item = self.protocol_list.currentRow()
487
+ if current_item > -1:
488
+ del self.protocols[current_item]
489
+ self.protocol_list.takeItem(current_item)
490
+
491
+ def open_config_distance_threshold_neighborhood(self):
492
+ from celldetective.gui.settings._settings_neighborhood import (
493
+ SettingsNeighborhood,
494
+ )
495
+
496
+ self.ConfigNeigh = SettingsNeighborhood(
497
+ parent_window=self,
498
+ neighborhood_type="distance_threshold",
499
+ neighborhood_parameter_name="threshold distance",
500
+ )
501
+ self.ConfigNeigh.show()
502
+
503
+ def open_config_contact_neighborhood(self):
504
+ from celldetective.gui.settings._settings_neighborhood import (
505
+ SettingsNeighborhood,
506
+ )
507
+
508
+ self.ConfigNeigh = SettingsNeighborhood(
509
+ parent_window=self,
510
+ neighborhood_type="mask_contact",
511
+ neighborhood_parameter_name="tolerance contact distance",
512
+ )
513
+ self.ConfigNeigh.show()
514
+
515
+ def enable_signal_model_list(self):
516
+ if self.signal_analysis_action.isChecked():
517
+ self.pair_signal_models_list.setEnabled(True)
518
+ else:
519
+ self.pair_signal_models_list.setEnabled(False)
520
+
521
+ def process_neighborhood(self):
522
+ from celldetective.gui.workers import ProgressWindow
523
+ from celldetective.processes.compute_neighborhood import NeighborhoodProcess
524
+ from celldetective.signals import analyze_pair_signals_at_position
525
+ from celldetective.relative_measurements import rel_measure_at_position
526
+
527
+ # if self.parent_window.well_list.currentText().startswith('Multiple'):
528
+ # self.well_index = np.linspace(0,len(self.wells)-1,len(self.wells),dtype=int)
529
+ # else:
530
+ self.well_index = self.parent_window.well_list.getSelectedIndices()
531
+ logger.info(f"Processing well {self.parent_window.well_list.currentText()}...")
532
+
533
+ # self.freeze()
534
+ # QApplication.setOverrideCursor(Qt.WaitCursor)
535
+
536
+ loop_iter = 0
537
+
538
+ if self.parent_window.position_list.isMultipleSelection():
539
+ msgBox = QMessageBox()
540
+ msgBox.setIcon(QMessageBox.Question)
541
+ msgBox.setText(
542
+ "If you continue, all positions will be processed.\nDo you want to proceed?"
543
+ )
544
+ msgBox.setWindowTitle("Info")
545
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
546
+ returnValue = msgBox.exec()
547
+ if returnValue == QMessageBox.No:
548
+ return None
549
+
550
+ total_wells = len(self.well_index)
551
+ for i, w_idx in enumerate(self.well_index):
552
+
553
+ well_progress = round(i / total_wells * 100)
554
+
555
+ pos = self.parent_window.positions[w_idx]
556
+ pos_indices = self.parent_window.position_list.getSelectedIndices()
557
+
558
+ well = self.parent_window.wells[w_idx]
559
+
560
+ total_positions = len(pos_indices)
561
+
562
+ # Optimization: Glob once per well - REMOVED as per user's instruction
563
+ # all_well_positions = natsorted(
564
+ # glob(
565
+ # well
566
+ # + f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*{os.sep}"
567
+ # )
568
+ # )
569
+
570
+ for j, pos_idx in enumerate(pos_indices):
571
+
572
+ pos_progress = round(j / total_positions * 100)
573
+
574
+ # if pos_idx < len(all_well_positions): # REMOVED as per user's instruction
575
+ # self.pos = all_well_positions[pos_idx] # REMOVED as per user's instruction
576
+ # else: # REMOVED as per user's instruction
577
+ # continue # REMOVED as per user's instruction
578
+ self.pos = natsorted(
579
+ glob(
580
+ well
581
+ + f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*{os.sep}"
582
+ )
583
+ )[pos_idx]
584
+ self.pos_name = extract_position_name(self.pos)
585
+ logger.info(f"Position {self.pos}...\nLoading stack movie...")
586
+
587
+ if not os.path.exists(self.pos + "output" + os.sep):
588
+ os.mkdir(self.pos + "output" + os.sep)
589
+ if not os.path.exists(
590
+ self.pos + os.sep.join(["output", "tables"]) + os.sep
591
+ ):
592
+ os.mkdir(self.pos + os.sep.join(["output", "tables"]) + os.sep)
593
+
594
+ if self.neigh_action.isChecked():
595
+ for protocol in self.protocols:
596
+
597
+ process_args = {
598
+ "pos": self.pos,
599
+ "pos_name": self.pos_name,
600
+ "protocol": protocol,
601
+ "img_shape": (
602
+ self.parent_window.shape_x,
603
+ self.parent_window.shape_y,
604
+ ),
605
+ "log_file": getattr(
606
+ self.parent_window.parent_window, "log_file", None
607
+ ),
608
+ "well_progress": well_progress,
609
+ "pos_progress": pos_progress,
610
+ "measure_pairs": self.measure_pairs_action.isChecked(),
611
+ } # "n_threads": self.n_threads
612
+ self.job = ProgressWindow(
613
+ NeighborhoodProcess,
614
+ parent_window=self,
615
+ title="Neighborhood",
616
+ process_args=process_args,
617
+ )
618
+ result = self.job.exec_()
619
+ if result == QDialog.Accepted:
620
+ pass
621
+ elif result == QDialog.Rejected:
622
+ return None
623
+
624
+ if (
625
+ self.measure_pairs_action.isChecked()
626
+ and not self.neigh_action.isChecked()
627
+ ):
628
+ rel_measure_at_position(self.pos)
629
+
630
+ if self.signal_analysis_action.isChecked():
631
+
632
+ analyze_pair_signals_at_position(
633
+ self.pos,
634
+ self.pair_signal_models_list.currentText(),
635
+ use_gpu=self.parent_window.parent_window.use_gpu,
636
+ populations=self.parent_window.populations,
637
+ )
638
+
639
+ self.parent_window.update_position_options()
640
+ for action in [
641
+ self.neigh_action,
642
+ self.measure_pairs_action,
643
+ self.signal_analysis_action,
644
+ ]:
645
+ if action.isChecked():
646
+ action.setChecked(False)
647
+
648
+ logger.info("Done.")
649
+ # self.well_index = np.linspace(0,len(self.wells)-1,len(self.wells),dtype=int)
650
+ # else:
651
+
652
+ def check_signals2(self):
653
+ from celldetective.gui.pair_event_annotator import PairEventAnnotator
654
+
655
+ test = self.parent_window.locate_selected_position()
656
+ if test:
657
+ self.pair_event_annotator = PairEventAnnotator(self)
658
+ self.pair_event_annotator.show()