celldetective 1.0.2__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 (66) hide show
  1. celldetective/__init__.py +2 -0
  2. celldetective/__main__.py +432 -0
  3. celldetective/datasets/segmentation_annotations/blank +0 -0
  4. celldetective/datasets/signal_annotations/blank +0 -0
  5. celldetective/events.py +149 -0
  6. celldetective/extra_properties.py +100 -0
  7. celldetective/filters.py +89 -0
  8. celldetective/gui/__init__.py +20 -0
  9. celldetective/gui/about.py +44 -0
  10. celldetective/gui/analyze_block.py +563 -0
  11. celldetective/gui/btrack_options.py +898 -0
  12. celldetective/gui/classifier_widget.py +386 -0
  13. celldetective/gui/configure_new_exp.py +532 -0
  14. celldetective/gui/control_panel.py +438 -0
  15. celldetective/gui/gui_utils.py +495 -0
  16. celldetective/gui/json_readers.py +113 -0
  17. celldetective/gui/measurement_options.py +1425 -0
  18. celldetective/gui/neighborhood_options.py +452 -0
  19. celldetective/gui/plot_signals_ui.py +1042 -0
  20. celldetective/gui/process_block.py +1055 -0
  21. celldetective/gui/retrain_segmentation_model_options.py +706 -0
  22. celldetective/gui/retrain_signal_model_options.py +643 -0
  23. celldetective/gui/seg_model_loader.py +460 -0
  24. celldetective/gui/signal_annotator.py +2388 -0
  25. celldetective/gui/signal_annotator_options.py +340 -0
  26. celldetective/gui/styles.py +217 -0
  27. celldetective/gui/survival_ui.py +903 -0
  28. celldetective/gui/tableUI.py +608 -0
  29. celldetective/gui/thresholds_gui.py +1300 -0
  30. celldetective/icons/logo-large.png +0 -0
  31. celldetective/icons/logo.png +0 -0
  32. celldetective/icons/signals_icon.png +0 -0
  33. celldetective/icons/splash-test.png +0 -0
  34. celldetective/icons/splash.png +0 -0
  35. celldetective/icons/splash0.png +0 -0
  36. celldetective/icons/survival2.png +0 -0
  37. celldetective/icons/vignette_signals2.png +0 -0
  38. celldetective/icons/vignette_signals2.svg +114 -0
  39. celldetective/io.py +2050 -0
  40. celldetective/links/zenodo.json +561 -0
  41. celldetective/measure.py +1258 -0
  42. celldetective/models/segmentation_effectors/blank +0 -0
  43. celldetective/models/segmentation_generic/blank +0 -0
  44. celldetective/models/segmentation_targets/blank +0 -0
  45. celldetective/models/signal_detection/blank +0 -0
  46. celldetective/models/tracking_configs/mcf7.json +68 -0
  47. celldetective/models/tracking_configs/ricm.json +203 -0
  48. celldetective/models/tracking_configs/ricm2.json +203 -0
  49. celldetective/neighborhood.py +717 -0
  50. celldetective/scripts/analyze_signals.py +51 -0
  51. celldetective/scripts/measure_cells.py +275 -0
  52. celldetective/scripts/segment_cells.py +212 -0
  53. celldetective/scripts/segment_cells_thresholds.py +140 -0
  54. celldetective/scripts/track_cells.py +206 -0
  55. celldetective/scripts/train_segmentation_model.py +246 -0
  56. celldetective/scripts/train_signal_model.py +49 -0
  57. celldetective/segmentation.py +712 -0
  58. celldetective/signals.py +2826 -0
  59. celldetective/tracking.py +974 -0
  60. celldetective/utils.py +1681 -0
  61. celldetective-1.0.2.dist-info/LICENSE +674 -0
  62. celldetective-1.0.2.dist-info/METADATA +192 -0
  63. celldetective-1.0.2.dist-info/RECORD +66 -0
  64. celldetective-1.0.2.dist-info/WHEEL +5 -0
  65. celldetective-1.0.2.dist-info/entry_points.txt +2 -0
  66. celldetective-1.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,1055 @@
