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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. celldetective/__init__.py +25 -0
  2. celldetective/__main__.py +62 -43
  3. celldetective/_version.py +1 -1
  4. celldetective/extra_properties.py +477 -399
  5. celldetective/filters.py +192 -97
  6. celldetective/gui/InitWindow.py +541 -411
  7. celldetective/gui/__init__.py +0 -15
  8. celldetective/gui/about.py +44 -39
  9. celldetective/gui/analyze_block.py +120 -84
  10. celldetective/gui/base/__init__.py +0 -0
  11. celldetective/gui/base/channel_norm_generator.py +335 -0
  12. celldetective/gui/base/components.py +249 -0
  13. celldetective/gui/base/feature_choice.py +92 -0
  14. celldetective/gui/base/figure_canvas.py +52 -0
  15. celldetective/gui/base/list_widget.py +133 -0
  16. celldetective/gui/{styles.py → base/styles.py} +92 -36
  17. celldetective/gui/base/utils.py +33 -0
  18. celldetective/gui/base_annotator.py +900 -767
  19. celldetective/gui/classifier_widget.py +6 -22
  20. celldetective/gui/configure_new_exp.py +777 -671
  21. celldetective/gui/control_panel.py +635 -524
  22. celldetective/gui/dynamic_progress.py +449 -0
  23. celldetective/gui/event_annotator.py +2023 -1662
  24. celldetective/gui/generic_signal_plot.py +1292 -944
  25. celldetective/gui/gui_utils.py +899 -1289
  26. celldetective/gui/interactions_block.py +658 -0
  27. celldetective/gui/interactive_timeseries_viewer.py +447 -0
  28. celldetective/gui/json_readers.py +48 -15
  29. celldetective/gui/layouts/__init__.py +5 -0
  30. celldetective/gui/layouts/background_model_free_layout.py +537 -0
  31. celldetective/gui/layouts/channel_offset_layout.py +134 -0
  32. celldetective/gui/layouts/local_correction_layout.py +91 -0
  33. celldetective/gui/layouts/model_fit_layout.py +372 -0
  34. celldetective/gui/layouts/operation_layout.py +68 -0
  35. celldetective/gui/layouts/protocol_designer_layout.py +96 -0
  36. celldetective/gui/pair_event_annotator.py +3130 -2435
  37. celldetective/gui/plot_measurements.py +586 -267
  38. celldetective/gui/plot_signals_ui.py +724 -506
  39. celldetective/gui/preprocessing_block.py +395 -0
  40. celldetective/gui/process_block.py +1678 -1831
  41. celldetective/gui/seg_model_loader.py +580 -473
  42. celldetective/gui/settings/__init__.py +0 -7
  43. celldetective/gui/settings/_cellpose_model_params.py +181 -0
  44. celldetective/gui/settings/_event_detection_model_params.py +95 -0
  45. celldetective/gui/settings/_segmentation_model_params.py +159 -0
  46. celldetective/gui/settings/_settings_base.py +77 -65
  47. celldetective/gui/settings/_settings_event_model_training.py +752 -526
  48. celldetective/gui/settings/_settings_measurements.py +1133 -964
  49. celldetective/gui/settings/_settings_neighborhood.py +574 -488
  50. celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
  51. celldetective/gui/settings/_settings_signal_annotator.py +329 -305
  52. celldetective/gui/settings/_settings_tracking.py +1304 -1094
  53. celldetective/gui/settings/_stardist_model_params.py +98 -0
  54. celldetective/gui/survival_ui.py +422 -312
  55. celldetective/gui/tableUI.py +1665 -1701
  56. celldetective/gui/table_ops/_maths.py +295 -0
  57. celldetective/gui/table_ops/_merge_groups.py +140 -0
  58. celldetective/gui/table_ops/_merge_one_hot.py +95 -0
  59. celldetective/gui/table_ops/_query_table.py +43 -0
  60. celldetective/gui/table_ops/_rename_col.py +44 -0
  61. celldetective/gui/thresholds_gui.py +382 -179
  62. celldetective/gui/viewers/__init__.py +0 -0
  63. celldetective/gui/viewers/base_viewer.py +700 -0
  64. celldetective/gui/viewers/channel_offset_viewer.py +331 -0
  65. celldetective/gui/viewers/contour_viewer.py +394 -0
  66. celldetective/gui/viewers/size_viewer.py +153 -0
  67. celldetective/gui/viewers/spot_detection_viewer.py +341 -0
  68. celldetective/gui/viewers/threshold_viewer.py +309 -0
  69. celldetective/gui/workers.py +403 -126
  70. celldetective/log_manager.py +92 -0
  71. celldetective/measure.py +1895 -1478
  72. celldetective/napari/__init__.py +0 -0
  73. celldetective/napari/utils.py +1025 -0
  74. celldetective/neighborhood.py +1914 -1448
  75. celldetective/preprocessing.py +1620 -1220
  76. celldetective/processes/__init__.py +0 -0
  77. celldetective/processes/background_correction.py +271 -0
  78. celldetective/processes/compute_neighborhood.py +894 -0
  79. celldetective/processes/detect_events.py +246 -0
  80. celldetective/processes/downloader.py +137 -0
  81. celldetective/processes/measure_cells.py +565 -0
  82. celldetective/processes/segment_cells.py +760 -0
  83. celldetective/processes/track_cells.py +435 -0
  84. celldetective/processes/train_segmentation_model.py +694 -0
  85. celldetective/processes/train_signal_model.py +265 -0
  86. celldetective/processes/unified_process.py +292 -0
  87. celldetective/regionprops/_regionprops.py +358 -317
  88. celldetective/relative_measurements.py +987 -710
  89. celldetective/scripts/measure_cells.py +313 -212
  90. celldetective/scripts/measure_relative.py +90 -46
  91. celldetective/scripts/segment_cells.py +165 -104
  92. celldetective/scripts/segment_cells_thresholds.py +96 -68
  93. celldetective/scripts/track_cells.py +198 -149
  94. celldetective/scripts/train_segmentation_model.py +324 -201
  95. celldetective/scripts/train_signal_model.py +87 -45
  96. celldetective/segmentation.py +844 -749
  97. celldetective/signals.py +3514 -2861
  98. celldetective/tracking.py +30 -15
  99. celldetective/utils/__init__.py +0 -0
  100. celldetective/utils/cellpose_utils/__init__.py +133 -0
  101. celldetective/utils/color_mappings.py +42 -0
  102. celldetective/utils/data_cleaning.py +630 -0
  103. celldetective/utils/data_loaders.py +450 -0
  104. celldetective/utils/dataset_helpers.py +207 -0
  105. celldetective/utils/downloaders.py +235 -0
  106. celldetective/utils/event_detection/__init__.py +8 -0
  107. celldetective/utils/experiment.py +1782 -0
  108. celldetective/utils/image_augmenters.py +308 -0
  109. celldetective/utils/image_cleaning.py +74 -0
  110. celldetective/utils/image_loaders.py +926 -0
  111. celldetective/utils/image_transforms.py +335 -0
  112. celldetective/utils/io.py +62 -0
  113. celldetective/utils/mask_cleaning.py +348 -0
  114. celldetective/utils/mask_transforms.py +5 -0
  115. celldetective/utils/masks.py +184 -0
  116. celldetective/utils/maths.py +351 -0
  117. celldetective/utils/model_getters.py +325 -0
  118. celldetective/utils/model_loaders.py +296 -0
  119. celldetective/utils/normalization.py +380 -0
  120. celldetective/utils/parsing.py +465 -0
  121. celldetective/utils/plots/__init__.py +0 -0
  122. celldetective/utils/plots/regression.py +53 -0
  123. celldetective/utils/resources.py +34 -0
  124. celldetective/utils/stardist_utils/__init__.py +104 -0
  125. celldetective/utils/stats.py +90 -0
  126. celldetective/utils/types.py +21 -0
  127. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/METADATA +1 -1
  128. celldetective-1.5.0b1.dist-info/RECORD +187 -0
  129. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/WHEEL +1 -1
  130. tests/gui/test_new_project.py +129 -117
  131. tests/gui/test_project.py +127 -79
  132. tests/test_filters.py +39 -15
  133. tests/test_notebooks.py +8 -0
  134. tests/test_tracking.py +232 -13
  135. tests/test_utils.py +123 -77
  136. celldetective/gui/base_components.py +0 -23
  137. celldetective/gui/layouts.py +0 -1602
  138. celldetective/gui/processes/compute_neighborhood.py +0 -594
  139. celldetective/gui/processes/downloader.py +0 -111
  140. celldetective/gui/processes/measure_cells.py +0 -360
  141. celldetective/gui/processes/segment_cells.py +0 -499
  142. celldetective/gui/processes/track_cells.py +0 -303
  143. celldetective/gui/processes/train_segmentation_model.py +0 -270
  144. celldetective/gui/processes/train_signal_model.py +0 -108
  145. celldetective/gui/table_ops/merge_groups.py +0 -118
  146. celldetective/gui/viewers.py +0 -1354
  147. celldetective/io.py +0 -3663
  148. celldetective/utils.py +0 -3108
  149. celldetective-1.4.2.dist-info/RECORD +0 -123
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/entry_points.txt +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
  152. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/top_level.txt +0 -0
