celldetective 1.4.2__py3-none-any.whl → 1.5.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. celldetective/__init__.py +25 -0
  2. celldetective/__main__.py +62 -43
  3. celldetective/_version.py +1 -1
  4. celldetective/extra_properties.py +477 -399
  5. celldetective/filters.py +192 -97
  6. celldetective/gui/InitWindow.py +541 -411
  7. celldetective/gui/__init__.py +0 -15
  8. celldetective/gui/about.py +44 -39
  9. celldetective/gui/analyze_block.py +120 -84
  10. celldetective/gui/base/__init__.py +0 -0
  11. celldetective/gui/base/channel_norm_generator.py +335 -0
  12. celldetective/gui/base/components.py +249 -0
  13. celldetective/gui/base/feature_choice.py +92 -0
  14. celldetective/gui/base/figure_canvas.py +52 -0
  15. celldetective/gui/base/list_widget.py +133 -0
  16. celldetective/gui/{styles.py → base/styles.py} +92 -36
  17. celldetective/gui/base/utils.py +33 -0
  18. celldetective/gui/base_annotator.py +900 -767
  19. celldetective/gui/classifier_widget.py +6 -22
  20. celldetective/gui/configure_new_exp.py +777 -671
  21. celldetective/gui/control_panel.py +635 -524
  22. celldetective/gui/dynamic_progress.py +449 -0
  23. celldetective/gui/event_annotator.py +2023 -1662
  24. celldetective/gui/generic_signal_plot.py +1292 -944
  25. celldetective/gui/gui_utils.py +899 -1289
  26. celldetective/gui/interactions_block.py +658 -0
  27. celldetective/gui/interactive_timeseries_viewer.py +447 -0
  28. celldetective/gui/json_readers.py +48 -15
  29. celldetective/gui/layouts/__init__.py +5 -0
  30. celldetective/gui/layouts/background_model_free_layout.py +537 -0
  31. celldetective/gui/layouts/channel_offset_layout.py +134 -0
  32. celldetective/gui/layouts/local_correction_layout.py +91 -0
  33. celldetective/gui/layouts/model_fit_layout.py +372 -0
  34. celldetective/gui/layouts/operation_layout.py +68 -0
  35. celldetective/gui/layouts/protocol_designer_layout.py +96 -0
  36. celldetective/gui/pair_event_annotator.py +3130 -2435
  37. celldetective/gui/plot_measurements.py +586 -267
  38. celldetective/gui/plot_signals_ui.py +724 -506
  39. celldetective/gui/preprocessing_block.py +395 -0
  40. celldetective/gui/process_block.py +1678 -1831
  41. celldetective/gui/seg_model_loader.py +580 -473
  42. celldetective/gui/settings/__init__.py +0 -7
  43. celldetective/gui/settings/_cellpose_model_params.py +181 -0
  44. celldetective/gui/settings/_event_detection_model_params.py +95 -0
  45. celldetective/gui/settings/_segmentation_model_params.py +159 -0
  46. celldetective/gui/settings/_settings_base.py +77 -65
  47. celldetective/gui/settings/_settings_event_model_training.py +752 -526
  48. celldetective/gui/settings/_settings_measurements.py +1133 -964
  49. celldetective/gui/settings/_settings_neighborhood.py +574 -488
  50. celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
  51. celldetective/gui/settings/_settings_signal_annotator.py +329 -305
  52. celldetective/gui/settings/_settings_tracking.py +1304 -1094
  53. celldetective/gui/settings/_stardist_model_params.py +98 -0
  54. celldetective/gui/survival_ui.py +422 -312
  55. celldetective/gui/tableUI.py +1665 -1701
  56. celldetective/gui/table_ops/_maths.py +295 -0
  57. celldetective/gui/table_ops/_merge_groups.py +140 -0
  58. celldetective/gui/table_ops/_merge_one_hot.py +95 -0
  59. celldetective/gui/table_ops/_query_table.py +43 -0
  60. celldetective/gui/table_ops/_rename_col.py +44 -0
  61. celldetective/gui/thresholds_gui.py +382 -179
  62. celldetective/gui/viewers/__init__.py +0 -0
  63. celldetective/gui/viewers/base_viewer.py +700 -0
  64. celldetective/gui/viewers/channel_offset_viewer.py +331 -0
  65. celldetective/gui/viewers/contour_viewer.py +394 -0
  66. celldetective/gui/viewers/size_viewer.py +153 -0
  67. celldetective/gui/viewers/spot_detection_viewer.py +341 -0
  68. celldetective/gui/viewers/threshold_viewer.py +309 -0
  69. celldetective/gui/workers.py +304 -126
  70. celldetective/log_manager.py +92 -0
  71. celldetective/measure.py +1895 -1478
  72. celldetective/napari/__init__.py +0 -0
  73. celldetective/napari/utils.py +1025 -0
  74. celldetective/neighborhood.py +1914 -1448
  75. celldetective/preprocessing.py +1620 -1220
  76. celldetective/processes/__init__.py +0 -0
  77. celldetective/processes/background_correction.py +271 -0
  78. celldetective/processes/compute_neighborhood.py +894 -0
  79. celldetective/processes/detect_events.py +246 -0
  80. celldetective/processes/measure_cells.py +565 -0
  81. celldetective/processes/segment_cells.py +760 -0
  82. celldetective/processes/track_cells.py +435 -0
  83. celldetective/processes/train_segmentation_model.py +694 -0
  84. celldetective/processes/train_signal_model.py +265 -0
  85. celldetective/processes/unified_process.py +292 -0
  86. celldetective/regionprops/_regionprops.py +358 -317
  87. celldetective/relative_measurements.py +987 -710
  88. celldetective/scripts/measure_cells.py +313 -212
  89. celldetective/scripts/measure_relative.py +90 -46
  90. celldetective/scripts/segment_cells.py +165 -104
  91. celldetective/scripts/segment_cells_thresholds.py +96 -68
  92. celldetective/scripts/track_cells.py +198 -149
  93. celldetective/scripts/train_segmentation_model.py +324 -201
  94. celldetective/scripts/train_signal_model.py +87 -45
  95. celldetective/segmentation.py +844 -749
  96. celldetective/signals.py +3514 -2861
  97. celldetective/tracking.py +30 -15
  98. celldetective/utils/__init__.py +0 -0
  99. celldetective/utils/cellpose_utils/__init__.py +133 -0
  100. celldetective/utils/color_mappings.py +42 -0
  101. celldetective/utils/data_cleaning.py +630 -0
  102. celldetective/utils/data_loaders.py +450 -0
  103. celldetective/utils/dataset_helpers.py +207 -0
  104. celldetective/utils/downloaders.py +197 -0
  105. celldetective/utils/event_detection/__init__.py +8 -0
  106. celldetective/utils/experiment.py +1782 -0
  107. celldetective/utils/image_augmenters.py +308 -0
  108. celldetective/utils/image_cleaning.py +74 -0
  109. celldetective/utils/image_loaders.py +926 -0
  110. celldetective/utils/image_transforms.py +335 -0
  111. celldetective/utils/io.py +62 -0
  112. celldetective/utils/mask_cleaning.py +348 -0
  113. celldetective/utils/mask_transforms.py +5 -0
  114. celldetective/utils/masks.py +184 -0
  115. celldetective/utils/maths.py +351 -0
  116. celldetective/utils/model_getters.py +325 -0
  117. celldetective/utils/model_loaders.py +296 -0
  118. celldetective/utils/normalization.py +380 -0
  119. celldetective/utils/parsing.py +465 -0
  120. celldetective/utils/plots/__init__.py +0 -0
  121. celldetective/utils/plots/regression.py +53 -0
  122. celldetective/utils/resources.py +34 -0
  123. celldetective/utils/stardist_utils/__init__.py +104 -0
  124. celldetective/utils/stats.py +90 -0
  125. celldetective/utils/types.py +21 -0
  126. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
  127. celldetective-1.5.0b0.dist-info/RECORD +187 -0
  128. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
  129. tests/gui/test_new_project.py +129 -117
  130. tests/gui/test_project.py +127 -79
  131. tests/test_filters.py +39 -15
  132. tests/test_notebooks.py +8 -0
  133. tests/test_tracking.py +232 -13
  134. tests/test_utils.py +123 -77
  135. celldetective/gui/base_components.py +0 -23
  136. celldetective/gui/layouts.py +0 -1602
  137. celldetective/gui/processes/compute_neighborhood.py +0 -594
  138. celldetective/gui/processes/measure_cells.py +0 -360
  139. celldetective/gui/processes/segment_cells.py +0 -499
  140. celldetective/gui/processes/track_cells.py +0 -303
  141. celldetective/gui/processes/train_segmentation_model.py +0 -270
  142. celldetective/gui/processes/train_signal_model.py +0 -108
  143. celldetective/gui/table_ops/merge_groups.py +0 -118
  144. celldetective/gui/viewers.py +0 -1354
  145. celldetective/io.py +0 -3663
  146. celldetective/utils.py +0 -3108
  147. celldetective-1.4.2.dist-info/RECORD +0 -123
  148. /celldetective/{gui/processes → processes}/downloader.py +0 -0
  149. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/top_level.txt +0 -0
@@ -1,1848 +1,1695 @@
1
- from PyQt5.QtWidgets import QDialog, QFrame, QGridLayout, QComboBox, QListWidget, QLabel, QPushButton, QVBoxLayout, QHBoxLayout, QCheckBox, \
2
- QMessageBox
3
- from PyQt5.QtCore import Qt, QSize
1
+ from PyQt5.QtWidgets import (
2
+ QDialog,
3
+ QFrame,
4
+ QGridLayout,
5
+ QComboBox,
6
+ QLabel,
7
+ QPushButton,
8
+ QVBoxLayout,
9
+ QHBoxLayout,
10
+ QCheckBox,
11
+ QMessageBox,
12
+ )
13
+ from PyQt5.QtCore import Qt, QSize, QTimer, QThread, pyqtSignal
4
14
  from superqt.fonticon import icon
5
15
  from fonticon_mdi6 import MDI6
6
16
  import gc
7
- from PyQt5.QtGui import QDoubleValidator, QIntValidator
8
-
9
- from celldetective.gui.processes.compute_neighborhood import NeighborhoodProcess
10
- from celldetective.gui.event_annotator import MeasureAnnotator
11
- from celldetective.io import get_segmentation_models_list, control_segmentation_napari, get_signal_models_list, \
12
- control_tracks, load_experiment_tables, get_pair_signal_models_list
13
- from celldetective.io import locate_segmentation_model, extract_position_name, fix_missing_labels, locate_signal_model
14
- from celldetective.gui import SegmentationModelLoader, ClassifierWidget, \
15
- EventAnnotator, TableUI, CelldetectiveWidget, PairEventAnnotator
16
-
17
- from celldetective.gui.settings import SettingsSegmentation, SettingsMeasurements, SettingsTracking, \
18
- SettingsSignalAnnotator, SettingsNeighborhood, SettingsSegmentationModelTraining, SettingsEventDetectionModelTraining
19
-
20
- from celldetective.gui.gui_utils import QHSeperationLine
21
- from celldetective.relative_measurements import rel_measure_at_position
22
- from celldetective.signals import analyze_signals_at_position, analyze_pair_signals_at_position
23
- from celldetective.utils import extract_experiment_channels, remove_file_if_exists
17
+
18
+ from celldetective.utils.model_getters import (
19
+ get_signal_models_list,
20
+ get_segmentation_models_list,
21
+ )
22
+ from celldetective.utils.data_loaders import load_experiment_tables
23
+ from celldetective.utils.model_loaders import (
24
+ locate_signal_model,
25
+ locate_segmentation_model,
26
+ )
27
+ from celldetective.utils.image_loaders import fix_missing_labels
28
+
29
+ from celldetective.gui.base.components import (
30
+ CelldetectiveWidget,
31
+ CelldetectiveProgressDialog,
32
+ QHSeperationLine,
33
+ )
34
+
24
35
  import numpy as np
25
36
  from glob import glob
37
+ from celldetective import get_logger
38
+
39
+ logger = get_logger()
40
+
41
+
42
+ class NapariLoaderThread(QThread):
43
+ progress = pyqtSignal(int)
44
+ status = pyqtSignal(str)
45
+ finished_with_result = pyqtSignal(object)
46
+
47
+ def __init__(self, pos, prefix, population, threads):
48
+ super().__init__()
49
+ self.pos = pos
50
+ self.prefix = prefix
51
+ self.population = population
52
+ self.threads = threads
53
+ self._is_cancelled = False
54
+
55
+ def stop(self):
56
+ self._is_cancelled = True
57
+
58
+ def run(self):
59
+ from celldetective.napari.utils import control_tracks
60
+
61
+ def callback(p):
62
+ if self._is_cancelled:
63
+ return False
64
+ self.progress.emit(p)
65
+ return True
66
+
67
+ try:
68
+ res = control_tracks(
69
+ self.pos,
70
+ prefix=self.prefix,
71
+ population=self.population,
72
+ threads=self.threads,
73
+ progress_callback=callback,
74
+ prepare_only=True,
75
+ )
76
+ self.finished_with_result.emit(res)
77
+ except Exception as e:
78
+ self.finished_with_result.emit(e)
79
+
80
+
26
81
  from natsort import natsorted
27
82
  import os
28
- import pandas as pd
29
- from celldetective.gui.gui_utils import center_window
83
+
84
+ from celldetective.gui.base.utils import center_window
85
+ from celldetective.utils.io import remove_file_if_exists
30
86
  from tifffile import imwrite
31
87
  import json
32
- from celldetective.preprocessing import correct_background_model_free, correct_background_model, correct_channel_offset
33
88
  from celldetective.gui.gui_utils import help_generic