1
+ from PyQt5.QtWidgets import QFrame, QGridLayout, QComboBox, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QCheckBox, \
2
+ QMessageBox, QWidget, QLineEdit, QScrollArea
3
+ from PyQt5.QtCore import Qt, QSize
4
+ from superqt.fonticon import icon
5
+ from fonticon_mdi6 import MDI6
6
+ import gc
7
+
8
+ from celldetective.gui.signal_annotator import MeasureAnnotator
9
+ from celldetective.io import get_segmentation_models_list, control_segmentation_napari, get_signal_models_list, control_tracking_btrack, load_experiment_tables
10
+ from celldetective.io import locate_segmentation_model
11
+ from celldetective.gui import SegmentationModelLoader, ClassifierWidget, ConfigNeighborhoods, ConfigSegmentationModelTraining, ConfigTracking, SignalAnnotator, ConfigSignalModelTraining, ConfigMeasurements, ConfigSignalAnnotator, TableUI
12
+ from celldetective.gui.gui_utils import QHSeperationLine
13
+ from celldetective.segmentation import segment_at_position, segment_from_threshold_at_position
14
+ from celldetective.tracking import track_at_position
15
+ from celldetective.measure import measure_at_position
16
+ from celldetective.signals import analyze_signals_at_position
17
+ import numpy as np
18
+ from glob import glob
19
+ from natsort import natsorted
20
+ from superqt import QLabeledDoubleSlider
21
+ import os
22
+ import pandas as pd
23
+ from tqdm import tqdm
24
+ from celldetective.gui.gui_utils import center_window
25
+ from tifffile import imwrite
26
+ import json
27
+ import psutil
28
+ from celldetective.neighborhood import compute_neighborhood_at_position
29
+
30
+ class ProcessPanel(QFrame):
31
+ def __init__(self, parent, mode):
32
+
33
+ super().__init__()
34
+ self.parent = parent
35
+ self.mode = mode
36
+ self.exp_channels = self.parent.exp_channels
37
+ self.exp_dir = self.parent.exp_dir
38
+ self.threshold_config_targets = None
39
+ self.threshold_config_effectors = None
40
+ self.wells = np.array(self.parent.wells,dtype=str)
41
+ self.cellpose_calibrated = False
42
+ self.stardist_calibrated = False
43
+
44
+
45
+
46
+ self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
47
+ self.grid = QGridLayout(self)
48
+ self.generate_header()
49
+
50
+ def generate_header(self):
51
+
52
+ """
53
+ Read the mode and prepare a collapsable block to process a specific cell population.
54
+
55
+ """
56
+
57
+ panel_title = QLabel(f"PROCESS {self.mode.upper()}")
58
+ panel_title.setStyleSheet("""
59
+ font-weight: bold;
60
+ padding: 0px;
61
+ """)
62
+
63
+ self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
64
+
65
+ self.select_all_btn = QPushButton()
66
+ self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
67
+ self.select_all_btn.setIconSize(QSize(20, 20))
68
+ self.all_ticked = False
69
+ self.select_all_btn.clicked.connect(self.tick_all_actions)
70
+ self.select_all_btn.setStyleSheet(self.parent.parent.button_select_all)
71
+ self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
72
+ #self.to_disable.append(self.all_tc_actions)
73
+
74
+ self.collapse_btn = QPushButton()
75
+ self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
76
+ self.collapse_btn.setIconSize(QSize(25, 25))
77
+ self.collapse_btn.setStyleSheet(self.parent.parent.button_select_all)
78
+ self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
79
+
80
+ self.populate_contents()
81
+
82
+ self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
83
+ self.collapse_btn.clicked.connect(lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden()))
84
+ self.collapse_btn.clicked.connect(self.collapse_advanced)
85
+ self.ContentsFrame.hide()
86
+
87
+ def collapse_advanced(self):
88
+ if self.ContentsFrame.isHidden():
89
+ self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
90
+ self.collapse_btn.setIconSize(QSize(20, 20))
91
+ self.parent.scroll.setMinimumHeight(int(500))
92
+ #self.parent.w.adjustSize()
93
+ self.parent.adjustSize()
94
+ #self.parent.scroll.adjustSize()
95
+ else:
96
+ self.collapse_btn.setIcon(icon(MDI6.chevron_up,color="black"))
97
+ self.collapse_btn.setIconSize(QSize(20, 20))
98
+ #self.parent.w.adjustSize()
99
+ #self.parent.adjustSize()
100
+ self.parent.scroll.setMinimumHeight(min(int(880), int(0.8*self.parent.screen_height)))
101
+ self.parent.scroll.setMinimumWidth(410)
102
+
103
+ #self.parent.scroll.adjustSize()
104
+
105
+ def populate_contents(self):
106
+ self.ContentsFrame = QFrame()
107
+ self.grid_contents = QGridLayout(self.ContentsFrame)
108
+ self.grid_contents.setContentsMargins(0,0,0,0)
109
+ self.generate_segmentation_options()
110
+ self.generate_tracking_options()
111
+ self.generate_measure_options()
112
+ self.generate_signal_analysis_options()
113
+
114
+ self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
115
+ self.view_tab_btn = QPushButton("View table")
116
+ self.view_tab_btn.setStyleSheet(self.parent.parent.button_style_sheet_2)
117
+ self.view_tab_btn.clicked.connect(self.view_table_ui)
118
+ self.view_tab_btn.setToolTip('View table')
119
+ self.view_tab_btn.setIcon(icon(MDI6.table,color="#1565c0"))
120
+ self.view_tab_btn.setIconSize(QSize(20, 20))
121
+ #self.view_tab_btn.setEnabled(False)
122
+ self.grid_contents.addWidget(self.view_tab_btn, 10, 0, 1, 4)
123
+
124
+ self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
125
+ self.submit_btn = QPushButton("Submit")
126
+ self.submit_btn.setStyleSheet(self.parent.parent.button_style_sheet_2)
127
+ self.submit_btn.clicked.connect(self.process_population)
128
+ self.grid_contents.addWidget(self.submit_btn, 11, 0, 1, 4)
129
+
130
+ def generate_measure_options(self):
131
+
132
+ measure_layout = QHBoxLayout()
133
+
134
+ self.measure_action = QCheckBox("MEASURE")
135
+ self.measure_action.setStyleSheet("""
136
+ font-size: 10px;
137
+ padding-left: 10px;
138
+ padding-top: 5px;
139
+ """)
140
+ self.measure_action.setIcon(icon(MDI6.eyedropper,color="black"))
141
+ self.measure_action.setIconSize(QSize(20, 20))
142
+ self.measure_action.setToolTip("Measure.")
143
+ measure_layout.addWidget(self.measure_action, 90)
144
+ #self.to_disable.append(self.measure_action_tc)
145
+
146
+ self.classify_btn = QPushButton()
147
+ self.classify_btn.setIcon(icon(MDI6.scatter_plot, color="black"))
148
+ self.classify_btn.setIconSize(QSize(20, 20))
149
+ self.classify_btn.setToolTip("Classify data.")
150
+ self.classify_btn.setStyleSheet(self.parent.parent.button_select_all)
151
+ self.classify_btn.clicked.connect(self.open_classifier_ui)
152
+ measure_layout.addWidget(self.classify_btn, 5) #4,2,1,1, alignment=Qt.AlignRight
153
+
154
+ self.check_measurements_btn=QPushButton()
155
+ self.check_measurements_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
156
+ self.check_measurements_btn.setIconSize(QSize(20, 20))
157
+ self.check_measurements_btn.setToolTip("Explore measurements in-situ.")
158
+ self.check_measurements_btn.setStyleSheet(self.parent.parent.button_select_all)
159
+ self.check_measurements_btn.clicked.connect(self.check_measurements)
160
+ measure_layout.addWidget(self.check_measurements_btn, 5)
161
+
162
+
163
+ self.measurements_config_btn = QPushButton()
164
+ self.measurements_config_btn.setIcon(icon(MDI6.cog_outline,color="black"))
165
+ self.measurements_config_btn.setIconSize(QSize(20, 20))
166
+ self.measurements_config_btn.setToolTip("Configure measurements.")
167
+ self.measurements_config_btn.setStyleSheet(self.parent.parent.button_select_all)
168
+ self.measurements_config_btn.clicked.connect(self.open_measurement_configuration_ui)
169
+ measure_layout.addWidget(self.measurements_config_btn, 5) #4,2,1,1, alignment=Qt.AlignRight
170
+
171
+ self.grid_contents.addLayout(measure_layout,5,0,1,4)
172
+
173
+ def generate_signal_analysis_options(self):
174
+
175
+ signal_layout = QVBoxLayout()
176
+ signal_hlayout = QHBoxLayout()
177
+ self.signal_analysis_action = QCheckBox("SIGNAL ANALYSIS")
178
+ self.signal_analysis_action.setStyleSheet("""
179
+ font-size: 10px;
180
+ padding-left: 10px;
181
+ padding-top: 5px;
182
+ """)
183
+ self.signal_analysis_action.setIcon(icon(MDI6.chart_bell_curve_cumulative,color="black"))
184
+ self.signal_analysis_action.setIconSize(QSize(20, 20))
185
+ self.signal_analysis_action.setToolTip("Detect events in single-cell signals.")
186
+ self.signal_analysis_action.toggled.connect(self.enable_signal_model_list)
187
+ signal_hlayout.addWidget(self.signal_analysis_action, 90)
188
+
189
+ self.check_signals_btn = QPushButton()
190
+ self.check_signals_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
191
+ self.check_signals_btn.setIconSize(QSize(20, 20))
192
+ self.check_signals_btn.clicked.connect(self.check_signals)
193
+ self.check_signals_btn.setToolTip("Explore signals in-situ.")
194
+ self.check_signals_btn.setStyleSheet(self.parent.parent.button_select_all)
195
+ signal_hlayout.addWidget(self.check_signals_btn, 6)
196
+
197
+ self.config_signal_annotator_btn = QPushButton()
198
+ self.config_signal_annotator_btn.setIcon(icon(MDI6.cog_outline,color="black"))
199
+ self.config_signal_annotator_btn.setIconSize(QSize(20, 20))
200
+ self.config_signal_annotator_btn.setToolTip("Configure the dynamic visualizer.")
201
+ self.config_signal_annotator_btn.setStyleSheet(self.parent.parent.button_select_all)
202
+ self.config_signal_annotator_btn.clicked.connect(self.open_signal_annotator_configuration_ui)
203
+ signal_hlayout.addWidget(self.config_signal_annotator_btn, 6)
204
+
205
+ #self.to_disable.append(self.measure_action_tc)
206
+ signal_layout.addLayout(signal_hlayout)
207
+
208
+ signal_model_vbox = QVBoxLayout()
209
+ signal_model_vbox.setContentsMargins(25,0,25,0)
210
+
211
+ model_zoo_layout = QHBoxLayout()
212
+ model_zoo_layout.addWidget(QLabel("Model zoo:"),90)
213
+
214
+ self.signal_models_list = QComboBox()
215
+ self.signal_models_list.setEnabled(False)
216
+ self.refresh_signal_models()
217
+ #self.to_disable.append(self.cell_models_list)
218
+
219
+ self.train_signal_model_btn = QPushButton("TRAIN")
220
+ self.train_signal_model_btn.setToolTip("Open a dialog box to create a new target segmentation model.")
221
+ self.train_signal_model_btn.setIcon(icon(MDI6.redo_variant,color='black'))
222
+ self.train_signal_model_btn.setIconSize(QSize(20, 20))
223
+ self.train_signal_model_btn.setStyleSheet(self.parent.parent.button_style_sheet_3)
224
+ model_zoo_layout.addWidget(self.train_signal_model_btn, 5)
225
+ self.train_signal_model_btn.clicked.connect(self.open_signal_model_config_ui)
226
+
227
+ signal_model_vbox.addLayout(model_zoo_layout)
228
+ signal_model_vbox.addWidget(self.signal_models_list)
229
+
230
+ signal_layout.addLayout(signal_model_vbox)
231
+
232
+ self.grid_contents.addLayout(signal_layout,6,0,1,4)
233
+
234
+ def refresh_signal_models(self):
235
+ signal_models = get_signal_models_list()
236
+ self.signal_models_list.clear()
237
+ self.signal_models_list.addItems(signal_models)
238
+
239
+ def generate_tracking_options(self):
240
+ grid_track = QHBoxLayout()
241
+
242
+ self.track_action = QCheckBox("TRACK")
243
+ self.track_action.setIcon(icon(MDI6.chart_timeline_variant,color="black"))
244
+ self.track_action.setIconSize(QSize(20, 20))
245
+ self.track_action.setToolTip("Track the target cells using bTrack.")
246
+ self.track_action.setStyleSheet("""
247
+ font-size: 10px;
248
+ padding-left: 10px;
249
+ padding-top: 5px;
250
+ """)
251
+ grid_track.addWidget(self.track_action, 80)
252
+ #self.to_disable.append(self.track_action_tc)
253
+
254
+ # self.show_track_table_btn = QPushButton()
255
+ # self.show_track_table_btn.setIcon(icon(MDI6.table,color="black"))
256
+ # self.show_track_table_btn.setIconSize(QSize(20, 20))
257
+ # self.show_track_table_btn.setToolTip("Show trajectories table.")
258
+ # self.show_track_table_btn.setStyleSheet(self.parent.parent.button_select_all)
259
+ # #self.show_track_table_btn.clicked.connect(self.display_trajectory_table)
260
+ # self.show_track_table_btn.setEnabled(False)
261
+ # grid_track.addWidget(self.show_track_table_btn, 6) #4,3,1,1, alignment=Qt.AlignLeft
262
+
263
+ self.check_tracking_result_btn = QPushButton()
264
+ self.check_tracking_result_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
265
+ self.check_tracking_result_btn.setIconSize(QSize(20, 20))
266
+ self.check_tracking_result_btn.setToolTip("View raw bTrack output in napari.")
267
+ self.check_tracking_result_btn.setStyleSheet(self.parent.parent.button_select_all)
268
+ self.check_tracking_result_btn.clicked.connect(self.open_napari_tracking)
269
+ self.check_tracking_result_btn.setEnabled(False)
270
+ grid_track.addWidget(self.check_tracking_result_btn, 6) #4,3,1,1, alignment=Qt.AlignLeft
271
+
272
+ self.track_config_btn = QPushButton()
273
+ self.track_config_btn.setIcon(icon(MDI6.cog_outline,color="black"))
274
+ self.track_config_btn.setIconSize(QSize(20, 20))
275
+ self.track_config_btn.setToolTip("Configure tracking.")
276
+ self.track_config_btn.setStyleSheet(self.parent.parent.button_select_all)
277
+ self.track_config_btn.clicked.connect(self.open_tracking_configuration_ui)
278
+ grid_track.addWidget(self.track_config_btn, 6) #4,2,1,1, alignment=Qt.AlignRight
279
+
280
+ self.grid_contents.addLayout(grid_track, 4, 0, 1,4)
281
+
282
+
283
+ def generate_segmentation_options(self):
284
+
285
+ grid_segment = QHBoxLayout()
286
+ grid_segment.setContentsMargins(0,0,0,0)
287
+ grid_segment.setSpacing(0)
288
+
289
+ self.segment_action = QCheckBox("SEGMENT")
290
+ self.segment_action.setStyleSheet("""
291
+ font-size: 10px;
292
+ padding-left: 10px;
293
+ """)
294
+ self.segment_action.setIcon(icon(MDI6.bacteria, color='black'))
295
+ self.segment_action.setToolTip(f"Segment the {self.mode} cells on the images.")
296
+ self.segment_action.toggled.connect(self.enable_segmentation_model_list)
297
+ #self.to_disable.append(self.segment_action)
298
+ grid_segment.addWidget(self.segment_action, 90)
299
+
300
+ self.check_seg_btn = QPushButton()
301
+ self.check_seg_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
302
+ self.check_seg_btn.setIconSize(QSize(20, 20))
303
+ self.check_seg_btn.clicked.connect(self.check_segmentation)
304
+ self.check_seg_btn.setStyleSheet(self.parent.parent.button_select_all)
305
+ self.check_seg_btn.setToolTip("View segmentation output in napari.")
306
+ #self.check_seg_btn.setEnabled(False)
307
+ #self.to_disable.append(self.control_target_seg)
308
+ grid_segment.addWidget(self.check_seg_btn, 10)
309
+ self.grid_contents.addLayout(grid_segment, 0,0,1,4)
310
+
311
+ seg_option_vbox = QVBoxLayout()
312
+ seg_option_vbox.setContentsMargins(25,0,25,0)
313
+ model_zoo_layout = QHBoxLayout()
314
+ model_zoo_layout.addWidget(QLabel("Model zoo:"),90)
315
+ self.seg_model_list = QComboBox()
316
+ #self.to_disable.append(self.tc_seg_model_list)
317
+ self.seg_model_list.setGeometry(50, 50, 200, 30)
318
+ self.init_seg_model_list()
319
+
320
+
321
+ self.upload_model_btn = QPushButton("UPLOAD")
322
+ self.upload_model_btn.setIcon(icon(MDI6.upload,color="black"))
323
+ self.upload_model_btn.setIconSize(QSize(20, 20))
324
+ self.upload_model_btn.setStyleSheet(self.parent.parent.button_style_sheet_3)
325
+ self.upload_model_btn.setToolTip("Upload a new segmentation model\n(Deep learning or threshold-based).")
326
+ model_zoo_layout.addWidget(self.upload_model_btn, 5)
327
+ self.upload_model_btn.clicked.connect(self.upload_segmentation_model)
328
+ # self.to_disable.append(self.upload_tc_model)
329
+
330
+ self.train_btn = QPushButton("TRAIN")
331
+ self.train_btn.setToolTip("Train or retrain a segmentation model\non newly annotated data.")
332
+ self.train_btn.setIcon(icon(MDI6.redo_variant,color='black'))
333
+ self.train_btn.setIconSize(QSize(20, 20))
334
+ self.train_btn.setStyleSheet(self.parent.parent.button_style_sheet_3)
335
+ self.train_btn.clicked.connect(self.open_segmentation_model_config_ui)
336
+ model_zoo_layout.addWidget(self.train_btn, 5)
337
+ # self.train_button_tc.clicked.connect(self.train_stardist_model_tc)
338
+ # self.to_disable.append(self.train_button_tc)
339
+
340
+ seg_option_vbox.addLayout(model_zoo_layout)
341
+ seg_option_vbox.addWidget(self.seg_model_list)
342
+ self.seg_model_list.setEnabled(False)
343
+ self.grid_contents.addLayout(seg_option_vbox, 2, 0, 1, 4)
344
+
345
+ def check_segmentation(self):
346
+
347
+ if not os.path.exists(os.sep.join([self.parent.pos,f'labels_{self.mode}', os.sep])):
348
+ msgBox = QMessageBox()
349
+ msgBox.setIcon(QMessageBox.Question)
350
+ msgBox.setText("No labels can be found for this position. Do you want to annotate from scratch?")
351
+ msgBox.setWindowTitle("Info")
352
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
353
+ returnValue = msgBox.exec()
354
+ if returnValue == QMessageBox.No:
355
+ return None
356
+ else:
357
+ os.mkdir(os.sep.join([self.parent.pos,f'labels_{self.mode}']))
358
+ lbl = np.zeros((self.parent.shape_x, self.parent.shape_y), dtype=int)
359
+ for i in range(self.parent.len_movie):
360
+ imwrite(os.sep.join([self.parent.pos,f'labels_{self.mode}', str(i).zfill(4)+'.tif']), lbl)
361
+
362
+ #self.freeze()
363
+ #QApplication.setOverrideCursor(Qt.WaitCursor)
364
+ test = self.parent.locate_selected_position()
365
+ if test:
366
+ print('Memory use: ', dict(psutil.virtual_memory()._asdict()))
367
+ try:
368
+ control_segmentation_napari(self.parent.pos, prefix=self.parent.movie_prefix, population=self.mode,flush_memory=True)
369
+ except Exception as e:
370
+ msgBox = QMessageBox()
371
+ msgBox.setIcon(QMessageBox.Warning)
372
+ msgBox.setText(str(e))
373
+ msgBox.setWindowTitle("Warning")
374
+ msgBox.setStandardButtons(QMessageBox.Ok)
375
+ returnValue = msgBox.exec()
376
+ gc.collect()
377
+
378
+ def check_signals(self):
379
+
380
+ test = self.parent.locate_selected_position()
381
+ if test:
382
+ self.SignalAnnotator = SignalAnnotator(self)
383
+ self.SignalAnnotator.show()
384
+
385
+ def check_measurements(self):
386
+
387
+ test = self.parent.locate_selected_position()
388
+ if test:
389
+ self.MeasureAnnotator = MeasureAnnotator(self)
390
+ self.MeasureAnnotator.show()
391
+
392
+ def enable_segmentation_model_list(self):
393
+ if self.segment_action.isChecked():
394
+ self.seg_model_list.setEnabled(True)
395
+ else:
396
+ self.seg_model_list.setEnabled(False)
397
+
398
+ def enable_signal_model_list(self):
399
+ if self.signal_analysis_action.isChecked():
400
+ self.signal_models_list.setEnabled(True)
401
+ else:
402
+ self.signal_models_list.setEnabled(False)
403
+
404
+ def init_seg_model_list(self):
405
+
406
+ self.seg_model_list.clear()
407
+ self.seg_models = get_segmentation_models_list(mode=self.mode, return_path=False)
408
+ thresh = 40
409
+ self.models_truncated = [m[:thresh - 3]+'...' if len(m)>thresh else m for m in self.seg_models]
410
+ #self.seg_model_list.addItems(models_truncated)
411
+
412
+ self.seg_models_generic = get_segmentation_models_list(mode="generic", return_path=False)
413
+ self.seg_models.append('Threshold')
414
+ self.seg_models.extend(self.seg_models_generic)
415
+
416
+ #self.seg_models_generic.insert(0,'Threshold')
417
+ self.seg_model_list.addItems(self.seg_models)
418
+
419
+ for i in range(len(self.seg_models)):
420
+ self.seg_model_list.setItemData(i, self.seg_models[i], Qt.ToolTipRole)
421
+
422
+ self.seg_model_list.insertSeparator(len(self.models_truncated))
423
+
424
+
425
+ #if ("live_nuclei_channel" in self.exp_channels)*("dead_nuclei_channel" in self.exp_channels):
426
+ # print("both channels found")
427
+ # index = self.tc_seg_model_list.findText("MCF7_Hoescht_PI_w_primary_NK", Qt.MatchFixedString)
428
+ # if index >= 0:
429
+ # self.tc_seg_model_list.setCurrentIndex(index)
430
+ # elif ("live_nuclei_channel" in self.exp_channels)*("dead_nuclei_channel" not in self.exp_channels):
431
+ # index = self.tc_seg_model_list.findText("MCF7_Hoescht_w_primary_NK", Qt.MatchFixedString)
432
+ # if index >= 0:
433
+ # self.tc_seg_model_list.setCurrentIndex(index)
434
+ # elif ("live_nuclei_channel" not in self.exp_channels)*("dead_nuclei_channel" in self.exp_channels):
435
+ # index = self.tc_seg_model_list.findText("MCF7_PI_w_primary_NK", Qt.MatchFixedString)
436
+ # if index >= 0:
437
+ # self.tc_seg_model_list.setCurrentIndex(index)
438
+ # elif ("live_nuclei_channel" not in self.exp_channels)*("dead_nuclei_channel" not in self.exp_channels)*("adhesion_channel" in self.exp_channels):
439
+ # index = self.tc_seg_model_list.findText("RICM", Qt.MatchFixedString)
440
+ # if index >= 0:
441
+ # self.tc_seg_model_list.setCurrentIndex(index)
442
+
443
+ def tick_all_actions(self):
444
+ self.switch_all_ticks_option()
445
+ if self.all_ticked:
446
+ self.select_all_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
447
+ self.select_all_btn.setIconSize(QSize(20, 20))
448
+ self.segment_action.setChecked(True)
449
+ else:
450
+ self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
451
+ self.select_all_btn.setIconSize(QSize(20, 20))
452
+ self.segment_action.setChecked(False)
453
+
454
+ def switch_all_ticks_option(self):
455
+ if self.all_ticked == True:
456
+ self.all_ticked = False
457
+ else:
458
+ self.all_ticked = True
459
+
460
+ def upload_segmentation_model(self):
461
+
462
+ self.SegModelLoader = SegmentationModelLoader(self)
463
+ self.SegModelLoader.show()
464
+
465
+ def open_tracking_configuration_ui(self):
466
+
467
+ self.ConfigTracking = ConfigTracking(self)
468
+ self.ConfigTracking.show()
469
+
470
+ def open_signal_model_config_ui(self):
471
+
472
+ self.ConfigSignalTrain = ConfigSignalModelTraining(self)
473
+ self.ConfigSignalTrain.show()
474
+
475
+ def open_segmentation_model_config_ui(self):
476
+
477
+ self.ConfigSegmentationTrain = ConfigSegmentationModelTraining(self)
478
+ self.ConfigSegmentationTrain.show()
479
+
480
+ def open_measurement_configuration_ui(self):
481
+
482
+ self.ConfigMeasurements = ConfigMeasurements(self)
483
+ self.ConfigMeasurements.show()
484
+
485
+ def open_classifier_ui(self):
486
+
487
+ self.load_available_tables()
488
+ if self.df is None:
489
+
490
+ msgBox = QMessageBox()
491
+ msgBox.setIcon(QMessageBox.Warning)
492
+ msgBox.setText("No table was found...")
493
+ msgBox.setWindowTitle("Warning")
494
+ msgBox.setStandardButtons(QMessageBox.Ok)
495
+ returnValue = msgBox.exec()
496
+ if returnValue == QMessageBox.Ok:
497
+ return None
498
+ else:
499
+ return None
500
+ else:
501
+ self.ClassifierWidget = ClassifierWidget(self)
502
+ self.ClassifierWidget.show()
503
+
504
+ def open_signal_annotator_configuration_ui(self):
505
+ self.ConfigSignalAnnotator = ConfigSignalAnnotator(self)
506
+ self.ConfigSignalAnnotator.show()
507
+
508
+ def process_population(self):
509
+
510
+ if self.parent.well_list.currentText()=="*":
511
+ self.well_index = np.linspace(0,len(self.wells)-1,len(self.wells),dtype=int)
512
+ else:
513
+ self.well_index = [self.parent.well_list.currentIndex()]
514
+ print(f"Processing well {self.parent.well_list.currentText()}...")
515
+
516
+ # self.freeze()
517
+ # QApplication.setOverrideCursor(Qt.WaitCursor)
518
+
519
+ if self.mode=="targets":
520
+ self.threshold_config = self.threshold_config_targets
521
+ elif self.mode=="effectors":
522
+ self.threshold_config = self.threshold_config_effectors
523
+
524
+ loop_iter=0
525
+
526
+ if self.parent.position_list.currentText()=="*":
527
+ msgBox = QMessageBox()
528
+ msgBox.setIcon(QMessageBox.Question)
529
+ msgBox.setText("If you continue, all positions will be processed.\nDo you want to proceed?")
530
+ msgBox.setWindowTitle("Info")
531
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
532
+ returnValue = msgBox.exec()
533
+ if returnValue == QMessageBox.No:
534
+ return None
535
+
536
+ if self.seg_model_list.currentIndex() > len(self.models_truncated):
537
+ self.model_name = self.seg_models[self.seg_model_list.currentIndex()-1]
538
+ else:
539
+ self.model_name = self.seg_models[self.seg_model_list.currentIndex()]
540
+ print(self.model_name, self.seg_model_list.currentIndex())
541
+
542
+ if self.model_name.startswith('CP') and self.model_name in self.seg_models_generic and not self.cellpose_calibrated:
543
+
544
+ self.diamWidget = QWidget()
545
+ self.diamWidget.setWindowTitle('Estimate diameter')
546
+
547
+ layout = QVBoxLayout()
548
+ self.diamWidget.setLayout(layout)
549
+ self.diameter_le = QLineEdit('40')
550
+
551
+ self.cellpose_channel_cb = [QComboBox() for i in range(2)]
552
+ self.cellpose_channel_template = ['brightfield_channel', 'live_nuclei_channel']
553
+ if self.model_name=="CP_nuclei":
554
+ self.cellpose_channel_template = ['live_nuclei_channel', 'None']
555
+
556
+
557
+ for k in range(2):
558
+ hbox_channel = QHBoxLayout()
559
+ hbox_channel.addWidget(QLabel(f'channel {k+1}: '))
560
+ hbox_channel.addWidget(self.cellpose_channel_cb[k])
561
+ if k==1:
562
+ self.cellpose_channel_cb[k].addItems(list(self.exp_channels)+['None'])
563
+ else:
564
+ self.cellpose_channel_cb[k].addItems(list(self.exp_channels))
565
+ idx = self.cellpose_channel_cb[k].findText(self.cellpose_channel_template[k])
566
+ self.cellpose_channel_cb[k].setCurrentIndex(idx)
567
+ layout.addLayout(hbox_channel)
568
+
569
+ hbox = QHBoxLayout()
570
+ hbox.addWidget(QLabel('diameter [px]: '), 33)
571
+ hbox.addWidget(self.diameter_le, 66)
572
+ layout.addLayout(hbox)
573
+
574
+ self.flow_slider = QLabeledDoubleSlider()
575
+ self.flow_slider.setOrientation(1)
576
+ self.flow_slider.setRange(-6,6)
577
+ self.flow_slider.setValue(0.4)
578
+
579
+ hbox = QHBoxLayout()
580
+ hbox.addWidget(QLabel('flow threshold: '), 33)
581
+ hbox.addWidget(self.flow_slider, 66)
582
+ layout.addLayout(hbox)
583
+
584
+ self.cellprob_slider = QLabeledDoubleSlider()
585
+ self.cellprob_slider.setOrientation(1)
586
+ self.cellprob_slider.setRange(-6,6)
587
+ self.cellprob_slider.setValue(0.)
588
+
589
+ hbox = QHBoxLayout()
590
+ hbox.addWidget(QLabel('cellprob threshold: '), 33)
591
+ hbox.addWidget(self.cellprob_slider, 66)
592
+ layout.addLayout(hbox)
593
+
594
+ self.set_cellpose_scale_btn = QPushButton('set')
595
+ self.set_cellpose_scale_btn.clicked.connect(self.set_cellpose_scale)
596
+ layout.addWidget(self.set_cellpose_scale_btn)
597
+
598
+ self.diamWidget.show()
599
+ center_window(self.diamWidget)
600
+ return None
601
+
602
+
603
+ if self.model_name.startswith('SD') and self.model_name in self.seg_models_generic and not self.stardist_calibrated:
604
+
605
+ self.diamWidget = QWidget()
606
+ self.diamWidget.setWindowTitle('Channels')
607
+
608
+ layout = QVBoxLayout()
609
+ self.diamWidget.setLayout(layout)
610
+
611
+ self.stardist_channel_cb = [QComboBox() for i in range(1)]
612
+ self.stardist_channel_template = ['live_nuclei_channel']
613
+ max_i = 1
614
+ if self.model_name=="SD_versatile_he":
615
+ self.stardist_channel_template = ["H&E_1","H&E_2","H&E_3"]
616
+ self.stardist_channel_cb = [QComboBox() for i in range(3)]
617
+ max_i = 3
618
+
619
+ for k in range(max_i):
620
+ hbox_channel = QHBoxLayout()
621
+ hbox_channel.addWidget(QLabel(f'channel {k+1}: '))
622
+ hbox_channel.addWidget(self.stardist_channel_cb[k])
623
+ if k==1:
624
+ self.stardist_channel_cb[k].addItems(list(self.exp_channels)+['None'])
625
+ else:
626
+ self.stardist_channel_cb[k].addItems(list(self.exp_channels))
627
+ idx = self.stardist_channel_cb[k].findText(self.stardist_channel_template[k])
628
+ self.stardist_channel_cb[k].setCurrentIndex(idx)
629
+ layout.addLayout(hbox_channel)
630
+
631
+ self.set_stardist_scale_btn = QPushButton('set')
632
+ self.set_stardist_scale_btn.clicked.connect(self.set_stardist_scale)
633
+ layout.addWidget(self.set_stardist_scale_btn)
634
+
635
+ self.diamWidget.show()
636
+ center_window(self.diamWidget)
637
+ return None
638
+
639
+
640
+ for w_idx in self.well_index:
641
+
642
+ pos = self.parent.positions[w_idx]
643
+ if self.parent.position_list.currentText()=="*":
644
+ pos_indices = np.linspace(0,len(pos)-1,len(pos),dtype=int)
645
+ print("Processing all positions...")
646
+ else:
647
+ pos_indices = natsorted([pos.index(self.parent.position_list.currentText())])
648
+ print(f"Processing position {self.parent.position_list.currentText()}...")
649
+
650
+ well = self.parent.wells[w_idx]
651
+
652
+ for pos_idx in pos_indices:
653
+
654
+ self.pos = natsorted(glob(well+f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*/"))[pos_idx]
655
+ print(f"Position {self.pos}...\nLoading stack movie...")
656
+
657
+ if not os.path.exists(self.pos + 'output/'):
658
+ os.mkdir(self.pos + 'output/')
659
+ if not os.path.exists(self.pos + 'output/tables/'):
660
+ os.mkdir(self.pos + 'output/tables/')
661
+
662
+ if self.segment_action.isChecked():
663
+
664
+ if len(glob(os.sep.join([self.pos, f'labels_{self.mode}','*.tif'])))>0 and self.parent.position_list.currentText()!="*":
665
+ msgBox = QMessageBox()
666
+ msgBox.setIcon(QMessageBox.Question)
667
+ msgBox.setText("Labels have already been produced for this position. Do you want to segment again?")
668
+ msgBox.setWindowTitle("Info")
669
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
670
+ returnValue = msgBox.exec()
671
+ if returnValue == QMessageBox.No:
672
+ return None
673
+
674
+ if (self.seg_model_list.currentText()=="Threshold"):
675
+ if self.threshold_config is None:
676
+ msgBox = QMessageBox()
677
+ msgBox.setIcon(QMessageBox.Warning)
678
+ msgBox.setText("Please set a threshold configuration from the upload menu first. Abort.")
679
+ msgBox.setWindowTitle("Warning")
680
+ msgBox.setStandardButtons(QMessageBox.Ok)
681
+ returnValue = msgBox.exec()
682
+ if returnValue == QMessageBox.Ok:
683
+ return None
684
+ else:
685
+ print(f"Segmentation from threshold config: {self.threshold_config}")
686
+ segment_from_threshold_at_position(self.pos, self.mode, self.threshold_config, threads=self.parent.parent.n_threads)
687
+ else:
688
+ segment_at_position(self.pos, self.mode, self.model_name, stack_prefix=self.parent.movie_prefix, use_gpu=self.parent.parent.use_gpu, threads=self.parent.parent.n_threads)
689
+
690
+ if self.track_action.isChecked():
691
+ if os.path.exists(os.sep.join([self.pos, 'output', 'tables', f'trajectories_{self.mode}.csv'])) and self.parent.position_list.currentText()!="*":
692
+ msgBox = QMessageBox()
693
+ msgBox.setIcon(QMessageBox.Question)
694
+ msgBox.setText("A trajectory set already exists. Previously annotated data for\nthis position will be lost. Do you want to proceed?")
695
+ msgBox.setWindowTitle("Info")
696
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
697
+ returnValue = msgBox.exec()
698
+ if returnValue == QMessageBox.No:
699
+ return None
700
+ track_at_position(self.pos, self.mode, threads=self.parent.parent.n_threads)
701
+
702
+ if self.measure_action.isChecked():
703
+ measure_at_position(self.pos, self.mode, threads=self.parent.parent.n_threads)
704
+
705
+ table = os.sep.join([self.pos, 'output', 'tables', f'trajectories_{self.mode}.csv'])
706
+ if self.signal_analysis_action.isChecked() and os.path.exists(table):
707
+ print('table exists')
708
+ table = pd.read_csv(table)
709
+ cols = list(table.columns)
710
+ print(table, cols)
711
+ if 'class_color' in cols:
712
+ print(cols, 'class_color in cols')
713
+ colors = list(table['class_color'].to_numpy())
714
+ if 'tab:orange' in colors or 'tab:cyan' in colors:
715
+ if self.parent.position_list.currentText()!="*":
716
+ msgBox = QMessageBox()
717
+ msgBox.setIcon(QMessageBox.Question)
718
+ msgBox.setText("The signals of the cells in the position appear to have been annotated... Do you want to proceed?")
719
+ msgBox.setWindowTitle("Info")
720
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
721
+ returnValue = msgBox.exec()
722
+ if returnValue == QMessageBox.No:
723
+ return None
724
+ analyze_signals_at_position(self.pos, self.signal_models_list.currentText(), self.mode)
725
+
726
+
727
+ # self.stack = None
728
+ self.parent.update_position_options()
729
+ if self.segment_action.isChecked():
730
+ self.segment_action.setChecked(False)
731
+
732
+ # QApplication.restoreOverrideCursor()
733
+ # self.unfreeze()
734
+
735
+ def open_napari_tracking(self):
736
+ control_tracking_btrack(self.parent.pos, prefix=self.parent.movie_prefix, population=self.mode, threads=self.parent.parent.n_threads)
737
+
738
+ def view_table_ui(self):
739
+
740
+ print('Load table...')
741
+ self.load_available_tables()
742
+
743
+ if self.df is not None:
744
+ plot_mode = 'plot_track_signals'
745
+ if 'TRACK_ID' not in list(self.df.columns):
746
+ plot_mode = 'static'
747
+ self.tab_ui = TableUI(self.df, f"Well {self.parent.well_list.currentText()}; Position {self.parent.position_list.currentText()}", population=self.mode, plot_mode=plot_mode)
748
+ self.tab_ui.show()
749
+ else:
750
+ print('Table could not be loaded...')
751
+ msgBox = QMessageBox()
752
+ msgBox.setIcon(QMessageBox.Warning)
753
+ msgBox.setText("No table could be loaded...")
754
+ msgBox.setWindowTitle("Info")
755
+ msgBox.setStandardButtons(QMessageBox.Ok)
756
+ returnValue = msgBox.exec()
757
+ if returnValue == QMessageBox.Ok:
758
+ return None
759
+
760
+ # def interpret_pos_location(self):
761
+
762
+ # """
763
+ # Read the well/position selection from the control panel to decide which data to load
764
+ # Set position_indices to None if all positions must be taken
765
+
766
+ # """
767
+
768
+ # if self.well_option==len(self.wells):
769
+ # self.well_indices = np.arange(len(self.wells))
770
+ # else:
771
+ # self.well_indices = np.array([self.well_option],dtype=int)
772
+
773
+ # if self.position_option==0:
774
+ # self.position_indices = None
775
+ # else:
776
+ # self.position_indices = np.array([self.position_option],dtype=int)
777
+
778
+ def load_available_tables(self):
779
+
780
+ """
781
+ Load the tables of the selected wells/positions from the control Panel for the population of interest
782
+
783
+ """
784
+
785
+ self.well_option = self.parent.well_list.currentIndex()
786
+ if self.well_option==len(self.wells):
787
+ wo = '*'
788
+ else:
789
+ wo = self.well_option
790
+ self.position_option = self.parent.position_list.currentIndex()
791
+ if self.position_option==0:
792
+ po = '*'
793
+ else:
794
+ po = self.position_option - 1
795
+
796
+ self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=wo, position_option=po, population=self.mode, return_pos_info=True)
797
+ if self.df is None:
798
+ print('No table could be found...')
799
+
800
+ def set_cellpose_scale(self):
801
+
802
+ scale = self.parent.PxToUm * float(self.diameter_le.text()) / 30.0
803
+ if self.model_name=="CP_nuclei":
804
+ scale = self.parent.PxToUm * float(self.diameter_le.text()) / 17.0
805
+ flow_thresh = self.flow_slider.value()
806
+ cellprob_thresh = self.cellprob_slider.value()
807
+ model_complete_path = locate_segmentation_model(self.model_name)
808
+ input_config_path = model_complete_path+"config_input.json"
809
+ new_channels = [self.cellpose_channel_cb[i].currentText() for i in range(2)]
810
+ print(new_channels)
811
+ with open(input_config_path) as config_file:
812
+ input_config = json.load(config_file)
813
+
814
+ input_config['spatial_calibration'] = scale
815
+ input_config['channels'] = new_channels
816
+ input_config['flow_threshold'] = flow_thresh
817
+ input_config['cellprob_threshold'] = cellprob_thresh
818
+ with open(input_config_path, 'w') as f:
819
+ json.dump(input_config, f, indent=4)
820
+
821
+ self.cellpose_calibrated = True
822
+ print('model scale automatically computed: ', scale)
823
+ self.diamWidget.close()
824
+ self.process_population()
825
+
826
+ def set_stardist_scale(self):
827
+
828
+ # scale = self.parent.PxToUm * float(self.diameter_le.text()) / 30.0
829
+ # if self.model_name=="CP_nuclei":
830
+ # scale = self.parent.PxToUm * float(self.diameter_le.text()) / 17.0
831
+ # flow_thresh = self.flow_slider.value()
832
+ # cellprob_thresh = self.cellprob_slider.value()
833
+ model_complete_path = locate_segmentation_model(self.model_name)
834
+ input_config_path = model_complete_path+"config_input.json"
835
+ new_channels = [self.stardist_channel_cb[i].currentText() for i in range(len(self.stardist_channel_cb))]
836
+ with open(input_config_path) as config_file:
837
+ input_config = json.load(config_file)
838
+
839
+ # input_config['spatial_calibration'] = scale
840
+ input_config['channels'] = new_channels
841
+ # input_config['flow_threshold'] = flow_thresh
842
+ # input_config['cellprob_threshold'] = cellprob_thresh
843
+ with open(input_config_path, 'w') as f:
844
+ json.dump(input_config, f, indent=4)
845
+
846
+ self.stardist_calibrated = True
847
+ self.diamWidget.close()
848
+ self.process_population()
849
+
850
+
851
+ class NeighPanel(QFrame):
852
+ def __init__(self, parent):
853
+
854
+ super().__init__()
855
+ self.parent = parent
856
+ self.exp_channels = self.parent.exp_channels
857
+ self.exp_dir = self.parent.exp_dir
858
+ self.wells = np.array(self.parent.wells,dtype=str)
859
+
860
+ self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
861
+ self.grid = QGridLayout(self)
862
+ self.generate_header()
863
+
864
+ def generate_header(self):
865
+
866
+ """
867
+ Read the mode and prepare a collapsable block to process a specific cell population.
868
+
869
+ """
870
+
871
+ panel_title = QLabel(f"NEIGHBORHOOD")
872
+ panel_title.setStyleSheet("""
873
+ font-weight: bold;
874
+ padding: 0px;
875
+ """)
876
+
877
+ self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
878
+
879
+ self.select_all_btn = QPushButton()
880
+ self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
881
+ self.select_all_btn.setIconSize(QSize(20, 20))
882
+ self.all_ticked = False
883
+ #self.select_all_btn.clicked.connect(self.tick_all_actions)
884
+ self.select_all_btn.setStyleSheet(self.parent.parent.button_select_all)
885
+ self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
886
+ #self.to_disable.append(self.all_tc_actions)
887
+
888
+ self.collapse_btn = QPushButton()
889
+ self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
890
+ self.collapse_btn.setIconSize(QSize(25, 25))
891
+ self.collapse_btn.setStyleSheet(self.parent.parent.button_select_all)
892
+ self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
893
+
894
+ self.populate_contents()
895
+
896
+ self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
897
+ self.collapse_btn.clicked.connect(lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden()))
898
+ self.collapse_btn.clicked.connect(self.collapse_advanced)
899
+ self.ContentsFrame.hide()
900
+
901
+ def collapse_advanced(self):
902
+ if self.ContentsFrame.isHidden():
903
+ self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
904
+ self.collapse_btn.setIconSize(QSize(20, 20))
905
+ self.parent.w.adjustSize()
906
+ self.parent.adjustSize()
907
+ else:
908
+ self.collapse_btn.setIcon(icon(MDI6.chevron_up,color="black"))
909
+ self.collapse_btn.setIconSize(QSize(20, 20))
910
+ self.parent.w.adjustSize()
911
+ self.parent.adjustSize()
912
+
913
+ def populate_contents(self):
914
+
915
+ self.ContentsFrame = QFrame()
916
+ self.grid_contents = QGridLayout(self.ContentsFrame)
917
+ self.grid_contents.setContentsMargins(0,0,0,0)
918
+
919
+
920
+ # DISTANCE NEIGHBORHOOD
921
+ dist_neigh_hbox = QHBoxLayout()
922
+ dist_neigh_hbox.setContentsMargins(0,0,0,0)
923
+ dist_neigh_hbox.setSpacing(0)
924
+
925
+ self.dist_neigh_action = QCheckBox("DISTANCE CUT")
926
+ self.dist_neigh_action.setStyleSheet("""
927
+ font-size: 10px;
928
+ padding-left: 10px;
929
+ """)
930
+ self.dist_neigh_action.setIcon(icon(MDI6.circle_expand, color='black'))
931
+ self.dist_neigh_action.setToolTip("Match cells for which the center of mass is within a threshold distance of each other.")
932
+ #self.segment_action.toggled.connect(self.enable_segmentation_model_list)
933
+ #self.to_disable.append(self.segment_action)
934
+ dist_neigh_hbox.addWidget(self.dist_neigh_action, 95)
935
+
936
+ self.config_distance_neigh_btn = QPushButton()
937
+ self.config_distance_neigh_btn.setIcon(icon(MDI6.cog_outline,color="black"))
938
+ self.config_distance_neigh_btn.setIconSize(QSize(20, 20))
939
+ self.config_distance_neigh_btn.setToolTip("Configure distance cut neighbourhood computation.")
940
+ self.config_distance_neigh_btn.setStyleSheet(self.parent.parent.button_select_all)
941
+ self.config_distance_neigh_btn.clicked.connect(self.open_config_neighborhood)
942
+ dist_neigh_hbox.addWidget(self.config_distance_neigh_btn,5)
943
+
944
+ self.grid_contents.addLayout(dist_neigh_hbox, 0,0,1,4)
945
+
946
+ # MASK INTERSECTION NEIGHBORHOOD
947
+ # mask_neigh_hbox = QHBoxLayout()
948
+ # mask_neigh_hbox.setContentsMargins(0,0,0,0)
949
+ # mask_neigh_hbox.setSpacing(0)
950
+
951
+ # self.mask_neigh_action = QCheckBox("MASK INTERSECTION")
952
+ # self.mask_neigh_action.setStyleSheet("""
953
+ # font-size: 10px;
954
+ # padding-left: 10px;
955
+ # """)
956
+ # self.mask_neigh_action.setIcon(icon(MDI6.domino_mask, color='black'))
957
+ # self.mask_neigh_action.setToolTip("Match cells that are co-localizing.")
958
+ # #self.segment_action.toggled.connect(self.enable_segmentation_model_list)
959
+ # #self.to_disable.append(self.segment_action)
960
+ # mask_neigh_hbox.addWidget(self.mask_neigh_action, 95)
961
+
962
+ # self.config_mask_neigh_btn = QPushButton()
963
+ # self.config_mask_neigh_btn.setIcon(icon(MDI6.cog_outline,color="black"))
964
+ # self.config_mask_neigh_btn.setIconSize(QSize(20, 20))
965
+ # self.config_mask_neigh_btn.setToolTip("Configure mask intersection computation.")
966
+ # self.config_mask_neigh_btn.setStyleSheet(self.parent.parent.button_select_all)
967
+ # #self.config_distance_neigh_btn.clicked.connect(self.open_signal_annotator_configuration_ui)
968
+ # mask_neigh_hbox.addWidget(self.config_mask_neigh_btn,5)
969
+
970
+ # self.grid_contents.addLayout(mask_neigh_hbox, 1,0,1,4)
971
+
972
+ self.grid_contents.addWidget(QHSeperationLine(), 2, 0, 1, 4)
973
+ self.submit_btn = QPushButton("Submit")
974
+ self.submit_btn.setStyleSheet(self.parent.parent.button_style_sheet_2)
975
+ self.submit_btn.clicked.connect(self.process_neighborhood)
976
+ self.grid_contents.addWidget(self.submit_btn, 3, 0, 1, 4)
977
+
978
+ def open_config_neighborhood(self):
979
+
980
+ self.ConfigNeigh = ConfigNeighborhoods(self)
981
+ self.ConfigNeigh.show()
982
+
983
+ def process_neighborhood(self):
984
+
985
+ if self.parent.well_list.currentText()=="*":
986
+ self.well_index = np.linspace(0,len(self.wells)-1,len(self.wells),dtype=int)
987
+ else:
988
+ self.well_index = [self.parent.well_list.currentIndex()]
989
+ print(f"Processing well {self.parent.well_list.currentText()}...")
990
+
991
+ # self.freeze()
992
+ # QApplication.setOverrideCursor(Qt.WaitCursor)
993
+
994
+ loop_iter=0
995
+
996
+ if self.parent.position_list.currentText()=="*":
997
+ msgBox = QMessageBox()
998
+ msgBox.setIcon(QMessageBox.Question)
999
+ msgBox.setText("If you continue, all positions will be processed.\nDo you want to proceed?")
1000
+ msgBox.setWindowTitle("Info")
1001
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1002
+ returnValue = msgBox.exec()
1003
+ if returnValue == QMessageBox.No:
1004
+ return None
1005
+
1006
+ for w_idx in self.well_index:
1007
+
1008
+ pos = self.parent.positions[w_idx]
1009
+ if self.parent.position_list.currentText()=="*":
1010
+ pos_indices = np.linspace(0,len(pos)-1,len(pos),dtype=int)
1011
+ print("Processing all positions...")
1012
+ else:
1013
+ pos_indices = natsorted([pos.index(self.parent.position_list.currentText())])
1014
+ print(f"Processing position {self.parent.position_list.currentText()}...")
1015
+
1016
+ well = self.parent.wells[w_idx]
1017
+
1018
+ for pos_idx in pos_indices:
1019
+
1020
+ self.pos = natsorted(glob(well+f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*{os.sep}"))[pos_idx]
1021
+ print(f"Position {self.pos}...\nLoading stack movie...")
1022
+
1023
+ if not os.path.exists(self.pos + 'output' + os.sep):
1024
+ os.mkdir(self.pos + 'output' + os.sep)
1025
+ if not os.path.exists(self.pos + os.sep.join(['output','tables'])+os.sep):
1026
+ os.mkdir(self.pos + os.sep.join(['output','tables'])+os.sep)
1027
+
1028
+ if self.dist_neigh_action.isChecked():
1029
+
1030
+ config = self.exp_dir + os.sep.join(["configs","neighborhood_instructions.json"])
1031
+
1032
+ if not os.path.exists(config):
1033
+ print('config could not be found', config)
1034
+ msgBox = QMessageBox()
1035
+ msgBox.setIcon(QMessageBox.Warning)
1036
+ msgBox.setText("Please define a neighborhood first.")
1037
+ msgBox.setWindowTitle("Info")
1038
+ msgBox.setStandardButtons(QMessageBox.Ok)
1039
+ returnValue = msgBox.exec()
1040
+ return None
1041
+
1042
+ with open(config, 'r') as f:
1043
+ config = json.load(f)
1044
+
1045
+ compute_neighborhood_at_position(self.pos,
1046
+ config['distance'],
1047
+ population=config['population'],
1048
+ theta_dist=None,
1049
+ img_shape=(self.parent.shape_x,self.parent.shape_y),
1050
+ return_tables=False,
1051
+ clear_neigh=config['clear_neigh'],
1052
+ event_time_col=config['event_time_col'],
1053
+ neighborhood_kwargs=config['neighborhood_kwargs'],
1054
+ )
1055
+ print('Done.')