@@ -2,11 +2,19 @@
2
2
  Copyright © 2023 Laboratoire Adhesion et Inflammation, Authored by Remy Torro.
3
3
  """
4
4
 
5
- from PyQt5.QtWidgets import QComboBox, QLabel, QRadioButton, QLineEdit, QApplication, QPushButton, QScrollArea, QVBoxLayout, QHBoxLayout
5
+ from PyQt5.QtWidgets import (
6
+ QComboBox,
7
+ QLabel,
8
+ QRadioButton,
9
+ QLineEdit,
10
+ QPushButton,
11
+ QVBoxLayout,
12
+ QHBoxLayout,
13
+ )
6
14
  from PyQt5.QtCore import Qt, QSize
7
- from celldetective.gui.gui_utils import center_window, QHSeperationLine
15
+ from celldetective.gui.base.components import QHSeperationLine
8
16
  from superqt import QLabeledDoubleSlider, QLabeledSlider
9
- from celldetective.utils import extract_experiment_channels, get_software_location
17
+ from celldetective.utils.experiment import extract_experiment_channels
10
18
  import json
11
19
  import numpy as np
12
20
  from superqt.fonticon import icon
@@ -14,306 +22,322 @@ from fonticon_mdi6 import MDI6
14
22
  import os
15
23
  from celldetective.gui.settings._settings_base import CelldetectiveSettingsPanel
16
24
 
25
+
17
26
  class SettingsSignalAnnotator(CelldetectiveSettingsPanel):
18
-
19
- """
20
- UI to set normalization and animation parameters for the annotator tool.
21
-
22
- """
23
-
24
- def __init__(self, parent_window=None):
25
-
26
- self.parent_window = parent_window
27
- self.mode = self.parent_window.mode
28
- self.exp_dir = self.parent_window.exp_dir
29
-
30
- self.instructions_path = self.parent_window.exp_dir + f"configs/signal_annotator_config_{self.mode}.json"
31
- if self.mode == "pairs":
32
- self.instructions_path = self.parent_window.exp_dir + "configs/signal_annotator_config_neighborhood.json"
33
-
34
- self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
35
- self.channel_names = np.array(self.channel_names)
36
- self.channels = np.array(self.channels)
37
- self.log_option = False
38
-
39
- super().__init__(title="Configure signal annotator")
40
-
41
- self._add_to_layout()
42
- self._load_previous_instructions()
43
-
44
- self._adjustSize()
45
- self.resize(int(self.width()), int(self._screen_height * 0.55))
46
-
47
- def _add_to_layout(self):
48
-
49
- sub_layout = QVBoxLayout()
50
- sub_layout.setContentsMargins(10,10,10,20)
51
- sub_layout.setContentsMargins(30,30,30,30)
52
- sub_layout.addWidget(self._modality_lbl)
53
-
54
- # Create radio buttons
55
- option_layout = QHBoxLayout()
56
- option_layout.addWidget(self.gs_btn, alignment=Qt.AlignCenter)
57
- option_layout.addWidget(self.rgb_btn, alignment=Qt.AlignCenter)
58
- sub_layout.addLayout(option_layout)
59
-
60
- btn_hbox = QHBoxLayout()
61
- btn_hbox.addWidget(QLabel(''), 90)
62
- btn_hbox.addWidget(self.log_btn, 5,alignment=Qt.AlignRight)
63
- btn_hbox.addWidget(self.percentile_btn, 5,alignment=Qt.AlignRight)
64
- sub_layout.addLayout(btn_hbox)
65
-
66
- for i in range(3):
67
- hlayout = QHBoxLayout()
68
- hlayout.addWidget(self.channel_cbs_lbls[i], 20)
69
- hlayout.addWidget(self.channel_cbs[i], 80)
70
- sub_layout.addLayout(hlayout)
71
-
72
- hlayout2 = QHBoxLayout()
73
- hlayout2.addWidget(self.min_val_lbls[i], 20)
74
- hlayout2.addWidget(self.min_val_les[i], 80)
75
- sub_layout.addLayout(hlayout2)
76
-
77
- hlayout3 = QHBoxLayout()
78
- hlayout3.addWidget(self.max_val_lbls[i], 20)
79
- hlayout3.addWidget(self.max_val_les[i], 80)
80
- sub_layout.addLayout(hlayout3)
81
-
82
- sub_layout.addWidget(self.hsep)
83
- hbox_frac = QHBoxLayout()
84
- hbox_frac.addWidget(self._fraction_lbl, 20)
85
- hbox_frac.addWidget(self.fraction_slider, 80)
86
- sub_layout.addLayout(hbox_frac)
87
-
88
- hbox_interval = QHBoxLayout()
89
- hbox_interval.addWidget(self._interval_lbl, 20)
90
- hbox_interval.addWidget(self.interval_slider, 80)
91
- sub_layout.addLayout(hbox_interval)
92
-
93
- self._layout.addLayout(sub_layout)
94
-
95
- self._layout.addWidget(self.submit_btn)
96
-
97
- def _create_widgets(self):
98
-
99
- """
100
- Create the widgets.
101
-
102
- """
103
- super()._create_widgets()
104
-
105
- self._modality_lbl = QLabel("Modality: ")
106
- self._fraction_lbl = QLabel("fraction: ")
107
- self._interval_lbl = QLabel('interval [ms]: ')
108
-
109
- self.gs_btn = QRadioButton('grayscale')
110
- self.gs_btn.setChecked(True)
111
-
112
- self.rgb_btn = QRadioButton('RGB')
113
-
114
-
115
- self.percentile_btn = QPushButton()
116
- self.percentile_btn.setIcon(icon(MDI6.percent_circle_outline,color="black"))
117
- self.percentile_btn.setIconSize(QSize(20, 20))
118
- self.percentile_btn.setStyleSheet(self.button_select_all)
119
- self.percentile_btn.setToolTip("Switch to percentile normalization values.")
120
- self.percentile_btn.clicked.connect(self.switch_to_absolute_normalization_mode)
121
-
122
- self.log_btn = QPushButton()
123
- self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
124
- self.log_btn.setStyleSheet(self.button_select_all)
125
- self.log_btn.clicked.connect(self.switch_to_log)
126
- self.log_btn.setToolTip("Log-transform the intensities.")
127
- self.log_btn.setIconSize(QSize(20, 20))
128
-
129
- self.channel_cbs = [QComboBox() for i in range(3)]
130
- self.channel_cbs_lbls = [QLabel() for i in range(3)]
131
-
132
- self.min_val_les = [QLineEdit('0') for i in range(3)]
133
- self.min_val_lbls = [QLabel('Min value: ') for i in range(3)]
134
- self.max_val_les = [QLineEdit('10000') for i in range(3)]
135
- self.max_val_lbls = [QLabel('Max value: ') for i in range(3)]
136
- self.percentile_mode = False
137
-
138
- self.rgb_text = ['R: ', 'G: ', 'B: ']
139
-
140
- for i in range(3):
141
-
142
- self.channel_cbs[i].addItems(self.channel_names)
143
- self.channel_cbs[i].setCurrentIndex(i)
144
- self.channel_cbs_lbls[i].setText(self.rgb_text[i])
145
-
146
- self.min_val_les[i].setValidator(self._floatValidator)
147
- self.max_val_les[i].setValidator(self._floatValidator)
148
-
149
-
150
- self.enable_channels()
151
-
152
- self.gs_btn.toggled.connect(self.enable_channels)
153
- self.rgb_btn.toggled.connect(self.enable_channels)
154
-
155
- self.hsep = QHSeperationLine()
156
-
157
- self.fraction_slider = QLabeledDoubleSlider()
158
- self.fraction_slider.setSingleStep(0.05)
159
- self.fraction_slider.setTickInterval(0.05)
160
- self.fraction_slider.setSingleStep(1)
161
- self.fraction_slider.setOrientation(Qt.Horizontal)
162
- self.fraction_slider.setRange(0.1,1)
163
- self.fraction_slider.setValue(0.25)
164
-
165
- self.interval_slider = QLabeledSlider()
166
- self.interval_slider.setSingleStep(1)
167
- self.interval_slider.setTickInterval(1)
168
- self.interval_slider.setSingleStep(1)
169
- self.interval_slider.setOrientation(Qt.Horizontal)
170
- self.interval_slider.setRange(1,1000)
171
- self.interval_slider.setValue(1)
172
-
173
- def enable_channels(self):
174
-
175
- """
176
- Enable three channels when RGB mode is checked.
177
-
178
- """
179
-
180
- if self.gs_btn.isChecked():
181
-
182
- self.log_btn.setEnabled(True)
183
- self.percentile_btn.setEnabled(False)
184
-
185
- for k in range(1,3):
186
- self.channel_cbs[k].setEnabled(False)
187
- self.channel_cbs_lbls[k].setEnabled(False)
188
-
189
- for k in range(3):
190
- self.min_val_les[k].setEnabled(False)
191
- self.min_val_lbls[k].setEnabled(False)
192
- self.max_val_les[k].setEnabled(False)
193
- self.max_val_lbls[k].setEnabled(False)
194
-
195
- elif self.rgb_btn.isChecked():
196
-
197
- self.log_btn.setEnabled(False)
198
- self.percentile_btn.setEnabled(True)
199
-
200
- for k in range(3):
201
- self.channel_cbs[k].setEnabled(True)
202
- self.channel_cbs_lbls[k].setEnabled(True)
203
-
204
- self.min_val_les[k].setEnabled(True)
205
- self.min_val_lbls[k].setEnabled(True)
206
- self.max_val_les[k].setEnabled(True)
207
- self.max_val_lbls[k].setEnabled(True)
208
-
209
- def switch_to_absolute_normalization_mode(self):
210
-
211
- """
212
- Use absolute or percentile values for the normalization of each individual channel.
213
-
214
- """
215
-
216
- if self.percentile_mode:
217
- self.percentile_mode = False
218
- self.percentile_btn.setIcon(icon(MDI6.percent_circle_outline,color="black"))
219
- self.percentile_btn.setIconSize(QSize(20, 20))
220
- self.percentile_btn.setToolTip("Switch to percentile normalization values.")
221
- for k in range(3):
222
- self.min_val_lbls[k].setText('Min value: ')
223
- self.min_val_les[k].setText('0')
224
- self.max_val_lbls[k].setText('Max value: ')
225
- self.max_val_les[k].setText('10000')
226
- else:
227
- self.percentile_mode = True
228
- self.percentile_btn.setIcon(icon(MDI6.percent_circle,color="black"))
229
- self.percentile_btn.setIconSize(QSize(20, 20))
230
- self.percentile_btn.setToolTip("Switch to absolute normalization values.")
231
- for k in range(3):
232
- self.min_val_lbls[k].setText('Min percentile: ')
233
- self.min_val_les[k].setText('0.01')
234
- self.max_val_lbls[k].setText('Max percentile: ')
235
- self.max_val_les[k].setText('99.99')
236
-
237
- def _write_instructions(self):
238
-
239
- """
240
- Save the current configuration.
241
-
242
- """
243
-
244
- instructions = {'rgb_mode': self.rgb_btn.isChecked(), 'percentile_mode': self.percentile_mode, 'fraction': float(self.fraction_slider.value()), 'interval': int(self.interval_slider.value()), 'log': self.log_option}
245
- max_i = 3 if self.rgb_btn.isChecked() else 1
246
- channels = []
247
- for i in range(max_i):
248
- channels.append([self.channel_cbs[i].currentText(), float(self.min_val_les[i].text().replace(',','.')), float(self.max_val_les[i].text().replace(',','.'))])
249
- instructions.update({'channels': channels})
250
-
251
- print('Instructions: ', instructions)
252
- file_name = self.instructions_path
253
- with open(file_name, 'w') as f:
254
- json.dump(instructions, f, indent=4)
255
- print('Done.')
256
- self.close()
257
-
258
- def _load_previous_instructions(self):
259
-
260
- """
261
- Read and set the widgets to the last configuration.
262
-
263
- """
264
-
265
- print('Reading instructions..')
266
- if os.path.exists(self.instructions_path):
267
- with open(self.instructions_path, 'r') as f:
268
- instructions = json.load(f)
269
- print(instructions)
270
-
271
- if 'rgb_mode' in instructions:
272
- rgb_mode = instructions['rgb_mode']
273
- if rgb_mode:
274
- self.rgb_btn.setChecked(True)
275
- self.gs_btn.setChecked(False)
276
-
277
- if 'percentile_mode' in instructions:
278
- percentile_mode = instructions['percentile_mode']
279
- if percentile_mode:
280
- self.percentile_btn.click()
281
-
282
- if 'channels' in instructions:
283
- channels = instructions['channels']
284
-
285
- if len(channels)==1:
286
- max_iter = 1
287
- else:
288
- max_iter = 3
289
-
290
- for i in range(max_iter):
291
- idx = self.channel_cbs[i].findText(channels[i][0])
292
- self.channel_cbs[i].setCurrentIndex(idx)
293
- self.min_val_les[i].setText(str(channels[i][1]).replace('.',','))
294
- self.max_val_les[i].setText(str(channels[i][2]).replace('.',','))
295
-
296
- if 'fraction' in instructions:
297
- fraction = instructions['fraction']
298
- self.fraction_slider.setValue(fraction)
299
-
300
- if 'interval' in instructions:
301
- interval = instructions['interval']
302
- self.interval_slider.setValue(interval)
303
-
304
- if 'log' in instructions:
305
- self.log_option = not instructions['log']
306
- self.switch_to_log()
307
-
308
- def switch_to_log(self):
309
-
310
- """
311
- Switch threshold histogram to log scale. Auto adjust.
312
- """
313
-
314
- if not self.log_option:
315
- self.log_btn.setIcon(icon(MDI6.math_log,color="#1565c0"))
316
- self.log_option = True
317
- else:
318
- self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
319
- self.log_option = False
27
+ """
28
+ UI to set normalization and animation parameters for the annotator tool.
29
+
30
+ """
31
+
32
+ def __init__(self, parent_window=None):
33
+
34
+ self.parent_window = parent_window
35
+ self.mode = self.parent_window.mode
36
+ self.exp_dir = self.parent_window.exp_dir
37
+ self.percentile_mode = False
38
+
39
+ self.instructions_path = (
40
+ self.parent_window.exp_dir
41
+ + f"configs/signal_annotator_config_{self.mode}.json"
42
+ )
43
+ if self.mode == "pairs":
44
+ self.instructions_path = (
45
+ self.parent_window.exp_dir
46
+ + "configs/signal_annotator_config_neighborhood.json"
47
+ )
48
+
49
+ self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
50
+ self.channel_names = np.array(self.channel_names)
51
+ self.channels = np.array(self.channels)
52
+ self.log_option = False
53
+
54
+ super().__init__(title="Configure signal annotator")
55
+
56
+ self._add_to_layout()
57
+ self._load_previous_instructions()
58
+
59
+ self._adjust_size()
60
+ self.resize(int(self.width() * 1.4), int(self._screen_height * 0.65))
61
+
62
+ def _add_to_layout(self):
63
+
64
+ sub_layout = QVBoxLayout()
65
+ sub_layout.setContentsMargins(10, 10, 10, 20)
66
+ sub_layout.setContentsMargins(30, 30, 30, 30)
67
+ sub_layout.addWidget(self._modality_lbl)
68
+
69
+ # Create radio buttons
70
+ option_layout = QHBoxLayout()
71
+ option_layout.addWidget(self.gs_btn, alignment=Qt.AlignCenter)
72
+ option_layout.addWidget(self.rgb_btn, alignment=Qt.AlignCenter)
73
+ sub_layout.addLayout(option_layout)
74
+
75
+ btn_hbox = QHBoxLayout()
76
+ btn_hbox.addWidget(QLabel(""), 90)
77
+ btn_hbox.addWidget(self.log_btn, 5, alignment=Qt.AlignRight)
78
+ btn_hbox.addWidget(self.percentile_btn, 5, alignment=Qt.AlignRight)
79
+ sub_layout.addLayout(btn_hbox)
80
+
81
+ for i in range(3):
82
+ hlayout = QHBoxLayout()
83
+ hlayout.addWidget(self.channel_cbs_lbls[i], 20)
84
+ hlayout.addWidget(self.channel_cbs[i], 80)
85
+ sub_layout.addLayout(hlayout)
86
+
87
+ hlayout2 = QHBoxLayout()
88
+ hlayout2.addWidget(self.min_val_lbls[i], 20)
89
+ hlayout2.addWidget(self.min_val_les[i], 80)
90
+ sub_layout.addLayout(hlayout2)
91
+
92
+ hlayout3 = QHBoxLayout()
93
+ hlayout3.addWidget(self.max_val_lbls[i], 20)
94
+ hlayout3.addWidget(self.max_val_les[i], 80)
95
+ sub_layout.addLayout(hlayout3)
96
+
97
+ sub_layout.addWidget(self.hsep)
98
+ hbox_frac = QHBoxLayout()
99
+ hbox_frac.addWidget(self._fraction_lbl, 20)
100
+ hbox_frac.addWidget(self.fraction_slider, 80)
101
+ sub_layout.addLayout(hbox_frac)
102
+
103
+ hbox_interval = QHBoxLayout()
104
+ hbox_interval.addWidget(self._interval_lbl, 20)
105
+ hbox_interval.addWidget(self.interval_slider, 80)
106
+ sub_layout.addLayout(hbox_interval)
107
+
108
+ self._layout.addLayout(sub_layout)
109
+
110
+ self._layout.addWidget(self.submit_btn)
111
+
112
+ def _create_widgets(self):
113
+ """
114
+ Create the widgets.
115
+
116
+ """
117
+ super()._create_widgets()
118
+
119
+ self._modality_lbl = QLabel("Modality: ")
120
+ self._fraction_lbl = QLabel("fraction: ")
121
+ self._interval_lbl = QLabel("interval [ms]: ")
122
+
123
+ self.gs_btn = QRadioButton("grayscale")
124
+ self.gs_btn.setChecked(True)
125
+
126
+ self.rgb_btn = QRadioButton("RGB")
127
+
128
+ self.percentile_btn = QPushButton()
129
+ self.percentile_btn.setIcon(icon(MDI6.percent_circle_outline, color="black"))
130
+ self.percentile_btn.setIconSize(QSize(20, 20))
131
+ self.percentile_btn.setStyleSheet(self.button_select_all)
132
+ self.percentile_btn.setToolTip("Switch to percentile normalization values.")
133
+ self.percentile_btn.clicked.connect(self.switch_to_absolute_normalization_mode)
134
+
135
+ self.log_btn = QPushButton()
136
+ self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
137
+ self.log_btn.setStyleSheet(self.button_select_all)
138
+ self.log_btn.clicked.connect(self.switch_to_log)
139
+ self.log_btn.setToolTip("Log-transform the intensities.")
140
+ self.log_btn.setIconSize(QSize(20, 20))
141
+
142
+ self.channel_cbs = [QComboBox() for _ in range(3)]
143
+ self.channel_cbs_lbls = [QLabel() for _ in range(3)]
144
+
145
+ self.min_val_les = [QLineEdit("0") for _ in range(3)]
146
+ self.min_val_lbls = [QLabel("Min value: ") for _ in range(3)]
147
+ self.max_val_les = [QLineEdit("10000") for _ in range(3)]
148
+ self.max_val_lbls = [QLabel("Max value: ") for _ in range(3)]
149
+
150
+ self.rgb_text = ["R: ", "G: ", "B: "]
151
+
152
+ for i in range(3):
153
+
154
+ self.channel_cbs[i].addItems(self.channel_names)
155
+ self.channel_cbs[i].setCurrentIndex(i)
156
+ self.channel_cbs_lbls[i].setText(self.rgb_text[i])
157
+
158
+ self.min_val_les[i].setValidator(self._floatValidator)
159
+ self.max_val_les[i].setValidator(self._floatValidator)
160
+
161
+ self.enable_channels()
162
+
163
+ self.gs_btn.toggled.connect(self.enable_channels)
164
+ self.rgb_btn.toggled.connect(self.enable_channels)
165
+
166
+ self.hsep = QHSeperationLine()
167
+
168
+ self.fraction_slider = QLabeledDoubleSlider()
169
+ self.fraction_slider.setSingleStep(0.05)
170
+ self.fraction_slider.setTickInterval(0.05)
171
+ self.fraction_slider.setSingleStep(1)
172
+ self.fraction_slider.setOrientation(Qt.Horizontal)
173
+ self.fraction_slider.setRange(0.1, 1)
174
+ self.fraction_slider.setValue(0.25)
175
+
176
+ self.interval_slider = QLabeledSlider()
177
+ self.interval_slider.setSingleStep(1)
178
+ self.interval_slider.setTickInterval(1)
179
+ self.interval_slider.setSingleStep(1)
180
+ self.interval_slider.setOrientation(Qt.Horizontal)
181
+ self.interval_slider.setRange(1, 1000)
182
+ self.interval_slider.setValue(1)
183
+
184
+ def enable_channels(self):
185
+ """
186
+ Enable three channels when RGB mode is checked.
187
+
188
+ """
189
+
190
+ if self.gs_btn.isChecked():
191
+
192
+ self.log_btn.setEnabled(True)
193
+ self.percentile_btn.setEnabled(False)
194
+
195
+ for k in range(1, 3):
196
+ self.channel_cbs[k].setEnabled(False)
197
+ self.channel_cbs_lbls[k].setEnabled(False)
198
+
199
+ for k in range(3):
200
+ self.min_val_les[k].setEnabled(False)
201
+ self.min_val_lbls[k].setEnabled(False)
202
+ self.max_val_les[k].setEnabled(False)
203
+ self.max_val_lbls[k].setEnabled(False)
204
+
205
+ elif self.rgb_btn.isChecked():
206
+
207
+ self.log_btn.setEnabled(False)
208
+ self.percentile_btn.setEnabled(True)
209
+
210
+ for k in range(3):
211
+ self.channel_cbs[k].setEnabled(True)
212
+ self.channel_cbs_lbls[k].setEnabled(True)
213
+
214
+ self.min_val_les[k].setEnabled(True)
215
+ self.min_val_lbls[k].setEnabled(True)
216
+ self.max_val_les[k].setEnabled(True)
217
+ self.max_val_lbls[k].setEnabled(True)
218
+
219
+ def switch_to_absolute_normalization_mode(self):
220
+ """
221
+ Use absolute or percentile values for the normalization of each individual channel.
222
+
223
+ """
224
+
225
+ if self.percentile_mode:
226
+ self.percentile_mode = False
227
+ self.percentile_btn.setIcon(
228
+ icon(MDI6.percent_circle_outline, color="black")
229
+ )
230
+ self.percentile_btn.setIconSize(QSize(20, 20))
231
+ self.percentile_btn.setToolTip("Switch to percentile normalization values.")
232
+ for k in range(3):
233
+ self.min_val_lbls[k].setText("Min value: ")
234
+ self.min_val_les[k].setText("0")
235
+ self.max_val_lbls[k].setText("Max value: ")
236
+ self.max_val_les[k].setText("10000")
237
+ else:
238
+ self.percentile_mode = True
239
+ self.percentile_btn.setIcon(icon(MDI6.percent_circle, color="black"))
240
+ self.percentile_btn.setIconSize(QSize(20, 20))
241
+ self.percentile_btn.setToolTip("Switch to absolute normalization values.")
242
+ for k in range(3):
243
+ self.min_val_lbls[k].setText("Min percentile: ")
244
+ self.min_val_les[k].setText("0.01")
245
+ self.max_val_lbls[k].setText("Max percentile: ")
246
+ self.max_val_les[k].setText("99.99")
247
+
248
+ def _write_instructions(self):
249
+ """
250
+ Save the current configuration.
251
+
252
+ """
253
+
254
+ instructions = {
255
+ "rgb_mode": self.rgb_btn.isChecked(),
256
+ "percentile_mode": self.percentile_mode,
257
+ "fraction": float(self.fraction_slider.value()),
258
+ "interval": int(self.interval_slider.value()),
259
+ "log": self.log_option,
260
+ }
261
+ max_i = 3 if self.rgb_btn.isChecked() else 1
262
+ channels = []
263
+ for i in range(max_i):
264
+ channels.append(
265
+ [
266
+ self.channel_cbs[i].currentText(),
267
+ float(self.min_val_les[i].text().replace(",", ".")),
268
+ float(self.max_val_les[i].text().replace(",", ".")),
269
+ ]
270
+ )
271
+ instructions.update({"channels": channels})
272
+
273
+ print("Instructions: ", instructions)
274
+ file_name = self.instructions_path
275
+ with open(file_name, "w") as f:
276
+ json.dump(instructions, f, indent=4)
277
+ print("Done.")
278
+ self.close()
279
+
280
+ def _load_previous_instructions(self):
281
+ """
282
+ Read and set the widgets to the last configuration.
283
+
284
+ """
285
+
286
+ print("Reading instructions..")
287
+ if os.path.exists(self.instructions_path):
288
+ with open(self.instructions_path, "r") as f:
289
+ instructions = json.load(f)
290
+ print(instructions)
291
+
292
+ if "rgb_mode" in instructions:
293
+ rgb_mode = instructions["rgb_mode"]
294
+ if rgb_mode:
295
+ self.rgb_btn.setChecked(True)
296
+ self.gs_btn.setChecked(False)
297
+
298
+ if "percentile_mode" in instructions:
299
+ percentile_mode = instructions["percentile_mode"]
300
+ if percentile_mode:
301
+ self.percentile_btn.click()
302
+
303
+ if "channels" in instructions:
304
+ channels = instructions["channels"]
305
+
306
+ if len(channels) == 1:
307
+ max_iter = 1
308
+ else:
309
+ max_iter = 3
310
+
311
+ for i in range(max_iter):
312
+ idx = self.channel_cbs[i].findText(channels[i][0])
313
+ self.channel_cbs[i].setCurrentIndex(idx)
314
+ self.min_val_les[i].setText(
315
+ str(channels[i][1]).replace(".", ",")
316
+ )
317
+ self.max_val_les[i].setText(
318
+ str(channels[i][2]).replace(".", ",")
319
+ )
320
+
321
+ if "fraction" in instructions:
322
+ fraction = instructions["fraction"]
323
+ self.fraction_slider.setValue(fraction)
324
+
325
+ if "interval" in instructions:
326
+ interval = instructions["interval"]
327
+ self.interval_slider.setValue(interval)
328
+
329
+ if "log" in instructions:
330
+ self.log_option = not instructions["log"]
331
+ self.switch_to_log()
332
+
333
+ def switch_to_log(self):
334
+ """
335
+ Switch threshold histogram to log scale. Auto adjust.
336
+ """
337
+
338
+ if not self.log_option:
339
+ self.log_btn.setIcon(icon(MDI6.math_log, color="#1565c0"))
340
+ self.log_option = True
341
+ else:
342
+ self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
343
+ self.log_option = False