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,425 +1,555 @@
1
1
  import gc
2
2
  import json
3
3
  import os
4
+ import threading
5
+ import time
4
6
  from glob import glob
5
7
  from subprocess import Popen, check_output
6
8
 
7
- from PyQt5.QtCore import QUrl, Qt
9
+ from PyQt5.QtCore import QUrl, Qt, QThread
8
10
  from PyQt5.QtGui import QDesktopServices, QIntValidator
9
- from PyQt5.QtWidgets import QAction, QApplication, QCheckBox, QDialog, QFileDialog, QHBoxLayout, QLabel, QLineEdit, \
10
- QMenu, QPushButton, QVBoxLayout
11
+ from PyQt5.QtWidgets import (
12
+ QAction,
13
+ QApplication,
14
+ QCheckBox,
15
+ QDialog,
16
+ QFileDialog,
17
+ QHBoxLayout,
18
+ QLabel,
19
+ QLineEdit,
20
+ QMenu,
21
+ QPushButton,
22
+ QMessageBox,
23
+ QVBoxLayout,
24
+ )
11
25
  from fonticon_mdi6 import MDI6
12
26
  from psutil import cpu_count
13
27
  from superqt.fonticon import icon
28
+ from celldetective.gui.base.components import (
29
+ CelldetectiveWidget,
30
+ CelldetectiveMainWindow,
31
+ generic_message,
32
+ )
33
+ from celldetective.gui.base.utils import center_window, pretty_table
34
+ from celldetective.log_manager import get_logger
14
35
 
15
- from celldetective.gui import ConfigNewExperiment, ControlPanel, CelldetectiveMainWindow, CelldetectiveWidget
16
- from celldetective.gui.about import AboutWidget
17
- from celldetective.gui.gui_utils import center_window, generic_message
18
- from celldetective.gui.processes.downloader import DownloadProcess
19
- from celldetective.gui.workers import ProgressWindow
20
- from celldetective.io import correct_annotation, extract_well_name_and_number
21
- from celldetective.utils import download_zenodo_file, pretty_table
36
+ logger = get_logger("celldetective")
22
37
 
23
38
 
24
- class AppInitWindow(CelldetectiveMainWindow):
39
+ class BackgroundLoader(QThread):
40
+ def run(self):
41
+ logger.info("Loading background packages...")
42
+ try:
43
+ from celldetective.gui.control_panel import ControlPanel
44
+
45
+ self.ControlPanel = ControlPanel
46
+ from celldetective.gui.about import AboutWidget
47
+
48
+ self.AboutWidget = AboutWidget
49
+ from celldetective.processes.downloader import DownloadProcess
50
+
51
+ self.DownloadProcess = DownloadProcess
52
+ from celldetective.gui.configure_new_exp import ConfigNewExperiment
25
53
 
