celldetective 1.4.1.post1__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 +642 -554
  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 -1700
  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 +1332 -1011
  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.1.post1.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.1.post1.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 +425 -144
  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.1.post1.dist-info/RECORD +0 -123
  148. /celldetective/{gui/processes → processes}/downloader.py +0 -0
  149. {celldetective-1.4.1.post1.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
  150. {celldetective-1.4.1.post1.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
  151. {celldetective-1.4.1.post1.dist-info → celldetective-1.5.0b0.dist-info}/top_level.txt +0 -0
@@ -1,8 +1,20 @@
1
- from PyQt5.QtWidgets import QGridLayout, QComboBox, QVBoxLayout, QLabel, QLineEdit, QHBoxLayout, QRadioButton, QFileDialog, QPushButton, QMessageBox
2
- from PyQt5.QtCore import Qt, QSize
3
- from celldetective.gui.gui_utils import center_window, generic_message
4
- from celldetective.gui.layouts import ChannelNormGenerator
5
- from celldetective.gui import ThresholdConfigWizard, CelldetectiveWidget
1
+ from PyQt5.QtWidgets import (
2
+ QGridLayout,
3
+ QComboBox,
4
+ QVBoxLayout,
5
+ QLabel,
6
+ QLineEdit,
7
+ QHBoxLayout,
8
+ QRadioButton,
9
+ QFileDialog,
10
+ QPushButton,
11
+ QMessageBox,
12
+ )
13
+ from PyQt5.QtCore import Qt, QSize, QTimer
14
+ from celldetective.gui.base.components import generic_message
15
+ from celldetective.gui.base.utils import center_window
16
+ from celldetective.gui.base.channel_norm_generator import ChannelNormGenerator
17
+ from celldetective.gui.base.components import CelldetectiveWidget
6
18
  from PyQt5.QtGui import QDoubleValidator
7
19
  from superqt.fonticon import icon
8
20
  from fonticon_mdi6 import MDI6
@@ -11,475 +23,570 @@ from glob import glob
11
23
  import os
12
24
  import json
13
25
  import shutil
14
- from celldetective.gui import Styles
15
26
  import gc
16
- from cellpose.models import CellposeModel
17
27
 
18
28
 
19
29
  class SegmentationModelLoader(CelldetectiveWidget):
20
-
21
- """
22
- Upload a segmentation model or define a Threshold pipeline.
23
- """
24
-
25
- def __init__(self, parent_window):
26
-
27
- super().__init__()
28
- self.parent_window = parent_window
29
- self.mode = self.parent_window.mode
30
- self.target_folder = f"segmentation_{self.mode}"
31
- self.setWindowTitle('Upload model')
32
- self.generate_content()
33
- center_window(self)
34
-
35
- def generate_content(self):
36
-
37
- self.layout = QGridLayout(self)
38
- self.layout.addWidget(QLabel('Select:'), 0, 0, 1, 1)
39
- self.layout.setSpacing(5)
40
-
41
- option_layout = QHBoxLayout()
42
- option_layout.setContentsMargins(10,10,10,10)
43
-
44
- # Create radio buttons
45
- self.stardist_button = QRadioButton('StarDist')
46
- #self.stardist_button.setIcon(QIcon(abs_path+f"/icons/star.png"))
47
-
48
- self.cellpose_button = QRadioButton('Cellpose')
49
- #self.cellpose_button.setIcon(QIcon(abs_path+f"/icons/cellpose.png"))
50
-
51
- self.threshold_button = QRadioButton('Threshold')
52
-
53
- option_layout.addWidget(self.threshold_button)
54
- option_layout.addWidget(self.stardist_button)
55
- option_layout.addWidget(self.cellpose_button)
56
-
57
- self.layout.addLayout(option_layout, 1,0,1,2, alignment=Qt.AlignCenter)
58
- self.generate_base_block()
59
- self.layout.addLayout(self.base_block, 2,0,1,2)
60
- # self.combos = self.channel_layout.channel_cbs
61
- # self.cb_labels = self.channel_layout.channel_labels
62
-
63
- self.generate_cellpose_options()
64
- self.layout.addLayout(self.cellpose_block, 3,0,1,2)
65
- self.generate_threshold_options()
66
-
67
- # Create file dialog
68
- self.file_dialog = QFileDialog()
69
-
70
- # Create button to open file dialog
71
- self.open_dialog_button = QPushButton('Choose File')
72
- self.open_dialog_button.clicked.connect(self.showDialog)
73
- self.file_label = QLabel('No file chosen', self)
74
- self.layout.addWidget(self.open_dialog_button, 9, 0, 1, 1)
75
- self.layout.addWidget(self.file_label, 9, 1, 1, 1)
76
-
77
-
78
- self.merge_lbl = QLabel('Merging option: ')
79
- self.merge_cb = QComboBox()
80
- self.merge_cb.addItems(['OR'])
81
- merge_hbox = QHBoxLayout()
82
- merge_hbox.addWidget(self.merge_lbl, 33)
83
- merge_hbox.addWidget(self.merge_cb, 66)
84
- self.layout.addLayout(merge_hbox, 10, 0, 1, 2)
85
- self.merge_lbl.hide()
86
- self.merge_cb.hide()
87
-
88
- self.upload_button = QPushButton("Upload")
89
- self.upload_button.clicked.connect(self.upload_model)
90
- self.upload_button.setIcon(icon(MDI6.upload,color="white"))
91
- self.upload_button.setIconSize(QSize(25, 25))
92
- self.upload_button.setStyleSheet(self.button_style_sheet)
93
- self.upload_button.setEnabled(False)
94
- self.layout.addWidget(self.upload_button, 11, 0, 1, 1)
95
-
96
- self.base_block_options = [self.calibration_label, self.spatial_calib_le,*self.channel_layout.channel_cbs,*self.channel_layout.channel_labels,*self.channel_layout.normalization_mode_btns, *self.channel_layout.normalization_clip_btns, *self.channel_layout.normalization_min_value_lbl,
97
- *self.channel_layout.normalization_min_value_le, *self.channel_layout.normalization_max_value_lbl,*self.channel_layout.normalization_max_value_le,
98
- self.channel_layout.add_col_btn]
99
-
100
- self.stardist_button.toggled.connect(self.show_seg_options)
101
- self.cellpose_button.toggled.connect(self.show_seg_options)
102
- self.threshold_button.toggled.connect(self.show_seg_options)
103
-
104
- for cb in self.channel_layout.channel_cbs:
105
- cb.activated.connect(self.unlock_upload)
106
-
107
- self.setLayout(self.layout)
108
-
109
- self.threshold_button.setChecked(True)
110
- self.show()
111
-
112
- def unlock_upload(self):
113
- if self.stardist_button.isChecked():
114
- if np.any([c.currentText()!='--' for c in self.channel_layout.channel_cbs]):
115
- self.upload_button.setEnabled(True)
116
- else:
117
- self.upload_button.setEnabled(False)
118
- elif self.cellpose_button.isChecked():
119
- if np.any([c.currentText()!='--' for c in self.channel_layout.channel_cbs]):
120
- self.upload_button.setEnabled(True)
121
- else:
122
- self.upload_button.setEnabled(False)
123
-
124
- def generate_base_block(self):
125
-
126
- """
127
- Create widgets common to StarDist and Cellpose.
128
- """
129
-
130
- self.base_block = QVBoxLayout()
131
-
132
- pixel_calib_layout = QHBoxLayout()
133
- self.calibration_label = QLabel("input spatial\ncalibration: ")
134
- self.spatial_calib_le = QLineEdit("0,1")
135
- self.qdv = QDoubleValidator(0.0, np.amax([self.parent_window.parent_window.shape_x, self.parent_window.parent_window.shape_y]), 8, notation=QDoubleValidator.StandardNotation)
136
- self.spatial_calib_le.setValidator(self.qdv)
137
- pixel_calib_layout.addWidget(self.calibration_label, 30)
138
- pixel_calib_layout.addWidget(self.spatial_calib_le, 70)
139
- self.base_block.addLayout(pixel_calib_layout)
140
-
141
- self.channel_layout = ChannelNormGenerator(self, mode='channels',init_n_channels=2)
142
- self.channel_layout.setContentsMargins(0,0,0,0)
143
- self.base_block.addLayout(self.channel_layout)
144
-
145
-
146
- def generate_cellpose_options(self):
147
-
148
- """
149
- Create Cellpose specific fields to use the model properly when calling it from the app.
150
- """
151
-
152
- self.cellpose_block = QVBoxLayout()
153
- self.cp_diameter_label = QLabel('cell\ndiameter: ')
154
- self.cp_diameter_le = QLineEdit("30,0")
155
- self.cp_diameter_le.setValidator(self.qdv)
156
- diam_hbox = QHBoxLayout()
157
- diam_hbox.addWidget(self.cp_diameter_label, 30)
158
- diam_hbox.addWidget(self.cp_diameter_le, 70)
159
- self.cellpose_block.addLayout(diam_hbox)
160
-
161
- qdv_prob = QDoubleValidator(-6, 6, 8, notation=QDoubleValidator.StandardNotation)
162
- self.cp_cellprob_label = QLabel('cellprob\nthreshold: ')
163
- self.cp_cellprob_le = QLineEdit('0,0')
164
- self.cp_cellprob_le.setValidator(qdv_prob)
165
- cellprob_hbox = QHBoxLayout()
166
- cellprob_hbox.addWidget(self.cp_cellprob_label, 30)
167
- cellprob_hbox.addWidget(self.cp_cellprob_le, 70)
168
- self.cellpose_block.addLayout(cellprob_hbox)
169
-
170
- self.cp_flow_label = QLabel('flow threshold: ')
171
- self.cp_flow_le = QLineEdit('0,4')
172
- self.cp_flow_le.setValidator(qdv_prob)
173
- flow_hbox = QHBoxLayout()
174
- flow_hbox.addWidget(self.cp_flow_label, 30)
175
- flow_hbox.addWidget(self.cp_flow_le, 70)
176
- self.cellpose_block.addLayout(flow_hbox)
177
-
178
- self.cellpose_options = [self.cp_diameter_label,self.cp_diameter_le,self.cp_cellprob_label,
179
- self.cp_cellprob_le, self.cp_flow_label, self.cp_flow_le]
180
- for c in self.cellpose_options:
181
- c.hide()
182
-
183
- def generate_threshold_options(self):
184
-
185
- """
186
- Show the threshold config pipeline button.
187
- """
188
-
189
- self.threshold_config_button = QPushButton("Threshold Config Wizard")
190
- self.threshold_config_button.setIcon(icon(MDI6.auto_fix,color="#1565c0"))
191
- self.threshold_config_button.setIconSize(QSize(20, 20))
192
- self.threshold_config_button.setVisible(False)
193
- self.threshold_config_button.setStyleSheet(self.button_style_sheet_2)
194
- self.threshold_config_button.clicked.connect(self.open_threshold_config_wizard)
195
- self.layout.addWidget(self.threshold_config_button,3,0,1,2)
196
- self.threshold_config_button.hide()
197
-
198
- def showDialog(self):
199
-
200
- """
201
- Import the model in the proper folder.
202
- """
203
-
204
- # Set the file dialog according to the option chosen
205
- if self.stardist_button.isChecked():
206
- self.seg_mode = "stardist"
207
- self.file_dialog.setFileMode(QFileDialog.Directory)
208
- elif self.cellpose_button.isChecked():
209
- self.seg_mode = "cellpose"
210
- self.file_dialog.setFileMode(QFileDialog.ExistingFile)
211
- elif self.threshold_button.isChecked():
212
- self.seg_mode = "threshold"
213
- self.file_dialog.setFileMode(QFileDialog.ExistingFile)
214
-
215
- # If accepted check validity of data
216
- if self.seg_mode!='threshold':
217
- if self.file_dialog.exec_() == QFileDialog.Accepted:
218
- self.filename = self.file_dialog.selectedFiles()[0]
219
- if self.seg_mode=="stardist":
220
- subfiles = glob(self.filename+"/*")
221
- subfiles = [s.replace('\\','/') for s in subfiles]
222
- if self.filename+"/thresholds.json" in subfiles:
223
- self.file_label.setText(self.filename.split("/")[-1])
224
- self.modelname = self.filename.split("/")[-1]
225
- self.destination = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]+f"/models/{self.target_folder}/"+self.modelname
226
- self.folder_dest = self.destination
227
- else:
228
- generic_message(
229
- "StarDist model not recognized... Please ensure that it contains a thresholds.json file or that it is a valid StarDist model...")
230
- return None
231
-
232
- if self.seg_mode=="cellpose":
233
- self.file_label.setText(self.filename.split("/")[-1])
234
- self.modelname = self.filename.split("/")[-1]
235
- print(f"Transferring Cellpose model {self.filename}...")
236
- self.folder_dest = os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]+f"/models/{self.target_folder}/"+self.modelname
237
- self.destination = self.folder_dest+f"/{self.modelname}"
238
-
239
- else:
240
- self.filename, _ = QFileDialog.getOpenFileNames(
241
- None,
242
- "Load threshold configuration(s)...",
243
- "",
244
- "Json Configs (*.json)",
245
- )
246
- if self.filename:
247
- n_files = len(self.filename)
248
- print(f'You loaded {n_files} threshold configuration files...')
249
- for i,filename in enumerate(self.filename):
250
- print(f"Config {i}: ",filename,"...")
251
-
252
- if n_files==1:
253
- self.merge_cb.hide()
254
- self.merge_lbl.hide()
255
- self.file_label.setText(self.filename[0].split("/")[-1])
256
- else:
257
- generic_message(
258
- "You selected more than one pipeline. Please set a merging procedure for the resulting masks...")
259
- self.merge_cb.show()
260
- self.merge_lbl.show()
261
- self.file_label.setText(f"{n_files} configs loaded...")
262
-
263
- def show_seg_options(self):
264
-
265
- """
266
- Show the relevant widgets, mask the others.
267
- """
268
-
269
- self.filename = None
270
- self.file_label.setText('No file chosen')
271
- self.base_block_options = [self.calibration_label, self.spatial_calib_le,*self.channel_layout.channel_cbs,*self.channel_layout.channel_labels,*self.channel_layout.normalization_mode_btns, *self.channel_layout.normalization_clip_btns, *self.channel_layout.normalization_min_value_lbl,
272
- *self.channel_layout.normalization_min_value_le, *self.channel_layout.normalization_max_value_lbl,*self.channel_layout.normalization_max_value_le,
273
- self.channel_layout.add_col_btn]
274
-
275
- if self.cellpose_button.isChecked():
276
- self.spatial_calib_le.setToolTip('Cellpose rescales the images such that the cells are 30.0 pixels. You can compute the scale from the training data as\n(pixel calibration [µm] in training images)*(cell diameter [px] in training images)/(30 [px]).\nIf you pass images with a different calibration to the model, they will be rescaled automatically.\nThe rescaling is ignored if you pass a diameter different from 30 px below.')
277
- self.channel_layout.channel_labels[0].setText('cyto: ')
278
- self.channel_layout.channel_labels[1].setText('nuclei: ')
279
- for c in [self.threshold_config_button, self.merge_lbl, self.merge_cb]:
280
- c.hide()
281
- for c in self.cellpose_options+self.base_block_options:
282
- c.show()
283
- self.unlock_upload()
284
- elif self.stardist_button.isChecked():
285
- self.spatial_calib_le.setToolTip('')
286
- self.channel_layout.channel_labels[0].setText('channel 1: ')
287
- self.channel_layout.channel_labels[1].setText('channel 2: ')
288
- for c in self.base_block_options:
289
- c.show()
290
- for c in self.cellpose_options+[self.threshold_config_button, self.merge_lbl, self.merge_cb]:
291
- c.hide()
292
- self.unlock_upload()
293
- else:
294
- for c in self.base_block_options + self.cellpose_options:
295
- c.hide()
296
- self.threshold_config_button.show()
297
- self.upload_button.setEnabled(True)
298
-
299
- self.resize(self.sizeHint());
300
- #self.adjustSize()
301
-
302
- def upload_model(self):
303
-
304
- """
305
- Upload the model.
306
- """
307
-
308
- channels = []
309
- for i in range(len(self.channel_layout.channel_cbs)):
310
- channels.append(self.channel_layout.channel_cbs[i].currentText())
311
-
312
- if self.file_label.text()=='No file chosen':
313
- generic_message('Please select a model first...')
314
- return None
315
-
316
- if not self.threshold_button.isChecked():
317
-
318
- if self.stardist_button.isChecked():
319
-
320
- try:
321
- shutil.copytree(self.filename, self.destination)
322
- except FileExistsError:
323
- generic_message("A model with the same name already exists in the models folder. Please rename it.")
324
- return None
325
-
326
- elif self.cellpose_button.isChecked():
327
-
328
- try:
329
- if not os.path.exists(self.folder_dest):
330
- os.mkdir(self.folder_dest)
331
- shutil.copy(self.filename, self.destination)
332
- except FileExistsError:
333
- generic_message("A model with the same name already exists in the models folder. Please rename it.")
334
- return None
335
- try:
336
- model = CellposeModel(pretrained_model=self.destination, model_type=None, nchan=len(channels))
337
- self.scale_model = model.diam_mean
338
- print(f'{self.scale_model=}')
339
- del model
340
- gc.collect()
341
- except Exception as e:
342
- print(e)
343
- msgBox = QMessageBox()
344
- msgBox.setIcon(QMessageBox.Critical)
345
- msgBox.setText(f"Cellpose model could not be loaded...")
346
- msgBox.setWindowTitle("Error")
347
- msgBox.setStandardButtons(QMessageBox.Ok)
348
- returnValue = msgBox.exec()
349
- if returnValue == QMessageBox.Ok:
350
- return None
351
-
352
- self.generate_input_config()
353
- self.parent_window.init_seg_model_list()
354
- self.close()
355
- else:
356
- if not isinstance(self.filename, list):
357
- if not self.filename is None:
358
- self.filename = [self.filename]
359
-
360
- idx = self.parent_window.parent_window.populations.index(self.mode)
361
- self.parent_window.threshold_configs[idx] = self.filename
362
- self.parent_window.seg_model_list.setCurrentText('Threshold')
363
- self.close()
364
-
365
- def generate_input_config(self):
366
-
367
- """
368
- Check the ticked options and input parameters to create
369
- a configuration to use the uploaded model properly.
370
- """
371
-
372
- if self.threshold_button.isChecked():
373
- model_type = "threshold"
374
- return None
375
-
376
- channels = []
377
- for i in range(len(self.channel_layout.channel_cbs)):
378
- channels.append(self.channel_layout.channel_cbs[i].currentText())
379
-
380
- slots_to_keep = np.where(np.array(channels)!='--')[0]
381
- while '--' in channels:
382
- channels.remove('--')
383
-
384
- norm_values = np.array([[float(a.replace(',','.')),float(b.replace(',','.'))] for a,b in zip([l.text() for l in self.channel_layout.normalization_min_value_le],
385
- [l.text() for l in self.channel_layout.normalization_max_value_le])])
386
- norm_values = norm_values[slots_to_keep]
387
- norm_values = [list(v) for v in norm_values]
388
-
389
- clip_values = np.array(self.channel_layout.clip_option)
390
- clip_values = list(clip_values[slots_to_keep])
391
- clip_values = [bool(c) for c in clip_values]
392
-
393
- normalization_mode = np.array(self.channel_layout.normalization_mode)
394
- normalization_mode = list(normalization_mode[slots_to_keep])
395
- normalization_mode = [bool(m) for m in normalization_mode]
396
-
397
- dico = {}
398
-
399
- # Check model option
400
- if self.stardist_button.isChecked():
401
-
402
- # Get channels
403
- # channels = []
404
- # for c in self.combos:
405
- # if c.currentText()!="--":
406
- # channels.append(c.currentText())
407
- model_type = "stardist"
408
- spatial_calib = float(self.spatial_calib_le.text().replace(',','.'))
409
- #normalize = self.normalize_checkbox.isChecked()
410
- dico.update({"channels": channels,
411
- #"normalize": normalize,
412
- "spatial_calibration": spatial_calib,
413
- 'normalization_percentile': normalization_mode,
414
- 'normalization_clip': clip_values,
415
- 'normalization_values': norm_values,
416
- })
417
-
418
- elif self.cellpose_button.isChecked():
419
-
420
- # Get channels (cyto and nucleus)
421
- # channels = []
422
- # for c in self.combos[:2]:
423
- # if c.currentText()!="--":
424
- # channels.append(c.currentText())
425
-
426
- model_type = "cellpose"
427
- #cellpose requires at least two channels
428
- if len(channels)==1:
429
- channels += ['None']
430
- normalization_mode += [True]
431
- clip_values += [True]
432
- norm_values += [[1,99]]
433
-
434
- diameter = float(self.cp_diameter_le.text().replace(',','.'))
435
- cellprob_threshold = float(self.cp_cellprob_le.text().replace(',','.'))
436
- flow_threshold = float(self.cp_flow_le.text().replace(',','.'))
437
- #normalize = self.normalize_checkbox.isChecked()
438
- spatial_calib = float(self.spatial_calib_le.text().replace(',','.'))
439
- # assume 30 px diameter in cellpose model
440
- spatial_calib = spatial_calib * diameter / self.scale_model
441
- diameter = 30.0
442
-
443
- dico.update({"channels": channels,
444
- "diameter": diameter,
445
- "cellprob_threshold": cellprob_threshold,
446
- "flow_threshold": flow_threshold,
447
- #"normalize": normalize,
448
- "spatial_calibration": spatial_calib,
449
- 'normalization_percentile': normalization_mode,
450
- 'normalization_clip': clip_values,
451
- 'normalization_values': norm_values,
452
- })
453
-
454
-
455
- dico.update({"model_type": model_type})
456
- print(f"{dico=}")
457
- json_object = json.dumps(dico, indent=4)
458
-
459
- # Writing to sample.json
460
- # if os.path.exists(self.folder_dest):
461
-
462
- # msgBox = QMessageBox()
463
- # msgBox.setIcon(QMessageBox.Warning)
464
- # msgBox.setText("A model with the same name already exists. Do you want to replace it?")
465
- # msgBox.setWindowTitle("Confirm")
466
- # msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
467
- # returnValue = msgBox.exec()
468
- # if returnValue == QMessageBox.No:
469
- # return None
470
- # elif returnValue == QMessageBox.Yes:
471
- # shutil.rmtree(self.folder_dest)
472
- #os.mkdir(self.folder_dest)
473
-
474
- print("Configuration successfully written in ",self.folder_dest+os.sep+"config_input.json")
475
- with open(self.folder_dest+os.sep+"config_input.json", "w") as outfile:
476
- outfile.write(json_object)
477
-
478
- def open_threshold_config_wizard(self):
479
-
480
- self.parent_window.parent_window.locate_image()
481
- if self.parent_window.parent_window.current_stack is None:
482
- return None
483
- else:
484
- self.ThreshWizard = ThresholdConfigWizard(self)
485
- self.ThreshWizard.show()
30
+ """
31
+ Upload a segmentation model or define a Threshold pipeline.
32
+ """
33
+
34
+ def __init__(self, parent_window):
35
+
36
+ super().__init__()
37
+ self.parent_window = parent_window
38
+ self.mode = self.parent_window.mode
39
+ self.target_folder = f"segmentation_{self.mode}"
40
+ self.setWindowTitle("Upload model")
41
+ self.generate_content()
42
+
43
+ def generate_content(self):
44
+
45
+ self.layout = QGridLayout(self)
46
+ self.layout.addWidget(QLabel("Select:"), 0, 0, 1, 1)
47
+ self.layout.setSpacing(5)
48
+
49
+ option_layout = QHBoxLayout()
50
+ option_layout.setContentsMargins(10, 10, 10, 10)
51
+
52
+ # Create radio buttons
53
+ self.stardist_button = QRadioButton("StarDist")
54
+ # self.stardist_button.setIcon(QIcon(abs_path+f"/icons/star.png"))
55
+
56
+ self.cellpose_button = QRadioButton("Cellpose")
57
+ # self.cellpose_button.setIcon(QIcon(abs_path+f"/icons/cellpose_utils.png"))
58
+
59
+ self.threshold_button = QRadioButton("Threshold")
60
+
61
+ option_layout.addWidget(self.threshold_button)
62
+ option_layout.addWidget(self.stardist_button)
63
+ option_layout.addWidget(self.cellpose_button)
64
+
65
+ self.layout.addLayout(option_layout, 1, 0, 1, 2, alignment=Qt.AlignCenter)
66
+ self.generate_base_block()
67
+ self.layout.addLayout(self.base_block, 2, 0, 1, 2)
68
+ # self.combos = self.channel_layout.channel_cbs
69
+ # self.cb_labels = self.channel_layout.channel_labels
70
+
71
+ self.generate_cellpose_options()
72
+ self.layout.addLayout(self.cellpose_block, 3, 0, 1, 2)
73
+ self.generate_threshold_options()
74
+
75
+ # Create file dialog
76
+ self.file_dialog = QFileDialog()
77
+
78
+ # Create button to open file dialog
79
+ self.open_dialog_button = QPushButton("Choose File")
80
+ self.open_dialog_button.clicked.connect(self.showDialog)
81
+ self.file_label = QLabel("No file chosen", self)
82
+ self.layout.addWidget(self.open_dialog_button, 9, 0, 1, 1)
83
+ self.layout.addWidget(self.file_label, 9, 1, 1, 1)
84
+
85
+ self.merge_lbl = QLabel("Merging option: ")
86
+ self.merge_cb = QComboBox()
87
+ self.merge_cb.addItems(["OR"])
88
+ merge_hbox = QHBoxLayout()
89
+ merge_hbox.addWidget(self.merge_lbl, 33)
90
+ merge_hbox.addWidget(self.merge_cb, 66)
91
+ self.layout.addLayout(merge_hbox, 10, 0, 1, 2)
92
+ self.merge_lbl.hide()
93
+ self.merge_cb.hide()
94
+
95
+ self.upload_button = QPushButton("Upload")
96
+ self.upload_button.clicked.connect(self.upload_model)
97
+ self.upload_button.setIcon(icon(MDI6.upload, color="white"))
98
+ self.upload_button.setIconSize(QSize(25, 25))
99
+ self.upload_button.setStyleSheet(self.button_style_sheet)
100
+ self.upload_button.setEnabled(False)
101
+ self.layout.addWidget(self.upload_button, 11, 0, 1, 1)
102
+
103
+ self.base_block_options = [
104
+ self.calibration_label,
105
+ self.spatial_calib_le,
106
+ *self.channel_layout.channel_cbs,
107
+ *self.channel_layout.channel_labels,
108
+ *self.channel_layout.normalization_mode_btns,
109
+ *self.channel_layout.normalization_clip_btns,
110
+ *self.channel_layout.normalization_min_value_lbl,
111
+ *self.channel_layout.normalization_min_value_le,
112
+ *self.channel_layout.normalization_max_value_lbl,
113
+ *self.channel_layout.normalization_max_value_le,
114
+ self.channel_layout.add_col_btn,
115
+ ]
116
+
117
+ self.stardist_button.toggled.connect(self.show_seg_options)
118
+ self.cellpose_button.toggled.connect(self.show_seg_options)
119
+ self.threshold_button.toggled.connect(self.show_seg_options)
120
+
121
+ for cb in self.channel_layout.channel_cbs:
122
+ cb.activated.connect(self.unlock_upload)
123
+
124
+ self.setLayout(self.layout)
125
+
126
+ self.threshold_button.setChecked(True)
127
+ self.show()
128
+
129
+ def unlock_upload(self):
130
+ if self.stardist_button.isChecked():
131
+ if np.any(
132
+ [c.currentText() != "--" for c in self.channel_layout.channel_cbs]
133
+ ):
134
+ self.upload_button.setEnabled(True)
135
+ else:
136
+ self.upload_button.setEnabled(False)
137
+ elif self.cellpose_button.isChecked():
138
+ if np.any(
139
+ [c.currentText() != "--" for c in self.channel_layout.channel_cbs]
140
+ ):
141
+ self.upload_button.setEnabled(True)
142
+ else:
143
+ self.upload_button.setEnabled(False)
144
+
145
+ def generate_base_block(self):
146
+ """
147
+ Create widgets common to StarDist and Cellpose.
148
+ """
149
+
150
+ self.base_block = QVBoxLayout()
151
+
152
+ pixel_calib_layout = QHBoxLayout()
153
+ self.calibration_label = QLabel("input spatial\ncalibration: ")
154
+ self.spatial_calib_le = QLineEdit("0,1")
155
+ self.qdv = QDoubleValidator(
156
+ 0.0,
157
+ np.amax(
158
+ [
159
+ self.parent_window.parent_window.shape_x,
160
+ self.parent_window.parent_window.shape_y,
161
+ ]
162
+ ),
163
+ 8,
164
+ notation=QDoubleValidator.StandardNotation,
165
+ )
166
+ self.spatial_calib_le.setValidator(self.qdv)
167
+ pixel_calib_layout.addWidget(self.calibration_label, 30)
168
+ pixel_calib_layout.addWidget(self.spatial_calib_le, 70)
169
+ self.base_block.addLayout(pixel_calib_layout)
170
+
171
+ self.channel_layout = ChannelNormGenerator(
172
+ self, mode="channels", init_n_channels=2
173
+ )
174
+ self.channel_layout.setContentsMargins(0, 0, 0, 0)
175
+ self.base_block.addLayout(self.channel_layout)
176
+
177
+ def generate_cellpose_options(self):
178
+ """
179
+ Create Cellpose specific fields to use the model properly when calling it from the app.
180
+ """
181
+
182
+ self.cellpose_block = QVBoxLayout()
183
+ self.cp_diameter_label = QLabel("cell\ndiameter: ")
184
+ self.cp_diameter_le = QLineEdit("30,0")
185
+ self.cp_diameter_le.setValidator(self.qdv)
186
+ diam_hbox = QHBoxLayout()
187
+ diam_hbox.addWidget(self.cp_diameter_label, 30)
188
+ diam_hbox.addWidget(self.cp_diameter_le, 70)
189
+ self.cellpose_block.addLayout(diam_hbox)
190
+
191
+ qdv_prob = QDoubleValidator(
192
+ -6, 6, 8, notation=QDoubleValidator.StandardNotation
193
+ )
194
+ self.cp_cellprob_label = QLabel("cellprob\nthreshold: ")
195
+ self.cp_cellprob_le = QLineEdit("0,0")
196
+ self.cp_cellprob_le.setValidator(qdv_prob)
197
+ cellprob_hbox = QHBoxLayout()
198
+ cellprob_hbox.addWidget(self.cp_cellprob_label, 30)
199
+ cellprob_hbox.addWidget(self.cp_cellprob_le, 70)
200
+ self.cellpose_block.addLayout(cellprob_hbox)
201
+
202
+ self.cp_flow_label = QLabel("flow threshold: ")
203
+ self.cp_flow_le = QLineEdit("0,4")
204
+ self.cp_flow_le.setValidator(qdv_prob)
205
+ flow_hbox = QHBoxLayout()
206
+ flow_hbox.addWidget(self.cp_flow_label, 30)
207
+ flow_hbox.addWidget(self.cp_flow_le, 70)
208
+ self.cellpose_block.addLayout(flow_hbox)
209
+
210
+ self.cellpose_options = [
211
+ self.cp_diameter_label,
212
+ self.cp_diameter_le,
213
+ self.cp_cellprob_label,
214
+ self.cp_cellprob_le,
215
+ self.cp_flow_label,
216
+ self.cp_flow_le,
217
+ ]
218
+ for c in self.cellpose_options:
219
+ c.hide()
220
+
221
+ def generate_threshold_options(self):
222
+ """
223
+ Show the threshold config pipeline button.
224
+ """
225
+
226
+ self.threshold_config_button = QPushButton("Threshold Config Wizard")
227
+ self.threshold_config_button.setIcon(icon(MDI6.auto_fix, color="#1565c0"))
228
+ self.threshold_config_button.setIconSize(QSize(20, 20))
229
+ self.threshold_config_button.setVisible(False)
230
+ self.threshold_config_button.setStyleSheet(self.button_style_sheet_2)
231
+ self.threshold_config_button.clicked.connect(self.open_threshold_config_wizard)
232
+ self.layout.addWidget(self.threshold_config_button, 3, 0, 1, 2)
233
+ self.threshold_config_button.hide()
234
+
235
+ def showDialog(self):
236
+ """
237
+ Import the model in the proper folder.
238
+ """
239
+
240
+ # Set the file dialog according to the option chosen
241
+ if self.stardist_button.isChecked():
242
+ self.seg_mode = "stardist"
243
+ self.file_dialog.setFileMode(QFileDialog.Directory)
244
+ elif self.cellpose_button.isChecked():
245
+ self.seg_mode = "cellpose"
246
+ self.file_dialog.setFileMode(QFileDialog.ExistingFile)
247
+ elif self.threshold_button.isChecked():
248
+ self.seg_mode = "threshold"
249
+ self.file_dialog.setFileMode(QFileDialog.ExistingFile)
250
+
251
+ # If accepted check validity of data
252
+ if self.seg_mode != "threshold":
253
+ if self.file_dialog.exec_() == QFileDialog.Accepted:
254
+ self.filename = self.file_dialog.selectedFiles()[0]
255
+ if self.seg_mode == "stardist":
256
+ subfiles = glob(self.filename + "/*")
257
+ subfiles = [s.replace("\\", "/") for s in subfiles]
258
+ if self.filename + "/thresholds.json" in subfiles:
259
+ self.file_label.setText(self.filename.split("/")[-1])
260
+ self.modelname = self.filename.split("/")[-1]
261
+ self.destination = (
262
+ os.path.split(os.path.dirname(os.path.realpath(__file__)))[
263
+ 0
264
+ ]
265
+ + f"/models/{self.target_folder}/"
266
+ + self.modelname
267
+ )
268
+ self.folder_dest = self.destination
269
+ else:
270
+ generic_message(
271
+ "StarDist model not recognized... Please ensure that it contains a thresholds.json file or that it is a valid StarDist model..."
272
+ )
273
+ return None
274
+
275
+ if self.seg_mode == "cellpose":
276
+ self.file_label.setText(self.filename.split("/")[-1])
277
+ self.modelname = self.filename.split("/")[-1]
278
+ print(f"Transferring Cellpose model {self.filename}...")
279
+ self.folder_dest = (
280
+ os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]
281
+ + f"/models/{self.target_folder}/"
282
+ + self.modelname
283
+ )
284
+ self.destination = self.folder_dest + f"/{self.modelname}"
285
+
286
+ else:
287
+ start_dir = self.parent_window.exp_dir
288
+ if os.path.isdir(os.path.join(start_dir, "configs")):
289
+ start_dir = os.path.join(start_dir, "configs")
290
+
291
+ self.filename, _ = QFileDialog.getOpenFileNames(
292
+ None,
293
+ "Load threshold configuration(s)...",
294
+ start_dir,
295
+ "Json Configs (*.json)",
296
+ )
297
+ if self.filename:
298
+ n_files = len(self.filename)
299
+ print(f"You loaded {n_files} threshold configuration files...")
300
+ for i, filename in enumerate(self.filename):
301
+ print(f"Config {i}: ", filename, "...")
302
+
303
+ if n_files == 1:
304
+ self.merge_cb.hide()
305
+ self.merge_lbl.hide()
306
+ self.file_label.setText(self.filename[0].split("/")[-1])
307
+ else:
308
+ generic_message(
309
+ "You selected more than one pipeline. Please set a merging procedure for the resulting masks..."
310
+ )
311
+ self.merge_cb.show()
312
+ self.merge_lbl.show()
313
+ self.file_label.setText(f"{n_files} configs loaded...")
314
+
315
+ def show_seg_options(self):
316
+ """
317
+ Show the relevant widgets, mask the others.
318
+ """
319
+
320
+ self.filename = None
321
+ self.file_label.setText("No file chosen")
322
+ self.base_block_options = [
323
+ self.calibration_label,
324
+ self.spatial_calib_le,
325
+ *self.channel_layout.channel_cbs,
326
+ *self.channel_layout.channel_labels,
327
+ *self.channel_layout.normalization_mode_btns,
328
+ *self.channel_layout.normalization_clip_btns,
329
+ *self.channel_layout.normalization_min_value_lbl,
330
+ *self.channel_layout.normalization_min_value_le,
331
+ *self.channel_layout.normalization_max_value_lbl,
332
+ *self.channel_layout.normalization_max_value_le,
333
+ self.channel_layout.add_col_btn,
334
+ ]
335
+
336
+ if self.cellpose_button.isChecked():
337
+ self.spatial_calib_le.setToolTip(
338
+ "Cellpose rescales the images such that the cells are 30.0 pixels. You can compute the scale from the training data as\n(pixel calibration [µm] in training images)*(cell diameter [px] in training images)/(30 [px]).\nIf you pass images with a different calibration to the model, they will be rescaled automatically.\nThe rescaling is ignored if you pass a diameter different from 30 px below."
339
+ )
340
+ self.channel_layout.channel_labels[0].setText("cyto: ")
341
+ self.channel_layout.channel_labels[1].setText("nuclei: ")
342
+ for c in [self.threshold_config_button, self.merge_lbl, self.merge_cb]:
343
+ c.hide()
344
+ for c in self.cellpose_options + self.base_block_options:
345
+ c.show()
346
+ self.unlock_upload()
347
+ elif self.stardist_button.isChecked():
348
+ self.spatial_calib_le.setToolTip("")
349
+ self.channel_layout.channel_labels[0].setText("channel 1: ")
350
+ self.channel_layout.channel_labels[1].setText("channel 2: ")
351
+ for c in self.base_block_options:
352
+ c.show()
353
+ for c in self.cellpose_options + [
354
+ self.threshold_config_button,
355
+ self.merge_lbl,
356
+ self.merge_cb,
357
+ ]:
358
+ c.hide()
359
+ self.unlock_upload()
360
+ else:
361
+ for c in self.base_block_options + self.cellpose_options:
362
+ c.hide()
363
+ self.threshold_config_button.show()
364
+ self.upload_button.setEnabled(True)
365
+
366
+ self.resize(self.sizeHint())
367
+ # self.adjustSize()
368
+
369
+ def upload_model(self):
370
+ """
371
+ Upload the model.
372
+ """
373
+
374
+ channels = []
375
+ for i in range(len(self.channel_layout.channel_cbs)):
376
+ channels.append(self.channel_layout.channel_cbs[i].currentText())
377
+
378
+ if self.file_label.text() == "No file chosen":
379
+ generic_message("Please select a model first...")
380
+ return None
381
+
382
+ if not self.threshold_button.isChecked():
383
+
384
+ if self.stardist_button.isChecked():
385
+
386
+ try:
387
+ shutil.copytree(self.filename, self.destination)
388
+ except FileExistsError:
389
+ generic_message(
390
+ "A model with the same name already exists in the models folder. Please rename it."
391
+ )
392
+ return None
393
+
394
+ elif self.cellpose_button.isChecked():
395
+
396
+ try:
397
+ if not os.path.exists(self.folder_dest):
398
+ os.mkdir(self.folder_dest)
399
+ shutil.copy(self.filename, self.destination)
400
+ except FileExistsError:
401
+ generic_message(
402
+ "A model with the same name already exists in the models folder. Please rename it."
403
+ )
404
+ return None
405
+ try:
406
+ from cellpose.models import CellposeModel
407
+
408
+ model = CellposeModel(
409
+ pretrained_model=self.destination,
410
+ model_type=None,
411
+ nchan=len(channels),
412
+ )
413
+ self.scale_model = model.diam_mean
414
+ print(f"{self.scale_model=}")
415
+ del model
416
+ gc.collect()
417
+ except Exception as e:
418
+ print(e)
419
+ msgBox = QMessageBox()
420
+ msgBox.setIcon(QMessageBox.Critical)
421
+ msgBox.setText(f"Cellpose model could not be loaded...")
422
+ msgBox.setWindowTitle("Error")
423
+ msgBox.setStandardButtons(QMessageBox.Ok)
424
+ returnValue = msgBox.exec()
425
+ if returnValue == QMessageBox.Ok:
426
+ return None
427
+
428
+ self.generate_input_config()
429
+ self.parent_window.init_seg_model_list()
430
+ self.close()
431
+ else:
432
+ if not isinstance(self.filename, list):
433
+ if not self.filename is None:
434
+ self.filename = [self.filename]
435
+
436
+ idx = self.parent_window.parent_window.populations.index(self.mode)
437
+ self.parent_window.threshold_configs[idx] = self.filename
438
+ self.parent_window.seg_model_list.setCurrentText("Threshold")
439
+ self.close()
440
+
441
+ def generate_input_config(self):
442
+ """
443
+ Check the ticked options and input parameters to create
444
+ a configuration to use the uploaded model properly.
445
+ """
446
+
447
+ if self.threshold_button.isChecked():
448
+ model_type = "threshold"
449
+ return None
450
+
451
+ channels = []
452
+ for i in range(len(self.channel_layout.channel_cbs)):
453
+ channels.append(self.channel_layout.channel_cbs[i].currentText())
454
+
455
+ slots_to_keep = np.where(np.array(channels) != "--")[0]
456
+ while "--" in channels:
457
+ channels.remove("--")
458
+
459
+ norm_values = np.array(
460
+ [
461
+ [float(a.replace(",", ".")), float(b.replace(",", "."))]
462
+ for a, b in zip(
463
+ [l.text() for l in self.channel_layout.normalization_min_value_le],
464
+ [l.text() for l in self.channel_layout.normalization_max_value_le],
465
+ )
466
+ ]
467
+ )
468
+ norm_values = norm_values[slots_to_keep]
469
+ norm_values = [list(v) for v in norm_values]
470
+
471
+ clip_values = np.array(self.channel_layout.clip_option)
472
+ clip_values = list(clip_values[slots_to_keep])
473
+ clip_values = [bool(c) for c in clip_values]
474
+
475
+ normalization_mode = np.array(self.channel_layout.normalization_mode)
476
+ normalization_mode = list(normalization_mode[slots_to_keep])
477
+ normalization_mode = [bool(m) for m in normalization_mode]
478
+
479
+ dico = {}
480
+
481
+ # Check model option
482
+ if self.stardist_button.isChecked():
483
+
484
+ # Get channels
485
+ # channels = []
486
+ # for c in self.combos:
487
+ # if c.currentText()!="--":
488
+ # channels.append(c.currentText())
489
+ model_type = "stardist"
490
+ spatial_calib = float(self.spatial_calib_le.text().replace(",", "."))
491
+ # normalize = self.normalize_checkbox.isChecked()
492
+ dico.update(
493
+ {
494
+ "channels": channels,
495
+ # "normalize": normalize,
496
+ "spatial_calibration": spatial_calib,
497
+ "normalization_percentile": normalization_mode,
498
+ "normalization_clip": clip_values,
499
+ "normalization_values": norm_values,
500
+ }
501
+ )
502
+
503
+ elif self.cellpose_button.isChecked():
504
+
505
+ # Get channels (cyto and nucleus)
506
+ # channels = []
507
+ # for c in self.combos[:2]:
508
+ # if c.currentText()!="--":
509
+ # channels.append(c.currentText())
510
+
511
+ model_type = "cellpose"
512
+ # cellpose_utils requires at least two channels
513
+ if len(channels) == 1:
514
+ channels += ["None"]
515
+ normalization_mode += [True]
516
+ clip_values += [True]
517
+ norm_values += [[1, 99]]
518
+
519
+ diameter = float(self.cp_diameter_le.text().replace(",", "."))
520
+ cellprob_threshold = float(self.cp_cellprob_le.text().replace(",", "."))
521
+ flow_threshold = float(self.cp_flow_le.text().replace(",", "."))
522
+ # normalize = self.normalize_checkbox.isChecked()
523
+ spatial_calib = float(self.spatial_calib_le.text().replace(",", "."))
524
+ # assume 30 px diameter in cellpose_utils model
525
+ spatial_calib = spatial_calib * diameter / self.scale_model
526
+ diameter = 30.0
527
+
528
+ dico.update(
529
+ {
530
+ "channels": channels,
531
+ "diameter": diameter,
532
+ "cellprob_threshold": cellprob_threshold,
533
+ "flow_threshold": flow_threshold,
534
+ # "normalize": normalize,
535
+ "spatial_calibration": spatial_calib,
536
+ "normalization_percentile": normalization_mode,
537
+ "normalization_clip": clip_values,
538
+ "normalization_values": norm_values,
539
+ }
540
+ )
541
+
542
+ dico.update({"model_type": model_type})
543
+ print(f"{dico=}")
544
+ json_object = json.dumps(dico, indent=4)
545
+
546
+ # Writing to sample.json
547
+ # if os.path.exists(self.folder_dest):
548
+
549
+ # msgBox = QMessageBox()
550
+ # msgBox.setIcon(QMessageBox.Warning)
551
+ # msgBox.setText("A model with the same name already exists. Do you want to replace it?")
552
+ # msgBox.setWindowTitle("Confirm")
553
+ # msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
554
+ # returnValue = msgBox.exec()
555
+ # if returnValue == QMessageBox.No:
556
+ # return None
557
+ # elif returnValue == QMessageBox.Yes:
558
+ # shutil.rmtree(self.folder_dest)
559
+ # os.mkdir(self.folder_dest)
560
+
561
+ print(
562
+ "Configuration successfully written in ",
563
+ self.folder_dest + os.sep + "config_input.json",
564
+ )
565
+ with open(self.folder_dest + os.sep + "config_input.json", "w") as outfile:
566
+ outfile.write(json_object)
567
+
568
+ def open_threshold_config_wizard(self):
569
+
570
+ self.parent_window.parent_window.locate_image()
571
+ if self.parent_window.parent_window.current_stack is None:
572
+ return None
573
+ else:
574
+ from celldetective.gui.thresholds_gui import ThresholdConfigWizard
575
+
576
+ self.thresh_wizard = ThresholdConfigWizard(self)
577
+ self.thresh_wizard.show()
578
+
579
+ def safe_resize():
580
+ try:
581
+ self.thresh_wizard.resize(
582
+ self.thresh_wizard.width() + 1,
583
+ self.thresh_wizard.height() + 1,
584
+ )
585
+ except RuntimeError:
586
+ pass
587
+
588
+ try:
589
+ QTimer.singleShot(100, safe_resize)
590
+ center_window(self.thresh_wizard)
591
+ except:
592
+ pass