34
- from celldetective.gui.layouts import SignalModelParamsWidget, SegModelParamsWidget, CellposeParamsWidget, StarDistParamsWidget, BackgroundModelFreeCorrectionLayout, ProtocolDesignerLayout, BackgroundFitCorrectionLayout, ChannelOffsetOptionsLayout
35
- from celldetective.gui import Styles
36
- from celldetective.utils import get_software_location
37
-
38
- from celldetective.gui.workers import ProgressWindow
39
- from celldetective.gui.processes.segment_cells import SegmentCellThresholdProcess, SegmentCellDLProcess
40
- from celldetective.gui.processes.track_cells import TrackingProcess
41
- from celldetective.gui.processes.measure_cells import MeasurementProcess
42
-
43
- class ProcessPanel(QFrame, Styles):
44
-
45
- def __init__(self, parent_window, mode):
46
-
47
- super().__init__()
48
- self.parent_window = parent_window
49
- self.mode = mode
50
- self.exp_channels = self.parent_window.exp_channels
51
- self.exp_dir = self.parent_window.exp_dir
52
- self.exp_config = self.parent_window.exp_config
53
- self.movie_prefix = self.parent_window.movie_prefix
54
- self.threshold_configs = [None for _ in range(len(self.parent_window.populations))]
55
- self.wells = np.array(self.parent_window.wells, dtype=str)
56
- self.cellpose_calibrated = False
57
- self.stardist_calibrated = False
58
- self.segChannelsSet = False
59
- self.signalChannelsSet = False
60
- self.flipSeg = False
61
-
62
- self.use_gpu = self.parent_window.parent_window.use_gpu
63
- self.n_threads = self.parent_window.parent_window.n_threads
64
-
65
- self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
66
- self.grid = QGridLayout(self)
67
- self.grid.setContentsMargins(5, 5, 5, 5)
68
- self.generate_header()
69
-
70
- def generate_header(self):
71
-
72
- """
73
- Read the mode and prepare a collapsable block to process a specific cell population.
74
-
75
- """
76
-
77
- panel_title = QLabel(f"PROCESS {self.mode.upper()} ")
78
- panel_title.setStyleSheet("""
79
- font-weight: bold;
80
- padding: 0px;
81
- """)
82
-
83
- title_hbox = QHBoxLayout()
84
- self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
85
-
86
- # self.help_pop_btn = QPushButton()
87
- # self.help_pop_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
88
- # self.help_pop_btn.setIconSize(QSize(20, 20))
89
- # self.help_pop_btn.clicked.connect(self.help_population)
90
- # self.help_pop_btn.setStyleSheet(self.button_select_all)
91
- # self.help_pop_btn.setToolTip("Help.")
92
- # self.grid.addWidget(self.help_pop_btn, 0, 0, 1, 3, alignment=Qt.AlignRight)
93
-
94
- # self.select_all_btn = QPushButton()
95
- # self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
96
- # self.select_all_btn.setIconSize(QSize(20, 20))
97
- # self.all_ticked = False
98
- # self.select_all_btn.clicked.connect(self.tick_all_actions)
99
- # self.select_all_btn.setStyleSheet(self.button_select_all)
100
- #self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
101
- #self.to_disable.append(self.all_tc_actions)
102
-
103
- self.collapse_btn = QPushButton()
104
- self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
105
- self.collapse_btn.setIconSize(QSize(25, 25))
106
- self.collapse_btn.setStyleSheet(self.button_select_all)
107
- #self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
108
-
109
- title_hbox.addWidget(QLabel(), 5) #self.select_all_btn
110
- title_hbox.addWidget(QLabel(), 85, alignment=Qt.AlignCenter)
111
- # title_hbox.addWidget(self.help_pop_btn, 5)
112
- title_hbox.addWidget(self.collapse_btn, 5)
113
-
114
- self.grid.addLayout(title_hbox, 0, 0, 1, 4)
115
- self.populate_contents()
116
-
117
- self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
118
- self.collapse_btn.clicked.connect(lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden()))
119
- self.collapse_btn.clicked.connect(self.collapse_advanced)
120
- self.ContentsFrame.hide()
121
-
122
- def collapse_advanced(self):
123
-
124
- panels_open = [not p.ContentsFrame.isHidden() for p in self.parent_window.ProcessPopulations]
125
- interactions_open = not self.parent_window.NeighPanel.ContentsFrame.isHidden()
126
- preprocessing_open = not self.parent_window.PreprocessingPanel.ContentsFrame.isHidden()
127
- is_open = np.array(panels_open+[interactions_open, preprocessing_open])
128
-
129
- if self.ContentsFrame.isHidden():
130
- self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
131
- self.collapse_btn.setIconSize(QSize(20, 20))
132
- if len(is_open[is_open])==0:
133
- self.parent_window.scroll.setMinimumHeight(int(550))
134
- self.parent_window.adjustSize()
135
- else:
136
- self.collapse_btn.setIcon(icon(MDI6.chevron_up, color="black"))
137
- self.collapse_btn.setIconSize(QSize(20, 20))
138
- self.parent_window.scroll.setMinimumHeight(min(int(930), int(0.9*self.parent_window.screen_height)))
139
-
140
-
141
- # def help_population(self):
142
-
143
- # """
144
- # Helper to choose a proper cell population structure.
145
- # """
146
-
147
- # dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','cell-populations.json'])
148
-
149
- # with open(dict_path) as f:
150
- # d = json.load(f)
151
-
152
- # suggestion = help_generic(d)
153
- # if isinstance(suggestion, str):
154
- # print(f"{suggestion=}")
155
- # msgBox = QMessageBox()
156
- # msgBox.setIcon(QMessageBox.Information)
157
- # msgBox.setTextFormat(Qt.RichText)
158
- # msgBox.setText(suggestion)
159
- # msgBox.setWindowTitle("Info")
160
- # msgBox.setStandardButtons(QMessageBox.Ok)
161
- # returnValue = msgBox.exec()
162
- # if returnValue == QMessageBox.Ok:
163
- # return None
164
-
165
- def populate_contents(self):
166
- self.ContentsFrame = QFrame()
167
- self.ContentsFrame.setContentsMargins(5,5,5,5)
168
- self.grid_contents = QGridLayout(self.ContentsFrame)
169
- self.grid_contents.setContentsMargins(0,0,0,0)
170
- self.generate_segmentation_options()
171
- self.generate_tracking_options()
172
- self.generate_measure_options()
173
- self.generate_signal_analysis_options()
174
-
175
- self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
176
- self.view_tab_btn = QPushButton("Explore table")
177
- self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
178
- self.view_tab_btn.clicked.connect(self.view_table_ui)
179
- self.view_tab_btn.setToolTip('Explore table')
180
- self.view_tab_btn.setIcon(icon(MDI6.table,color="#1565c0"))
181
- self.view_tab_btn.setIconSize(QSize(20, 20))
182
- #self.view_tab_btn.setEnabled(False)
183
- self.grid_contents.addWidget(self.view_tab_btn, 10, 0, 1, 4)
184
-
185
- self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
186
- self.submit_btn = QPushButton("Submit")
187
- self.submit_btn.setStyleSheet(self.button_style_sheet)
188
- self.submit_btn.clicked.connect(self.process_population)
189
- self.grid_contents.addWidget(self.submit_btn, 11, 0, 1, 4)
190
-
191
- def generate_measure_options(self):
192
-
193
- measure_layout = QHBoxLayout()
194
-
195
- self.measure_action = QCheckBox("MEASURE")
196
- self.measure_action.setStyleSheet(self.menu_check_style)
197
-
198
- self.measure_action.setIcon(icon(MDI6.eyedropper,color="black"))
199
- self.measure_action.setIconSize(QSize(20, 20))
200
- self.measure_action.setToolTip("Measure.")
201
- measure_layout.addWidget(self.measure_action, 90)
202
- #self.to_disable.append(self.measure_action_tc)
203
-
204
- self.classify_btn = QPushButton()
205
- self.classify_btn.setIcon(icon(MDI6.scatter_plot, color="black"))
206
- self.classify_btn.setIconSize(QSize(20, 20))
207
- self.classify_btn.setToolTip("Classify data.")
208
- self.classify_btn.setStyleSheet(self.button_select_all)
209
- self.classify_btn.clicked.connect(self.open_classifier_ui)
210
- measure_layout.addWidget(self.classify_btn, 5) #4,2,1,1, alignment=Qt.AlignRight
211
-
212
- self.check_measurements_btn=QPushButton()
213
- self.check_measurements_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
214
- self.check_measurements_btn.setIconSize(QSize(20, 20))
215
- self.check_measurements_btn.setToolTip("Explore measurements in-situ.")
216
- self.check_measurements_btn.setStyleSheet(self.button_select_all)
217
- self.check_measurements_btn.clicked.connect(self.check_measurements)
218
- measure_layout.addWidget(self.check_measurements_btn, 5)
219
-
220
-
221
- self.measurements_config_btn = QPushButton()
222
- self.measurements_config_btn.setIcon(icon(MDI6.cog_outline,color="black"))
223
- self.measurements_config_btn.setIconSize(QSize(20, 20))
224
- self.measurements_config_btn.setToolTip("Configure measurements.")
225
- self.measurements_config_btn.setStyleSheet(self.button_select_all)
226
- self.measurements_config_btn.clicked.connect(self.open_measurement_configuration_ui)
227
- measure_layout.addWidget(self.measurements_config_btn, 5) #4,2,1,1, alignment=Qt.AlignRight
228
-
229
- self.grid_contents.addLayout(measure_layout,5,0,1,4)
230
-
231
- def generate_signal_analysis_options(self):
232
-
233
- signal_layout = QVBoxLayout()
234
- signal_hlayout = QHBoxLayout()
235
- self.signal_analysis_action = QCheckBox("DETECT EVENTS")
236
- self.signal_analysis_action.setStyleSheet(self.menu_check_style)
237
- self.signal_analysis_action.setIcon(icon(MDI6.chart_bell_curve_cumulative,color="black"))
238
- self.signal_analysis_action.setIconSize(QSize(20, 20))
239
- self.signal_analysis_action.setToolTip("Detect events in single-cell signals.")
240
- self.signal_analysis_action.toggled.connect(self.enable_signal_model_list)
241
- signal_hlayout.addWidget(self.signal_analysis_action, 90)
242
-
243
- self.check_signals_btn = QPushButton()
244
- self.check_signals_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
245
- self.check_signals_btn.setIconSize(QSize(20, 20))
246
- self.check_signals_btn.clicked.connect(self.check_signals)
247
- self.check_signals_btn.setToolTip("Explore signals in-situ.")
248
- self.check_signals_btn.setStyleSheet(self.button_select_all)
249
- signal_hlayout.addWidget(self.check_signals_btn, 6)
250
-
251
- self.config_signal_annotator_btn = QPushButton()
252
- self.config_signal_annotator_btn.setIcon(icon(MDI6.cog_outline,color="black"))
253
- self.config_signal_annotator_btn.setIconSize(QSize(20, 20))
254
- self.config_signal_annotator_btn.setToolTip("Configure the dynamic visualizer.")
255
- self.config_signal_annotator_btn.setStyleSheet(self.button_select_all)
256
- self.config_signal_annotator_btn.clicked.connect(self.open_signal_annotator_configuration_ui)
257
- signal_hlayout.addWidget(self.config_signal_annotator_btn, 6)
258
-
259
- #self.to_disable.append(self.measure_action_tc)
260
- signal_layout.addLayout(signal_hlayout)
261
-
262
- signal_model_vbox = QVBoxLayout()
263
- signal_model_vbox.setContentsMargins(25,0,25,0)
264
-
265
- model_zoo_layout = QHBoxLayout()
266
- model_zoo_layout.addWidget(QLabel("Model zoo:"),90)
267
-
268
- self.signal_models_list = QComboBox()
269
- self.signal_models_list.setEnabled(False)
270
- self.refresh_signal_models()
271
- #self.to_disable.append(self.cell_models_list)
272
-
273
- self.train_signal_model_btn = QPushButton("TRAIN")
274
- self.train_signal_model_btn.setToolTip("Train or retrain an event detection model\non newly annotated data.")
275
- self.train_signal_model_btn.setIcon(icon(MDI6.redo_variant,color='black'))
276
- self.train_signal_model_btn.setIconSize(QSize(20, 20))
277
- self.train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
278
- model_zoo_layout.addWidget(self.train_signal_model_btn, 5)
279
- self.train_signal_model_btn.clicked.connect(self.open_signal_model_config_ui)
280
-
281
- signal_model_vbox.addLayout(model_zoo_layout)
282
- signal_model_vbox.addWidget(self.signal_models_list)
283
-
284
- signal_layout.addLayout(signal_model_vbox)
285
-
286
- self.grid_contents.addLayout(signal_layout,6,0,1,4)
287
-
288
- def refresh_signal_models(self):
289
- self.signal_models = get_signal_models_list()
290
- self.signal_models_list.clear()
291
-
292
- thresh = 35
293
- models_truncated = [m[:thresh - 3]+'...' if len(m)>thresh else m for m in self.signal_models]
294
-
295
- self.signal_models_list.addItems(models_truncated)
296
- for i in range(len(self.signal_models)):
297
- self.signal_models_list.setItemData(i, self.signal_models[i], Qt.ToolTipRole)
298
-
299
-
300
- def generate_tracking_options(self):
301
-
302
- grid_track = QHBoxLayout()
303
-
304
- self.track_action = QCheckBox("TRACK")
305
- self.track_action.setStyleSheet(self.menu_check_style)
306
- self.track_action.setIcon(icon(MDI6.chart_timeline_variant,color="black"))
307
- self.track_action.setIconSize(QSize(20, 20))
308
- self.track_action.setToolTip(f"Track the {self.mode[:-1]} cells.")
309
- grid_track.addWidget(self.track_action, 75)
310
-
311
- self.delete_tracks_btn = QPushButton()
312
- self.delete_tracks_btn.setIcon(icon(MDI6.trash_can,color="black"))
313
- self.delete_tracks_btn.setIconSize(QSize(20, 20))
314
- self.delete_tracks_btn.setToolTip("Delete existing tracks.")
315
- self.delete_tracks_btn.setStyleSheet(self.button_select_all)
316
- self.delete_tracks_btn.clicked.connect(self.delete_tracks)
317
- self.delete_tracks_btn.setEnabled(True)
318
- self.delete_tracks_btn.hide()
319
- grid_track.addWidget(self.delete_tracks_btn, 6) #4,3,1,1, alignment=Qt.AlignLeft
320
-
321
- self.check_tracking_result_btn = QPushButton()
322
- self.check_tracking_result_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
323
- self.check_tracking_result_btn.setIconSize(QSize(20, 20))
324
- self.check_tracking_result_btn.setToolTip("View tracking output in napari.")
325
- self.check_tracking_result_btn.setStyleSheet(self.button_select_all)
326
- self.check_tracking_result_btn.clicked.connect(self.open_napari_tracking)
327
- self.check_tracking_result_btn.setEnabled(False)
328
- grid_track.addWidget(self.check_tracking_result_btn, 6) #4,3,1,1, alignment=Qt.AlignLeft
329
-
330
- self.track_config_btn = QPushButton()
331
- self.track_config_btn.setIcon(icon(MDI6.cog_outline,color="black"))
332
- self.track_config_btn.setIconSize(QSize(20, 20))
333
- self.track_config_btn.setToolTip("Configure tracking.")
334
- self.track_config_btn.setStyleSheet(self.button_select_all)
335
- self.track_config_btn.clicked.connect(self.open_tracking_configuration_ui)
336
- grid_track.addWidget(self.track_config_btn, 6) #4,2,1,1, alignment=Qt.AlignRight
337
-
338
- self.help_track_btn = QPushButton()
339
- self.help_track_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
340
- self.help_track_btn.setIconSize(QSize(20, 20))
341
- self.help_track_btn.clicked.connect(self.help_tracking)
342
- self.help_track_btn.setStyleSheet(self.button_select_all)
343
- self.help_track_btn.setToolTip("Help.")
344
- grid_track.addWidget(self.help_track_btn, 6) #4,2,1,1, alignment=Qt.AlignRight
345
-
346
- self.grid_contents.addLayout(grid_track, 4, 0, 1,4)
347
-
348
- def delete_tracks(self):
349
-
350
- msgBox = QMessageBox()
351
- msgBox.setIcon(QMessageBox.Question)
352
- msgBox.setText("Do you want to erase the tracks? All subsequent annotations will be erased...")
353
- msgBox.setWindowTitle("Info")
354
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
355
- returnValue = msgBox.exec()
356
- if returnValue == QMessageBox.No:
357
- return None
358
- elif returnValue == QMessageBox.Yes:
359
- remove_file_if_exists(os.sep.join([self.parent_window.pos, 'output', 'tables', f'trajectories_{self.mode}.csv']))
360
- remove_file_if_exists(os.sep.join([self.parent_window.pos, 'output', 'tables', f'trajectories_{self.mode}.pkl']))
361
- remove_file_if_exists(os.sep.join([self.parent_window.pos, 'output', 'tables', f'napari_{self.mode[:-1]}_trajectories.npy']))
362
- remove_file_if_exists(os.sep.join([self.parent_window.pos, 'output', 'tables', f'trajectories_pairs.csv']))
363
- self.parent_window.update_position_options()
364
- else:
365
- return None
366
-
367
- def generate_segmentation_options(self):
368
-
369
- grid_segment = QHBoxLayout()
370
- grid_segment.setContentsMargins(0,0,0,0)
371
- grid_segment.setSpacing(0)
372
-
373
- self.segment_action = QCheckBox("SEGMENT")
374
- self.segment_action.setStyleSheet(self.menu_check_style)
375
- self.segment_action.setIcon(icon(MDI6.bacteria, color='black'))
376
- self.segment_action.setToolTip(f"Segment the {self.mode[:-1]} cells on the images.")
377
- self.segment_action.toggled.connect(self.enable_segmentation_model_list)
378
- #self.to_disable.append(self.segment_action)
379
- grid_segment.addWidget(self.segment_action, 90)
380
-
381
- # self.flip_segment_btn = QPushButton()
382
- # self.flip_segment_btn.setIcon(icon(MDI6.camera_flip_outline,color="black"))
383
- # self.flip_segment_btn.setIconSize(QSize(20, 20))
384
- # self.flip_segment_btn.clicked.connect(self.flip_segmentation)
385
- # self.flip_segment_btn.setStyleSheet(self.button_select_all)
386
- # self.flip_segment_btn.setToolTip("Flip the order of the frames for segmentation.")
387
- # grid_segment.addWidget(self.flip_segment_btn, 5)
388
-
389
- self.segmentation_config_btn = QPushButton()
390
- self.segmentation_config_btn.setIcon(icon(MDI6.cog_outline,color="black"))
391
- self.segmentation_config_btn.setIconSize(QSize(20, 20))
392
- self.segmentation_config_btn.setToolTip("Configure segmentation.")
393
- self.segmentation_config_btn.setStyleSheet(self.button_select_all)
394
- self.segmentation_config_btn.clicked.connect(self.open_segmentation_configuration_ui)
395
- grid_segment.addWidget(self.segmentation_config_btn, 5)
396
-
397
-
398
- self.check_seg_btn = QPushButton()
399
- self.check_seg_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
400
- self.check_seg_btn.setIconSize(QSize(20, 20))
401
- self.check_seg_btn.clicked.connect(self.check_segmentation)
402
- self.check_seg_btn.setStyleSheet(self.button_select_all)
403
- self.check_seg_btn.setToolTip("View segmentation output in napari.")
404
- grid_segment.addWidget(self.check_seg_btn, 5)
405
-
406
- self.help_seg_btn = QPushButton()
407
- self.help_seg_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
408
- self.help_seg_btn.setIconSize(QSize(20, 20))
409
- self.help_seg_btn.clicked.connect(self.help_segmentation)
410
- self.help_seg_btn.setStyleSheet(self.button_select_all)
411
- self.help_seg_btn.setToolTip("Help.")
412
- grid_segment.addWidget(self.help_seg_btn, 5)
413
- self.grid_contents.addLayout(grid_segment, 0,0,1,4)
414
-
415
- seg_option_vbox = QVBoxLayout()
416
- seg_option_vbox.setContentsMargins(25,0,25,0)
417
- model_zoo_layout = QHBoxLayout()
418
- model_zoo_layout.addWidget(QLabel("Model zoo:"),90)
419
- self.seg_model_list = QComboBox()
420
- self.seg_model_list.currentIndexChanged.connect(self.reset_generalist_setup)
421
- #self.to_disable.append(self.tc_seg_model_list)
422
- self.seg_model_list.setGeometry(50, 50, 200, 30)
423
- self.init_seg_model_list()
424
-
425
-
426
- self.upload_model_btn = QPushButton("UPLOAD")
427
- self.upload_model_btn.setIcon(icon(MDI6.upload,color="black"))
428
- self.upload_model_btn.setIconSize(QSize(20, 20))
429
- self.upload_model_btn.setStyleSheet(self.button_style_sheet_3)
430
- self.upload_model_btn.setToolTip("Upload a new segmentation model\n(Deep learning or threshold-based).")
431
- model_zoo_layout.addWidget(self.upload_model_btn, 5)
432
- self.upload_model_btn.clicked.connect(self.upload_segmentation_model)
433
- # self.to_disable.append(self.upload_tc_model)
434
-
435
- self.train_btn = QPushButton("TRAIN")
436
- self.train_btn.setToolTip("Train or retrain a segmentation model\non newly annotated data.")
437
- self.train_btn.setIcon(icon(MDI6.redo_variant,color='black'))
438
- self.train_btn.setIconSize(QSize(20, 20))
439
- self.train_btn.setStyleSheet(self.button_style_sheet_3)
440
- self.train_btn.clicked.connect(self.open_segmentation_model_config_ui)
441
- model_zoo_layout.addWidget(self.train_btn, 5)
442
- # self.train_button_tc.clicked.connect(self.train_stardist_model_tc)
443
- # self.to_disable.append(self.train_button_tc)
444
-
445
- seg_option_vbox.addLayout(model_zoo_layout)
446
- seg_option_vbox.addWidget(self.seg_model_list)
447
- self.seg_model_list.setEnabled(False)
448
- self.grid_contents.addLayout(seg_option_vbox, 2, 0, 1, 4)
449
-
450
- def flip_segmentation(self):
451
- if not self.flipSeg:
452
- self.flipSeg = True
453
- self.flip_segment_btn.setIcon(icon(MDI6.camera_flip,color=self.celldetective_blue))
454
- self.flip_segment_btn.setIconSize(QSize(20, 20))
455
- self.flip_segment_btn.setToolTip("Unflip the order of the frames for segmentation.")
456
- else:
457
- self.flipSeg = False
458
- self.flip_segment_btn.setIcon(icon(MDI6.camera_flip_outline,color='black'))
459
- self.flip_segment_btn.setIconSize(QSize(20, 20))
460
- self.flip_segment_btn.setToolTip("Flip the order of the frames for segmentation.")
461
-
462
- def help_segmentation(self):
463
-
464
- """
465
- Widget with different decision helper decision trees.
466
- """
467
-
468
- self.help_w = CelldetectiveWidget()
469
- self.help_w.setWindowTitle('Helper')
470
- layout = QVBoxLayout()
471
- seg_strategy_btn = QPushButton('A guide to choose a segmentation strategy.')
472
- seg_strategy_btn.setIcon(icon(MDI6.help_circle,color=self.celldetective_blue))
473
- seg_strategy_btn.setIconSize(QSize(40, 40))
474
- seg_strategy_btn.setStyleSheet(self.button_style_sheet_5)
475
- seg_strategy_btn.clicked.connect(self.help_seg_strategy)
476
-
477
- dl_strategy_btn = QPushButton('A guide to choose your Deep learning segmentation strategy.')
478
- dl_strategy_btn.setIcon(icon(MDI6.help_circle,color=self.celldetective_blue))
479
- dl_strategy_btn.setIconSize(QSize(40, 40))
480
- dl_strategy_btn.setStyleSheet(self.button_style_sheet_5)
481
- dl_strategy_btn.clicked.connect(self.help_seg_dl_strategy)
482
-
483
- layout.addWidget(seg_strategy_btn)
484
- layout.addWidget(dl_strategy_btn)
485
-
486
- self.help_w.setLayout(layout)
487
- center_window(self.help_w)
488
- self.help_w.show()
489
-
490
- return None
491
-
492
- def help_seg_strategy(self):
493
-
494
- """
495
- Helper for segmentation strategy between threshold-based and Deep learning.
496
- """
497
-
498
- dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','Threshold-vs-DL.json'])
499
-
500
- with open(dict_path) as f:
501
- d = json.load(f)
502
-
503
- suggestion = help_generic(d)
504
- if isinstance(suggestion, str):
505
- print(f"{suggestion=}")
506
- msgBox = QMessageBox()
507
- msgBox.setIcon(QMessageBox.Information)
508
- msgBox.setTextFormat(Qt.RichText)
509
- msgBox.setText(f"The suggested technique is {suggestion}.\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>.")
510
- msgBox.setWindowTitle("Info")
511
- msgBox.setStandardButtons(QMessageBox.Ok)
512
- returnValue = msgBox.exec()
513
- if returnValue == QMessageBox.Ok:
514
- return None
515
-
516
- def help_seg_dl_strategy(self):
517
-
518
- """
519
- Helper for DL segmentation strategy, between pretrained models and custom models.
520
- """
521
-
522
- dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','DL-segmentation-strategy.json'])
523
-
524
- with open(dict_path) as f:
525
- d = json.load(f)
526
-
527
- suggestion = help_generic(d)
528
- if isinstance(suggestion, str):
529
- print(f"{suggestion=}")
530
- msgBox = QMessageBox()
531
- msgBox.setIcon(QMessageBox.Information)
532
- msgBox.setText(f"The suggested technique is {suggestion}.")
533
- msgBox.setWindowTitle("Info")
534
- msgBox.setStandardButtons(QMessageBox.Ok)
535
- returnValue = msgBox.exec()
536
- if returnValue == QMessageBox.Ok:
537
- return None
538
-
539
- def help_tracking(self):
540
-
541
- """
542
- Helper for segmentation strategy between threshold-based and Deep learning.
543
- """
544
-
545
- dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','tracking.json'])
546
-
547
- with open(dict_path) as f:
548
- d = json.load(f)
549
-
550
- suggestion = help_generic(d)
551
- if isinstance(suggestion, str):
552
- print(f"{suggestion=}")
553
- msgBox = QMessageBox()
554
- msgBox.setIcon(QMessageBox.Information)
555
- msgBox.setTextFormat(Qt.RichText)
556
- msgBox.setText(f"{suggestion}")
557
- msgBox.setWindowTitle("Info")
558
- msgBox.setStandardButtons(QMessageBox.Ok)
559
- returnValue = msgBox.exec()
560
- if returnValue == QMessageBox.Ok:
561
- return None
562
-
563
- def check_segmentation(self):
564
-
565
- if not os.path.exists(os.sep.join([self.parent_window.pos,f'labels_{self.mode}', os.sep])):
566
- msgBox = QMessageBox()
567
- msgBox.setIcon(QMessageBox.Question)
568
- msgBox.setText("No labels can be found for this position. Do you want to annotate from scratch?")
569
- msgBox.setWindowTitle("Info")
570
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
571
- returnValue = msgBox.exec()
572
- if returnValue == QMessageBox.No:
573
- return None
574
- else:
575
- os.mkdir(os.sep.join([self.parent_window.pos,f'labels_{self.mode}']))
576
- lbl = np.zeros((self.parent_window.shape_x, self.parent_window.shape_y), dtype=int)
577
- for i in range(self.parent_window.len_movie):
578
- imwrite(os.sep.join([self.parent_window.pos,f'labels_{self.mode}', str(i).zfill(4)+'.tif']), lbl)
579
-
580
- #self.freeze()
581
- #QApplication.setOverrideCursor(Qt.WaitCursor)
582
- test = self.parent_window.locate_selected_position()
583
- if test:
584
- #print('Memory use: ', dict(psutil.virtual_memory()._asdict()))
585
- print(f"Loading images and labels into napari...")
586
- try:
587
- control_segmentation_napari(self.parent_window.pos, prefix=self.parent_window.movie_prefix, population=self.mode,flush_memory=True)
588
- except FileNotFoundError as e:
589
- msgBox = QMessageBox()
590
- msgBox.setIcon(QMessageBox.Warning)
591
- msgBox.setText(str(e))
592
- msgBox.setWindowTitle("Warning")
593
- msgBox.setStandardButtons(QMessageBox.Ok)
594
- _ = msgBox.exec()
595
- return
596
- except Exception as e:
597
- print(f'Task unsuccessful... Exception {e}...')
598
- msgBox = QMessageBox()
599
- msgBox.setIcon(QMessageBox.Warning)
600
- msgBox.setText(str(e))
601
- msgBox.setWindowTitle("Warning")
602
- msgBox.setStandardButtons(QMessageBox.Ok)
603
- _ = msgBox.exec()
604
-
605
- msgBox = QMessageBox()
606
- msgBox.setIcon(QMessageBox.Question)
607
- msgBox.setText("Would you like to pass empty frames to fix the asymmetry?")
608
- msgBox.setWindowTitle("Question")
609
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
610
- returnValue = msgBox.exec()
611
- if returnValue == QMessageBox.Yes:
612
- print('Fixing the missing labels...')
613
- fix_missing_labels(self.parent_window.pos, prefix=self.parent_window.movie_prefix,population=self.mode)
614
- try:
615
- control_segmentation_napari(self.parent_window.pos, prefix=self.parent_window.movie_prefix, population=self.mode,flush_memory=True)
616
- except Exception as e:
617
- print(f'Error {e}')
618
- return None
619
- else:
620
- return None
621
-
622
- gc.collect()
623
-
624
- def check_signals(self):
625
-
626
- test = self.parent_window.locate_selected_position()
627
- if test:
628
- self.event_annotator = EventAnnotator(self)
629
- self.event_annotator.show()
630
-
631
- def check_measurements(self):
632
-
633
- test = self.parent_window.locate_selected_position()
634
- if test:
635
- self.measure_annotator = MeasureAnnotator(self)
636
- self.measure_annotator.show()
637
-
638
- def enable_segmentation_model_list(self):
639
- if self.segment_action.isChecked():
640
- self.seg_model_list.setEnabled(True)
641
- else:
642
- self.seg_model_list.setEnabled(False)
643
-
644
- def enable_signal_model_list(self):
645
- if self.signal_analysis_action.isChecked():
646
- self.signal_models_list.setEnabled(True)
647
- else:
648
- self.signal_models_list.setEnabled(False)
649
-
650
- def init_seg_model_list(self):
651
-
652
- self.seg_model_list.clear()
653
- self.seg_models_specific = get_segmentation_models_list(mode=self.mode, return_path=False)
654
- self.seg_models = self.seg_models_specific.copy()
655
- self.n_specific_seg_models = len(self.seg_models)
656
-
657
- self.seg_models_generic = get_segmentation_models_list(mode="generic", return_path=False)
658
- self.seg_models.append('Threshold')
659
- self.seg_models.extend(self.seg_models_generic)
660
-
661
- thresh = 35
662
- self.models_truncated = [m[:thresh - 3]+'...' if len(m)>thresh else m for m in self.seg_models]
663
-
664
- self.seg_model_list.addItems(self.models_truncated)
665
-
666
- for i in range(len(self.seg_models)):
667
- self.seg_model_list.setItemData(i, self.seg_models[i], Qt.ToolTipRole)
668
-
669
- self.seg_model_list.insertSeparator(self.n_specific_seg_models)
670
-
671
- # def tick_all_actions(self):
672
- # self.switch_all_ticks_option()
673
- # if self.all_ticked:
674
- # self.select_all_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
675
- # self.select_all_btn.setIconSize(QSize(20, 20))
676
- # self.segment_action.setChecked(True)
677
- # else:
678
- # self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
679
- # self.select_all_btn.setIconSize(QSize(20, 20))
680
- # self.segment_action.setChecked(False)
681
-
682
- # def switch_all_ticks_option(self):
683
- # if self.all_ticked == True:
684
- # self.all_ticked = False
685
- # else:
686
- # self.all_ticked = True
687
-
688
- def upload_segmentation_model(self):
689
- print('Load a segmentation model or pipeline...')
690
- self.SegModelLoader = SegmentationModelLoader(self)
691
- self.SegModelLoader.show()
692
-
693
- def open_tracking_configuration_ui(self):
694
- print('Set the tracking parameters...')
695
- self.settings_tracking = SettingsTracking(self)
696
- self.settings_tracking.show()
697
-
698
- def open_signal_model_config_ui(self):
699
- print('Set the training parameters for new signal models...')
700
- self.settings_event_detection_training = SettingsEventDetectionModelTraining(self)
701
- self.settings_event_detection_training.show()
702
-
703
- def open_segmentation_model_config_ui(self):
704
- print('Set the training parameters for a new segmentation model...')
705
- self.settings_segmentation_training = SettingsSegmentationModelTraining(self)
706
- self.settings_segmentation_training.show()
707
-
708
- def open_measurement_configuration_ui(self):
709
- print('Set the measurements to be performed...')
710
- self.settings_measurements = SettingsMeasurements(self)
711
- self.settings_measurements.show()
712
-
713
- def open_segmentation_configuration_ui(self):
714
- print('Set the segmentation settings to be performed...')
715
- self.settings_segmentation = SettingsSegmentation(self)
716
- self.settings_segmentation.show()
717
-
718
- def open_classifier_ui(self):
719
-
720
- self.load_available_tables()
721
- if self.df is None:
722
-
723
- msgBox = QMessageBox()
724
- msgBox.setIcon(QMessageBox.Warning)
725
- msgBox.setText("No table was found...")
726
- msgBox.setWindowTitle("Warning")
727
- msgBox.setStandardButtons(QMessageBox.Ok)
728
- returnValue = msgBox.exec()
729
- if returnValue == QMessageBox.Ok:
730
- return None
731
- else:
732
- return None
733
- else:
734
- self.ClassifierWidget = ClassifierWidget(self)
735
- self.ClassifierWidget.show()
736
-
737
- def open_signal_annotator_configuration_ui(self):
738
- self.settings_signal_annotator = SettingsSignalAnnotator(self)
739
- self.settings_signal_annotator.show()
740
-
741
- def reset_generalist_setup(self, index):
742
- self.cellpose_calibrated = False
743
- self.stardist_calibrated = False
744
- self.segChannelsSet = False
745
-
746
- def reset_signals(self):
747
- self.signalChannelsSet = False
748
-
749
- def process_population(self):
750
-
751
- # if self.parent_window.well_list.currentText().startswith('Multiple'):
752
- # self.well_index = np.linspace(0,len(self.wells)-1,len(self.wells),dtype=int)
753
- # else:
754
-
755
- self.well_index = self.parent_window.well_list.getSelectedIndices()
756
- if len(self.well_index)==0:
757
- msgBox = QMessageBox()
758
- msgBox.setIcon(QMessageBox.Warning)
759
- msgBox.setText("Please select at least one well first...")
760
- msgBox.setWindowTitle("Warning")
761
- msgBox.setStandardButtons(QMessageBox.Ok)
762
- returnValue = msgBox.exec()
763
- if returnValue == QMessageBox.Ok:
764
- return None
765
- else:
766
- return None
767
-
768
- print(f"Processing {self.parent_window.well_list.currentText()}...")
769
-
770
- # self.freeze()
771
- # QApplication.setOverrideCursor(Qt.WaitCursor)
772
-
773
- idx = self.parent_window.populations.index(self.mode)
774
- self.threshold_config = self.threshold_configs[idx]
775
-
776
- self.load_available_tables()
777
-
778
- if self.df is not None and self.segment_action.isChecked():
779
- msgBox = QMessageBox()
780
- msgBox.setIcon(QMessageBox.Question)
781
- msgBox.setText("Measurement tables have been found... Re-segmenting may create mismatches between the cell labels and the associated measurements. Do you want to erase the tables post-segmentation?")
782
- msgBox.setWindowTitle("Info")
783
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
784
- returnValue = msgBox.exec()
785
- if returnValue == QMessageBox.No:
786
- pass
787
- elif returnValue == QMessageBox.Cancel:
788
- return None
789
- else:
790
- print('erase tabs!')
791
- tabs = [pos+os.sep.join(['output', 'tables', f'trajectories_{self.mode}.csv']) for pos in self.df_pos_info['pos_path'].unique()]
792
- #tabs += [pos+os.sep.join(['output', 'tables', f'trajectories_pairs.csv']) for pos in self.df_pos_info['pos_path'].unique()]
793
- tabs += [pos+os.sep.join(['output', 'tables', f'napari_{self.mode}_trajectories.npy']) for pos in self.df_pos_info['pos_path'].unique()]
794
- for t in tabs:
795
- remove_file_if_exists(t.replace('.csv','.pkl'))
796
- try:
797
- os.remove(t)
798
- except:
799
- pass
800
- loop_iter=0
801
-
802
- if self.parent_window.position_list.isMultipleSelection():
803
- msgBox = QMessageBox()
804
- msgBox.setIcon(QMessageBox.Question)
805
- msgBox.setText("If you continue, several positions will be processed.\nDo you want to proceed?")
806
- msgBox.setWindowTitle("Info")
807
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
808
- returnValue = msgBox.exec()
809
- if returnValue == QMessageBox.No:
810
- return None
811
-
812
- if self.seg_model_list.currentIndex() > self.n_specific_seg_models:
813
- self.model_name = self.seg_models[self.seg_model_list.currentIndex()-1]
814
- else:
815
- self.model_name = self.seg_models[self.seg_model_list.currentIndex()]
816
-
817
- if self.segment_action.isChecked() and self.model_name.startswith('CP') and self.model_name in self.seg_models_generic and not self.cellpose_calibrated:
818
-
819
- self.diamWidget = CellposeParamsWidget(self, model_name=self.model_name)
820
- self.diamWidget.show()
821
- return None
822
-
823
- elif self.segment_action.isChecked() and self.model_name.startswith('SD') and self.model_name in self.seg_models_generic and not self.stardist_calibrated:
824
-
825
- self.diamWidget = StarDistParamsWidget(self, model_name = self.model_name)
826
- self.diamWidget.show()
827
- return None
828
-
829
- elif self.segment_action.isChecked() and self.model_name in self.seg_models_specific and not self.segChannelsSet:
830
-
831
- self.segChannelWidget = SegModelParamsWidget(self, model_name = self.model_name)
832
- self.segChannelWidget.show()
833
- return None
834
-
835
- if self.signal_analysis_action.isChecked() and not self.signalChannelsSet:
836
- self.signal_model_name = self.signal_models[self.signal_models_list.currentIndex()]
837
- self.signalChannelWidget = SignalModelParamsWidget(self, model_name = self.signal_model_name)
838
- self.signalChannelWidget.show()
839
- return None
840
-
841
-
842
- self.movie_prefix = self.parent_window.movie_prefix
843
-
844
- for w_idx in self.well_index:
845
-
846
- pos = self.parent_window.positions[w_idx]
847
- pos_indices = self.parent_window.position_list.getSelectedIndices()
848
- #print(f"Processing position {self.parent_window.position_list.currentText()}...")
849
-
850
- well = self.parent_window.wells[w_idx]
851
-
852
- for pos_idx in pos_indices:
853
-
854
- self.pos = natsorted(glob(well+f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*/"))[pos_idx]
855
- print(f"Position {self.pos}...\nLoading stack movie...")
856
- self.pos_name = extract_position_name(self.pos)
857
-
858
- if not os.path.exists(self.pos + 'output/'):
859
- os.mkdir(self.pos + 'output/')
860
- if not os.path.exists(self.pos + 'output/tables/'):
861
- os.mkdir(self.pos + 'output/tables/')
862
-
863
- if self.segment_action.isChecked():
864
-
865
- if len(glob(os.sep.join([self.pos, f'labels_{self.mode}','*.tif'])))>0 and not self.parent_window.position_list.isMultipleSelection():
866
- msgBox = QMessageBox()
867
- msgBox.setIcon(QMessageBox.Question)
868
- msgBox.setText("Labels have already been produced for this position. Do you want to segment again?")
869
- msgBox.setWindowTitle("Info")
870
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
871
- returnValue = msgBox.exec()
872
- if returnValue == QMessageBox.No:
873
- return None
874
-
875
- if (self.seg_model_list.currentText()=="Threshold"):
876
- if self.threshold_config is None:
877
- msgBox = QMessageBox()
878
- msgBox.setIcon(QMessageBox.Warning)
879
- msgBox.setText("Please set a threshold configuration from the upload menu first. Abort.")
880
- msgBox.setWindowTitle("Warning")
881
- msgBox.setStandardButtons(QMessageBox.Ok)
882
- returnValue = msgBox.exec()
883
- if returnValue == QMessageBox.Ok:
884
- return None
885
- else:
886
- print(f"Segmentation from threshold config: {self.threshold_config}")
887
- process_args = {"pos": self.pos, "mode": self.mode, "n_threads": self.n_threads, "threshold_instructions": self.threshold_config, "use_gpu": self.use_gpu}
888
- self.job = ProgressWindow(SegmentCellThresholdProcess, parent_window=self, title="Segment", process_args = process_args)
889
- result = self.job.exec_()
890
- if result == QDialog.Accepted:
891
- pass
892
- elif result == QDialog.Rejected:
893
- self.reset_generalist_setup(0)
894
- return None
895
- #segment_from_threshold_at_position(self.pos, self.mode, self.threshold_config, threads=self.parent_window.parent_window.n_threads)
896
- else:
897
- # model = locate_segmentation_model(self.model_name)
898
- # if model is None:
899
- # process = {"output_dir": self.output_dir, "file": self.model_name}
900
- # self.download_model_job = ProgressWindow(DownloadProcess, parent_window=self, title="Download", process_args = args)
901
-
902
- process_args = {"pos": self.pos, "mode": self.mode, "n_threads": self.n_threads, "model_name": self.model_name, "use_gpu": self.use_gpu}
903
- self.job = ProgressWindow(SegmentCellDLProcess, parent_window=self, title="Segment", process_args = process_args)
904
- result = self.job.exec_()
905
- if result == QDialog.Accepted:
906
- pass
907
- elif result == QDialog.Rejected:
908
- self.reset_generalist_setup(0)
909
- return None
910
-
911
- if self.track_action.isChecked():
912
- if os.path.exists(os.sep.join([self.pos, 'output', 'tables', f'trajectories_{self.mode}.csv'])) and not self.parent_window.position_list.isMultipleSelection():
913
- msgBox = QMessageBox()
914
- msgBox.setIcon(QMessageBox.Question)
915
- msgBox.setText("A measurement table already exists. Previously annotated data for\nthis position will be lost. Do you want to proceed?")
916
- msgBox.setWindowTitle("Info")
917
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
918
- returnValue = msgBox.exec()
919
- if returnValue == QMessageBox.No:
920
- return None
921
-
922
- process_args = {"pos": self.pos, "mode": self.mode, "n_threads": self.n_threads}
923
- self.job = ProgressWindow(TrackingProcess, parent_window=self, title="Tracking", process_args=process_args)
924
- result = self.job.exec_()
925
- if result == QDialog.Accepted:
926
- pass
927
- elif result == QDialog.Rejected:
928
- return None
929
- #track_at_position(self.pos, self.mode, threads=self.parent_window.parent_window.n_threads)
930
-
931
- if self.measure_action.isChecked():
932
- process_args = {"pos": self.pos, "mode": self.mode, "n_threads": self.n_threads}
933
- self.job = ProgressWindow(MeasurementProcess, parent_window=self, title="Measurement", process_args=process_args)
934
- result = self.job.exec_()
935
- if result == QDialog.Accepted:
936
- pass
937
- elif result == QDialog.Rejected:
938
- return None
939
- #measure_at_position(self.pos, self.mode, threads=self.parent_window.parent_window.n_threads)
940
-
941
- table = os.sep.join([self.pos, 'output', 'tables', f'trajectories_{self.mode}.csv'])
942
- if self.signal_analysis_action.isChecked() and os.path.exists(table):
943
- table = pd.read_csv(table)
944
- cols = list(table.columns)
945
- if 'class_color' in cols:
946
- colors = list(table['class_color'].to_numpy())
947
- if 'tab:orange' in colors or 'tab:cyan' in colors:
948
- if not self.parent_window.position_list.isMultipleSelection():
949
- msgBox = QMessageBox()
950
- msgBox.setIcon(QMessageBox.Question)
951
- msgBox.setText("The signals of the cells in the position appear to have been annotated... Do you want to proceed?")
952
- msgBox.setWindowTitle("Info")
953
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
954
- returnValue = msgBox.exec()
955
- if returnValue == QMessageBox.No:
956
- return None
957
- self.signal_model_name = self.signal_models[self.signal_models_list.currentIndex()]
958
- analyze_signals_at_position(self.pos, self.signal_model_name, self.mode)
959
-
960
-
961
- # self.stack = None
962
- self.parent_window.update_position_options()
963
- for action in [self.segment_action, self.track_action, self.measure_action, self.signal_analysis_action]:
964
- if action.isChecked():
965
- action.setChecked(False)
966
-
967
- self.reset_generalist_setup(0)
968
- self.reset_signals()
969
-
970
- def open_napari_tracking(self):
971
- print(f'View the tracks before post-processing for position {self.parent_window.pos} in napari...')
972
- try:
973
- control_tracks(self.parent_window.pos, prefix=self.parent_window.movie_prefix, population=self.mode, threads=self.parent_window.parent_window.n_threads)
974
- except FileNotFoundError as e:
975
- msgBox = QMessageBox()
976
- msgBox.setIcon(QMessageBox.Warning)
977
- msgBox.setText(str(e))
978
- msgBox.setWindowTitle("Warning")
979
- msgBox.setStandardButtons(QMessageBox.Ok)
980
- _ = msgBox.exec()
981
- return
982
-
983
- def view_table_ui(self):
984
-
985
- print('Load table...')
986
- self.load_available_tables()
987
-
988
- if self.df is not None:
989
- plot_mode = 'plot_track_signals'
990
- if 'TRACK_ID' not in list(self.df.columns):
991
- plot_mode = 'static'
992
- self.tab_ui = TableUI(self.df, f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}", population=self.mode, plot_mode=plot_mode, save_inplace_option=True)
993
- self.tab_ui.show()
994
- else:
995
- print('Table could not be loaded...')
996
- msgBox = QMessageBox()
997
- msgBox.setIcon(QMessageBox.Warning)
998
- msgBox.setText("No table could be loaded...")
999
- msgBox.setWindowTitle("Info")
1000
- msgBox.setStandardButtons(QMessageBox.Ok)
1001
- returnValue = msgBox.exec()
1002
- if returnValue == QMessageBox.Ok:
1003
- return None
1004
-
1005
- def load_available_tables(self):
1006
-
1007
- """
1008
- Load the tables of the selected wells/positions from the control Panel for the population of interest
1009
-
1010
- """
1011
-
1012
- self.well_option = self.parent_window.well_list.getSelectedIndices()
1013
- self.position_option = self.parent_window.position_list.getSelectedIndices()
1014
-
1015
- self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population=self.mode, return_pos_info=True)
1016
- self.signals = []
1017
- if self.df is not None:
1018
- self.signals = list(self.df.columns)
1019
- if self.df is None:
1020
- print('No table could be found for the selected position(s)...')
1021
-
1022
- def set_cellpose_scale(self):
1023
-
1024
- scale = self.parent_window.PxToUm * float(self.diamWidget.diameter_le.get_threshold()) / 30.0
1025
- if self.model_name=="CP_nuclei":
1026
- scale = self.parent_window.PxToUm * float(self.diamWidget.diameter_le.get_threshold()) / 17.0
1027
- flow_thresh = self.diamWidget.flow_slider.value()
1028
- cellprob_thresh = self.diamWidget.cellprob_slider.value()
1029
- model_complete_path = locate_segmentation_model(self.model_name)
1030
- input_config_path = model_complete_path+"config_input.json"
1031
- new_channels = [self.diamWidget.cellpose_channel_cb[i].currentText() for i in range(2)]
1032
- with open(input_config_path) as config_file:
1033
- input_config = json.load(config_file)
1034
-
1035
- input_config['spatial_calibration'] = scale
1036
- input_config['channels'] = new_channels
1037
- input_config['flow_threshold'] = flow_thresh
1038
- input_config['cellprob_threshold'] = cellprob_thresh
1039
- with open(input_config_path, 'w') as f:
1040
- json.dump(input_config, f, indent=4)
1041
-
1042
- self.cellpose_calibrated = True
1043
- print('model scale automatically computed: ', scale)
1044
- self.diamWidget.close()
1045
- self.process_population()
1046
-
1047
- def set_stardist_scale(self):
1048
-
1049
- model_complete_path = locate_segmentation_model(self.model_name)
1050
- input_config_path = model_complete_path+"config_input.json"
1051
- new_channels = [self.diamWidget.stardist_channel_cb[i].currentText() for i in range(len(self.diamWidget.stardist_channel_cb))]
1052
- with open(input_config_path) as config_file:
1053
- input_config = json.load(config_file)
1054
-
1055
- input_config['channels'] = new_channels
1056
- with open(input_config_path, 'w') as f:
1057
- json.dump(input_config, f, indent=4)
1058
-
1059
- self.stardist_calibrated = True
1060
- self.diamWidget.close()
1061
- self.process_population()
1062
-
1063
- def set_selected_channels_for_segmentation(self):
1064
-
1065
- model_complete_path = locate_segmentation_model(self.model_name)
1066
- input_config_path = model_complete_path+"config_input.json"
1067
- new_channels = [self.segChannelWidget.channel_cbs[i].currentText() for i in range(len(self.segChannelWidget.channel_cbs))]
1068
- target_cell_size = None
1069
- if hasattr(self.segChannelWidget, "diameter_le"):
1070
- target_cell_size = float(self.segChannelWidget.diameter_le.get_threshold())
1071
-
1072
- with open(input_config_path) as config_file:
1073
- input_config = json.load(config_file)
1074
-
1075
- input_config.update({'selected_channels': new_channels, 'target_cell_size_um': target_cell_size})
1076
-
1077
- #input_config['channels'] = new_channels
1078
- with open(input_config_path, 'w') as f:
1079
- json.dump(input_config, f, indent=4)
1080
-
1081
- self.segChannelsSet = True
1082
- self.segChannelWidget.close()
1083
- self.process_population()
1084
-
1085
- def set_selected_signals_for_event_detection(self):
1086
- self.signal_model_name = self.signal_models[self.signal_models_list.currentIndex()]
1087
- model_complete_path = locate_signal_model(self.signal_model_name)
1088
- input_config_path = model_complete_path+"config_input.json"
1089
- new_channels = [self.signalChannelWidget.channel_cbs[i].currentText() for i in range(len(self.signalChannelWidget.channel_cbs))]
1090
- with open(input_config_path) as config_file:
1091
- input_config = json.load(config_file)
1092
-
1093
- input_config.update({'selected_channels': new_channels})
1094
-
1095
- #input_config['channels'] = new_channels
1096
- with open(input_config_path, 'w') as f:
1097
- json.dump(input_config, f, indent=4)
1098
-
1099
- self.signalChannelsSet = True
1100
- self.signalChannelWidget.close()
1101
- self.process_population()
1102
-
1103
-
1104
-
1105
- class NeighPanel(QFrame, Styles):
1106
- def __init__(self, parent_window):
1107
-
1108
- super().__init__()
1109
- self.parent_window = parent_window
1110
- self.exp_channels = self.parent_window.exp_channels
1111
- self.exp_dir = self.parent_window.exp_dir
1112
- self.wells = np.array(self.parent_window.wells,dtype=str)
1113
- self.protocols = []
1114
- self.mode='neighborhood'
1115
-
1116
- self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
1117
- self.grid = QGridLayout(self)
1118
- self.generate_header()
1119
-
1120
- def generate_header(self):
1121
-
1122
- """
1123
- Read the mode and prepare a collapsable block to process a specific cell population.
1124
-
1125
- """
1126
-
1127
- panel_title = QLabel(f"INTERACTIONS")
1128
- panel_title.setStyleSheet(self.block_title)
1129
-
1130
- self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
1131
-
1132
- # self.select_all_btn = QPushButton()
1133
- # self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
1134
- # self.select_all_btn.setIconSize(QSize(20, 20))
1135
- # self.all_ticked = False
1136
- # self.select_all_btn.setStyleSheet(self.button_select_all)
1137
- # self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
1138
-
1139
- self.collapse_btn = QPushButton()
1140
- self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
1141
- self.collapse_btn.setIconSize(QSize(25, 25))
1142
- self.collapse_btn.setStyleSheet(self.button_select_all)
1143
- self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
1144
-
1145
- self.populate_contents()
1146
-
1147
- self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
1148
- self.collapse_btn.clicked.connect(lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden()))
1149
- self.collapse_btn.clicked.connect(self.collapse_advanced)
1150
- self.ContentsFrame.hide()
1151
-
1152
- def collapse_advanced(self):
1153
-
1154
- panels_open = [not p.ContentsFrame.isHidden() for p in self.parent_window.ProcessPopulations]
1155
- interactions_open = not self.parent_window.NeighPanel.ContentsFrame.isHidden()
1156
- preprocessing_open = not self.parent_window.PreprocessingPanel.ContentsFrame.isHidden()
1157
- is_open = np.array(panels_open+[interactions_open, preprocessing_open])
1158
-
1159
- if self.ContentsFrame.isHidden():
1160
- self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
1161
- self.collapse_btn.setIconSize(QSize(20, 20))
1162
- if len(is_open[is_open])==0:
1163
- self.parent_window.scroll.setMinimumHeight(int(550))
1164
- self.parent_window.adjustSize()
1165
- else:
1166
- self.collapse_btn.setIcon(icon(MDI6.chevron_up,color="black"))
1167
- self.collapse_btn.setIconSize(QSize(20, 20))
1168
- self.parent_window.scroll.setMinimumHeight(min(int(1000), int(0.9*self.parent_window.screen_height)))
1169
-
1170
-
1171
- def populate_contents(self):
1172
-
1173
- self.ContentsFrame = QFrame()
1174
- self.grid_contents = QGridLayout(self.ContentsFrame)
1175
- self.grid_contents.setContentsMargins(0,0,0,0)
1176
- self.grid_contents.setSpacing(3)
1177
-
1178
- # Button to compute the neighborhoods
1179
- neigh_option_hbox = QHBoxLayout()
1180
- self.neigh_action = QCheckBox('NEIGHBORHOODS')
1181
- self.neigh_action.setStyleSheet(self.menu_check_style)
1182
-
1183
- #self.neigh_action.setIcon(icon(MDI6.eyedropper, color="black"))
1184
- #self.neigh_action.setIconSize(QSize(20, 20))
1185
- self.neigh_action.setToolTip(
1186
- "Compute neighborhoods in list below.")
1187
-
1188
- neigh_option_hbox.addWidget(self.neigh_action,90)
1189
-
1190
-
1191
- self.help_neigh_btn = QPushButton()
1192
- self.help_neigh_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
1193
- self.help_neigh_btn.setIconSize(QSize(20, 20))
1194
- self.help_neigh_btn.clicked.connect(self.help_neighborhood)
1195
- self.help_neigh_btn.setStyleSheet(self.button_select_all)
1196
- self.help_neigh_btn.setToolTip("Help.")
1197
- neigh_option_hbox.addWidget(self.help_neigh_btn,5,alignment=Qt.AlignRight)
1198
-
1199
- self.grid_contents.addLayout(neigh_option_hbox, 1,0,1,4)
1200
-
1201
-
1202
- neigh_options_layout = QVBoxLayout()
1203
-
1204
- neigh_options_vbox = QVBoxLayout()
1205
-
1206
- # DISTANCE NEIGHBORHOOD
1207
- dist_neigh_hbox = QHBoxLayout()
1208
- dist_neigh_hbox.setContentsMargins(0,0,0,0)
1209
- dist_neigh_hbox.setSpacing(0)
1210
-
1211
- self.dist_neigh_action = QLabel("ISOTROPIC DISTANCE THRESHOLD")
1212
- self.dist_neigh_action.setStyleSheet(self.action_lbl_style_sheet)
1213
- #self.dist_neigh_action.setIcon(icon(MDI6.circle_expand, color='black'))
1214
- self.dist_neigh_action.setToolTip("")
1215
- self.dist_neigh_action.setToolTip("Define an isotropic neighborhood between the center of mass\nof the cells, within a threshold distance.")
1216
- #self.segment_action.toggled.connect(self.enable_segmentation_model_list)
1217
- #self.to_disable.append(self.segment_action)
1218
-
1219
- self.config_distance_neigh_btn = QPushButton()
1220
- self.config_distance_neigh_btn.setIcon(icon(MDI6.plus,color="black"))
1221
- self.config_distance_neigh_btn.setIconSize(QSize(20, 20))
1222
- self.config_distance_neigh_btn.setToolTip("Configure.")
1223
- self.config_distance_neigh_btn.setStyleSheet(self.button_select_all)
1224
- self.config_distance_neigh_btn.clicked.connect(self.open_config_distance_threshold_neighborhood)
1225
- dist_neigh_hbox.addWidget(self.config_distance_neigh_btn,5)
1226
- dist_neigh_hbox.addWidget(self.dist_neigh_action, 95)
1227
- neigh_options_vbox.addLayout(dist_neigh_hbox)
1228
-
1229
- # CONTACT NEIGHBORHOOD
1230
- contact_neighborhood_layout = QHBoxLayout()
1231
- contact_neighborhood_layout.setContentsMargins(0,0,0,0)
1232
- contact_neighborhood_layout.setSpacing(0)
1233
-
1234
- self.contact_neigh_action = QLabel("MASK CONTACT")
1235
- self.contact_neigh_action.setToolTip("Identify touching cell masks, within a threshold edge distance.")
1236
- self.contact_neigh_action.setStyleSheet(self.action_lbl_style_sheet)
1237
- #self.contact_neigh_action.setIcon(icon(MDI6.transition_masked, color='black'))
1238
- self.contact_neigh_action.setToolTip("")
1239
-
1240
- self.config_contact_neigh_btn = QPushButton()
1241
- self.config_contact_neigh_btn.setIcon(icon(MDI6.plus,color="black"))
1242
- self.config_contact_neigh_btn.setIconSize(QSize(20, 20))
1243
- self.config_contact_neigh_btn.setToolTip("Configure.")
1244
- self.config_contact_neigh_btn.setStyleSheet(self.button_select_all)
1245
- self.config_contact_neigh_btn.clicked.connect(self.open_config_contact_neighborhood)
1246
- contact_neighborhood_layout.addWidget(self.config_contact_neigh_btn,5)
1247
- contact_neighborhood_layout.addWidget(self.contact_neigh_action, 95)
1248
- neigh_options_vbox.addLayout(contact_neighborhood_layout)
1249
- #self.grid_contents.addLayout(neigh_options_vbox, 2,0,1,4)
1250
-
1251
- #self.grid_contents.addWidget(QHSeperationLine(), 3, 0, 1, 4)
1252
-
1253
- self.delete_protocol_btn = QPushButton('')
1254
- self.delete_protocol_btn.setStyleSheet(self.button_select_all)
1255
- self.delete_protocol_btn.setIcon(icon(MDI6.trash_can, color="black"))
1256
- self.delete_protocol_btn.setToolTip("Remove a neighborhood computation.")
1257
- self.delete_protocol_btn.setIconSize(QSize(20, 20))
1258
- self.delete_protocol_btn.clicked.connect(self.remove_protocol_from_list)
1259
-
1260
- self.protocol_list_lbl = QLabel('Neighborhoods to compute: ')
1261
- self.protocol_list = QListWidget()
1262
- self.protocol_list.setToolTip("Neighborhoods to compute sequentially.")
1263
-
1264
- list_header_layout = QHBoxLayout()
1265
- list_header_layout.addWidget(self.protocol_list_lbl)
1266
- list_header_layout.addWidget(self.delete_protocol_btn, alignment=Qt.AlignRight)
1267
- #self.grid_contents.addLayout(list_header_layout, 4, 0, 1, 4)
1268
- #self.grid_contents.addWidget(self.protocol_list, 5, 0, 1, 4)
1269
-
1270
- neigh_options_layout.addLayout(neigh_options_vbox)
1271
- neigh_options_layout.addWidget(QHSeperationLine())
1272
- neigh_options_layout.addLayout(list_header_layout)
1273
- neigh_options_layout.addWidget(self.protocol_list)
1274
-
1275
- neigh_options_layout.setContentsMargins(30,5,30,5)
1276
- neigh_options_layout.setSpacing(1)
1277
- self.grid_contents.addLayout(neigh_options_layout, 5, 0, 1, 4)
1278
-
1279
-
1280
- rel_layout = QHBoxLayout()
1281
- self.measure_pairs_action = QCheckBox("MEASURE PAIRS")
1282
- self.measure_pairs_action.setStyleSheet(self.menu_check_style)
1283
-
1284
- self.measure_pairs_action.setIcon(icon(MDI6.eyedropper, color="black"))
1285
- self.measure_pairs_action.setIconSize(QSize(20, 20))
1286
- self.measure_pairs_action.setToolTip("Measure the relative quantities defined for the cell pairs, for all neighborhoods.")
1287
- rel_layout.addWidget(self.measure_pairs_action, 90)
1288
-
1289
- self.classify_pairs_btn = QPushButton()
1290
- self.classify_pairs_btn.setIcon(icon(MDI6.scatter_plot, color="black"))
1291
- self.classify_pairs_btn.setIconSize(QSize(20, 20))
1292
- self.classify_pairs_btn.setToolTip("Classify data.")
1293
- self.classify_pairs_btn.setStyleSheet(self.button_select_all)
1294
- self.classify_pairs_btn.clicked.connect(self.open_classifier_ui_pairs)
1295
- rel_layout.addWidget(self.classify_pairs_btn, 5) #4,2,1,1, alignment=Qt.AlignRight
1296
-
1297
- self.grid_contents.addLayout(rel_layout, 6, 0, 1, 4)
1298
-
1299
- signal_layout = QVBoxLayout()
1300
- signal_hlayout = QHBoxLayout()
1301
- self.signal_analysis_action = QCheckBox("DETECT PAIR EVENTS")
1302
- self.signal_analysis_action.setStyleSheet(self.menu_check_style)
1303
-
1304
- self.signal_analysis_action.setIcon(icon(MDI6.chart_bell_curve_cumulative, color="black"))
1305
- self.signal_analysis_action.setIconSize(QSize(20, 20))
1306
- self.signal_analysis_action.setToolTip("Detect cell pair events using a DL model.")
1307
- self.signal_analysis_action.toggled.connect(self.enable_signal_model_list)
1308
- signal_hlayout.addWidget(self.signal_analysis_action, 90)
1309
-
1310
- self.check_signals_btn = QPushButton()
1311
- self.check_signals_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
1312
- self.check_signals_btn.setIconSize(QSize(20, 20))
1313
- self.check_signals_btn.clicked.connect(self.check_signals2)
1314
- self.check_signals_btn.setToolTip("Annotate dynamic cell pairs.")
1315
- self.check_signals_btn.setStyleSheet(self.button_select_all)
1316
- signal_hlayout.addWidget(self.check_signals_btn, 6)
1317
-
1318
- self.config_signal_annotator_btn = QPushButton()
1319
- self.config_signal_annotator_btn.setIcon(icon(MDI6.cog_outline, color="black"))
1320
- self.config_signal_annotator_btn.setIconSize(QSize(20, 20))
1321
- self.config_signal_annotator_btn.setToolTip("Configure the animation of the annotation tool.")
1322
- self.config_signal_annotator_btn.setStyleSheet(self.button_select_all)
1323
- self.config_signal_annotator_btn.clicked.connect(self.open_signal_annotator_configuration_ui)
1324
- signal_hlayout.addWidget(self.config_signal_annotator_btn, 6)
1325
- signal_layout.addLayout(signal_hlayout)
1326
- # self.to_disable.append(self.measure_action_tc)
1327
- pair_signal_model_vbox = QVBoxLayout()
1328
- pair_signal_model_vbox.setContentsMargins(25, 0, 25, 0)
1329
-
1330
- pair_model_zoo_layout = QHBoxLayout()
1331
- pair_model_zoo_layout.addWidget(QLabel("Model zoo:"), 90)
1332
-
1333
- self.pair_signal_models_list = QComboBox()
1334
- self.pair_signal_models_list.setEnabled(False)
1335
- self.refresh_signal_models()
1336
- # self.to_disable.append(self.cell_models_list)
1337
-
1338
- self.pair_train_signal_model_btn = QPushButton("TRAIN")
1339
- self.pair_train_signal_model_btn.setToolTip("Train a cell pair event detection model.")
1340
- self.pair_train_signal_model_btn.setIcon(icon(MDI6.redo_variant, color='black'))
1341
- self.pair_train_signal_model_btn.setIconSize(QSize(20, 20))
1342
- self.pair_train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
1343
- pair_model_zoo_layout.addWidget(self.pair_train_signal_model_btn, 5)
1344
- self.pair_train_signal_model_btn.clicked.connect(self.open_signal_model_config_ui)
1345
-
1346
- pair_signal_model_vbox.addLayout(pair_model_zoo_layout)
1347
- pair_signal_model_vbox.addWidget(self.pair_signal_models_list)
1348
-
1349
- signal_layout.addLayout(pair_signal_model_vbox)
1350
- self.grid_contents.addLayout(signal_layout, 7, 0, 1, 4)
1351
- self.grid_contents.addWidget(QHSeperationLine(), 11, 0, 1, 4)
1352
-
1353
- self.view_tab_btn = QPushButton("Explore table")
1354
- self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
1355
- self.view_tab_btn.clicked.connect(self.view_table_ui)
1356
- self.view_tab_btn.setToolTip('Explore table')
1357
- self.view_tab_btn.setIcon(icon(MDI6.table,color="#1565c0"))
1358
- self.view_tab_btn.setIconSize(QSize(20, 20))
1359
- #self.view_tab_btn.setEnabled(False)
1360
- self.grid_contents.addWidget(self.view_tab_btn, 12, 0, 1, 4)
1361
-
1362
- #self.grid_contents.addWidget(QLabel(''), 12, 0, 1, 4)
1363
-
1364
- self.submit_btn = QPushButton("Submit")
1365
- self.submit_btn.setStyleSheet(self.button_style_sheet)
1366
- self.submit_btn.setToolTip("Compute the neighborhoods of the selected positions.")
1367
- self.submit_btn.clicked.connect(self.process_neighborhood)
1368
- self.grid_contents.addWidget(self.submit_btn, 14, 0, 1, 4)
1369
-
1370
- self.neigh_action.toggled.connect(self.activate_neigh_options)
1371
- self.neigh_action.setChecked(True)
1372
- self.neigh_action.setChecked(False)
1373
-
1374
- def open_classifier_ui_pairs(self):
1375
-
1376
- self.mode = "pairs"
1377
- self.load_available_tables()
1378
- if self.df is None:
1379
-
1380
- msgBox = QMessageBox()
1381
- msgBox.setIcon(QMessageBox.Warning)
1382
- msgBox.setText("No table was found...")
1383
- msgBox.setWindowTitle("Warning")
1384
- msgBox.setStandardButtons(QMessageBox.Ok)
1385
- returnValue = msgBox.exec()
1386
- if returnValue == QMessageBox.Ok:
1387
- return None
1388
- else:
1389
- return None
1390
- else:
1391
- self.ClassifierWidget = ClassifierWidget(self)
1392
- self.ClassifierWidget.show()
1393
-
1394
-
1395
- def help_neighborhood(self):
1396
-
1397
- """
1398
- Helper for neighborhood strategy.
1399
- """
1400
-
1401
- dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','neighborhood.json'])
1402
-
1403
- with open(dict_path) as f:
1404
- d = json.load(f)
1405
-
1406
- suggestion = help_generic(d)
1407
- if isinstance(suggestion, str):
1408
- print(f"{suggestion=}")
1409
- msgBox = QMessageBox()
1410
- msgBox.setIcon(QMessageBox.Information)
1411
- msgBox.setTextFormat(Qt.RichText)
1412
- msgBox.setText(f"{suggestion}\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/interactions.html#neighborhood'>here</a>.")
1413
- msgBox.setWindowTitle("Info")
1414
- msgBox.setStandardButtons(QMessageBox.Ok)
1415
- returnValue = msgBox.exec()
1416
- if returnValue == QMessageBox.Ok:
1417
- return None
1418
-
1419
-
1420
- def load_available_tables(self):
1421
-
1422
- """
1423
- Load the tables of the selected wells/positions from the control Panel for the population of interest
1424
-
1425
- """
1426
-
1427
- self.well_option = self.parent_window.well_list.getSelectedIndices()
1428
- self.position_option = self.parent_window.position_list.getSelectedIndices()
1429
-
1430
- self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population="pairs", return_pos_info=True)
1431
- if self.df is None:
1432
- print('No table could be found...')
1433
-
1434
-
1435
- def view_table_ui(self):
1436
-
1437
- print('Load table...')
1438
- self.load_available_tables()
1439
-
1440
- if self.df is not None:
1441
- plot_mode = 'static'
1442
- self.tab_ui = TableUI(self.df, f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}", population='pairs', plot_mode=plot_mode, save_inplace_option=True)
1443
- self.tab_ui.show()
1444
- else:
1445
- print('Table could not be loaded...')
1446
- msgBox = QMessageBox()
1447
- msgBox.setIcon(QMessageBox.Warning)
1448
- msgBox.setText("No table could be loaded...")
1449
- msgBox.setWindowTitle("Info")
1450
- msgBox.setStandardButtons(QMessageBox.Ok)
1451
- returnValue = msgBox.exec()
1452
- if returnValue == QMessageBox.Ok:
1453
- return None
1454
-
1455
-
1456
- def activate_neigh_options(self):
1457
-
1458
- if self.neigh_action.isChecked():
1459
- self.dist_neigh_action.setEnabled(True)
1460
- self.contact_neigh_action.setEnabled(True)
1461
- self.config_distance_neigh_btn.setEnabled(True)
1462
- self.config_contact_neigh_btn.setEnabled(True)
1463
- self.protocol_list_lbl.setEnabled(True)
1464
- self.protocol_list.setEnabled(True)
1465
- self.delete_protocol_btn.setEnabled(True)
1466
- else:
1467
- self.dist_neigh_action.setEnabled(False)
1468
- self.contact_neigh_action.setEnabled(False)
1469
- self.config_distance_neigh_btn.setEnabled(False)
1470
- self.config_contact_neigh_btn.setEnabled(False)
1471
- self.protocol_list_lbl.setEnabled(False)
1472
- self.protocol_list.setEnabled(False)
1473
- self.delete_protocol_btn.setEnabled(False)
1474
-
1475
- def refresh_signal_models(self):
1476
- signal_models = get_pair_signal_models_list()
1477
- self.pair_signal_models_list.clear()
1478
- self.pair_signal_models_list.addItems(signal_models)
1479
-
1480
- def open_signal_annotator_configuration_ui(self):
1481
- self.mode = 'pairs'
1482
- self.config_signal_annotator = SettingsSignalAnnotator(self)
1483
- self.config_signal_annotator.show()
1484
-
1485
- def open_signal_model_config_ui(self):
1486
- self.settings_pair_event_detection_training = SettingsEventDetectionModelTraining(self, signal_mode='pairs')
1487
- self.settings_pair_event_detection_training.show()
1488
-
1489
- def remove_protocol_from_list(self):
1490
-
1491
- current_item = self.protocol_list.currentRow()
1492
- if current_item > -1:
1493
- del self.protocols[current_item]
1494
- self.protocol_list.takeItem(current_item)
1495
-
1496
- def open_config_distance_threshold_neighborhood(self):
1497
-
1498
- self.ConfigNeigh = SettingsNeighborhood(parent_window=self,
1499
- neighborhood_type='distance_threshold',
1500
- neighborhood_parameter_name='threshold distance',
1501
- )
1502
- self.ConfigNeigh.show()
1503
-
1504
- def open_config_contact_neighborhood(self):
1505
-
1506
- self.ConfigNeigh = SettingsNeighborhood(parent_window=self,
1507
- neighborhood_type='mask_contact',
1508
- neighborhood_parameter_name='tolerance contact distance',
1509
- )
1510
- self.ConfigNeigh.show()
1511
-
1512
- def enable_signal_model_list(self):
1513
- if self.signal_analysis_action.isChecked():
1514
- self.pair_signal_models_list.setEnabled(True)
1515
- else:
1516
- self.pair_signal_models_list.setEnabled(False)
1517
-
1518
- def process_neighborhood(self):
1519
-
1520
- # if self.parent_window.well_list.currentText().startswith('Multiple'):
1521
- # self.well_index = np.linspace(0,len(self.wells)-1,len(self.wells),dtype=int)
1522
- # else:
1523
- self.well_index = self.parent_window.well_list.getSelectedIndices()
1524
- print(f"Processing well {self.parent_window.well_list.currentText()}...")
1525
-
1526
- # self.freeze()
1527
- # QApplication.setOverrideCursor(Qt.WaitCursor)
1528
-
1529
- loop_iter=0
1530
-
1531
- if self.parent_window.position_list.isMultipleSelection():
1532
- msgBox = QMessageBox()
1533
- msgBox.setIcon(QMessageBox.Question)
1534
- msgBox.setText("If you continue, all positions will be processed.\nDo you want to proceed?")
1535
- msgBox.setWindowTitle("Info")
1536
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1537
- returnValue = msgBox.exec()
1538
- if returnValue == QMessageBox.No:
1539
- return None
1540
-
1541
- for w_idx in self.well_index:
1542
-
1543
- pos = self.parent_window.positions[w_idx]
1544
- pos_indices = self.parent_window.position_list.getSelectedIndices()
1545
-
1546
- well = self.parent_window.wells[w_idx]
1547
-
1548
- for pos_idx in pos_indices:
1549
-
1550
- self.pos = natsorted(glob(well+f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*{os.sep}"))[pos_idx]
1551
- self.pos_name = extract_position_name(self.pos)
1552
- print(f"Position {self.pos}...\nLoading stack movie...")
1553
-
1554
- if not os.path.exists(self.pos + 'output' + os.sep):
1555
- os.mkdir(self.pos + 'output' + os.sep)
1556
- if not os.path.exists(self.pos + os.sep.join(['output','tables'])+os.sep):
1557
- os.mkdir(self.pos + os.sep.join(['output','tables'])+os.sep)
1558
-
1559
- if self.neigh_action.isChecked():
1560
- for protocol in self.protocols:
1561
-
1562
- process_args = {"pos": self.pos, "pos_name": self.pos_name,"protocol": protocol,"img_shape": (self.parent_window.shape_x,self.parent_window.shape_y)} #"n_threads": self.n_threads
1563
- self.job = ProgressWindow(NeighborhoodProcess, parent_window=self, title="Neighborhood",
1564
- process_args=process_args)
1565
- result = self.job.exec_()
1566
- if result == QDialog.Accepted:
1567
- pass
1568
- elif result == QDialog.Rejected:
1569
- return None
1570
-
1571
- if self.measure_pairs_action.isChecked():
1572
- rel_measure_at_position(self.pos)
1573
-
1574
- if self.signal_analysis_action.isChecked():
1575
-
1576
- analyze_pair_signals_at_position(self.pos, self.pair_signal_models_list.currentText(), use_gpu=self.parent_window.parent_window.use_gpu, populations=self.parent_window.populations)
1577
-
1578
- self.parent_window.update_position_options()
1579
- for action in [self.neigh_action, self.measure_pairs_action, self.signal_analysis_action]:
1580
- if action.isChecked():
1581
- action.setChecked(False)
1582
-
1583
- print('Done.')
1584
-
1585
- def check_signals2(self):
1586
-
1587
- test = self.parent_window.locate_selected_position()
1588
- if test:
1589
- self.pair_event_annotator = PairEventAnnotator(self)
1590
- self.pair_event_annotator.show()
1591
-
1592
-
1593
- class PreprocessingPanel(QFrame, Styles):
1594
-
1595
- def __init__(self, parent_window):
1596
-
1597
- super().__init__()
1598
- self.parent_window = parent_window
1599
- self.exp_channels = self.parent_window.exp_channels
1600
- self.exp_dir = self.parent_window.exp_dir
1601
- self.wells = np.array(self.parent_window.wells,dtype=str)
1602
- exp_config = self.exp_dir + "config.ini"
1603
- self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
1604
- self.channel_names = np.array(self.channel_names)
1605
- self.background_correction = []
1606
- self.onlyFloat = QDoubleValidator()
1607
- self.onlyInt = QIntValidator()
1608
-
1609
- self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
1610
- self.grid = QGridLayout(self)
89
+ from celldetective.gui.base.styles import Styles
90
+ from celldetective import get_software_location
91
+ import pandas as pd
1611
92
 
1612
- self.generate_header()
93
+ import logging
1613
94
 
1614
- def generate_header(self):
95
+ logger = logging.getLogger("celldetective")
1615
96
 
1616
- """
1617
- Read the mode and prepare a collapsable block to process a specific cell population.
1618
97
 
1619
- """
98
+ class ProcessPanel(QFrame, Styles):
1620
99
 
1621
- panel_title = QLabel(f"PREPROCESSING")
1622
- panel_title.setStyleSheet("""
1623
- font-weight: bold;
1624
- padding: 0px;
1625
- """)
1626
-
1627
- self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
1628
-
1629
- # self.select_all_btn = QPushButton()
1630
- # self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
1631
- # self.select_all_btn.setIconSize(QSize(20, 20))
1632
- # self.all_ticked = False
1633
- # #self.select_all_btn.clicked.connect(self.tick_all_actions)
1634
- # self.select_all_btn.setStyleSheet(self.button_select_all)
1635
- # self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
1636
- #self.to_disable.append(self.all_tc_actions)
1637
-
1638
- self.collapse_btn = QPushButton()
1639
- self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
1640
- self.collapse_btn.setIconSize(QSize(25, 25))
1641
- self.collapse_btn.setStyleSheet(self.button_select_all)
1642
- self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
1643
-
1644
- self.populate_contents()
1645
-
1646
- self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
1647
- self.collapse_btn.clicked.connect(lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden()))
1648
- self.collapse_btn.clicked.connect(self.collapse_advanced)
1649
- self.ContentsFrame.hide()
1650
-
1651
- def collapse_advanced(self):
1652
-
1653
- panels_open = [not p.ContentsFrame.isHidden() for p in self.parent_window.ProcessPopulations]
1654
- interactions_open = not self.parent_window.NeighPanel.ContentsFrame.isHidden()
1655
- preprocessing_open = not self.parent_window.PreprocessingPanel.ContentsFrame.isHidden()
1656
- is_open = np.array(panels_open+[interactions_open, preprocessing_open])
1657
-
1658
- if self.ContentsFrame.isHidden():
1659
- self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
1660
- self.collapse_btn.setIconSize(QSize(20, 20))
1661
- if len(is_open[is_open])==0:
1662
- self.parent_window.scroll.setMinimumHeight(int(550))
1663
- self.parent_window.adjustSize()
1664
- else:
1665
- self.collapse_btn.setIcon(icon(MDI6.chevron_up,color="black"))
1666
- self.collapse_btn.setIconSize(QSize(20, 20))
1667
- self.parent_window.scroll.setMinimumHeight(min(int(930), int(0.9*self.parent_window.screen_height)))
1668
-
1669
- def populate_contents(self):
1670
-
1671
- self.ContentsFrame = QFrame()
1672
- self.grid_contents = QGridLayout(self.ContentsFrame)
1673
-
1674
- self.model_free_correction_layout = BackgroundModelFreeCorrectionLayout(self)
1675
- self.fit_correction_layout = BackgroundFitCorrectionLayout(self)
1676
-
1677
- self.protocol_layout = ProtocolDesignerLayout(parent_window=self,
1678
- tab_layouts=[self.fit_correction_layout, self.model_free_correction_layout],
1679
- tab_names=['Fit', 'Model-free'],
1680
- title='BACKGROUND CORRECTION',
1681
- list_title='Corrections to apply:')
1682
-
1683
- self.help_background_btn = QPushButton()
1684
- self.help_background_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
1685
- self.help_background_btn.setIconSize(QSize(20, 20))
1686
- self.help_background_btn.clicked.connect(self.help_background)
1687
- self.help_background_btn.setStyleSheet(self.button_select_all)
1688
- self.help_background_btn.setToolTip("Help.")
1689
-
1690
- self.protocol_layout.title_layout.addWidget(self.help_background_btn, 5, alignment=Qt.AlignRight)
1691
-
1692
- self.channel_offset_correction_layout = QVBoxLayout()
1693
-
1694
- self.channel_shift_lbl = QLabel("CHANNEL OFFSET CORRECTION")
1695
- self.channel_shift_lbl.setStyleSheet("""
100
+ def __init__(self, parent_window, mode):
101
+
102
+ super().__init__()
103
+ self.parent_window = parent_window
104
+ self.mode = mode
105
+ self.exp_channels = self.parent_window.exp_channels
106
+ self.exp_dir = self.parent_window.exp_dir
107
+ self.exp_config = self.parent_window.exp_config
108
+ self.movie_prefix = self.parent_window.movie_prefix
109
+ self.threshold_configs = [
110
+ None for _ in range(len(self.parent_window.populations))
111
+ ]
112
+ self.wells = np.array(self.parent_window.wells, dtype=str)
113
+ self.cellpose_calibrated = False
114
+ self.stardist_calibrated = False
115
+ self.segChannelsSet = False
116
+ self.signalChannelsSet = False
117
+ self.flipSeg = False
118
+
119
+ self.use_gpu = self.parent_window.parent_window.use_gpu
120
+ self.n_threads = self.parent_window.parent_window.n_threads
121
+
122
+ self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
123
+ self.grid = QGridLayout(self)
124
+ self.grid.setContentsMargins(5, 5, 5, 5)
125
+ self.generate_header()
126
+
127
+ def generate_header(self):
128
+ """
129
+ Read the mode and prepare a collapsable block to process a specific cell population.
130
+
131
+ """
132
+
133
+ panel_title = QLabel(f"PROCESS {self.mode.upper()} ")
134
+ panel_title.setStyleSheet(
135
+ """
1696
136
  font-weight: bold;
1697
137
  padding: 0px;
1698
- """)
1699
- self.channel_offset_correction_layout.addWidget(self.channel_shift_lbl, alignment=Qt.AlignCenter)
1700
-
1701
- self.channel_offset_options_layout = ChannelOffsetOptionsLayout(self)
1702
- self.channel_offset_correction_layout.addLayout(self.channel_offset_options_layout)
1703
-
1704
- self.protocol_layout.correction_layout.addWidget(QLabel(''))
1705
- self.protocol_layout.correction_layout.addLayout(self.channel_offset_correction_layout)
1706
-
1707
- self.grid_contents.addLayout(self.protocol_layout,0,0,1,4)
1708
-
1709
- self.submit_preprocessing_btn = QPushButton("Submit")
1710
- self.submit_preprocessing_btn.setStyleSheet(self.button_style_sheet)
1711
- self.submit_preprocessing_btn.clicked.connect(self.launch_preprocessing)
1712
-
1713
- self.grid_contents.addWidget(self.submit_preprocessing_btn, 1,0,1,4)
1714
-
1715
- def add_offset_instructions_to_parent_list(self):
1716
- print('adding instructions')
1717
-
1718
-
1719
- def launch_preprocessing(self):
1720
-
1721
- msgBox1 = QMessageBox()
1722
- msgBox1.setIcon(QMessageBox.Question)
1723
- msgBox1.setText("Do you want to apply the preprocessing\nto all wells and positions?")
1724
- msgBox1.setWindowTitle("Selection")
1725
- msgBox1.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
1726
- returnValue = msgBox1.exec()
1727
- if returnValue == QMessageBox.Cancel:
1728
- return None
1729
- elif returnValue == QMessageBox.Yes:
1730
- self.parent_window.well_list.selectAll()
1731
- self.parent_window.position_list.selectAll()
1732
- elif returnValue == QMessageBox.No:
1733
- msgBox2 = QMessageBox()
1734
- msgBox2.setIcon(QMessageBox.Question)
1735
- msgBox2.setText("Do you want to apply the preprocessing\nto the positions selected at the top only?")
1736
- msgBox2.setWindowTitle("Selection")
1737
- msgBox2.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
1738
- returnValue = msgBox2.exec()
1739
- if returnValue == QMessageBox.Cancel:
1740
- return None
1741
- if returnValue == QMessageBox.No:
1742
- return None
1743
-
1744
- print('Proceed with correction...')
1745
-
1746
- # if self.parent_window.well_list.currentText()=='*':
1747
- # well_option = "*"
1748
- # else:
1749
- well_option = self.parent_window.well_list.getSelectedIndices()
1750
- position_option = self.parent_window.position_list.getSelectedIndices()
1751
-
1752
- for k,correction_protocol in enumerate(self.protocol_layout.protocols):
1753
-
1754
- movie_prefix = None
1755
- export_prefix = 'Corrected'
1756
- if k>0:
1757
- # switch source stack to cumulate multi-channel preprocessing
1758
- movie_prefix = 'Corrected'
1759
- export_prefix = None
1760
-
1761
- if correction_protocol['correction_type']=='model-free':
1762
- print(f'Model-free correction; {movie_prefix=} {export_prefix=}')
1763
- correct_background_model_free(self.exp_dir,
1764
- well_option=well_option,
1765
- position_option=position_option,
1766
- export = True,
1767
- return_stacks=False,
1768
- show_progress_per_well = True,
1769
- show_progress_per_pos = True,
1770
- movie_prefix = movie_prefix,
1771
- export_prefix = export_prefix,
1772
- **correction_protocol,
1773
- )
1774
-
1775
- elif correction_protocol['correction_type']=='fit':
1776
- print(f'Fit correction; {movie_prefix=} {export_prefix=} {correction_protocol=}')
1777
- correct_background_model(self.exp_dir,
1778
- well_option=well_option,
1779
- position_option=position_option,
1780
- export= True,
1781
- return_stacks=False,
1782
- show_progress_per_well = True,
1783
- show_progress_per_pos = True,
1784
- movie_prefix = movie_prefix,
1785
- export_prefix = export_prefix,
1786
- **correction_protocol,
1787
- )
1788
- elif correction_protocol['correction_type']=='offset':
1789
- print(f'Offset correction; {movie_prefix=} {export_prefix=} {correction_protocol=}')
1790
- correct_channel_offset(self.exp_dir,
1791
- well_option=well_option,
1792
- position_option=position_option,
1793
- export= True,
1794
- return_stacks=False,
1795
- show_progress_per_well = True,
1796
- show_progress_per_pos = True,
1797
- movie_prefix = movie_prefix,
1798
- export_prefix = export_prefix,
1799
- **correction_protocol,
1800
- )
1801
- print('Done.')
1802
-
1803
-
1804
- def locate_image(self):
1805
-
1806
- """
1807
- Load the first frame of the first movie found in the experiment folder as a sample.
1808
- """
1809
-
1810
- print(f"{self.parent_window.pos}")
1811
- movies = glob(self.parent_window.pos + os.sep.join(['movie', f"{self.parent_window.movie_prefix}*.tif"]))
1812
-
1813
- if len(movies) == 0:
1814
- msgBox = QMessageBox()
1815
- msgBox.setIcon(QMessageBox.Warning)
1816
- msgBox.setText("Please select a position containing a movie...")
1817
- msgBox.setWindowTitle("Warning")
1818
- msgBox.setStandardButtons(QMessageBox.Ok)
1819
- returnValue = msgBox.exec()
1820
- if returnValue == QMessageBox.Ok:
1821
- self.current_stack = None
1822
- return None
1823
- else:
1824
- self.current_stack = movies[0]
1825
-
1826
- def help_background(self):
1827
-
1828
- """
1829
- Helper to choose a proper cell population structure.
1830
- """
1831
-
1832
- dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','preprocessing.json'])
1833
-
1834
- with open(dict_path) as f:
1835
- d = json.load(f)
1836
-
1837
- suggestion = help_generic(d)
1838
- if isinstance(suggestion, str):
1839
- print(f"{suggestion=}")
1840
- msgBox = QMessageBox()
1841
- msgBox.setIcon(QMessageBox.Information)
1842
- msgBox.setTextFormat(Qt.RichText)
1843
- msgBox.setText(suggestion)
1844
- msgBox.setWindowTitle("Info")
1845
- msgBox.setStandardButtons(QMessageBox.Ok)
1846
- returnValue = msgBox.exec()
1847
- if returnValue == QMessageBox.Ok:
1848
- return None
138
+ """
139
+ )
140
+
141
+ title_hbox = QHBoxLayout()
142
+ self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
143
+
144
+ # self.help_pop_btn = QPushButton()
145
+ # self.help_pop_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
146
+ # self.help_pop_btn.setIconSize(QSize(20, 20))
147
+ # self.help_pop_btn.clicked.connect(self.help_population)
148
+ # self.help_pop_btn.setStyleSheet(self.button_select_all)
149
+ # self.help_pop_btn.setToolTip("Help.")
150
+ # self.grid.addWidget(self.help_pop_btn, 0, 0, 1, 3, alignment=Qt.AlignRight)
151
+
152
+ # self.select_all_btn = QPushButton()
153
+ # self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
154
+ # self.select_all_btn.setIconSize(QSize(20, 20))
155
+ # self.all_ticked = False
156
+ # self.select_all_btn.clicked.connect(self.tick_all_actions)
157
+ # self.select_all_btn.setStyleSheet(self.button_select_all)
158
+ # self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
159
+ # self.to_disable.append(self.all_tc_actions)
160
+
161
+ self.collapse_btn = QPushButton()
162
+ self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
163
+ self.collapse_btn.setIconSize(QSize(25, 25))
164
+ self.collapse_btn.setStyleSheet(self.button_select_all)
165
+ # self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
166
+
167
+ title_hbox.addWidget(QLabel(), 5) # self.select_all_btn
168
+ title_hbox.addWidget(QLabel(), 85, alignment=Qt.AlignCenter)
169
+ # title_hbox.addWidget(self.help_pop_btn, 5)
170
+ title_hbox.addWidget(self.collapse_btn, 5)
171
+
172
+ self.grid.addLayout(title_hbox, 0, 0, 1, 4)
173
+ self.populate_contents()
174
+
175
+ self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
176
+ self.collapse_btn.clicked.connect(
177
+ lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden())
178
+ )
179
+ self.collapse_btn.clicked.connect(self.collapse_advanced)
180
+ self.ContentsFrame.hide()
181
+
182
+ def collapse_advanced(self):
183
+
184
+ panels_open = [
185
+ not p.ContentsFrame.isHidden()
186
+ for p in self.parent_window.ProcessPopulations
187
+ ]
188
+ interactions_open = not self.parent_window.NeighPanel.ContentsFrame.isHidden()
189
+ preprocessing_open = (
190
+ not self.parent_window.PreprocessingPanel.ContentsFrame.isHidden()
191
+ )
192
+ is_open = np.array(panels_open + [interactions_open, preprocessing_open])
193
+
194
+ if self.ContentsFrame.isHidden():
195
+ self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
196
+ self.collapse_btn.setIconSize(QSize(20, 20))
197
+ if len(is_open[is_open]) == 0:
198
+ self.parent_window.scroll.setMinimumHeight(int(550))
199
+ self.parent_window.adjustSize()
200
+ else:
201
+ self.collapse_btn.setIcon(icon(MDI6.chevron_up, color="black"))
202
+ self.collapse_btn.setIconSize(QSize(20, 20))
203
+ self.parent_window.scroll.setMinimumHeight(
204
+ min(int(930), int(0.9 * self.parent_window.screen_height))
205
+ )
206
+ try:
207
+ QTimer.singleShot(10, lambda: center_window(self.window()))
208
+ except:
209
+ pass
210
+
211
+ def populate_contents(self):
212
+ self.ContentsFrame = QFrame()
213
+ self.ContentsFrame.setContentsMargins(5, 5, 5, 5)
214
+ self.grid_contents = QGridLayout(self.ContentsFrame)
215
+ self.grid_contents.setContentsMargins(0, 0, 0, 0)
216
+ self.generate_segmentation_options()
217
+ self.generate_tracking_options()
218
+ self.generate_measure_options()
219
+ self.generate_signal_analysis_options()
220
+
221
+ self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
222
+ self.view_tab_btn = QPushButton("Explore table")
223
+ self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
224
+ self.view_tab_btn.clicked.connect(self.view_table_ui)
225
+ self.view_tab_btn.setToolTip("Explore table")
226
+ self.view_tab_btn.setIcon(icon(MDI6.table, color="#1565c0"))
227
+ self.view_tab_btn.setIconSize(QSize(20, 20))
228
+ # self.view_tab_btn.setEnabled(False)
229
+ self.grid_contents.addWidget(self.view_tab_btn, 10, 0, 1, 4)
230
+
231
+ self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
232
+ self.submit_btn = QPushButton("Submit")
233
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
234
+ self.submit_btn.clicked.connect(self.process_population)
235
+ self.grid_contents.addWidget(self.submit_btn, 11, 0, 1, 4)
236
+
237
+ for action in [
238
+ self.segment_action,
239
+ self.track_action,
240
+ self.measure_action,
241
+ self.signal_analysis_action,
242
+ ]:
243
+ action.toggled.connect(self.check_readiness)
244
+ self.check_readiness()
245
+
246
+ def check_readiness(self):
247
+ if (
248
+ self.segment_action.isChecked()
249
+ or self.track_action.isChecked()
250
+ or self.measure_action.isChecked()
251
+ or self.signal_analysis_action.isChecked()
252
+ ):
253
+ self.submit_btn.setEnabled(True)
254
+ else:
255
+ self.submit_btn.setEnabled(False)
256
+
257
+ def generate_measure_options(self):
258
+
259
+ measure_layout = QHBoxLayout()
260
+
261
+ self.measure_action = QCheckBox("MEASURE")
262
+ self.measure_action.setStyleSheet(self.menu_check_style)
263
+
264
+ self.measure_action.setIcon(icon(MDI6.eyedropper, color="black"))
265
+ self.measure_action.setIconSize(QSize(20, 20))
266
+ self.measure_action.setToolTip("Measure.")
267
+ measure_layout.addWidget(self.measure_action, 90)
268
+ # self.to_disable.append(self.measure_action_tc)
269
+
270
+ self.classify_btn = QPushButton()
271
+ self.classify_btn.setIcon(icon(MDI6.scatter_plot, color="black"))
272
+ self.classify_btn.setIconSize(QSize(20, 20))
273
+ self.classify_btn.setToolTip("Classify data.")
274
+ self.classify_btn.setStyleSheet(self.button_select_all)
275
+ self.classify_btn.clicked.connect(self.open_classifier_ui)
276
+ measure_layout.addWidget(
277
+ self.classify_btn, 5
278
+ ) # 4,2,1,1, alignment=Qt.AlignRight
279
+
280
+ self.check_measurements_btn = QPushButton()
281
+ self.check_measurements_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
282
+ self.check_measurements_btn.setIconSize(QSize(20, 20))
283
+ self.check_measurements_btn.setToolTip("Explore measurements in-situ.")
284
+ self.check_measurements_btn.setStyleSheet(self.button_select_all)
285
+ self.check_measurements_btn.clicked.connect(self.check_measurements)
286
+ measure_layout.addWidget(self.check_measurements_btn, 5)
287
+
288
+ self.measurements_config_btn = QPushButton()
289
+ self.measurements_config_btn.setIcon(icon(MDI6.cog_outline, color="black"))
290
+ self.measurements_config_btn.setIconSize(QSize(20, 20))
291
+ self.measurements_config_btn.setToolTip("Configure measurements.")
292
+ self.measurements_config_btn.setStyleSheet(self.button_select_all)
293
+ self.measurements_config_btn.clicked.connect(
294
+ self.open_measurement_configuration_ui
295
+ )
296
+ measure_layout.addWidget(
297
+ self.measurements_config_btn, 5
298
+ ) # 4,2,1,1, alignment=Qt.AlignRight
299
+
300
+ self.grid_contents.addLayout(measure_layout, 5, 0, 1, 4)
301
+
302
+ def generate_signal_analysis_options(self):
303
+
304
+ signal_layout = QVBoxLayout()
305
+ signal_hlayout = QHBoxLayout()
306
+ self.signal_analysis_action = QCheckBox("DETECT EVENTS")
307
+ self.signal_analysis_action.setStyleSheet(self.menu_check_style)
308
+ self.signal_analysis_action.setIcon(
309
+ icon(MDI6.chart_bell_curve_cumulative, color="black")
310
+ )
311
+ self.signal_analysis_action.setIconSize(QSize(20, 20))
312
+ self.signal_analysis_action.setToolTip("Detect events in single-cell signals.")
313
+ self.signal_analysis_action.toggled.connect(self.enable_signal_model_list)
314
+ signal_hlayout.addWidget(self.signal_analysis_action, 90)
315
+
316
+ self.check_signals_btn = QPushButton()
317
+ self.check_signals_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
318
+ self.check_signals_btn.setIconSize(QSize(20, 20))
319
+ self.check_signals_btn.clicked.connect(self.check_signals)
320
+ self.check_signals_btn.setToolTip("Explore signals in-situ.")
321
+ self.check_signals_btn.setStyleSheet(self.button_select_all)
322
+ signal_hlayout.addWidget(self.check_signals_btn, 6)
323
+
324
+ self.config_signal_annotator_btn = QPushButton()
325
+ self.config_signal_annotator_btn.setIcon(icon(MDI6.cog_outline, color="black"))
326
+ self.config_signal_annotator_btn.setIconSize(QSize(20, 20))
327
+ self.config_signal_annotator_btn.setToolTip("Configure the dynamic visualizer.")
328
+ self.config_signal_annotator_btn.setStyleSheet(self.button_select_all)
329
+ self.config_signal_annotator_btn.clicked.connect(
330
+ self.open_signal_annotator_configuration_ui
331
+ )
332
+ signal_hlayout.addWidget(self.config_signal_annotator_btn, 6)
333
+
334
+ # self.to_disable.append(self.measure_action_tc)
335
+ signal_layout.addLayout(signal_hlayout)
336
+
337
+ signal_model_vbox = QVBoxLayout()
338
+ signal_model_vbox.setContentsMargins(25, 0, 25, 0)
339
+
340
+ model_zoo_layout = QHBoxLayout()
341
+ model_zoo_layout.addWidget(QLabel("Model zoo:"), 90)
342
+
343
+ self.signal_models_list = QComboBox()
344
+ self.signal_models_list.setEnabled(False)
345
+ self.refresh_signal_models()
346
+ # self.to_disable.append(self.cell_models_list)
347
+
348
+ self.train_signal_model_btn = QPushButton("TRAIN")
349
+ self.train_signal_model_btn.setToolTip(
350
+ "Train or retrain an event detection model\non newly annotated data."
351
+ )
352
+ self.train_signal_model_btn.setIcon(icon(MDI6.redo_variant, color="black"))
353
+ self.train_signal_model_btn.setIconSize(QSize(20, 20))
354
+ self.train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
355
+ model_zoo_layout.addWidget(self.train_signal_model_btn, 5)
356
+ self.train_signal_model_btn.clicked.connect(self.open_signal_model_config_ui)
357
+
358
+ signal_model_vbox.addLayout(model_zoo_layout)
359
+ signal_model_vbox.addWidget(self.signal_models_list)
360
+
361
+ signal_layout.addLayout(signal_model_vbox)
362
+
363
+ self.grid_contents.addLayout(signal_layout, 6, 0, 1, 4)
364
+
365
+ def refresh_signal_models(self):
366
+ self.signal_models = get_signal_models_list()
367
+ self.signal_models_list.clear()
368
+
369
+ thresh = 35
370
+ models_truncated = [
371
+ m[: thresh - 3] + "..." if len(m) > thresh else m
372
+ for m in self.signal_models
373
+ ]
374
+
375
+ self.signal_models_list.addItems(models_truncated)
376
+ for i in range(len(self.signal_models)):
377
+ self.signal_models_list.setItemData(
378
+ i, self.signal_models[i], Qt.ToolTipRole
379
+ )
380
+
381
+ def generate_tracking_options(self):
382
+
383
+ grid_track = QHBoxLayout()
384
+
385
+ self.track_action = QCheckBox("TRACK")
386
+ self.track_action.setStyleSheet(self.menu_check_style)
387
+ self.track_action.setIcon(icon(MDI6.chart_timeline_variant, color="black"))
388
+ self.track_action.setIconSize(QSize(20, 20))
389
+ self.track_action.setToolTip(f"Track the {self.mode[:-1]} cells.")
390
+ grid_track.addWidget(self.track_action, 75)
391
+
392
+ self.delete_tracks_btn = QPushButton()
393
+ self.delete_tracks_btn.setIcon(icon(MDI6.trash_can, color="black"))
394
+ self.delete_tracks_btn.setIconSize(QSize(20, 20))
395
+ self.delete_tracks_btn.setToolTip("Delete existing tracks.")
396
+ self.delete_tracks_btn.setStyleSheet(self.button_select_all)
397
+ self.delete_tracks_btn.clicked.connect(self.delete_tracks)
398
+ self.delete_tracks_btn.setEnabled(True)
399
+ self.delete_tracks_btn.hide()
400
+ grid_track.addWidget(
401
+ self.delete_tracks_btn, 6
402
+ ) # 4,3,1,1, alignment=Qt.AlignLeft
403
+
404
+ self.check_tracking_result_btn = QPushButton()
405
+ self.check_tracking_result_btn.setIcon(
406
+ icon(MDI6.eye_check_outline, color="black")
407
+ )
408
+ self.check_tracking_result_btn.setIconSize(QSize(20, 20))
409
+ self.check_tracking_result_btn.setToolTip("View tracking output in napari.")
410
+ self.check_tracking_result_btn.setStyleSheet(self.button_select_all)
411
+ self.check_tracking_result_btn.clicked.connect(self.open_napari_tracking)
412
+ self.check_tracking_result_btn.setEnabled(False)
413
+ grid_track.addWidget(
414
+ self.check_tracking_result_btn, 6
415
+ ) # 4,3,1,1, alignment=Qt.AlignLeft
416
+
417
+ self.track_config_btn = QPushButton()
418
+ self.track_config_btn.setIcon(icon(MDI6.cog_outline, color="black"))
419
+ self.track_config_btn.setIconSize(QSize(20, 20))
420
+ self.track_config_btn.setToolTip("Configure tracking.")
421
+ self.track_config_btn.setStyleSheet(self.button_select_all)
422
+ self.track_config_btn.clicked.connect(self.open_tracking_configuration_ui)
423
+ grid_track.addWidget(
424
+ self.track_config_btn, 6
425
+ ) # 4,2,1,1, alignment=Qt.AlignRight
426
+
427
+ self.help_track_btn = QPushButton()
428
+ self.help_track_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
429
+ self.help_track_btn.setIconSize(QSize(20, 20))
430
+ self.help_track_btn.clicked.connect(self.help_tracking)
431
+ self.help_track_btn.setStyleSheet(self.button_select_all)
432
+ self.help_track_btn.setToolTip("Help.")
433
+ grid_track.addWidget(self.help_track_btn, 6) # 4,2,1,1, alignment=Qt.AlignRight
434
+
435
+ self.grid_contents.addLayout(grid_track, 4, 0, 1, 4)
436
+
437
+ def delete_tracks(self):
438
+
439
+ msgBox = QMessageBox()
440
+ msgBox.setIcon(QMessageBox.Question)
441
+ msgBox.setText(
442
+ "Do you want to erase the tracks? All subsequent annotations will be erased..."
443
+ )
444
+ msgBox.setWindowTitle("Info")
445
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
446
+ returnValue = msgBox.exec()
447
+ if returnValue == QMessageBox.No:
448
+ return None
449
+ elif returnValue == QMessageBox.Yes:
450
+ remove_file_if_exists(
451
+ os.sep.join(
452
+ [
453
+ self.parent_window.pos,
454
+ "output",
455
+ "tables",
456
+ f"trajectories_{self.mode}.csv",
457
+ ]
458
+ )
459
+ )
460
+ remove_file_if_exists(
461
+ os.sep.join(
462
+ [
463
+ self.parent_window.pos,
464
+ "output",
465
+ "tables",
466
+ f"trajectories_{self.mode}.pkl",
467
+ ]
468
+ )
469
+ )
470
+ remove_file_if_exists(
471
+ os.sep.join(
472
+ [
473
+ self.parent_window.pos,
474
+ "output",
475
+ "tables",
476
+ f"napari_{self.mode[:-1]}_trajectories.npy",
477
+ ]
478
+ )
479
+ )
480
+ remove_file_if_exists(
481
+ os.sep.join(
482
+ [
483
+ self.parent_window.pos,
484
+ "output",
485
+ "tables",
486
+ f"trajectories_pairs.csv",
487
+ ]
488
+ )
489
+ )
490
+ try:
491
+ QTimer.singleShot(
492
+ 100, lambda: self.parent_window.update_position_options()
493
+ )
494
+ except Exception as _:
495
+ pass
496
+ else:
497
+ return None
498
+
499
+ def generate_segmentation_options(self):
500
+
501
+ grid_segment = QHBoxLayout()
502
+ grid_segment.setContentsMargins(0, 0, 0, 0)
503
+ grid_segment.setSpacing(0)
504
+
505
+ self.segment_action = QCheckBox("SEGMENT")
506
+ self.segment_action.setStyleSheet(self.menu_check_style)
507
+ self.segment_action.setIcon(icon(MDI6.bacteria, color="black"))
508
+ self.segment_action.setToolTip(
509
+ f"Segment the {self.mode[:-1]} cells on the images."
510
+ )
511
+ self.segment_action.toggled.connect(self.enable_segmentation_model_list)
512
+ # self.to_disable.append(self.segment_action)
513
+ grid_segment.addWidget(self.segment_action, 90)
514
+
515
+ # self.flip_segment_btn = QPushButton()
516
+ # self.flip_segment_btn.setIcon(icon(MDI6.camera_flip_outline,color="black"))
517
+ # self.flip_segment_btn.setIconSize(QSize(20, 20))
518
+ # self.flip_segment_btn.clicked.connect(self.flip_segmentation)
519
+ # self.flip_segment_btn.setStyleSheet(self.button_select_all)
520
+ # self.flip_segment_btn.setToolTip("Flip the order of the frames for segmentation.")
521
+ # grid_segment.addWidget(self.flip_segment_btn, 5)
522
+
523
+ self.segmentation_config_btn = QPushButton()
524
+ self.segmentation_config_btn.setIcon(icon(MDI6.cog_outline, color="black"))
525
+ self.segmentation_config_btn.setIconSize(QSize(20, 20))
526
+ self.segmentation_config_btn.setToolTip("Configure segmentation.")
527
+ self.segmentation_config_btn.setStyleSheet(self.button_select_all)
528
+ self.segmentation_config_btn.clicked.connect(
529
+ self.open_segmentation_configuration_ui
530
+ )
531
+ grid_segment.addWidget(self.segmentation_config_btn, 5)
532
+
533
+ self.check_seg_btn = QPushButton()
534
+ self.check_seg_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
535
+ self.check_seg_btn.setIconSize(QSize(20, 20))
536
+ self.check_seg_btn.clicked.connect(self.check_segmentation)
537
+ self.check_seg_btn.setStyleSheet(self.button_select_all)
538
+ self.check_seg_btn.setToolTip("View segmentation output in napari.")
539
+ grid_segment.addWidget(self.check_seg_btn, 5)
540
+
541
+ self.help_seg_btn = QPushButton()
542
+ self.help_seg_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
543
+ self.help_seg_btn.setIconSize(QSize(20, 20))
544
+ self.help_seg_btn.clicked.connect(self.help_segmentation)
545
+ self.help_seg_btn.setStyleSheet(self.button_select_all)
546
+ self.help_seg_btn.setToolTip("Help.")
547
+ grid_segment.addWidget(self.help_seg_btn, 5)
548
+ self.grid_contents.addLayout(grid_segment, 0, 0, 1, 4)
549
+
550
+ seg_option_vbox = QVBoxLayout()
551
+ seg_option_vbox.setContentsMargins(25, 0, 25, 0)
552
+ model_zoo_layout = QHBoxLayout()
553
+ model_zoo_layout.addWidget(QLabel("Model zoo:"), 90)
554
+ self.seg_model_list = QComboBox()
555
+ self.seg_model_list.currentIndexChanged.connect(self.reset_generalist_setup)
556
+ # self.to_disable.append(self.tc_seg_model_list)
557
+ self.seg_model_list.setGeometry(50, 50, 200, 30)
558
+ self.init_seg_model_list()
559
+
560
+ self.upload_model_btn = QPushButton("UPLOAD")
561
+ self.upload_model_btn.setIcon(icon(MDI6.upload, color="black"))
562
+ self.upload_model_btn.setIconSize(QSize(20, 20))
563
+ self.upload_model_btn.setStyleSheet(self.button_style_sheet_3)
564
+ self.upload_model_btn.setToolTip(
565
+ "Upload a new segmentation model\n(Deep learning or threshold-based)."
566
+ )
567
+ model_zoo_layout.addWidget(self.upload_model_btn, 5)
568
+ self.upload_model_btn.clicked.connect(self.upload_segmentation_model)
569
+ # self.to_disable.append(self.upload_tc_model)
570
+
571
+ self.train_btn = QPushButton("TRAIN")
572
+ self.train_btn.setToolTip(
573
+ "Train or retrain a segmentation model\non newly annotated data."
574
+ )
575
+ self.train_btn.setIcon(icon(MDI6.redo_variant, color="black"))
576
+ self.train_btn.setIconSize(QSize(20, 20))
577
+ self.train_btn.setStyleSheet(self.button_style_sheet_3)
578
+ self.train_btn.clicked.connect(self.open_segmentation_model_config_ui)
579
+ model_zoo_layout.addWidget(self.train_btn, 5)
580
+ # self.train_button_tc.clicked.connect(self.train_stardist_model_tc)
581
+ # self.to_disable.append(self.train_button_tc)
582
+
583
+ seg_option_vbox.addLayout(model_zoo_layout)
584
+ seg_option_vbox.addWidget(self.seg_model_list)
585
+ self.seg_model_list.setEnabled(False)
586
+ self.grid_contents.addLayout(seg_option_vbox, 2, 0, 1, 4)
587
+
588
+ def flip_segmentation(self):
589
+ if not self.flipSeg:
590
+ self.flipSeg = True
591
+ self.flip_segment_btn.setIcon(
592
+ icon(MDI6.camera_flip, color=self.celldetective_blue)
593
+ )
594
+ self.flip_segment_btn.setIconSize(QSize(20, 20))
595
+ self.flip_segment_btn.setToolTip(
596
+ "Unflip the order of the frames for segmentation."
597
+ )
598
+ else:
599
+ self.flipSeg = False
600
+ self.flip_segment_btn.setIcon(icon(MDI6.camera_flip_outline, color="black"))
601
+ self.flip_segment_btn.setIconSize(QSize(20, 20))
602
+ self.flip_segment_btn.setToolTip(
603
+ "Flip the order of the frames for segmentation."
604
+ )
605
+
606
+ def help_segmentation(self):
607
+ """
608
+ Widget with different decision helper decision trees.
609
+ """
610
+
611
+ self.help_w = CelldetectiveWidget()
612
+ self.help_w.setWindowTitle("Helper")
613
+ layout = QVBoxLayout()
614
+ seg_strategy_btn = QPushButton("A guide to choose a segmentation strategy.")
615
+ seg_strategy_btn.setIcon(icon(MDI6.help_circle, color=self.celldetective_blue))
616
+ seg_strategy_btn.setIconSize(QSize(40, 40))
617
+ seg_strategy_btn.setStyleSheet(self.button_style_sheet_5)
618
+ seg_strategy_btn.clicked.connect(self.help_seg_strategy)
619
+
620
+ dl_strategy_btn = QPushButton(
621
+ "A guide to choose your Deep learning segmentation strategy."
622
+ )
623
+ dl_strategy_btn.setIcon(icon(MDI6.help_circle, color=self.celldetective_blue))
624
+ dl_strategy_btn.setIconSize(QSize(40, 40))
625
+ dl_strategy_btn.setStyleSheet(self.button_style_sheet_5)
626
+ dl_strategy_btn.clicked.connect(self.help_seg_dl_strategy)
627
+
628
+ layout.addWidget(seg_strategy_btn)
629
+ layout.addWidget(dl_strategy_btn)
630
+
631
+ self.help_w.setLayout(layout)
632
+ center_window(self.help_w)
633
+ self.help_w.show()
634
+
635
+ return None
636
+
637
+ def help_seg_strategy(self):
638
+ """
639
+ Helper for segmentation strategy between threshold-based and Deep learning.
640
+ """
641
+
642
+ dict_path = os.sep.join(
643
+ [
644
+ get_software_location(),
645
+ "celldetective",
646
+ "gui",
647
+ "help",
648
+ "Threshold-vs-DL.json",
649
+ ]
650
+ )
651
+
652
+ with open(dict_path) as f:
653
+ d = json.load(f)
654
+
655
+ suggestion = help_generic(d)
656
+ if isinstance(suggestion, str):
657
+ logger.info(f"{suggestion=}")
658
+ msgBox = QMessageBox()
659
+ msgBox.setIcon(QMessageBox.Information)
660
+ msgBox.setTextFormat(Qt.RichText)
661
+ msgBox.setText(
662
+ f"The suggested technique is {suggestion}.\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>."
663
+ )
664
+ msgBox.setWindowTitle("Info")
665
+ msgBox.setStandardButtons(QMessageBox.Ok)
666
+ returnValue = msgBox.exec()
667
+ if returnValue == QMessageBox.Ok:
668
+ return None
669
+
670
+ def help_seg_dl_strategy(self):
671
+ """
672
+ Helper for DL segmentation strategy, between pretrained models and custom models.
673
+ """
674
+
675
+ dict_path = os.sep.join(
676
+ [
677
+ get_software_location(),
678
+ "celldetective",
679
+ "gui",
680
+ "help",
681
+ "DL-segmentation-strategy.json",
682
+ ]
683
+ )
684
+
685
+ with open(dict_path) as f:
686
+ d = json.load(f)
687
+
688
+ suggestion = help_generic(d)
689
+ if isinstance(suggestion, str):
690
+ logger.info(f"{suggestion=}")
691
+ msgBox = QMessageBox()
692
+ msgBox.setIcon(QMessageBox.Information)
693
+ msgBox.setText(f"The suggested technique is {suggestion}.")
694
+ msgBox.setWindowTitle("Info")
695
+ msgBox.setStandardButtons(QMessageBox.Ok)
696
+ returnValue = msgBox.exec()
697
+ if returnValue == QMessageBox.Ok:
698
+ return None
699
+
700
+ def help_tracking(self):
701
+ """
702
+ Helper for segmentation strategy between threshold-based and Deep learning.
703
+ """
704
+
705
+ dict_path = os.sep.join(
706
+ [get_software_location(), "celldetective", "gui", "help", "tracking.json"]
707
+ )
708
+
709
+ with open(dict_path) as f:
710
+ d = json.load(f)
711
+
712
+ suggestion = help_generic(d)
713
+ if isinstance(suggestion, str):
714
+ logger.info(f"{suggestion=}")
715
+ msgBox = QMessageBox()
716
+ msgBox.setIcon(QMessageBox.Information)
717
+ msgBox.setTextFormat(Qt.RichText)
718
+ msgBox.setText(f"{suggestion}")
719
+ msgBox.setWindowTitle("Info")
720
+ msgBox.setStandardButtons(QMessageBox.Ok)
721
+ returnValue = msgBox.exec()
722
+ if returnValue == QMessageBox.Ok:
723
+ return None
724
+
725
+ def check_segmentation(self):
726
+ from celldetective.napari.utils import control_segmentation_napari
727
+
728
+ if not os.path.exists(
729
+ os.sep.join([self.parent_window.pos, f"labels_{self.mode}", os.sep])
730
+ ):
731
+ msgBox = QMessageBox()
732
+ msgBox.setIcon(QMessageBox.Question)
733
+ msgBox.setText(
734
+ "No labels can be found for this position. Do you want to annotate from scratch?"
735
+ )
736
+ msgBox.setWindowTitle("Info")
737
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
738
+ returnValue = msgBox.exec()
739
+ if returnValue == QMessageBox.No:
740
+ return None
741
+ else:
742
+ os.mkdir(os.sep.join([self.parent_window.pos, f"labels_{self.mode}"]))
743
+ lbl = np.zeros(
744
+ (self.parent_window.shape_x, self.parent_window.shape_y), dtype=int
745
+ )
746
+ for i in range(self.parent_window.len_movie):
747
+ imwrite(
748
+ os.sep.join(
749
+ [
750
+ self.parent_window.pos,
751
+ f"labels_{self.mode}",
752
+ str(i).zfill(4) + ".tif",
753
+ ]
754
+ ),
755
+ lbl,
756
+ )
757
+
758
+ # self.freeze()
759
+ # QApplication.setOverrideCursor(Qt.WaitCursor)
760
+ test = self.parent_window.locate_selected_position()
761
+ if test:
762
+ # print('Memory use: ', dict(psutil.virtual_memory()._asdict()))
763
+ logger.info(f"Loading images and labels into napari...")
764
+ try:
765
+ control_segmentation_napari(
766
+ self.parent_window.pos,
767
+ prefix=self.parent_window.movie_prefix,
768
+ population=self.mode,
769
+ flush_memory=True,
770
+ )
771
+ except FileNotFoundError as e:
772
+ msgBox = QMessageBox()
773
+ msgBox.setIcon(QMessageBox.Warning)
774
+ msgBox.setText(str(e))
775
+ msgBox.setWindowTitle("Warning")
776
+ msgBox.setStandardButtons(QMessageBox.Ok)
777
+ _ = msgBox.exec()
778
+ return
779
+ except Exception as e:
780
+ logger.error(f"Task unsuccessful... Exception {e}...")
781
+ msgBox = QMessageBox()
782
+ msgBox.setIcon(QMessageBox.Warning)
783
+ msgBox.setText(str(e))
784
+ msgBox.setWindowTitle("Warning")
785
+ msgBox.setStandardButtons(QMessageBox.Ok)
786
+ _ = msgBox.exec()
787
+
788
+ msgBox = QMessageBox()
789
+ msgBox.setIcon(QMessageBox.Question)
790
+ msgBox.setText(
791
+ "Would you like to pass empty frames to fix the asymmetry?"
792
+ )
793
+ msgBox.setWindowTitle("Question")
794
+ msgBox.setStandardButtons(
795
+ QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
796
+ )
797
+ returnValue = msgBox.exec()
798
+ if returnValue == QMessageBox.Yes:
799
+ logger.info("Fixing the missing labels...")
800
+ fix_missing_labels(
801
+ self.parent_window.pos,
802
+ prefix=self.parent_window.movie_prefix,
803
+ population=self.mode,
804
+ )
805
+ try:
806
+ control_segmentation_napari(
807
+ self.parent_window.pos,
808
+ prefix=self.parent_window.movie_prefix,
809
+ population=self.mode,
810
+ flush_memory=True,
811
+ )
812
+ except Exception as e:
813
+ logger.error(f"Error {e}")
814
+ return None
815
+ else:
816
+ return None
817
+
818
+ gc.collect()
819
+
820
+ def check_signals(self):
821
+ from celldetective.gui.event_annotator import EventAnnotator, StackLoaderThread
822
+
823
+ test = self.parent_window.locate_selected_position()
824
+ if test:
825
+ self.event_annotator = EventAnnotator(self, lazy_load=True)
826
+
827
+ if not getattr(self.event_annotator, "proceed", True):
828
+ return
829
+
830
+ self.signal_loader = StackLoaderThread(self.event_annotator)
831
+
832
+ self.signal_progress = CelldetectiveProgressDialog(
833
+ "Loading data...", "Cancel", 0, 100, self, window_title="Please wait"
834
+ )
835
+
836
+ self.signal_progress.setValue(0)
837
+
838
+ self.signal_loader.progress.connect(self.signal_progress.setValue)
839
+ self.signal_loader.status_update.connect(self.signal_progress.setLabelText)
840
+ self.signal_progress.canceled.connect(self.signal_loader.stop)
841
+
842
+ def on_finished():
843
+ self.signal_progress.blockSignals(True)
844
+ self.signal_progress.close()
845
+ if not self.signal_loader._is_cancelled:
846
+ try:
847
+ self.event_annotator.finalize_init()
848
+ self.event_annotator.show()
849
+ try:
850
+ QTimer.singleShot(
851
+ 100,
852
+ lambda: self.event_annotator.resize(
853
+ self.event_annotator.width() + 1,
854
+ self.event_annotator.height() + 1,
855
+ ),
856
+ )
857
+ except:
858
+ pass
859
+ except Exception as e:
860
+ print(f"Error finalizing annotator: {e}")
861
+ else:
862
+ self.event_annotator.close()
863
+
864
+ self.signal_loader.finished.connect(on_finished)
865
+ self.signal_loader.start()
866
+
867
+ def check_measurements(self):
868
+ from celldetective.gui.event_annotator import MeasureAnnotator
869
+
870
+ test = self.parent_window.locate_selected_position()
871
+ if test:
872
+ self.measure_annotator = MeasureAnnotator(self)
873
+ self.measure_annotator.show()
874
+
875
+ def enable_segmentation_model_list(self):
876
+ if self.segment_action.isChecked():
877
+ self.seg_model_list.setEnabled(True)
878
+ else:
879
+ self.seg_model_list.setEnabled(False)
880
+
881
+ def enable_signal_model_list(self):
882
+ if self.signal_analysis_action.isChecked():
883
+ self.signal_models_list.setEnabled(True)
884
+ else:
885
+ self.signal_models_list.setEnabled(False)
886
+
887
+ def init_seg_model_list(self):
888
+
889
+ self.seg_model_list.clear()
890
+ self.seg_models_specific = get_segmentation_models_list(
891
+ mode=self.mode, return_path=False
892
+ )
893
+ self.seg_models = self.seg_models_specific.copy()
894
+ self.n_specific_seg_models = len(self.seg_models)
895
+
896
+ self.seg_models_generic = get_segmentation_models_list(
897
+ mode="generic", return_path=False
898
+ )
899
+ self.seg_models.append("Threshold")
900
+ self.seg_models.extend(self.seg_models_generic)
901
+
902
+ thresh = 35
903
+ self.models_truncated = [
904
+ m[: thresh - 3] + "..." if len(m) > thresh else m for m in self.seg_models
905
+ ]
906
+
907
+ self.seg_model_list.addItems(self.models_truncated)
908
+
909
+ for i in range(len(self.seg_models)):
910
+ self.seg_model_list.setItemData(i, self.seg_models[i], Qt.ToolTipRole)
911
+
912
+ self.seg_model_list.insertSeparator(self.n_specific_seg_models)
913
+
914
+ # def tick_all_actions(self):
915
+ # self.switch_all_ticks_option()
916
+ # if self.all_ticked:
917
+ # self.select_all_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
918
+ # self.select_all_btn.setIconSize(QSize(20, 20))
919
+ # self.segment_action.setChecked(True)
920
+ # else:
921
+ # self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
922
+ # self.select_all_btn.setIconSize(QSize(20, 20))
923
+ # self.segment_action.setChecked(False)
924
+
925
+ # def switch_all_ticks_option(self):
926
+ # if self.all_ticked == True:
927
+ # self.all_ticked = False
928
+ # else:
929
+ # self.all_ticked = True
930
+
931
+ def upload_segmentation_model(self):
932
+ from celldetective.gui.seg_model_loader import SegmentationModelLoader
933
+
934
+ logger.info("Load a segmentation model or pipeline...")
935
+ self.seg_model_loader = SegmentationModelLoader(self)
936
+ self.seg_model_loader.show()
937
+ center_window(self.seg_model_loader)
938
+
939
+ def open_tracking_configuration_ui(self):
940
+ from celldetective.gui.settings._settings_tracking import SettingsTracking
941
+
942
+ logger.info("Set the tracking parameters...")
943
+ self.settings_tracking = SettingsTracking(self)
944
+ self.settings_tracking.show()
945
+ center_window(self.settings_tracking)
946
+
947
+ def open_signal_model_config_ui(self):
948
+ from celldetective.gui.settings._settings_event_model_training import (
949
+ SettingsEventDetectionModelTraining,
950
+ )
951
+
952
+ logger.info("Set the training parameters for new signal models...")
953
+ self.settings_event_detection_training = SettingsEventDetectionModelTraining(
954
+ self
955
+ )
956
+ self.settings_event_detection_training.show()
957
+ center_window(self.settings_event_detection_training)
958
+
959
+ def open_segmentation_model_config_ui(self):
960
+ from celldetective.gui.settings._settings_segmentation_model_training import (
961
+ SettingsSegmentationModelTraining,
962
+ )
963
+
964
+ logger.info("Set the training parameters for a new segmentation model...")
965
+ self.settings_segmentation_training = SettingsSegmentationModelTraining(self)
966
+ self.settings_segmentation_training.show()
967
+ center_window(self.settings_segmentation_training)
968
+
969
+ def open_measurement_configuration_ui(self):
970
+ from celldetective.gui.settings._settings_measurements import (
971
+ SettingsMeasurements,
972
+ )
973
+
974
+ logger.info("Set the measurements to be performed...")
975
+ self.settings_measurements = SettingsMeasurements(self)
976
+ self.settings_measurements.show()
977
+ center_window(self.settings_measurements)
978
+
979
+ def open_segmentation_configuration_ui(self):
980
+ from celldetective.gui.settings._settings_segmentation import (
981
+ SettingsSegmentation,
982
+ )
983
+
984
+ logger.info("Set the segmentation settings to be performed...")
985
+ self.settings_segmentation = SettingsSegmentation(self)
986
+ self.settings_segmentation.show()
987
+
988
+ def open_classifier_ui(self):
989
+ from celldetective.gui.classifier_widget import ClassifierWidget
990
+
991
+ self.load_available_tables()
992
+ if self.df is None:
993
+
994
+ msgBox = QMessageBox()
995
+ msgBox.setIcon(QMessageBox.Warning)
996
+ msgBox.setText("No table was found...")
997
+ msgBox.setWindowTitle("Warning")
998
+ msgBox.setStandardButtons(QMessageBox.Ok)
999
+ returnValue = msgBox.exec()
1000
+ if returnValue == QMessageBox.Ok:
1001
+ return None
1002
+ else:
1003
+ return None
1004
+ else:
1005
+ self.classifier_widget = ClassifierWidget(self)
1006
+ self.classifier_widget.show()
1007
+ try:
1008
+
1009
+ def post_widget(wdg):
1010
+ try:
1011
+ wdg.resize(wdg.width() + 1, wdg.height() + 1)
1012
+ center_window(wdg)
1013
+ except Exception as _:
1014
+ pass
1015
+
1016
+ QTimer.singleShot(100, lambda: post_widget(self.classifier_widget))
1017
+ except Exception as _:
1018
+ pass
1019
+
1020
+ def open_signal_annotator_configuration_ui(self):
1021
+ from celldetective.gui.settings._settings_signal_annotator import (
1022
+ SettingsSignalAnnotator,
1023
+ )
1024
+
1025
+ self.settings_signal_annotator = SettingsSignalAnnotator(self)
1026
+ self.settings_signal_annotator.show()
1027
+ try:
1028
+ QTimer.singleShot(
1029
+ 100, lambda: center_window(self.settings_signal_annotator)
1030
+ )
1031
+ except Exception as _:
1032
+ pass
1033
+
1034
+ def reset_generalist_setup(self, index):
1035
+ self.cellpose_calibrated = False
1036
+ self.stardist_calibrated = False
1037
+ self.segChannelsSet = False
1038
+
1039
+ def reset_signals(self):
1040
+ self.signalChannelsSet = False
1041
+
1042
+ def process_population(self):
1043
+ from celldetective.processes.unified_process import UnifiedBatchProcess
1044
+ from celldetective.gui.workers import ProgressWindow
1045
+
1046
+ # Check positions/wells
1047
+ self.well_index = self.parent_window.well_list.getSelectedIndices()
1048
+ if len(self.well_index) == 0:
1049
+ msgBox = QMessageBox()
1050
+ msgBox.setIcon(QMessageBox.Warning)
1051
+ msgBox.setText("Please select at least one well first...")
1052
+ msgBox.setWindowTitle("Warning")
1053
+ msgBox.setStandardButtons(QMessageBox.Ok)
1054
+ returnValue = msgBox.exec()
1055
+ if returnValue == QMessageBox.Ok:
1056
+ return None
1057
+ else:
1058
+ return None
1059
+
1060
+ logger.info(f"Processing {self.parent_window.well_list.currentText()}...")
1061
+
1062
+ idx = self.parent_window.populations.index(self.mode)
1063
+ self.threshold_config = self.threshold_configs[idx]
1064
+
1065
+ self.load_available_tables()
1066
+
1067
+ # Checks for segmentation action
1068
+ if self.df is not None and self.segment_action.isChecked():
1069
+ msgBox = QMessageBox()
1070
+ msgBox.setIcon(QMessageBox.Question)
1071
+ msgBox.setText(
1072
+ "Measurement tables have been found... Re-segmenting may create mismatches between the cell labels and the associated measurements. Do you want to erase the tables post-segmentation?"
1073
+ )
1074
+ msgBox.setWindowTitle("Info")
1075
+ msgBox.setStandardButtons(
1076
+ QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
1077
+ )
1078
+ returnValue = msgBox.exec()
1079
+ if returnValue == QMessageBox.No:
1080
+ pass
1081
+ elif returnValue == QMessageBox.Cancel:
1082
+ return None
1083
+ else:
1084
+ logger.info("erase tabs!")
1085
+ tabs = [
1086
+ pos
1087
+ + os.sep.join(["output", "tables", f"trajectories_{self.mode}.csv"])
1088
+ for pos in self.df_pos_info["pos_path"].unique()
1089
+ ]
1090
+ # tabs += [pos+os.sep.join(['output', 'tables', f'trajectories_pairs.csv']) for pos in self.df_pos_info['pos_path'].unique()]
1091
+ tabs += [
1092
+ pos
1093
+ + os.sep.join(
1094
+ ["output", "tables", f"napari_{self.mode}_trajectories.npy"]
1095
+ )
1096
+ for pos in self.df_pos_info["pos_path"].unique()
1097
+ ]
1098
+ for t in tabs:
1099
+ remove_file_if_exists(t.replace(".csv", ".pkl"))
1100
+ try:
1101
+ os.remove(t)
1102
+ except:
1103
+ pass
1104
+
1105
+ if self.seg_model_list.currentIndex() > self.n_specific_seg_models:
1106
+ self.model_name = self.seg_models[self.seg_model_list.currentIndex() - 1]
1107
+ else:
1108
+ self.model_name = self.seg_models[self.seg_model_list.currentIndex()]
1109
+
1110
+ if (
1111
+ self.segment_action.isChecked()
1112
+ and self.model_name.startswith("CP")
1113
+ and self.model_name in self.seg_models_generic
1114
+ and not self.cellpose_calibrated
1115
+ ):
1116
+ from celldetective.gui.settings._cellpose_model_params import (
1117
+ CellposeParamsWidget,
1118
+ )
1119
+
1120
+ self.diamWidget = CellposeParamsWidget(self, model_name=self.model_name)
1121
+ self.diamWidget.show()
1122
+
1123
+ return None
1124
+ elif (
1125
+ self.segment_action.isChecked()
1126
+ and self.model_name.startswith("SD")
1127
+ and self.model_name in self.seg_models_generic
1128
+ and not self.stardist_calibrated
1129
+ ):
1130
+ from celldetective.gui.settings._stardist_model_params import (
1131
+ StarDistParamsWidget,
1132
+ )
1133
+
1134
+ self.diamWidget = StarDistParamsWidget(self, model_name=self.model_name)
1135
+ self.diamWidget.show()
1136
+
1137
+ return None
1138
+
1139
+ elif (
1140
+ self.segment_action.isChecked()
1141
+ and self.model_name in self.seg_models_specific
1142
+ and not self.segChannelsSet
1143
+ ):
1144
+ from celldetective.gui.settings._segmentation_model_params import (
1145
+ SegModelParamsWidget,
1146
+ )
1147
+
1148
+ self.segChannelWidget = SegModelParamsWidget(
1149
+ self, model_name=self.model_name
1150
+ )
1151
+ self.segChannelWidget.show()
1152
+
1153
+ return None
1154
+
1155
+ if self.signal_analysis_action.isChecked() and not self.signalChannelsSet:
1156
+ from celldetective.gui.settings._event_detection_model_params import (
1157
+ SignalModelParamsWidget,
1158
+ )
1159
+
1160
+ self.signal_model_name = self.signal_models[
1161
+ self.signal_models_list.currentIndex()
1162
+ ]
1163
+ self.signalChannelWidget = SignalModelParamsWidget(
1164
+ self, model_name=self.signal_model_name
1165
+ )
1166
+ self.signalChannelWidget.show()
1167
+
1168
+ return None
1169
+
1170
+ self.movie_prefix = self.parent_window.movie_prefix
1171
+
1172
+ if self.parent_window.position_list.isMultipleSelection():
1173
+ msgBox = QMessageBox()
1174
+ msgBox.setIcon(QMessageBox.Question)
1175
+ msgBox.setText(
1176
+ "If you continue, several positions will be processed.\nDo you want to proceed?"
1177
+ )
1178
+ msgBox.setWindowTitle("Info")
1179
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1180
+ returnValue = msgBox.exec()
1181
+ if returnValue == QMessageBox.No:
1182
+ return None
1183
+
1184
+ self.movie_prefix = self.parent_window.movie_prefix
1185
+
1186
+ # COLLECT POSITIONS
1187
+ batch_structure = {}
1188
+ all_positions_flat = (
1189
+ []
1190
+ ) # Keep flat list for legacy check logic or easy counting
1191
+
1192
+ for w_idx in self.well_index:
1193
+ well = self.parent_window.wells[w_idx]
1194
+
1195
+ batch_structure[w_idx] = {"well_name": well, "positions": []}
1196
+
1197
+ pos_indices = self.parent_window.position_list.getSelectedIndices()
1198
+ # Optimization: Glob once per well
1199
+ all_well_positions = natsorted(
1200
+ glob(
1201
+ well
1202
+ + f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*/"
1203
+ )
1204
+ )
1205
+ for pos_idx in pos_indices:
1206
+ if pos_idx < len(all_well_positions):
1207
+ pos = all_well_positions[pos_idx]
1208
+ else:
1209
+ logger.warning(
1210
+ f"Position index {pos_idx} out of range for well {well}"
1211
+ )
1212
+ continue
1213
+
1214
+ batch_structure[w_idx]["positions"].append(pos)
1215
+ all_positions_flat.append(pos)
1216
+
1217
+ # Check output folders creation
1218
+ if not os.path.exists(pos + "output/"):
1219
+ os.mkdir(pos + "output/")
1220
+ if not os.path.exists(pos + "output/tables/"):
1221
+ os.mkdir(pos + "output/tables/")
1222
+
1223
+ # BATCH SEGMENTATION
1224
+ # --- UNIFIED BATCH PROCESS SETUP ---
1225
+
1226
+ run_segmentation = self.segment_action.isChecked()
1227
+ run_tracking = self.track_action.isChecked()
1228
+ run_measurement = self.measure_action.isChecked()
1229
+ run_signals = self.signal_analysis_action.isChecked()
1230
+
1231
+ seg_args = {}
1232
+ track_args = {}
1233
+ measure_args = {}
1234
+ signal_args = {}
1235
+
1236
+ # 1. SEGMENTATION CHECKS & ARGS
1237
+ if run_segmentation:
1238
+ # Single-position overwrite check
1239
+ if (
1240
+ len(all_positions_flat) == 1
1241
+ and not self.parent_window.position_list.isMultipleSelection()
1242
+ ):
1243
+ p = all_positions_flat[0]
1244
+ if len(glob(os.sep.join([p, f"labels_{self.mode}", "*.tif"]))) > 0:
1245
+ msgBox = QMessageBox()
1246
+ msgBox.setIcon(QMessageBox.Question)
1247
+ msgBox.setText(
1248
+ "Labels have already been produced for this position. Do you want to segment again?"
1249
+ )
1250
+ msgBox.setWindowTitle("Info")
1251
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1252
+ if msgBox.exec() == QMessageBox.No:
1253
+ run_segmentation = False
1254
+
1255
+ # Threshold config check
1256
+ if run_segmentation and self.seg_model_list.currentText() == "Threshold":
1257
+ if self.threshold_config is None:
1258
+ msgBox = QMessageBox()
1259
+ msgBox.setIcon(QMessageBox.Warning)
1260
+ msgBox.setText(
1261
+ "Please set a threshold configuration from the upload menu first. Abort."
1262
+ )
1263
+ msgBox.setWindowTitle("Warning")
1264
+ msgBox.setStandardButtons(QMessageBox.Ok)
1265
+ msgBox.exec()
1266
+ return None
1267
+
1268
+ seg_args = {
1269
+ "mode": self.mode,
1270
+ "n_threads": self.n_threads,
1271
+ "threshold_instructions": self.threshold_config,
1272
+ "use_gpu": self.use_gpu,
1273
+ }
1274
+ elif run_segmentation: # Deep Learning
1275
+ # Prepare representative position for process initialization
1276
+ first_pos = None
1277
+ if all_positions_flat and isinstance(all_positions_flat[0], str):
1278
+ first_pos = all_positions_flat[0]
1279
+
1280
+ seg_args = {
1281
+ "mode": self.mode,
1282
+ "pos": first_pos,
1283
+ "n_threads": self.n_threads,
1284
+ "model_name": self.model_name,
1285
+ "use_gpu": self.use_gpu,
1286
+ }
1287
+
1288
+ # 2. TRACKING CHECKS & ARGS
1289
+ if run_tracking:
1290
+
1291
+ # Single-position overwrite check
1292
+ if (
1293
+ len(all_positions_flat) == 1
1294
+ and not self.parent_window.position_list.isMultipleSelection()
1295
+ ):
1296
+ p = all_positions_flat[0]
1297
+ table_path = os.sep.join(
1298
+ [p, "output", "tables", f"trajectories_{self.mode}.csv"]
1299
+ )
1300
+ if os.path.exists(table_path):
1301
+ msgBox = QMessageBox()
1302
+ msgBox.setIcon(QMessageBox.Question)
1303
+ msgBox.setText(
1304
+ "A measurement table already exists. Previously annotated data for\nthis position will be lost. Do you want to proceed?"
1305
+ )
1306
+ msgBox.setWindowTitle("Info")
1307
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1308
+ if msgBox.exec() == QMessageBox.No:
1309
+ run_tracking = False
1310
+
1311
+ if run_tracking:
1312
+ track_args = {"mode": self.mode, "n_threads": self.n_threads}
1313
+
1314
+ # 3. MEASUREMENT ARGS
1315
+ if run_measurement:
1316
+ measure_args = {"mode": self.mode, "n_threads": self.n_threads}
1317
+
1318
+ # 4. SIGNAL ANALYSIS CHECKS & ARGS
1319
+ if run_signals:
1320
+ if (
1321
+ len(all_positions_flat) == 1
1322
+ and not self.parent_window.position_list.isMultipleSelection()
1323
+ ):
1324
+ p = all_positions_flat[0]
1325
+ table_path = os.sep.join(
1326
+ [p, "output", "tables", f"trajectories_{self.mode}.csv"]
1327
+ )
1328
+ if os.path.exists(table_path):
1329
+ # Check for annotations (logic from original code)
1330
+ try:
1331
+ # Optimized reading: Check header first, then read interesting column
1332
+ header = pd.read_csv(table_path, nrows=0).columns
1333
+ if "class_color" in list(header):
1334
+ colors = pd.read_csv(table_path, usecols=["class_color"])[
1335
+ "class_color"
1336
+ ].unique()
1337
+ if "tab:orange" in colors or "tab:cyan" in colors:
1338
+ msgBox = QMessageBox()
1339
+ msgBox.setIcon(QMessageBox.Question)
1340
+ msgBox.setText(
1341
+ "The signals of the cells in the position appear to have been annotated... Do you want to proceed?"
1342
+ )
1343
+ msgBox.setWindowTitle("Info")
1344
+ msgBox.setStandardButtons(
1345
+ QMessageBox.Yes | QMessageBox.No
1346
+ )
1347
+ if msgBox.exec() == QMessageBox.No:
1348
+ run_signals = False
1349
+ except Exception as e:
1350
+ logger.warning(f"Could not check table for annotations: {e}")
1351
+
1352
+ if run_signals:
1353
+ self.signal_model_name = self.signal_models[
1354
+ self.signal_models_list.currentIndex()
1355
+ ]
1356
+
1357
+ model_complete_path = locate_signal_model(self.signal_model_name)
1358
+ input_config_path = os.path.join(
1359
+ model_complete_path, "config_input.json"
1360
+ )
1361
+ with open(input_config_path) as config_file:
1362
+ input_config = json.load(config_file)
1363
+
1364
+ channels = input_config.get(
1365
+ "selected_channels", input_config.get("channels", [])
1366
+ )
1367
+
1368
+ signal_args = {
1369
+ "model_name": self.signal_model_name,
1370
+ "mode": self.mode,
1371
+ "channels": channels,
1372
+ }
1373
+
1374
+ # --- EXECUTE UNIFIED PROCESS ---
1375
+ if any([run_segmentation, run_tracking, run_measurement, run_signals]):
1376
+
1377
+ process_args = {
1378
+ "batch_structure": batch_structure,
1379
+ "run_segmentation": run_segmentation,
1380
+ "run_tracking": run_tracking,
1381
+ "run_measurement": run_measurement,
1382
+ "run_signals": run_signals,
1383
+ "seg_args": seg_args,
1384
+ "track_args": track_args,
1385
+ "measure_args": measure_args,
1386
+ "signal_args": signal_args,
1387
+ "log_file": getattr(self.parent_window.parent_window, "log_file", None),
1388
+ }
1389
+
1390
+ self.job = ProgressWindow(
1391
+ UnifiedBatchProcess,
1392
+ parent_window=self,
1393
+ title=f"Processing {self.mode}",
1394
+ process_args=process_args,
1395
+ )
1396
+ result = self.job.exec_()
1397
+
1398
+ if result == QDialog.Rejected:
1399
+ self.reset_generalist_setup(0)
1400
+ return None
1401
+
1402
+ # Post-Process actions (like updating list)
1403
+ if run_tracking:
1404
+ self.parent_window.update_position_options()
1405
+
1406
+ if run_signals:
1407
+ from celldetective.gui.interactive_timeseries_viewer import (
1408
+ InteractiveEventViewer,
1409
+ )
1410
+
1411
+ self.parent_window.update_position_options()
1412
+
1413
+ if len(all_positions_flat) == 1:
1414
+ p = all_positions_flat[0]
1415
+ mode_fixed = self.mode
1416
+ if self.mode.lower() in ["target", "targets"]:
1417
+ mode_fixed = "targets"
1418
+ elif self.mode.lower() in ["effector", "effectors"]:
1419
+ mode_fixed = "effectors"
1420
+
1421
+ table_path = os.sep.join(
1422
+ [p, "output", "tables", f"trajectories_{mode_fixed}.csv"]
1423
+ )
1424
+
1425
+ if os.path.exists(table_path):
1426
+ # Determine event label
1427
+ event_label = None
1428
+ signal_name = None
1429
+ try:
1430
+ if hasattr(self, "signal_model_name"):
1431
+ model_complete_path = locate_signal_model(
1432
+ self.signal_model_name
1433
+ )
1434
+ input_config_path = os.path.join(
1435
+ model_complete_path, "config_input.json"
1436
+ )
1437
+ if os.path.exists(input_config_path):
1438
+ with open(input_config_path) as f:
1439
+ conf = json.load(f)
1440
+ event_label = conf.get("label", None)
1441
+ channels = conf.get("channels", [])
1442
+ if channels:
1443
+ signal_name = channels[0]
1444
+ except Exception as e:
1445
+ logger.warning(f"Could not determine event label: {e}")
1446
+
1447
+ logger.info(
1448
+ f"Launching Interactive Event Viewer for {table_path} with label {event_label}"
1449
+ )
1450
+ self.viewer = InteractiveEventViewer(
1451
+ table_path,
1452
+ signal_name=signal_name,
1453
+ event_label=event_label,
1454
+ parent=self,
1455
+ )
1456
+ self.viewer.show()
1457
+ center_window(self.viewer)
1458
+ else:
1459
+ logger.warning(
1460
+ f"Could not find table for interactive viewer: {table_path}"
1461
+ )
1462
+ for action in [
1463
+ self.segment_action,
1464
+ self.track_action,
1465
+ self.measure_action,
1466
+ self.signal_analysis_action,
1467
+ ]:
1468
+ if action.isChecked():
1469
+ action.setChecked(False)
1470
+
1471
+ self.reset_generalist_setup(0)
1472
+ self.reset_signals()
1473
+
1474
+ def open_napari_tracking(self):
1475
+
1476
+ logger.info(
1477
+ f"View the tracks before post-processing for position {self.parent_window.pos} in napari..."
1478
+ )
1479
+
1480
+ self.napari_loader = NapariLoaderThread(
1481
+ self.parent_window.pos,
1482
+ self.parent_window.movie_prefix,
1483
+ self.mode,
1484
+ self.parent_window.parent_window.n_threads,
1485
+ )
1486
+
1487
+ self.napari_progress = CelldetectiveProgressDialog(
1488
+ "Loading images, tracks and relabeling masks...",
1489
+ "Cancel",
1490
+ 0,
1491
+ 100,
1492
+ self,
1493
+ window_title="Preparing the napari viewer...",
1494
+ )
1495
+
1496
+ self.napari_progress.setValue(0)
1497
+ self.napari_loader.progress.connect(self.napari_progress.setValue)
1498
+ self.napari_loader.status.connect(self.napari_progress.setLabelText)
1499
+ self.napari_progress.canceled.connect(self.napari_loader.stop)
1500
+
1501
+ def on_finished(result):
1502
+ from celldetective.napari.utils import launch_napari_viewer
1503
+
1504
+ self.napari_progress.blockSignals(True)
1505
+ self.napari_progress.close()
1506
+ if self.napari_loader._is_cancelled:
1507
+ logger.info("Task was cancelled...")
1508
+ return
1509
+
1510
+ if isinstance(result, Exception):
1511
+ logger.error(f"napari loading error: {result}")
1512
+ msgBox = QMessageBox()
1513
+ msgBox.setIcon(QMessageBox.Warning)
1514
+ msgBox.setText(str(result))
1515
+ msgBox.setWindowTitle("Warning")
1516
+ msgBox.setStandardButtons(QMessageBox.Ok)
1517
+ _ = msgBox.exec()
1518
+ return
1519
+
1520
+ if result:
1521
+ logger.info("Launching the napari viewer with tracks...")
1522
+ try:
1523
+ launch_napari_viewer(**result)
1524
+ logger.info("napari viewer was closed...")
1525
+ except Exception as e:
1526
+ logger.error(f"Failed to launch Napari: {e}")
1527
+ QMessageBox.warning(self, "Error", f"Failed to launch Napari: {e}")
1528
+ else:
1529
+ logger.warning(
1530
+ "napari loading returned None (likely no trajectories found)."
1531
+ )
1532
+ QMessageBox.warning(
1533
+ self,
1534
+ "Warning",
1535
+ "Could not load tracks. Please ensure trajectories are computed.",
1536
+ )
1537
+
1538
+ self.napari_loader.finished_with_result.connect(on_finished)
1539
+ self.napari_loader.start()
1540
+
1541
+ def view_table_ui(self):
1542
+ from celldetective.gui.tableUI import TableUI
1543
+
1544
+ logger.info("Load table...")
1545
+ self.load_available_tables()
1546
+
1547
+ if self.df is not None:
1548
+ plot_mode = "plot_track_signals"
1549
+ if "TRACK_ID" not in list(self.df.columns):
1550
+ plot_mode = "static"
1551
+ self.tab_ui = TableUI(
1552
+ self.df,
1553
+ f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}",
1554
+ population=self.mode,
1555
+ plot_mode=plot_mode,
1556
+ save_inplace_option=True,
1557
+ )
1558
+ self.tab_ui.show()
1559
+ center_window(self.tab_ui)
1560
+ else:
1561
+ logger.info("Table could not be loaded...")
1562
+ msgBox = QMessageBox()
1563
+ msgBox.setIcon(QMessageBox.Warning)
1564
+ msgBox.setText("No table could be loaded...")
1565
+ msgBox.setWindowTitle("Info")
1566
+ msgBox.setStandardButtons(QMessageBox.Ok)
1567
+ returnValue = msgBox.exec()
1568
+ if returnValue == QMessageBox.Ok:
1569
+ return None
1570
+
1571
+ def load_available_tables(self):
1572
+ """
1573
+ Load the tables of the selected wells/positions from the control Panel for the population of interest
1574
+
1575
+ """
1576
+
1577
+ self.well_option = self.parent_window.well_list.getSelectedIndices()
1578
+ self.position_option = self.parent_window.position_list.getSelectedIndices()
1579
+
1580
+ self.df, self.df_pos_info = load_experiment_tables(
1581
+ self.exp_dir,
1582
+ well_option=self.well_option,
1583
+ position_option=self.position_option,
1584
+ population=self.mode,
1585
+ return_pos_info=True,
1586
+ )
1587
+ self.signals = []
1588
+ if self.df is not None:
1589
+ self.signals = list(self.df.columns)
1590
+ if self.df is None:
1591
+ logger.info("No table could be found for the selected position(s)...")
1592
+
1593
+ def set_cellpose_scale(self):
1594
+
1595
+ scale = (
1596
+ self.parent_window.PxToUm
1597
+ * float(self.diamWidget.diameter_le.get_threshold())
1598
+ / 30.0
1599
+ )
1600
+ if self.model_name == "CP_nuclei":
1601
+ scale = (
1602
+ self.parent_window.PxToUm
1603
+ * float(self.diamWidget.diameter_le.get_threshold())
1604
+ / 17.0
1605
+ )
1606
+ flow_thresh = self.diamWidget.flow_slider.value()
1607
+ cellprob_thresh = self.diamWidget.cellprob_slider.value()
1608
+ model_complete_path = locate_segmentation_model(self.model_name)
1609
+ input_config_path = model_complete_path + "config_input.json"
1610
+ new_channels = [
1611
+ self.diamWidget.cellpose_channel_cb[i].currentText() for i in range(2)
1612
+ ]
1613
+ with open(input_config_path) as config_file:
1614
+ input_config = json.load(config_file)
1615
+
1616
+ input_config["spatial_calibration"] = scale
1617
+ input_config["channels"] = new_channels
1618
+ input_config["flow_threshold"] = flow_thresh
1619
+ input_config["cellprob_threshold"] = cellprob_thresh
1620
+ with open(input_config_path, "w") as f:
1621
+ json.dump(input_config, f, indent=4)
1622
+
1623
+ self.cellpose_calibrated = True
1624
+ logger.info(f"model scale automatically computed: {scale}")
1625
+ self.diamWidget.close()
1626
+ self.process_population()
1627
+
1628
+ def set_stardist_scale(self):
1629
+
1630
+ model_complete_path = locate_segmentation_model(self.model_name)
1631
+ input_config_path = model_complete_path + "config_input.json"
1632
+ new_channels = [
1633
+ self.diamWidget.stardist_channel_cb[i].currentText()
1634
+ for i in range(len(self.diamWidget.stardist_channel_cb))
1635
+ ]
1636
+ with open(input_config_path) as config_file:
1637
+ input_config = json.load(config_file)
1638
+
1639
+ input_config["channels"] = new_channels
1640
+ with open(input_config_path, "w") as f:
1641
+ json.dump(input_config, f, indent=4)
1642
+
1643
+ self.stardist_calibrated = True
1644
+ self.diamWidget.close()
1645
+ self.process_population()
1646
+
1647
+ def set_selected_channels_for_segmentation(self):
1648
+
1649
+ model_complete_path = locate_segmentation_model(self.model_name)
1650
+ input_config_path = model_complete_path + "config_input.json"
1651
+ new_channels = [
1652
+ self.segChannelWidget.channel_cbs[i].currentText()
1653
+ for i in range(len(self.segChannelWidget.channel_cbs))
1654
+ ]
1655
+ target_cell_size = None
1656
+ if hasattr(self.segChannelWidget, "diameter_le"):
1657
+ target_cell_size = float(self.segChannelWidget.diameter_le.get_threshold())
1658
+
1659
+ with open(input_config_path) as config_file:
1660
+ input_config = json.load(config_file)
1661
+
1662
+ input_config.update(
1663
+ {"selected_channels": new_channels, "target_cell_size_um": target_cell_size}
1664
+ )
1665
+
1666
+ # input_config['channels'] = new_channels
1667
+ with open(input_config_path, "w") as f:
1668
+ json.dump(input_config, f, indent=4)
1669
+
1670
+ self.segChannelsSet = True
1671
+ self.segChannelWidget.close()
1672
+ self.process_population()
1673
+
1674
+ def set_selected_signals_for_event_detection(self):
1675
+ self.signal_model_name = self.signal_models[
1676
+ self.signal_models_list.currentIndex()
1677
+ ]
1678
+ model_complete_path = locate_signal_model(self.signal_model_name)
1679
+ input_config_path = model_complete_path + "config_input.json"
1680
+ new_channels = [
1681
+ self.signalChannelWidget.channel_cbs[i].currentText()
1682
+ for i in range(len(self.signalChannelWidget.channel_cbs))
1683
+ ]
1684
+ with open(input_config_path) as config_file:
1685
+ input_config = json.load(config_file)
1686
+
1687
+ input_config.update({"selected_channels": new_channels})
1688
+
1689
+ # input_config['channels'] = new_channels
1690
+ with open(input_config_path, "w") as f:
1691
+ json.dump(input_config, f, indent=4)
1692
+
1693
+ self.signalChannelsSet = True
1694
+ self.signalChannelWidget.close()
1695
+ self.process_population()