26
- """
27
- Initial window to set the experiment folder or create a new one.
28
- """
29
-
30
- def __init__(self, parent_window=None, software_location=None):
31
-
32
- super().__init__()
33
-
34
- self.parent_window = parent_window
35
- self.setWindowTitle("celldetective")
36
-
37
- self.n_threads = min([1, cpu_count()])
38
-
39
- try:
40
- check_output('nvidia-smi')
41
- print('NVIDIA GPU detected (activate or disable in Memory & Threads)...')
42
- self.use_gpu = True
43
- except Exception: # this command not being found can raise quite a few different errors depending on the configuration
44
- print('No NVIDIA GPU detected...')
45
- self.use_gpu = False
46
-
47
- self.soft_path = software_location
48
- self.onlyInt = QIntValidator()
49
-
50
- self._createActions()
51
- self._createMenuBar()
52
-
53
- app = QApplication.instance()
54
- self.screen = app.primaryScreen()
55
- self.geometry = self.screen.availableGeometry()
56
- self.screen_width, self.screen_height = self.geometry.getRect()[-2:]
57
-
58
- central_widget = CelldetectiveWidget()
59
- self.vertical_layout = QVBoxLayout(central_widget)
60
- self.vertical_layout.setContentsMargins(15, 15, 15, 15)
61
- self.vertical_layout.addWidget(QLabel("Experiment folder:"))
62
- self.create_locate_exp_hbox()
63
- self.create_buttons_hbox()
64
- self.setCentralWidget(central_widget)
65
- self.reload_previous_gpu_threads()
66
- center_window(self)
67
-
68
- self.show()
69
-
70
- def closeEvent(self, event):
71
-
72
- QApplication.closeAllWindows()
73
- event.accept()
74
- gc.collect()
75
-
76
- def create_locate_exp_hbox(self):
77
-
78
- self.locate_exp_layout = QHBoxLayout()
79
- self.locate_exp_layout.setContentsMargins(0, 5, 0, 0)
80
- self.experiment_path_selection = QLineEdit()
81
- self.experiment_path_selection.setAlignment(Qt.AlignLeft)
82
- self.experiment_path_selection.setEnabled(True)
83
- self.experiment_path_selection.setDragEnabled(True)
84
- self.experiment_path_selection.setFixedWidth(430)
85
- self.experiment_path_selection.textChanged[str].connect(self.check_path_and_enable_opening)
86
- try:
87
- self.foldername = os.getcwd()
88
- except FileNotFoundError as e:
89
- self.foldername = ""
90
- self.experiment_path_selection.setPlaceholderText('/path/to/experiment/folder/')
91
- self.locate_exp_layout.addWidget(self.experiment_path_selection, 90)
92
-
93
- self.browse_button = QPushButton("Browse...")
94
- self.browse_button.clicked.connect(self.browse_experiment_folder)
95
- self.browse_button.setStyleSheet(self.button_style_sheet)
96
- self.browse_button.setIcon(icon(MDI6.folder, color="white"))
97
- self.locate_exp_layout.addWidget(self.browse_button, 10)
98
- self.vertical_layout.addLayout(self.locate_exp_layout)
99
-
100
-
101
- def _createMenuBar(self):
102
-
103
- menuBar = self.menuBar()
104
- menuBar.clear()
105
- # Creating menus using a QMenu object
106
-
107
- fileMenu = QMenu("File", self)
108
- fileMenu.clear()
109
- fileMenu.addAction(self.newExpAction)
110
- fileMenu.addAction(self.openAction)
111
-
112
- fileMenu.addMenu(self.OpenRecentAction)
113
- self.OpenRecentAction.clear()
114
- if len(self.recentFileActs)>0:
115
- for i in range(len(self.recentFileActs)):
116
- self.OpenRecentAction.addAction(self.recentFileActs[i])
117
-
118
- fileMenu.addMenu(self.openDemo)
119
- self.openDemo.addAction(self.openSpreadingAssayDemo)
120
- self.openDemo.addAction(self.openCytotoxicityAssayDemo)
121
-
122
- fileMenu.addAction(self.openModels)
123
- fileMenu.addSeparator()
124
- fileMenu.addAction(self.exitAction)
125
- menuBar.addMenu(fileMenu)
126
-
127
- OptionsMenu = QMenu("Options", self)
128
- OptionsMenu.addAction(self.MemoryAndThreadsAction)
129
- menuBar.addMenu(OptionsMenu)
130
-
131
- PluginsMenu = QMenu("Plugins", self)
132
- PluginsMenu.addAction(self.CorrectAnnotationAction)
133
- menuBar.addMenu(PluginsMenu)
134
-
135
- helpMenu = QMenu("Help", self)
136
- helpMenu.clear()
137
- helpMenu.addAction(self.DocumentationAction)
138
- #helpMenu.addAction(self.SoftwareAction)
139
- helpMenu.addSeparator()
140
- helpMenu.addAction(self.AboutAction)
141
- menuBar.addMenu(helpMenu)
142
-
143
- #editMenu = menuBar.addMenu("&Edit")
144
- #helpMenu = menuBar.addMenu("&Help")
145
-
146
- def _createActions(self):
147
- # Creating action using the first constructor
148
- #self.newAction = QAction(self)
149
- #self.newAction.setText("&New")
150
- # Creating actions using the second constructor
151
- self.openAction = QAction('Open Project', self)
152
- self.openAction.setShortcut("Ctrl+O")
153
- self.openAction.setShortcutVisibleInContextMenu(True)
154
-
155
- self.openDemo = QMenu('Open Demo')
156
- self.openSpreadingAssayDemo = QAction('Spreading Assay Demo', self)
157
- self.openCytotoxicityAssayDemo = QAction('Cytotoxicity Assay Demo', self)
158
-
159
- self.MemoryAndThreadsAction = QAction('Threads')
160
-
161
- self.CorrectAnnotationAction = QAction('Correct a segmentation annotation')
162
-
163
- self.newExpAction = QAction('New', self)
164
- self.newExpAction.setShortcut("Ctrl+N")
165
- self.newExpAction.setShortcutVisibleInContextMenu(True)
166
- self.exitAction = QAction('Exit', self)
167
-
168
- self.openModels = QAction('Open Models Location')
169
- self.openModels.setShortcut("Ctrl+L")
170
- self.openModels.setShortcutVisibleInContextMenu(True)
171
-
172
- self.OpenRecentAction = QMenu('Open Recent Project')
173
- self.reload_previous_experiments()
174
-
175
- self.DocumentationAction = QAction("Documentation", self)
176
- self.DocumentationAction.setShortcut("Ctrl+D")
177
- self.DocumentationAction.setShortcutVisibleInContextMenu(True)
178
-
179
- #self.SoftwareAction = QAction("Software", self) #1st arg icon(MDI6.information)
180
- self.AboutAction = QAction("About celldetective", self)
181
-
182
- #self.DocumentationAction.triggered.connect(self.load_previous_config)
183
- self.openAction.triggered.connect(self.open_experiment)
184
- self.newExpAction.triggered.connect(self.create_new_experiment)
185
- self.exitAction.triggered.connect(self.close)
186
- self.openModels.triggered.connect(self.open_models_folder)
187
- self.AboutAction.triggered.connect(self.open_about_window)
188
- self.MemoryAndThreadsAction.triggered.connect(self.set_memory_and_threads)
189
- self.CorrectAnnotationAction.triggered.connect(self.correct_seg_annotation)
190
- self.DocumentationAction.triggered.connect(self.open_documentation)
191
-
192
- self.openSpreadingAssayDemo.triggered.connect(self.download_spreading_assay_demo)
193
- self.openCytotoxicityAssayDemo.triggered.connect(self.download_cytotoxicity_assay_demo)
194
-
195
- def download_spreading_assay_demo(self):
196
-
197
- self.target_dir = str(QFileDialog.getExistingDirectory(self, 'Select Folder for Download'))
198
- if self.target_dir=='':
199
- return None
200
-
201
- if not os.path.exists(os.sep.join([self.target_dir,'demo_ricm'])):
202
- self.output_dir = self.target_dir
203
- self.file = 'demo_ricm'
204
- process_args = {"output_dir": self.output_dir, "file": self.file}
205
- self.job = ProgressWindow(DownloadProcess, parent_window=self, title="Download", position_info=False, process_args=process_args)
206
- result = self.job.exec_()
207
- if result == QDialog.Accepted:
208
- pass
209
- elif result == QDialog.Rejected:
210
- return None
211
- #download_zenodo_file('demo_ricm', self.target_dir)
212
- self.experiment_path_selection.setText(os.sep.join([self.target_dir, 'demo_ricm']))
213
- self.validate_button.click()
214
-
215
- def download_cytotoxicity_assay_demo(self):
216
-
217
- self.target_dir = str(QFileDialog.getExistingDirectory(self, 'Select Folder for Download'))
218
- if self.target_dir=='':
219
- return None
220
-
221
- if not os.path.exists(os.sep.join([self.target_dir,'demo_adcc'])):
222
- download_zenodo_file('demo_adcc', self.target_dir)
223
- self.experiment_path_selection.setText(os.sep.join([self.target_dir, 'demo_adcc']))
224
- self.validate_button.click()
225
-
226
- def reload_previous_gpu_threads(self):
227
-
228
- self.recentFileActs = []
229
- self.threads_config_path = os.sep.join([self.soft_path,'celldetective','threads.json'])
230
- print('Reading previous Memory & Threads settings...')
231
- if os.path.exists(self.threads_config_path):
232
- with open(self.threads_config_path, 'r') as f:
233
- self.threads_config = json.load(f)
234
- if 'use_gpu' in self.threads_config:
235
- self.use_gpu = bool(self.threads_config['use_gpu'])
236
- print(f'Use GPU: {self.use_gpu}...')
237
- if 'n_threads' in self.threads_config:
238
- self.n_threads = int(self.threads_config['n_threads'])
239
- print(f'Number of threads: {self.n_threads}...')
240
-
241
-
242
- def reload_previous_experiments(self):
243
-
244
- recentExps = []
245
- self.recentFileActs = []
246
- if os.path.exists(os.sep.join([self.soft_path,'celldetective','recent.txt'])):
247
- recentExps = open(os.sep.join([self.soft_path,'celldetective','recent.txt']), 'r')
248
- recentExps = recentExps.readlines()
249
- recentExps = [r.strip() for r in recentExps]
250
- recentExps.reverse()
251
- recentExps = list(dict.fromkeys(recentExps))
252
- self.recentFileActs = [QAction(r,self) for r in recentExps]
253
- for r in self.recentFileActs:
254
- r.triggered.connect(lambda checked, item=r: self.load_recent_exp(item.text()))
255
-
256
- def correct_seg_annotation(self):
257
-
258
- self.filename,_ = QFileDialog.getOpenFileName(self,"Open Image", "/home/", "TIF Files (*.tif)")
259
- if self.filename!='':
260
- print('Opening ',self.filename,' in napari...')
261
- correct_annotation(self.filename)
262
- else:
263
- return None
264
-
265
- def set_memory_and_threads(self):
266
-
267
- print('setting memory and threads')
268
-
269
- self.ThreadsWidget = CelldetectiveWidget()
270
- self.ThreadsWidget.setWindowTitle("Threads")
271
- layout = QVBoxLayout()
272
- self.ThreadsWidget.setLayout(layout)
273
-
274
- self.threads_le = QLineEdit(str(self.n_threads))
275
- self.threads_le.setValidator(self.onlyInt)
276
-
277
- hbox = QHBoxLayout()
278
- hbox.addWidget(QLabel('Parallel threads: '), 33)
279
- hbox.addWidget(self.threads_le, 66)
280
- layout.addLayout(hbox)
281
-
282
- self.use_gpu_checkbox = QCheckBox()
283
- hbox2 = QHBoxLayout()
284
- hbox2.addWidget(QLabel('Use GPU: '), 33)
285
- hbox2.addWidget(self.use_gpu_checkbox, 66)
286
- layout.addLayout(hbox2)
287
- if self.use_gpu:
288
- self.use_gpu_checkbox.setChecked(True)
289
-
290
- self.validateThreadBtn = QPushButton('Submit')
291
- self.validateThreadBtn.setStyleSheet(self.button_style_sheet)
292
- self.validateThreadBtn.clicked.connect(self.set_threads)
293
- layout.addWidget(self.validateThreadBtn)
294
- center_window(self.ThreadsWidget)
295
- self.ThreadsWidget.show()
296
-
297
- def set_threads(self):
298
- self.n_threads = int(self.threads_le.text())
299
- self.use_gpu = bool(self.use_gpu_checkbox.isChecked())
300
- dico = {"use_gpu": self.use_gpu, "n_threads": self.n_threads}
301
- with open(self.threads_config_path, 'w') as f:
302
- json.dump(dico, f, indent=4)
303
- self.ThreadsWidget.close()
304
-
305
-
306
- def open_experiment(self):
307
-
308
- self.browse_experiment_folder()
309
- if self.experiment_path_selection.text()!='':
310
- self.open_directory()
311
-
312
- def load_recent_exp(self, path):
313
-
314
- self.experiment_path_selection.setText(path)
315
- print(f'Attempt to load experiment folder: {path}...')
316
- self.open_directory()
317
-
318
- def open_about_window(self):
319
- self.about_wdw = AboutWidget()
320
- self.about_wdw.show()
321
-
322
- def open_documentation(self):
323
- doc_url = QUrl('https://celldetective.readthedocs.io/')
324
- QDesktopServices.openUrl(doc_url)
325
-
326
- def open_models_folder(self):
327
-
328
- path = os.sep.join([self.soft_path,'celldetective','models',os.sep])
329
- try:
330
- Popen(f'explorer {os.path.realpath(path)}')
331
- except:
332
-
333
- try:
334
- os.system('xdg-open "%s"' % path)
335
- except:
336
- return None
337
-
338
- def create_buttons_hbox(self):
339
-
340
- self.buttons_layout = QHBoxLayout()
341
- self.buttons_layout.setContentsMargins(30,15,30,5)
342
- self.new_exp_button = QPushButton("New")
343
- self.new_exp_button.clicked.connect(self.create_new_experiment)
344
- self.new_exp_button.setStyleSheet(self.button_style_sheet_2)
345
- self.buttons_layout.addWidget(self.new_exp_button, 50)
346
-
347
- self.validate_button = QPushButton("Open")
348
- self.validate_button.clicked.connect(self.open_directory)
349
- self.validate_button.setStyleSheet(self.button_style_sheet)
350
- self.validate_button.setEnabled(False)
351
- self.validate_button.setShortcut("Return")
352
- self.buttons_layout.addWidget(self.validate_button, 50)
353
- self.vertical_layout.addLayout(self.buttons_layout)
354
-
355
- def check_path_and_enable_opening(self):
356
-
357
- """
358
- Enable 'Open' button if the text is a valid path.
359
- """
360
-
361
- text = self.experiment_path_selection.text()
362
- if (os.path.exists(text)) and os.path.exists(os.sep.join([text,"config.ini"])):
363
- self.validate_button.setEnabled(True)
364
- else:
365
- self.validate_button.setEnabled(False)
366
-
367
-
368
- def set_experiment_path(self, path):
369
- self.experiment_path_selection.setText(path)
370
-
371
- def create_new_experiment(self):
372
-
373
- print("Configuring new experiment...")
374
- self.new_exp_window = ConfigNewExperiment(self)
375
- self.new_exp_window.show()
376
-
377
- def open_directory(self):
378
-
379
- self.exp_dir = self.experiment_path_selection.text().replace('/', os.sep)
380
- print(f"Setting current directory to {self.exp_dir}...")
381
-
382
- wells = glob(os.sep.join([self.exp_dir,"W*"]))
383
- self.number_of_wells = len(wells)
384
- if self.number_of_wells==0:
385
- generic_message("No well was found in the experiment folder.\nPlease respect the W*/ nomenclature...", msg_type="critical")
386
- return None
387
- else:
388
- if self.number_of_wells==1:
389
- print(f"Found {self.number_of_wells} well...")
390
- elif self.number_of_wells>1:
391
- print(f"Found {self.number_of_wells} wells...")
392
-
393
- number_pos = {}
394
- for w in wells:
395
- well_name, well_nbr = extract_well_name_and_number(w)
396
- position_folders = glob(os.sep.join([w,f"{well_nbr}*", os.sep]))
397
- number_pos.update({well_name: len(position_folders)})
398
- print(f"Number of positions per well:")
399
- pretty_table(number_pos)
400
-
401
- with open(os.sep.join([self.soft_path,'celldetective','recent.txt']), 'a+') as f:
402
- f.write(self.exp_dir+'\n')
403
-
404
- self.control_panel = ControlPanel(self, self.exp_dir)
405
- self.control_panel.show()
406
-
407
- self.reload_previous_experiments()
408
- self._createMenuBar()
409
-
410
-
411
- def browse_experiment_folder(self):
412
-
413
- """
414
- Locate an experiment folder. If no configuration file is in the experiment, display a warning.
415
- """
416
-
417
- self.foldername = str(QFileDialog.getExistingDirectory(self, 'Select directory'))
418
- if self.foldername!='':
419
- self.experiment_path_selection.setText(self.foldername)
420
- else:
421
- return None
422
- if not os.path.exists(os.sep.join([self.foldername,"config.ini"])):
423
- generic_message("No configuration can be found in the selected folder...", msg_type="warning")
424
- self.experiment_path_selection.setText('')
425
- return None
54
+ self.ConfigNewExperiment = ConfigNewExperiment
55
+ import pandas
56
+ import matplotlib.pyplot
57
+ import scipy.ndimage
58
+ import tifffile
59
+ import numpy
60
+ except Exception:
61
+ logger.error("Background packages not loaded...")
62
+ logger.info("Background packages loaded...")
63
+
64
+
65
+ class AppInitWindow(CelldetectiveMainWindow):
66
+ """
67
+ Initial window to set the experiment folder or create a new one.
68
+ """
69
+
70
+ def __init__(self, parent_window=None, software_location=None):
71
+
72
+ super().__init__()
73
+
74
+ self.parent_window = parent_window
75
+ self.setWindowTitle("celldetective")
76
+
77
+ self.n_threads = min([1, cpu_count()])
78
+
79
+ self.use_gpu = False
80
+ self.gpu_thread = threading.Thread(target=self.check_gpu)
81
+ self.gpu_thread.start()
82
+
83
+ self.soft_path = software_location
84
+ self.onlyInt = QIntValidator()
85
+
86
+ self._create_actions()
87
+ self._create_menu_bar()
88
+
89
+ app = QApplication.instance()
90
+ self.screen = app.primaryScreen()
91
+ self.geometry = self.screen.availableGeometry()
92
+ self.screen_width, self.screen_height = self.geometry.getRect()[-2:]
93
+
94
+ central_widget = CelldetectiveWidget()
95
+ self.vertical_layout = QVBoxLayout(central_widget)
96
+ self.vertical_layout.setContentsMargins(15, 15, 15, 15)
97
+ self.vertical_layout.addWidget(QLabel("Experiment folder:"))
98
+ self.create_locate_exp_hbox()
99
+ self.create_buttons_hbox()
100
+ self.setCentralWidget(central_widget)
101
+ self.reload_previous_gpu_threads()
102
+ self.adjustSize()
103
+ self.setFixedSize(self.size())
104
+ self.show()
105
+
106
+ self.bg_loader = BackgroundLoader()
107
+ self.bg_loader.start()
108
+
109
+ def closeEvent(self, event):
110
+
111
+ QApplication.closeAllWindows()
112
+ event.accept()
113
+ gc.collect()
114
+
115
+ def check_gpu(self):
116
+ try:
117
+ if os.name == "nt":
118
+ kwargs = {"creationflags": 0x08000000}
119
+ else:
120
+ kwargs = {}
121
+ check_output("nvidia-smi", **kwargs)
122
+ logger.info(
123
+ "NVIDIA GPU detected (activate or disable in Memory & Threads)..."
124
+ )
125
+ self.use_gpu = True
126
+ except Exception as e:
127
+ logger.info(f"No NVIDIA GPU detected: {e}...")
128
+ self.use_gpu = False
129
+
130
+ def create_locate_exp_hbox(self):
131
+
132
+ self.locate_exp_layout = QHBoxLayout()
133
+ self.locate_exp_layout.setContentsMargins(0, 5, 0, 0)
134
+ self.experiment_path_selection = QLineEdit()
135
+ self.experiment_path_selection.setAlignment(Qt.AlignLeft)
136
+ self.experiment_path_selection.setEnabled(True)
137
+ self.experiment_path_selection.setDragEnabled(True)
138
+ self.experiment_path_selection.setFixedWidth(430)
139
+ self.experiment_path_selection.textChanged[str].connect(
140
+ self.check_path_and_enable_opening
141
+ )
142
+ try:
143
+ self.folder_name = os.getcwd()
144
+ except FileNotFoundError as _:
145
+ self.folder_name = ""
146
+ self.experiment_path_selection.setPlaceholderText("/path/to/experiment/folder/")
147
+ self.locate_exp_layout.addWidget(self.experiment_path_selection, 90)
148
+
149
+ self.browse_button = QPushButton("Browse...")
150
+ self.browse_button.clicked.connect(self.browse_experiment_folder)
151
+ self.browse_button.setStyleSheet(self.button_style_sheet)
152
+ self.browse_button.setIcon(icon(MDI6.folder, color="white"))
153
+ self.locate_exp_layout.addWidget(self.browse_button, 10)
154
+ self.vertical_layout.addLayout(self.locate_exp_layout)
155
+
156
+ def _create_menu_bar(self):
157
+
158
+ menu_bar = self.menuBar()
159
+ menu_bar.clear()
160
+ # Creating menus using a QMenu object
161
+
162
+ file_menu = QMenu("File", self)
163
+ file_menu.clear()
164
+ file_menu.addAction(self.new_exp_action)
165
+ file_menu.addAction(self.open_action)
166
+
167
+ file_menu.addMenu(self.OpenRecentAction)
168
+ self.OpenRecentAction.clear()
169
+ if len(self.recent_file_acts) > 0:
170
+ for i in range(len(self.recent_file_acts)):
171
+ self.OpenRecentAction.addAction(self.recent_file_acts[i])
172
+
173
+ file_menu.addMenu(self.openDemo)
174
+ self.openDemo.addAction(self.open_spreading_assay_demo)
175
+ self.openDemo.addAction(self.open_cytotoxicity_assay_demo)
176
+
177
+ file_menu.addAction(self.open_models)
178
+ file_menu.addSeparator()
179
+ file_menu.addAction(self.exit_action)
180
+ menu_bar.addMenu(file_menu)
181
+
182
+ options_menu = QMenu("Options", self)
183
+ options_menu.addAction(self.memory_and_threads_action)
184
+ menu_bar.addMenu(options_menu)
185
+
186
+ plugins_menu = QMenu("Plugins", self)
187
+ plugins_menu.addAction(self.correct_annotation_action)
188
+ menu_bar.addMenu(plugins_menu)
189
+
190
+ help_menu = QMenu("Help", self)
191
+ help_menu.clear()
192
+ help_menu.addAction(self.documentation_action)
193
+ # help_menu.addAction(self.SoftwareAction)
194
+ help_menu.addSeparator()
195
+ help_menu.addAction(self.about_action)
196
+ menu_bar.addMenu(help_menu)
197
+
198
+ # editMenu = menuBar.addMenu("&Edit")
199
+ # help_menu = menuBar.addMenu("&Help")
200
+
201
+ def _create_actions(self):
202
+ # Creating action using the first constructor
203
+ # self.newAction = QAction(self)
204
+ # self.newAction.setText("&New")
205
+ # Creating actions using the second constructor
206
+ self.open_action = QAction("Open Project", self)
207
+ self.open_action.setShortcut("Ctrl+O")
208
+ self.open_action.setShortcutVisibleInContextMenu(True)
209
+
210
+ self.openDemo = QMenu("Open Demo")
211
+ self.open_spreading_assay_demo = QAction("Spreading Assay Demo", self)
212
+ self.open_cytotoxicity_assay_demo = QAction("Cytotoxicity Assay Demo", self)
213
+
214
+ self.memory_and_threads_action = QAction("Threads")
215
+
216
+ self.correct_annotation_action = QAction("Correct a segmentation annotation")
217
+
218
+ self.new_exp_action = QAction("New", self)
219
+ self.new_exp_action.setShortcut("Ctrl+N")
220
+ self.new_exp_action.setShortcutVisibleInContextMenu(True)
221
+ self.exit_action = QAction("Exit", self)
222
+
223
+ self.open_models = QAction("Open Models Location")
224
+ self.open_models.setShortcut("Ctrl+L")
225
+ self.open_models.setShortcutVisibleInContextMenu(True)
226
+
227
+ self.OpenRecentAction = QMenu("Open Recent Project")
228
+ self.reload_previous_experiments()
229
+
230
+ self.documentation_action = QAction("Documentation", self)
231
+ self.documentation_action.setShortcut("Ctrl+D")
232
+ self.documentation_action.setShortcutVisibleInContextMenu(True)
233
+
234
+ # self.SoftwareAction = QAction("Software", self) #1st arg icon(MDI6.information)
235
+ self.about_action = QAction("About celldetective", self)
236
+
237
+ # self.DocumentationAction.triggered.connect(self.load_previous_config)
238
+ self.open_action.triggered.connect(self.open_experiment)
239
+ self.new_exp_action.triggered.connect(self.create_new_experiment)
240
+ self.exit_action.triggered.connect(self.close)
241
+ self.open_models.triggered.connect(self.open_models_folder)
242
+ self.about_action.triggered.connect(self.open_about_window)
243
+ self.memory_and_threads_action.triggered.connect(self.set_memory_and_threads)
244
+ self.correct_annotation_action.triggered.connect(self.correct_seg_annotation)
245
+ self.documentation_action.triggered.connect(self.open_documentation)
246
+
247
+ self.open_spreading_assay_demo.triggered.connect(
248
+ self.download_spreading_assay_demo
249
+ )
250
+ self.open_cytotoxicity_assay_demo.triggered.connect(
251
+ self.download_cytotoxicity_assay_demo
252
+ )
253
+
254
+ def download_spreading_assay_demo(self):
255
+ if self.bg_loader.isFinished() and hasattr(self.bg_loader, "DownloadProcess"):
256
+ DownloadProcess = self.bg_loader.DownloadProcess
257
+ else:
258
+ from celldetective.processes.downloader import DownloadProcess
259
+ from celldetective.gui.workers import ProgressWindow
260
+
261
+ self.target_dir = str(
262
+ QFileDialog.getExistingDirectory(self, "Select Folder for Download")
263
+ )
264
+ if self.target_dir == "":
265
+ return None
266
+
267
+ if not os.path.exists(os.sep.join([self.target_dir, "demo_ricm"])):
268
+ self.output_dir = self.target_dir
269
+ self.file = "demo_ricm"
270
+ process_args = {"output_dir": self.output_dir, "file": self.file}
271
+ self.job = ProgressWindow(
272
+ DownloadProcess,
273
+ parent_window=self,
274
+ title="Download",
275
+ position_info=False,
276
+ process_args=process_args,
277
+ )
278
+ result = self.job.exec_()
279
+ if result == QDialog.Accepted:
280
+ pass
281
+ elif result == QDialog.Rejected:
282
+ return None
283
+ # download_zenodo_file('demo_ricm', self.target_dir)
284
+ self.experiment_path_selection.setText(
285
+ os.sep.join([self.target_dir, "demo_ricm"])
286
+ )
287
+ self.validate_button.click()
288
+
289
+ def download_cytotoxicity_assay_demo(self):
290
+ from celldetective.utils.downloaders import download_zenodo_file
291
+
292
+ self.target_dir = str(
293
+ QFileDialog.getExistingDirectory(self, "Select Folder for Download")
294
+ )
295
+ if self.target_dir == "":
296
+ return None
297
+
298
+ if not os.path.exists(os.sep.join([self.target_dir, "demo_adcc"])):
299
+ download_zenodo_file("demo_adcc", self.target_dir)
300
+ self.experiment_path_selection.setText(
301
+ os.sep.join([self.target_dir, "demo_adcc"])
302
+ )
303
+ self.validate_button.click()
304
+
305
+ def reload_previous_gpu_threads(self):
306
+
307
+ self.recent_file_acts = []
308
+ self.threads_config_path = os.sep.join(
309
+ [self.soft_path, "celldetective", "threads.json"]
310
+ )
311
+ logger.info("Reading previous Memory & Threads settings...")
312
+ if os.path.exists(self.threads_config_path):
313
+ with open(self.threads_config_path, "r") as f:
314
+ self.threads_config = json.load(f)
315
+ if "use_gpu" in self.threads_config:
316
+ self.use_gpu = bool(self.threads_config["use_gpu"])
317
+ logger.info(f"Use GPU: {self.use_gpu}...")
318
+ if "n_threads" in self.threads_config:
319
+ self.n_threads = int(self.threads_config["n_threads"])
320
+ logger.info(f"Number of threads: {self.n_threads}...")
321
+
322
+ def reload_previous_experiments(self):
323
+
324
+ self.recent_file_acts = []
325
+ if os.path.exists(os.sep.join([self.soft_path, "celldetective", "recent.txt"])):
326
+ recent_exps = open(
327
+ os.sep.join([self.soft_path, "celldetective", "recent.txt"]), "r"
328
+ )
329
+ recent_exps = recent_exps.readlines()
330
+ recent_exps = [r.strip() for r in recent_exps]
331
+ recent_exps.reverse()
332
+ recent_exps = list(dict.fromkeys(recent_exps))
333
+ self.recent_file_acts = [QAction(r, self) for r in recent_exps]
334
+ for r in self.recent_file_acts:
335
+ r.triggered.connect(
336
+ lambda checked, item=r: self.load_recent_exp(item.text())
337
+ )
338
+
339
+ def correct_seg_annotation(self):
340
+ from celldetective.napari.utils import correct_annotation
341
+
342
+ self.filename, _ = QFileDialog.getOpenFileName(
343
+ self, "Open Image", "/home/", "TIF Files (*.tif)"
344
+ )
345
+ if self.filename != "":
346
+ logger.info(f"Opening {self.filename} in napari...")
347
+ correct_annotation(self.filename)
348
+ else:
349
+ return None
350
+
351
+ def set_memory_and_threads(self):
352
+
353
+ logger.info("Opening Memory & Threads...")
354
+
355
+ self.threads_widget = CelldetectiveWidget()
356
+ self.threads_widget.setWindowTitle("Threads")
357
+ layout = QVBoxLayout()
358
+ self.threads_widget.setLayout(layout)
359
+
360
+ self.threads_le = QLineEdit(str(self.n_threads))
361
+ self.threads_le.setValidator(self.onlyInt)
362
+
363
+ hbox = QHBoxLayout()
364
+ hbox.addWidget(QLabel("Parallel threads: "), 33)
365
+ hbox.addWidget(self.threads_le, 66)
366
+ layout.addLayout(hbox)
367
+
368
+ self.use_gpu_checkbox = QCheckBox()
369
+ hbox2 = QHBoxLayout()
370
+ hbox2.addWidget(QLabel("Use GPU: "), 33)
371
+ hbox2.addWidget(self.use_gpu_checkbox, 66)
372
+ layout.addLayout(hbox2)
373
+ if self.use_gpu:
374
+ self.use_gpu_checkbox.setChecked(True)
375
+
376
+ self.validate_thread_btn = QPushButton("Submit")
377
+ self.validate_thread_btn.setStyleSheet(self.button_style_sheet)
378
+ self.validate_thread_btn.clicked.connect(self.set_threads)
379
+ layout.addWidget(self.validate_thread_btn)
380
+ self.threads_widget.show()
381
+ center_window(self.threads_widget)
382
+
383
+ def set_threads(self):
384
+ self.n_threads = int(self.threads_le.text())
385
+ self.use_gpu = bool(self.use_gpu_checkbox.isChecked())
386
+ dico = {"use_gpu": self.use_gpu, "n_threads": self.n_threads}
387
+ with open(self.threads_config_path, "w") as f:
388
+ json.dump(dico, f, indent=4)
389
+ self.threads_widget.close()
390
+
391
+ def open_experiment(self):
392
+
393
+ self.browse_experiment_folder()
394
+ if self.experiment_path_selection.text() != "":
395
+ self.open_directory()
396
+
397
+ def load_recent_exp(self, path):
398
+
399
+ self.experiment_path_selection.setText(path)
400
+ logger.info(f"Attempt to load experiment folder: {path}...")
401
+ self.open_directory()
402
+
403
+ def open_about_window(self):
404
+ if self.bg_loader.isFinished() and hasattr(self.bg_loader, "AboutWidget"):
405
+ AboutWidget = self.bg_loader.AboutWidget
406
+ else:
407
+ from celldetective.gui.about import AboutWidget
408
+
409
+ self.about_wdw = AboutWidget()
410
+ self.about_wdw.show()
411
+
412
+ @staticmethod
413
+ def open_documentation():
414
+ doc_url = QUrl("https://celldetective.readthedocs.io/")
415
+ QDesktopServices.openUrl(doc_url)
416
+
417
+ def open_models_folder(self):
418
+
419
+ path = os.sep.join([self.soft_path, "celldetective", "models", os.sep])
420
+ try:
421
+ Popen(f"explorer {os.path.realpath(path)}")
422
+ except Exception as e:
423
+ logger.warning(f"{e}")
424
+ try:
425
+ os.system('xdg-open "%s"' % path)
426
+ except Exception as e:
427
+ logger.error(f"Error {e}...")
428
+ return None
429
+
430
+ def create_buttons_hbox(self):
431
+
432
+ self.buttons_layout = QHBoxLayout()
433
+ self.buttons_layout.setContentsMargins(30, 15, 30, 5)
434
+ self.new_exp_button = QPushButton("New")
435
+ self.new_exp_button.clicked.connect(self.create_new_experiment)
436
+ self.new_exp_button.setStyleSheet(self.button_style_sheet_2)
437
+ self.buttons_layout.addWidget(self.new_exp_button, 50)
438
+
439
+ self.validate_button = QPushButton("Open")
440
+ self.validate_button.clicked.connect(self.open_directory)
441
+ self.validate_button.setStyleSheet(self.button_style_sheet)
442
+ self.validate_button.setEnabled(False)
443
+ self.validate_button.setShortcut("Return")
444
+ self.buttons_layout.addWidget(self.validate_button, 50)
445
+ self.vertical_layout.addLayout(self.buttons_layout)
446
+
447
+ def check_path_and_enable_opening(self):
448
+ """
449
+ Enable 'Open' button if the text is a valid path.
450
+ """
451
+
452
+ text = self.experiment_path_selection.text()
453
+ if (os.path.exists(text)) and os.path.exists(os.sep.join([text, "config.ini"])):
454
+ self.validate_button.setEnabled(True)
455
+ else:
456
+ self.validate_button.setEnabled(False)
457
+
458
+ def set_experiment_path(self, path):
459
+ self.experiment_path_selection.setText(path)
460
+
461
+ def create_new_experiment(self):
462
+
463
+ if self.bg_loader.isFinished() and hasattr(
464
+ self.bg_loader, "ConfigNewExperiment"
465
+ ):
466
+ ConfigNewExperiment = self.bg_loader.ConfigNewExperiment
467
+ else:
468
+ from celldetective.gui.configure_new_exp import ConfigNewExperiment
469
+
470
+ logger.info("Configuring new experiment...")
471
+ self.new_exp_window = ConfigNewExperiment(self)
472
+ self.new_exp_window.show()
473
+ center_window(self.new_exp_window)
474
+
475
+ def open_directory(self):
476
+ self.t_ref = time.time()
477
+
478
+ from celldetective.utils.experiment import extract_well_name_and_number
479
+
480
+ self.exp_dir = self.experiment_path_selection.text().replace("/", os.sep)
481
+ logger.info(f"Setting current directory to {self.exp_dir}...")
482
+
483
+ wells = glob(os.sep.join([self.exp_dir, "W*"]))
484
+ self.number_of_wells = len(wells)
485
+ if self.number_of_wells == 0:
486
+ generic_message(
487
+ "No well was found in the experiment folder.\nPlease respect the W*/ nomenclature...",
488
+ msg_type="critical",
489
+ )
490
+ return None
491
+ else:
492
+ if self.number_of_wells == 1:
493
+ logger.info(f"Found {self.number_of_wells} well...")
494
+ elif self.number_of_wells > 1:
495
+ logger.info(f"Found {self.number_of_wells} wells...")
496
+
497
+ def log_position_stats(wells_list):
498
+ number_pos = {}
499
+ for w in wells_list:
500
+ well_name, well_nbr = extract_well_name_and_number(w)
501
+ position_folders = glob(os.sep.join([w, f"{well_nbr}*", os.sep]))
502
+ number_pos.update({well_name: len(position_folders)})
503
+ logger.info(f"Number of positions per well:")
504
+ pretty_table(number_pos)
505
+
506
+ with open(
507
+ os.sep.join([self.soft_path, "celldetective", "recent.txt"]), "a+"
508
+ ) as f:
509
+ f.write(self.exp_dir + "\n")
510
+
511
+ threading.Thread(
512
+ target=log_position_stats, args=(wells,), daemon=True
513
+ ).start()
514
+
515
+ if self.bg_loader.isFinished() and hasattr(self.bg_loader, "ControlPanel"):
516
+ ControlPanel = self.bg_loader.ControlPanel
517
+ else:
518
+ from celldetective.gui.control_panel import ControlPanel
519
+
520
+ try:
521
+ self.control_panel = ControlPanel(self, self.exp_dir)
522
+ self.control_panel.adjustSize()
523
+ self.control_panel.setFixedSize(self.control_panel.size())
524
+ self.control_panel.show()
525
+ center_window(self.control_panel)
526
+ except (AssertionError, FileNotFoundError) as e:
527
+ QMessageBox.critical(
528
+ self,
529
+ "Error Loading Experiment",
530
+ f"Could not load experiment configuration.\n\nError: {str(e)}\n\nPlease ensure 'config.ini' exists in the selected folder.",
531
+ )
532
+ return
533
+
534
+ self.reload_previous_experiments()
535
+ self._create_menu_bar()
536
+
537
+ def browse_experiment_folder(self):
538
+ """
539
+ Locate an experiment folder. If no configuration file is in the experiment, display a warning.
540
+ """
541
+
542
+ self.folder_name = str(
543
+ QFileDialog.getExistingDirectory(self, "Select directory")
544
+ )
545
+ if self.folder_name != "":
546
+ self.experiment_path_selection.setText(self.folder_name)
547
+ else:
548
+ return None
549
+ if not os.path.exists(os.sep.join([self.folder_name, "config.ini"])):
550
+ generic_message(
551
+ "No configuration can be found in the selected folder...",
552
+ msg_type="warning",
553
+ )
554
+ self.experiment_path_selection.setText("")
555
+ return None