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
@@ -1,989 +1,1158 @@
1
1
  from subprocess import Popen
2
2
 
3
- from PyQt5.QtWidgets import QMessageBox, QComboBox, QFrame, QCheckBox, \
4
- QGridLayout, QLineEdit, QVBoxLayout, QLabel, QHBoxLayout, QPushButton
5
- from PyQt5.QtCore import Qt, QSize
6
-
7
- from celldetective.gui.gui_utils import FeatureChoice, ListWidget, QHSeperationLine, FigureCanvas, \
8
- GeometryChoice, OperationChoice
3
+ from PyQt5.QtWidgets import (
4
+ QMessageBox,
5
+ QComboBox,
6
+ QFrame,
7
+ QCheckBox,
8
+ QGridLayout,
9
+ QLineEdit,
10
+ QVBoxLayout,
11
+ QLabel,
12
+ QHBoxLayout,
13
+ QPushButton,
14
+ )
15
+ from PyQt5.QtCore import Qt, QSize, QTimer
16
+
17
+ from celldetective.gui.base.utils import center_window
18
+ from celldetective.gui.gui_utils import (
19
+ GeometryChoice,
20
+ OperationChoice,
21
+ )
22
+ from celldetective.gui.base.list_widget import ListWidget
23
+ from celldetective.gui.base.feature_choice import FeatureChoice
24
+ from celldetective.gui.base.components import QHSeperationLine
9
25
  from superqt import QLabeledDoubleSlider
10
26
  from superqt.fonticon import icon
11
27
  from fonticon_mdi6 import MDI6
12
28
 
13
- from celldetective.utils import extract_experiment_channels, get_software_location
14
- from celldetective.io import load_frames, auto_load_number_of_frames
15
- from celldetective.measure import compute_haralick_features
29
+ from celldetective.utils.experiment import extract_experiment_channels
30
+ from celldetective.utils.image_loaders import auto_load_number_of_frames, load_frames
31
+
16
32
  import numpy as np
17
33
  import json
18
34
  import os
19
- import matplotlib.pyplot as plt
20
- from mpl_toolkits.axes_grid1 import make_axes_locatable
35
+
21
36
  from glob import glob
22
37
  from natsort import natsorted
23
- from tifffile import imread
38
+
24
39
  from pathlib import Path
25
40
 
26
- from celldetective.gui.viewers import CellEdgeVisualizer, SpotDetectionVisualizer
27
- from celldetective.gui.layouts import ProtocolDesignerLayout, BackgroundFitCorrectionLayout, LocalCorrectionLayout
41
+ from celldetective.gui.layouts import (
42
+ ProtocolDesignerLayout,
43
+ BackgroundFitCorrectionLayout,
44
+ LocalCorrectionLayout,
45
+ )
28
46
  from celldetective.gui.gui_utils import PreprocessingLayout2
29
47
  from celldetective.gui.settings._settings_base import CelldetectiveSettingsPanel
30
48
 
31
49
 
32
50
  class SettingsMeasurements(CelldetectiveSettingsPanel):
33
- """
34
- UI to set measurement instructions.
35
-
36
- """
37
-
38
- def __init__(self, parent_window=None):
39
-
40
- self.parent_window = parent_window
41
- self.mode = self.parent_window.mode
42
- self.exp_dir = self.parent_window.exp_dir
43
- self.background_correction = []
44
- self.config_name = f"btrack_config_{self.mode}.json"
45
- self.measure_instructions_path = self.parent_window.exp_dir + f"configs/measurement_instructions_{self.mode}.json"
46
- self.clear_previous = False
47
- self.config_path = self.exp_dir + self.config_name
48
- self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
49
- self.channel_names = np.array(self.channel_names)
50
- self.channels = np.array(self.channels)
51
-
52
- super().__init__("Configure measurements")
53
-
54
- self._add_to_layout()
55
- self._load_previous_instructions()
56
-
57
- self._adjustSize()
58
- self.resize(int(self.width()), int(self._screen_height * 0.8))
59
-
60
- def _create_widgets(self):
61
-
62
- """
63
- Create the multibox design.
64
-
65
- """
66
- super()._create_widgets()
67
-
68
- self.normalisation_frame = QFrame()
69
- self.normalisation_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
70
-
71
- self.local_correction_layout = LocalCorrectionLayout(self)
72
- self.fit_correction_layout = BackgroundFitCorrectionLayout(self)
73
-
74
- self.protocol_layout = ProtocolDesignerLayout(parent_window=self,
75
- tab_layouts=[ self.local_correction_layout, self.fit_correction_layout],
76
- tab_names=['Local', 'Fit'],
77
- title='BACKGROUND CORRECTION',
78
- list_title='Corrections to apply:'
79
- )
80
-
81
- self.normalisation_frame.setLayout(self.protocol_layout)
82
-
83
- #self.populate_normalisation_tabs()
84
-
85
- # first frame for FEATURES
86
- self.features_frame = QFrame()
87
- self.features_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
88
- self.populate_features_frame()
89
-
90
- # second frame for ISOTROPIC MEASUREMENTS
91
- self.iso_frame = QFrame()
92
- self.iso_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
93
- self.populate_iso_frame()
94
-
95
- self.spot_detection_frame = QFrame()
96
- self.spot_detection_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
97
- self.populate_spot_detection()
98
-
99
- self.clear_previous_btn = QCheckBox('clear previous measurements')
100
-
101
- def _add_to_layout(self):
102
- self._layout.addWidget(self.normalisation_frame)
103
- self._layout.addWidget(self.features_frame)
104
- self._layout.addWidget(self.iso_frame)
105
- self._layout.addWidget(self.spot_detection_frame)
106
- self._layout.addWidget(self.clear_previous_btn)
107
- self._layout.addWidget(self.submit_btn)
108
-
109
- def populate_iso_frame(self):
110
-
111
- """
112
- Add widgets and layout in the POST-PROCESSING frame.
113
- """
114
-
115
- grid = QGridLayout(self.iso_frame)
116
-
117
- self.iso_lbl = QLabel("Position-based measurements".upper())
118
- self.iso_lbl.setStyleSheet("""
51
+ """
52
+ UI to set measurement instructions.
53
+
54
+ """
55
+
56
+ def __init__(self, parent_window=None):
57
+
58
+ self.parent_window = parent_window
59
+ self.mode = self.parent_window.mode
60
+ self.exp_dir = self.parent_window.exp_dir
61
+ self.background_correction = []
62
+ self.config_name = f"btrack_config_{self.mode}.json"
63
+ self.measure_instructions_path = (
64
+ self.parent_window.exp_dir
65
+ + f"configs/measurement_instructions_{self.mode}.json"
66
+ )
67
+ self.clear_previous = False
68
+ self.config_path = self.exp_dir + self.config_name
69
+ self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
70
+ self.channel_names = np.array(self.channel_names)
71
+ self.channels = np.array(self.channels)
72
+
73
+ super().__init__("Configure measurements")
74
+
75
+ self._add_to_layout()
76
+ self._load_previous_instructions()
77
+
78
+ self._adjust_size()
79
+ self.resize(int(self.width()), int(self._screen_height * 0.8))
80
+
81
+ def _create_widgets(self):
82
+ """
83
+ Create the multibox design.
84
+
85
+ """
86
+ super()._create_widgets()
87
+
88
+ self.iso_lbl: QLabel = QLabel("Position-based measurements".upper())
89
+ self.feature_lbl: QLabel = QLabel("Mask-based measurements".upper())
90
+ self.radii_lbl: QLabel = QLabel("Measurement radii (from center):")
91
+ self.op_lbl: QLabel = QLabel("Operation to perform:")
92
+
93
+ self.normalisation_frame = QFrame()
94
+ self.normalisation_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
95
+
96
+ self.local_correction_layout = LocalCorrectionLayout(self)
97
+ self.fit_correction_layout = BackgroundFitCorrectionLayout(self)
98
+
99
+ self.protocol_layout = ProtocolDesignerLayout(
100
+ parent_window=self,
101
+ tab_layouts=[self.local_correction_layout, self.fit_correction_layout],
102
+ tab_names=["Local", "Fit"],
103
+ title="BACKGROUND CORRECTION",
104
+ list_title="Corrections to apply:",
105
+ )
106
+
107
+ self.normalisation_frame.setLayout(self.protocol_layout)
108
+
109
+ # self.populate_normalisation_tabs()
110
+
111
+ # first frame for FEATURES
112
+ self.features_frame = QFrame()
113
+ self.features_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
114
+ self.populate_features_frame()
115
+
116
+ # second frame for ISOTROPIC MEASUREMENTS
117
+ self.iso_frame = QFrame()
118
+ self.iso_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
119
+ self.populate_iso_frame()
120
+
121
+ self.spot_detection_frame = QFrame()
122
+ self.spot_detection_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
123
+ self.populate_spot_detection()
124
+
125
+ self.clear_previous_btn = QCheckBox("clear previous measurements")
126
+
127
+ def _add_to_layout(self):
128
+ self._layout.addWidget(self.normalisation_frame)
129
+ self._layout.addWidget(self.features_frame)
130
+ self._layout.addWidget(self.iso_frame)
131
+ self._layout.addWidget(self.spot_detection_frame)
132
+ self._layout.addWidget(self.clear_previous_btn)
133
+ self._layout.addWidget(self.submit_btn)
134
+
135
+ def populate_iso_frame(self):
136
+ """
137
+ Add widgets and layout in the POST-PROCESSING frame.
138
+ """
139
+
140
+ grid = QGridLayout(self.iso_frame)
141
+
142
+ self.iso_lbl.setStyleSheet(
143
+ """
119
144
  font-weight: bold;
120
145
  padding: 0px;
121
- """)
122
- grid.addWidget(self.iso_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
123
- self.generate_iso_contents()
124
- grid.addWidget(self.ContentsIso, 1, 0, 1, 4, alignment=Qt.AlignTop)
125
-
126
- def populate_features_frame(self):
146
+ """
147
+ )
148
+ grid.addWidget(self.iso_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
149
+ self.generate_iso_contents()
150
+ grid.addWidget(self.ContentsIso, 1, 0, 1, 4, alignment=Qt.AlignTop)
127
151
 
128
- """
129
- Add widgets and layout in the FEATURES frame.
130
- """
152
+ def populate_features_frame(self):
153
+ """
154
+ Add widgets and layout in the FEATURES frame.
155
+ """
131
156
 
132
- grid = QGridLayout(self.features_frame)
157
+ grid = QGridLayout(self.features_frame)
133
158
 
134
- self.feature_lbl = QLabel("Mask-based measurements".upper())
135
- self.feature_lbl.setStyleSheet("""
159
+ self.feature_lbl.setStyleSheet(
160
+ """
136
161
  font-weight: bold;
137
162
  padding: 0px;
138
- """)
139
- grid.addWidget(self.feature_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
140
-
141
- self.generate_feature_panel_contents()
142
- grid.addWidget(self.ContentsFeatures, 1, 0, 1, 4, alignment=Qt.AlignTop)
143
-
144
- def generate_iso_contents(self):
145
-
146
- self.ContentsIso = QFrame()
147
- layout = QVBoxLayout(self.ContentsIso)
148
- layout.setContentsMargins(0, 0, 0, 0)
149
-
150
- radii_layout = QHBoxLayout()
151
- self.radii_lbl = QLabel('Measurement radii (from center):')
152
- self.radii_lbl.setToolTip('Define radii or donughts for intensity measurements.')
153
- radii_layout.addWidget(self.radii_lbl, 90)
154
-
155
- self.del_radius_btn = QPushButton("")
156
- self.del_radius_btn.setStyleSheet(self.button_select_all)
157
- self.del_radius_btn.setIcon(icon(MDI6.trash_can, color="black"))
158
- self.del_radius_btn.setToolTip("Remove radius")
159
- self.del_radius_btn.setIconSize(QSize(20, 20))
160
- radii_layout.addWidget(self.del_radius_btn, 5)
161
-
162
- self.add_radius_btn = QPushButton("")
163
- self.add_radius_btn.setStyleSheet(self.button_select_all)
164
- self.add_radius_btn.setIcon(icon(MDI6.plus, color="black"))
165
- self.add_radius_btn.setToolTip("Add radius")
166
- self.add_radius_btn.setIconSize(QSize(20, 20))
167
- radii_layout.addWidget(self.add_radius_btn, 5)
168
- layout.addLayout(radii_layout)
169
-
170
- self.radii_list = ListWidget(GeometryChoice, initial_features=["10"], dtype=int)
171
- layout.addWidget(self.radii_list)
172
-
173
- self.del_radius_btn.clicked.connect(self.radii_list.removeSel)
174
- self.add_radius_btn.clicked.connect(self.radii_list.addItem)
175
-
176
- # Operation
177
- operation_layout = QHBoxLayout()
178
- self.op_lbl = QLabel('Operation to perform:')
179
- self.op_lbl.setToolTip('Set the operations to perform inside the ROI.')
180
- operation_layout.addWidget(self.op_lbl, 90)
181
-
182
- self.del_op_btn = QPushButton("")
183
- self.del_op_btn.setStyleSheet(self.button_select_all)
184
- self.del_op_btn.setIcon(icon(MDI6.trash_can, color="black"))
185
- self.del_op_btn.setToolTip("Remove operation")
186
- self.del_op_btn.setIconSize(QSize(20, 20))
187
- operation_layout.addWidget(self.del_op_btn, 5)
188
-
189
- self.add_op_btn = QPushButton("")
190
- self.add_op_btn.setStyleSheet(self.button_select_all)
191
- self.add_op_btn.setIcon(icon(MDI6.plus, color="black"))
192
- self.add_op_btn.setToolTip("Add operation")
193
- self.add_op_btn.setIconSize(QSize(20, 20))
194
- operation_layout.addWidget(self.add_op_btn, 5)
195
- layout.addLayout(operation_layout)
196
-
197
- self.operations_list = ListWidget(OperationChoice, initial_features=["mean"])
198
- layout.addWidget(self.operations_list)
199
-
200
- self.del_op_btn.clicked.connect(self.operations_list.removeSel)
201
- self.add_op_btn.clicked.connect(self.operations_list.addItem)
202
-
203
- def generate_feature_panel_contents(self):
204
-
205
- self.ContentsFeatures = QFrame()
206
- layout = QVBoxLayout(self.ContentsFeatures)
207
- layout.setContentsMargins(0, 0, 0, 0)
208
-
209
- feature_layout = QHBoxLayout()
210
- feature_layout.setContentsMargins(0, 0, 0, 0)
211
-
212
- self.feature_lbl = QLabel("Add features:")
213
- self.del_feature_btn = QPushButton("")
214
- self.del_feature_btn.setStyleSheet(self.button_select_all)
215
- self.del_feature_btn.setIcon(icon(MDI6.trash_can, color="black"))
216
- self.del_feature_btn.setToolTip("Remove feature")
217
- self.del_feature_btn.setIconSize(QSize(20, 20))
218
-
219
- self.add_feature_btn = QPushButton("")
220
- self.add_feature_btn.setStyleSheet(self.button_select_all)
221
- self.add_feature_btn.setIcon(icon(MDI6.filter_plus, color="black"))
222
- self.add_feature_btn.setToolTip("Add feature")
223
- self.add_feature_btn.setIconSize(QSize(20, 20))
224
-
225
- self.features_list = ListWidget(FeatureChoice, initial_features=['area', 'intensity_mean', ])
226
-
227
- self.create_feature_btn = QPushButton("")
228
- self.create_feature_btn.setStyleSheet(self.button_select_all)
229
- self.create_feature_btn.setIcon(icon(MDI6.file_cog, color="black"))
230
- self.create_feature_btn.setToolTip("Create new feature")
231
- self.create_feature_btn.setIconSize(QSize(20, 20))
232
-
233
- self.del_feature_btn.clicked.connect(self.features_list.removeSel)
234
- self.add_feature_btn.clicked.connect(self.features_list.addItem)
235
- self.create_feature_btn.clicked.connect(self.go_to_extraprops)
236
-
237
- feature_layout.addWidget(self.feature_lbl, 90)
238
- feature_layout.addWidget(self.del_feature_btn, 5)
239
- feature_layout.addWidget(self.add_feature_btn, 5)
240
- feature_layout.addWidget(self.create_feature_btn, 5)
241
-
242
- layout.addLayout(feature_layout)
243
- layout.addWidget(self.features_list)
244
-
245
- self.feat_sep2 = QHSeperationLine()
246
- layout.addWidget(self.feat_sep2)
247
-
248
- contour_layout = QHBoxLayout()
249
- self.border_dist_lbl = QLabel('Contour measurements (from edge of mask):')
250
- self.border_dist_lbl.setToolTip(
251
- 'Apply the intensity measurements defined above\nto a slice of each cell mask, defined as distance\nfrom the edge.')
252
- contour_layout.addWidget(self.border_dist_lbl, 90)
253
-
254
- self.del_contour_btn = QPushButton("")
255
- self.del_contour_btn.setStyleSheet(self.button_select_all)
256
- self.del_contour_btn.setIcon(icon(MDI6.trash_can, color="black"))
257
- self.del_contour_btn.setToolTip("Remove distance")
258
- self.del_contour_btn.setIconSize(QSize(20, 20))
259
- contour_layout.addWidget(self.del_contour_btn, 5)
260
-
261
- self.add_contour_btn = QPushButton("")
262
- self.add_contour_btn.setStyleSheet(self.button_select_all)
263
- self.add_contour_btn.setIcon(icon(MDI6.plus, color="black"))
264
- self.add_contour_btn.setToolTip("Add distance")
265
- self.add_contour_btn.setIconSize(QSize(20, 20))
266
- contour_layout.addWidget(self.add_contour_btn, 5)
267
-
268
- self.view_contour_btn = QPushButton("")
269
- self.view_contour_btn.setStyleSheet(self.button_select_all)
270
- self.view_contour_btn.setIcon(icon(MDI6.eye_plus_outline, color="black"))
271
- self.view_contour_btn.setToolTip("View contour")
272
- self.view_contour_btn.setIconSize(QSize(20, 20))
273
- contour_layout.addWidget(self.view_contour_btn, 5)
274
-
275
- layout.addLayout(contour_layout)
276
-
277
- self.contours_list = ListWidget(GeometryChoice, initial_features=[], dtype=int)
278
- layout.addWidget(self.contours_list)
279
-
280
- self.del_contour_btn.clicked.connect(self.contours_list.removeSel)
281
- self.add_contour_btn.clicked.connect(self.contours_list.addItem)
282
- self.view_contour_btn.clicked.connect(self.view_selected_contour)
283
-
284
- self.feat_sep3 = QHSeperationLine()
285
- layout.addWidget(self.feat_sep3)
286
-
287
- # Haralick features parameters
288
- self.activate_haralick_btn = QCheckBox('Measure Haralick texture features')
289
- self.activate_haralick_btn.toggled.connect(self.show_haralick_options)
290
-
291
- self.haralick_channel_choice = QComboBox()
292
- self.haralick_channel_choice.addItems(self.channel_names)
293
- self.haralick_channel_lbl = QLabel('Target channel: ')
294
-
295
- self.haralick_distance_le = QLineEdit("1")
296
- self.haralick_distance_lbl = QLabel('Distance: ')
297
-
298
- self.haralick_n_gray_levels_le = QLineEdit("256")
299
- self.haralick_n_gray_levels_lbl = QLabel('# gray levels: ')
300
-
301
- # Slider to set vmin & vmax
302
- self.haralick_scale_slider = QLabeledDoubleSlider()
303
- self.haralick_scale_slider.setSingleStep(0.05)
304
- self.haralick_scale_slider.setTickInterval(0.05)
305
- self.haralick_scale_slider.setSingleStep(1)
306
- self.haralick_scale_slider.setOrientation(Qt.Horizontal)
307
- self.haralick_scale_slider.setRange(0, 1)
308
- self.haralick_scale_slider.setValue(0.5)
309
- self.haralick_scale_lbl = QLabel('Scale: ')
310
-
311
- self.haralick_percentile_min_le = QLineEdit('0.01')
312
- self.haralick_percentile_max_le = QLineEdit('99.9')
313
- self.haralick_normalization_mode_btn = QPushButton()
314
- self.haralick_normalization_mode_btn.setStyleSheet(self.button_select_all)
315
- self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
316
- self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
317
- self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
318
- self.percentile_mode = True
319
-
320
- min_percentile_hbox = QHBoxLayout()
321
- min_percentile_hbox.addWidget(self.haralick_percentile_min_le, 90)
322
- min_percentile_hbox.addWidget(self.haralick_normalization_mode_btn, 10)
323
- min_percentile_hbox.setContentsMargins(0, 0, 0, 0)
324
-
325
- self.haralick_percentile_min_lbl = QLabel('Min percentile: ')
326
- self.haralick_percentile_max_lbl = QLabel('Max percentile: ')
327
-
328
- self.haralick_hist_btn = QPushButton()
329
- self.haralick_hist_btn.clicked.connect(self.control_haralick_intensity_histogram)
330
- self.haralick_hist_btn.setIcon(icon(MDI6.poll, color="k"))
331
- self.haralick_hist_btn.setStyleSheet(self.button_select_all)
332
-
333
- self.haralick_digit_btn = QPushButton()
334
- self.haralick_digit_btn.clicked.connect(self.control_haralick_digitalization)
335
- self.haralick_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
336
- self.haralick_digit_btn.setStyleSheet(self.button_select_all)
337
-
338
- self.haralick_layout = QVBoxLayout()
339
- self.haralick_layout.setContentsMargins(20, 20, 20, 20)
340
-
341
- activate_layout = QHBoxLayout()
342
- activate_layout.addWidget(self.activate_haralick_btn, 80)
343
- activate_layout.addWidget(self.haralick_hist_btn, 10)
344
- activate_layout.addWidget(self.haralick_digit_btn, 10)
345
- self.haralick_layout.addLayout(activate_layout)
346
-
347
- channel_layout = QHBoxLayout()
348
- channel_layout.addWidget(self.haralick_channel_lbl, 40)
349
- channel_layout.addWidget(self.haralick_channel_choice, 60)
350
- self.haralick_layout.addLayout(channel_layout)
351
-
352
- distance_layout = QHBoxLayout()
353
- distance_layout.addWidget(self.haralick_distance_lbl, 40)
354
- distance_layout.addWidget(self.haralick_distance_le, 60)
355
- self.haralick_layout.addLayout(distance_layout)
356
-
357
- gl_layout = QHBoxLayout()
358
- gl_layout.addWidget(self.haralick_n_gray_levels_lbl, 40)
359
- gl_layout.addWidget(self.haralick_n_gray_levels_le, 60)
360
- self.haralick_layout.addLayout(gl_layout)
361
-
362
- slider_layout = QHBoxLayout()
363
- slider_layout.addWidget(self.haralick_scale_lbl, 40)
364
- slider_layout.addWidget(self.haralick_scale_slider, 60)
365
- self.haralick_layout.addLayout(slider_layout)
366
-
367
- slider_min_percentile_layout = QHBoxLayout()
368
- slider_min_percentile_layout.addWidget(self.haralick_percentile_min_lbl, 40)
369
- # slider_min_percentile_layout.addWidget(self.haralick_percentile_min_le,55)
370
- slider_min_percentile_layout.addLayout(min_percentile_hbox, 60)
371
- # slider_min_percentile_layout.addWidget(self.haralick_normalization_mode_btn, 5)
372
- self.haralick_layout.addLayout(slider_min_percentile_layout)
373
-
374
- slider_max_percentile_layout = QHBoxLayout()
375
- slider_max_percentile_layout.addWidget(self.haralick_percentile_max_lbl, 40)
376
- slider_max_percentile_layout.addWidget(self.haralick_percentile_max_le, 60)
377
- self.haralick_layout.addLayout(slider_max_percentile_layout)
378
-
379
- self.haralick_to_hide = [self.haralick_hist_btn, self.haralick_digit_btn, self.haralick_channel_lbl,
380
- self.haralick_channel_choice,
381
- self.haralick_distance_le, self.haralick_distance_lbl, self.haralick_n_gray_levels_le,
382
- self.haralick_n_gray_levels_lbl,
383
- self.haralick_scale_lbl, self.haralick_scale_slider, self.haralick_percentile_min_lbl,
384
- self.haralick_percentile_min_le,
385
- self.haralick_percentile_max_lbl, self.haralick_percentile_max_le,
386
- self.haralick_normalization_mode_btn]
387
-
388
- self.features_to_disable = [self.feature_lbl, self.del_feature_btn, self.add_feature_btn, self.features_list,
389
- self.activate_haralick_btn]
390
-
391
- self.activate_haralick_btn.setChecked(False)
392
- for f in self.haralick_to_hide:
393
- f.setEnabled(False)
394
-
395
- self.haralick_normalization_mode_btn.clicked.connect(self.switch_to_absolute_normalization_mode)
396
- layout.addLayout(self.haralick_layout)
397
-
398
- def go_to_extraprops(self):
399
-
400
- path = os.sep.join([self._software_path,'celldetective',os.sep,'extra_properties.py'])
401
- try:
402
- Popen(f'explorer {os.path.realpath(path)}')
403
- except:
404
-
405
- try:
406
- os.system('xdg-open "%s"' % path)
407
- except:
408
- return None
409
-
410
- def switch_to_absolute_normalization_mode(self):
411
-
412
- if self.percentile_mode:
413
- self.percentile_mode = False
414
- self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle_outline, color="black"))
415
- self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
416
- self.haralick_normalization_mode_btn.setToolTip("Switch to percentile normalization values.")
417
- self.haralick_percentile_min_lbl.setText('Min value: ')
418
- self.haralick_percentile_max_lbl.setText('Max value: ')
419
- self.haralick_percentile_min_le.setText('0')
420
- self.haralick_percentile_max_le.setText('10000')
421
-
422
- else:
423
- self.percentile_mode = True
424
- self.haralick_normalization_mode_btn.setIcon(icon(MDI6.percent_circle, color="black"))
425
- self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
426
- self.haralick_normalization_mode_btn.setToolTip("Switch to absolute normalization values.")
427
- self.haralick_percentile_min_lbl.setText('Min percentile: ')
428
- self.haralick_percentile_max_lbl.setText('Max percentile: ')
429
- self.haralick_percentile_min_le.setText('0.01')
430
- self.haralick_percentile_max_le.setText('99.99')
431
-
432
- def show_haralick_options(self):
433
-
434
- """
435
- Show the Haralick texture options.
436
- """
437
-
438
- if self.activate_haralick_btn.isChecked():
439
- for element in self.haralick_to_hide:
440
- element.setEnabled(True)
441
- else:
442
- for element in self.haralick_to_hide:
443
- element.setEnabled(False)
444
-
445
- def adjustScrollArea(self):
446
-
447
- """
448
- Auto-adjust scroll area to fill space
449
- (from https://stackoverflow.com/questions/66417576/make-qscrollarea-use-all-available-space-of-qmainwindow-height-axis)
450
- """
451
-
452
- step = 5
453
- while self.scroll_area.verticalScrollBar().isVisible() and self.height() < self.maximumHeight():
454
- self.resize(self.width(), self.height() + step)
455
-
456
- def _write_instructions(self):
457
-
458
- """
459
- Write the selected options in a json file for later reading by the software.
460
- """
461
-
462
- print(f"{self.spot_preprocessing.list.items=}")
463
-
464
- print('Writing instructions...')
465
- measurement_options = {}
466
- background_correction = self.protocol_layout.protocols
467
- if not background_correction:
468
- background_correction = None
469
- measurement_options.update({'background_correction': background_correction})
470
- features = self.features_list.getItems()
471
- if not features:
472
- features = None
473
- measurement_options.update({'features': features})
474
-
475
- border_distances = self.contours_list.getItems()
476
- if not border_distances:
477
- border_distances = None
478
- measurement_options.update({'border_distances': border_distances})
479
-
480
- self.extract_haralick_options()
481
- measurement_options.update({'haralick_options': self.haralick_options})
482
-
483
- intensity_measurement_radii = self.radii_list.getItems()
484
- if not intensity_measurement_radii:
485
- intensity_measurement_radii = None
486
-
487
- isotropic_operations = self.operations_list.getItems()
488
- if not isotropic_operations:
489
- isotropic_operations = None
490
- intensity_measurement_radii = None
491
- measurement_options.update({'intensity_measurement_radii': intensity_measurement_radii,
492
- 'isotropic_operations': isotropic_operations})
493
- spot_detection = None
494
- if self.spot_check.isChecked():
495
- image_preprocessing = self.spot_preprocessing.list.items
496
- if image_preprocessing==[]:
497
- image_preprocessing = None
498
- spot_detection = {'channel': self.spot_channel.currentText(), 'diameter': float(self.diameter_value.text().replace(',','.')),
499
- 'threshold': float(self.threshold_value.text().replace(',','.')), 'image_preprocessing': image_preprocessing}
500
- measurement_options.update({'spot_detection': spot_detection})
501
- if self.clear_previous_btn.isChecked():
502
- self.clear_previous = True
503
- else:
504
- self.clear_previous = False
505
- measurement_options.update({'clear_previous': self.clear_previous})
506
-
507
-
508
- print('Measurement instructions: ', measurement_options)
509
- file_name = self.measure_instructions_path
510
- with open(file_name, 'w') as f:
511
- json.dump(measurement_options, f, indent=4)
512
-
513
- print('Done.')
514
- self.close()
515
-
516
- def extract_haralick_options(self):
517
-
518
- if self.activate_haralick_btn.isChecked():
519
- self.haralick_options = {"target_channel": self.haralick_channel_choice.currentIndex(),
520
- "scale_factor": float(self.haralick_scale_slider.value()),
521
- "n_intensity_bins": int(self.haralick_n_gray_levels_le.text()),
522
- "distance": int(self.haralick_distance_le.text()),
523
- }
524
- if self.percentile_mode:
525
- self.haralick_options.update({"percentiles": (
526
- float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text())),
527
- "clip_values": None})
528
- else:
529
- self.haralick_options.update({"percentiles": None, "clip_values": (
530
- float(self.haralick_percentile_min_le.text()), float(self.haralick_percentile_max_le.text()))})
531
-
532
- else:
533
- self.haralick_options = None
534
-
535
- def _load_previous_instructions(self):
536
-
537
- """
538
- Read the measurmeent options from a previously written json file and format properly for the UI.
539
- """
540
-
541
- print('Reading instructions..')
542
- if os.path.exists(self.measure_instructions_path):
543
- with open(self.measure_instructions_path, 'r') as f:
544
- measurement_instructions = json.load(f)
545
- print(measurement_instructions)
546
- if 'background_correction' in measurement_instructions:
547
- self.protocol_layout.protocols = measurement_instructions['background_correction']
548
- if self.protocol_layout.protocols is None:
549
- self.protocol_layout.protocols = []
550
- if (self.protocol_layout.protocols is not None) and len(self.protocol_layout.protocols) > 0:
551
- self.protocol_layout.protocol_list.clear()
552
- for norm_params in self.protocol_layout.protocols:
553
- normalisation_description = ""
554
- for index, (key, value) in enumerate(norm_params.items()):
555
- if index > 0:
556
- normalisation_description += ", "
557
- normalisation_description += str(key) + " : " + str(value)
558
- self.protocol_layout.protocol_list.addItem(normalisation_description)
559
- else:
560
- self.protocol_layout.protocol_list.clear()
561
- if 'features' in measurement_instructions:
562
- features = measurement_instructions['features']
563
- if (features is not None) and len(features) > 0:
564
- self.features_list.list_widget.clear()
565
- self.features_list.list_widget.addItems(features)
566
- else:
567
- self.features_list.list_widget.clear()
568
-
569
- if 'spot_detection' in measurement_instructions:
570
- spot_detection = measurement_instructions['spot_detection']
571
- if spot_detection is not None:
572
- self.spot_check.setChecked(True)
573
- if 'channel' in spot_detection:
574
- idx = spot_detection['channel']
575
- self.spot_channel.setCurrentText(idx)
576
- self.diameter_value.setText(str(spot_detection['diameter']))
577
- self.threshold_value.setText(str(spot_detection['threshold']))
578
- if 'image_preprocessing' in spot_detection:
579
- items = spot_detection['image_preprocessing']
580
- if items is not None:
581
- items_for_list = [a[0] for a in items]
582
- for it in items_for_list:
583
- self.spot_preprocessing.list.addItemToList(it)
584
- self.spot_preprocessing.list.items = items
585
-
586
- if 'border_distances' in measurement_instructions:
587
- border_distances = measurement_instructions['border_distances']
588
- if border_distances is not None:
589
- if isinstance(border_distances, int):
590
- distances = [border_distances]
591
- elif isinstance(border_distances, list):
592
- distances = []
593
- for d in border_distances:
594
- if isinstance(d, int) | isinstance(d, float):
595
- distances.append(str(int(d)))
596
- elif isinstance(d, list):
597
- distances.append(str(int(d[0])) + '-' + str(int(d[1])))
598
- self.contours_list.list_widget.clear()
599
- self.contours_list.list_widget.addItems(distances)
600
-
601
- if 'haralick_options' in measurement_instructions:
602
- haralick_options = measurement_instructions['haralick_options']
603
- if haralick_options is None:
604
- self.activate_haralick_btn.setChecked(False)
605
- self.show_haralick_options()
606
- else:
607
- self.activate_haralick_btn.setChecked(True)
608
- self.show_haralick_options()
609
- if 'target_channel' in haralick_options:
610
- idx = haralick_options['target_channel']
611
- self.haralick_channel_choice.setCurrentIndex(idx)
612
- if 'scale_factor' in haralick_options:
613
- self.haralick_scale_slider.setValue(float(haralick_options['scale_factor']))
614
- if ('percentiles' in haralick_options) and (haralick_options['percentiles'] is not None):
615
- perc = list(haralick_options['percentiles'])
616
- self.haralick_percentile_min_le.setText(str(perc[0]))
617
- self.haralick_percentile_max_le.setText(str(perc[1]))
618
- if ('clip_values' in haralick_options) and (haralick_options['clip_values'] is not None):
619
- values = list(haralick_options['clip_values'])
620
- self.percentile_mode = True
621
- self.switch_to_absolute_normalization_mode()
622
- self.haralick_percentile_min_le.setText(str(values[0]))
623
- self.haralick_percentile_max_le.setText(str(values[1]))
624
- if 'n_intensity_bins' in haralick_options:
625
- self.haralick_n_gray_levels_le.setText(str(haralick_options['n_intensity_bins']))
626
- if 'distance' in haralick_options:
627
- self.haralick_distance_le.setText(str(haralick_options['distance']))
628
-
629
- if 'intensity_measurement_radii' in measurement_instructions:
630
- intensity_measurement_radii = measurement_instructions['intensity_measurement_radii']
631
- if intensity_measurement_radii is not None:
632
- if isinstance(intensity_measurement_radii, int):
633
- radii = [intensity_measurement_radii]
634
- elif isinstance(intensity_measurement_radii, list):
635
- radii = []
636
- for r in intensity_measurement_radii:
637
- if isinstance(r, int) | isinstance(r, float):
638
- radii.append(str(int(r)))
639
- elif isinstance(r, list):
640
- radii.append(str(int(r[0])) + '-' + str(int(r[1])))
641
- self.radii_list.list_widget.clear()
642
- self.radii_list.list_widget.addItems(radii)
643
- else:
644
- self.radii_list.list_widget.clear()
645
-
646
- if 'isotropic_operations' in measurement_instructions:
647
- isotropic_operations = measurement_instructions['isotropic_operations']
648
- if (isotropic_operations is not None) and len(isotropic_operations) > 0:
649
- self.operations_list.list_widget.clear()
650
- self.operations_list.list_widget.addItems(isotropic_operations)
651
- else:
652
- self.operations_list.list_widget.clear()
653
-
654
- if 'clear_previous' in measurement_instructions:
655
- self.clear_previous = measurement_instructions['clear_previous']
656
- self.clear_previous_btn.setChecked(self.clear_previous)
657
-
658
- def locate_image(self):
659
-
660
- """
661
- Load the first frame of the first movie found in the experiment folder as a sample.
662
- """
663
-
664
- movies = glob(self.parent_window.parent_window.pos + os.sep.join(['movie', f"{self.parent_window.movie_prefix}*.tif"]))
665
-
666
- if len(movies) == 0:
667
- msgBox = QMessageBox()
668
- msgBox.setIcon(QMessageBox.Warning)
669
- msgBox.setText("Please select a position containing a movie...")
670
- msgBox.setWindowTitle("Warning")
671
- msgBox.setStandardButtons(QMessageBox.Ok)
672
- returnValue = msgBox.exec()
673
- if returnValue == QMessageBox.Ok:
674
- self.current_stack = None
675
- self.test_frame = None
676
- return None
677
- else:
678
- self.current_stack = movies[0]
679
- self.stack_length = auto_load_number_of_frames(self.current_stack)
680
- self.mid_time = self.stack_length // 2
681
- indices = len(self.channel_names) * self.mid_time + np.arange(len(self.channel_names))
682
- self.test_frame = load_frames(list(indices.astype(int)),self.current_stack, normalize_input=False)
683
-
684
- def control_haralick_digitalization(self):
685
-
686
- """
687
- Load an image for the first experiment movie found.
688
- Apply the Haralick parameters and check the result of the digitization (normalization + binning of intensities).
689
-
690
- """
691
-
692
- self.locate_image() # pb here
693
- self.extract_haralick_options()
694
- if self.test_frame is not None:
695
-
696
- digitized_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
697
- channels=self.channel_names, return_digit_image_only=True,
698
- **self.haralick_options
699
- )
700
-
701
- self.fig, self.ax = plt.subplots()
702
- divider = make_axes_locatable(self.ax)
703
- cax = divider.append_axes('right', size='5%', pad=0.05)
704
-
705
- self.imshow_digit_window = FigureCanvas(self.fig, title="Haralick: control digitization")
706
- self.ax.clear()
707
- im = self.ax.imshow(digitized_img, cmap='gray')
708
- self.fig.colorbar(im, cax=cax, orientation='vertical')
709
- self.ax.set_xticks([])
710
- self.ax.set_yticks([])
711
- self.fig.set_facecolor('none') # or 'None'
712
- self.fig.canvas.setStyleSheet("background-color: transparent;")
713
- self.imshow_digit_window.canvas.draw()
714
- self.imshow_digit_window.show()
715
-
716
- def control_haralick_intensity_histogram(self):
717
-
718
- """
719
- Load an image for the first experiment movie found.
720
- Apply the Haralick normalization parameters and check the normalized intensity histogram.
721
-
722
- """
723
-
724
- self.locate_image()
725
- self.extract_haralick_options()
726
- if self.test_frame is not None:
727
- norm_img = compute_haralick_features(self.test_frame, np.zeros(self.test_frame.shape[:2]),
728
- channels=self.channel_names, return_norm_image_only=True,
729
- **self.haralick_options
730
- )
731
- self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
732
- self.hist_window = FigureCanvas(self.fig, title="Haralick: control digitized histogram")
733
- self.ax.clear()
734
- flat = norm_img.flatten()
735
- self.ax.hist(flat[flat==flat], bins=self.haralick_options['n_intensity_bins'])
736
- self.ax.set_xlabel('gray level value')
737
- self.ax.set_ylabel('#')
738
- plt.tight_layout()
739
- self.fig.set_facecolor('none') # or 'None'
740
- self.fig.canvas.setStyleSheet("background-color: transparent;")
741
- self.hist_window.canvas.draw()
742
- self.hist_window.show()
743
-
744
- def view_selected_contour(self):
745
-
746
- """
747
- Show the ROI for the selected contour measurement on experimental data.
748
-
749
- """
750
- self.locate_image()
751
-
752
- if self.current_stack is not None:
753
-
754
- self.viewer = CellEdgeVisualizer(cell_type=self.mode,
755
- stack_path=self.current_stack,
756
- parent_list_widget=self.contours_list.list_widget,
757
- n_channels=len(self.channel_names),
758
- target_channel=0,
759
- window_title='Set an edge measurement',
760
- channel_cb=True,
761
- channel_names = self.channel_names,
762
- PxToUm = 1,
763
- )
764
- self.viewer.show()
765
-
766
- def locate_mask(self):
767
-
768
- """
769
- Load the first mask of the detected movie.
770
- """
771
-
772
- labels_path = str(Path(self.current_stack).parent.parent) + os.sep+f'labels_{self.mode}'+os.sep
773
- masks = natsorted(glob(labels_path + '*.tif'))
774
- if len(masks) == 0:
775
- print('no mask found')
776
- self.test_mask = None
777
- else:
778
- self.test_mask = imread(masks[self.mid_time])
779
-
780
- def switch_channel_contour(self, value):
781
-
782
- """
783
- Adjust intensity values when changing channels in the contour visualizer.
784
-
785
- """
786
-
787
- self.im_contour.set_array(self.test_frame[:, :, value])
788
- self.im_contour.set_clim(vmin=np.percentile(self.test_frame[:, :, value].flatten(), 1),
789
- vmax=np.percentile(self.test_frame[:, :, value].flatten(), 99.99))
790
- self.contrast_slider_contour.setRange(np.amin(self.test_frame[:, :, value]),
791
- np.amax(self.test_frame[:, :, value]))
792
- self.contrast_slider_contour.setValue([np.percentile(self.test_frame[:, :, value].flatten(), 1),
793
- np.percentile(self.test_frame[:, :, value].flatten(), 99.99)])
794
- self.fig_contour.canvas.draw_idle()
795
-
796
- def contrast_im_contour(self, value):
797
- vmin = value[0]
798
- vmax = value[1]
799
- self.im_contour.set_clim(vmin=vmin, vmax=vmax)
800
- self.fig_contour.canvas.draw_idle()
801
-
802
- def make_contour_transparent(self, value):
803
-
804
- self.im_mask.set_alpha(value)
805
- self.fig_contour.canvas.draw_idle()
806
-
807
- def remove_item_from_list(self):
808
- current_item = self.normalisation_list.currentRow()
809
- if current_item > -1:
810
- del self.background_correction[current_item]
811
- self.normalisation_list.takeItem(current_item)
812
-
813
- def check_the_information(self):
814
-
815
- if self.tabs.currentIndex() == 0:
816
- if self.background_correction is None:
817
- self.background_correction = []
818
- for index, normalisation_opt in enumerate(self.background_correction ):
819
- if self.tab1_channel_dropdown.currentText() in normalisation_opt['target channel']:
820
- result = self.channel_already_in_list()
821
- if result != QMessageBox.Yes:
822
- return False
823
- else:
824
- self.background_correction .remove(normalisation_opt)
825
- self.normalisation_list.takeItem(index)
826
- return True
827
-
828
- def display_message_box(self, missing_info):
829
- QMessageBox.about(self, "Message Box Title", "Please " + missing_info + " for background correction")
830
-
831
- def channel_already_in_list(self):
832
- response = QMessageBox.question(self, "Message Box Title",
833
- "The background correction parameters for this channel already exist, "
834
- "continuing will erase the previous configurations. Are you sure you want to "
835
- "proceed?",
836
- QMessageBox.No | QMessageBox.Yes, QMessageBox.No)
837
- return response
838
-
839
- def fun(self, x, y):
840
- return x ** 2 + y
841
-
842
- def view_normalisation_contour(self):
843
-
844
- """
845
- Show the ROI for the selected contour measurement on experimental data.
846
-
847
- """
848
-
849
- self.locate_image()
850
-
851
- if self.current_stack is not None:
852
-
853
- self.viewer = CellEdgeVisualizer(cell_type=self.mode,
854
- stack_path=self.current_stack,
855
- parent_le = self.tab1_txt_distance,
856
- n_channels=len(self.channel_names),
857
- target_channel=self.tab1_channel_dropdown.currentIndex(),
858
- edge_range = (0,30),
859
- initial_edge= self.tab1_txt_distance.get_threshold(),
860
- invert=True,
861
- window_title='Set an edge distance to estimate local intensity',
862
- channel_cb=False,
863
- PxToUm = 1,
864
- )
865
- self.viewer.show()
866
-
867
-
868
- def populate_spot_detection(self):
869
-
870
- layout = QVBoxLayout(self.spot_detection_frame)
871
-
872
- self.spot_detection_lbl = QLabel("SPOT DETECTION")
873
- self.spot_detection_lbl.setStyleSheet("""font-weight: bold;padding: 0px;""")
874
- layout.addWidget(self.spot_detection_lbl, alignment=Qt.AlignCenter)
875
-
876
- perform_hbox = QHBoxLayout()
877
- self.spot_check = QCheckBox('Perform spot detection')
878
- self.spot_check.toggled.connect(self.enable_spot_detection)
879
- perform_hbox.addWidget(self.spot_check, 95)
880
-
881
- self.spot_viewer_btn = QPushButton()
882
- self.spot_viewer_btn.clicked.connect(self.spot_preview)
883
- self.spot_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
884
- self.spot_viewer_btn.setStyleSheet(self.button_select_all)
885
- self.spot_viewer_btn.setToolTip('Set detection parameters visually.')
886
- perform_hbox.addWidget(self.spot_viewer_btn, 5)
887
- layout.addLayout(perform_hbox)
888
-
889
- channel_hbox = QHBoxLayout()
890
- self.spot_channel_lbl = QLabel("Channel: ")
891
- self.spot_channel = QComboBox()
892
- self.spot_channel.addItems(self.channel_names)
893
- channel_hbox.addWidget(self.spot_channel_lbl, 30)
894
- channel_hbox.addWidget(self.spot_channel, 70)
895
- layout.addLayout(channel_hbox)
896
-
897
- self.spot_preprocessing = PreprocessingLayout2(fraction=30, parent_window=self)
898
- layout.addLayout(self.spot_preprocessing)
899
-
900
- # continue switching to VBox + HBox down
901
- diam_hbox = QHBoxLayout()
902
- self.diameter_lbl = QLabel('Spot diameter: ')
903
- self.diameter_value = QLineEdit()
904
- self.diameter_value.setValidator(self._floatValidator)
905
- self.diameter_value.setText('7')
906
- self.diameter_value.textChanged.connect(self.enable_spot_preview)
907
-
908
- diam_hbox.addWidget(self.diameter_lbl,30)
909
- diam_hbox.addWidget(self.diameter_value, 70)
910
- layout.addLayout(diam_hbox)
911
-
912
- thresh_hbox = QHBoxLayout()
913
- self.threshold_lbl = QLabel('Spot threshold: ')
914
- self.threshold_value = QLineEdit()
915
- self.threshold_value.setValidator(self._floatValidator)
916
- self.threshold_value.setText('0')
917
- self.threshold_value.textChanged.connect(self.enable_spot_preview)
918
-
919
- thresh_hbox.addWidget(self.threshold_lbl, 30)
920
- thresh_hbox.addWidget(self.threshold_value, 70)
921
- layout.addLayout(thresh_hbox)
922
-
923
- self.spot_detection_widgets = [self.spot_channel,
924
- self.spot_channel_lbl,
925
- self.diameter_value,
926
- self.diameter_lbl,
927
- self.threshold_value,
928
- self.threshold_lbl,
929
- self.spot_viewer_btn,
930
- #self.invert_check,
931
- #self.invert_value_le,
932
- self.spot_preprocessing.list,
933
- self.spot_preprocessing.add_filter_btn,
934
- self.spot_preprocessing.delete_filter_btn,
935
- self.spot_preprocessing.preprocess_lbl
936
- ]
937
- for wg in self.spot_detection_widgets:
938
- wg.setEnabled(False)
939
-
940
- def enable_spot_preview(self):
941
-
942
- diam = self.diameter_value.text().replace(',','').replace('.','')
943
- thresh = self.threshold_value.text().replace(',','').replace('.','')
944
- if diam.isnumeric() and thresh.isnumeric():
945
- self.spot_viewer_btn.setEnabled(True)
946
- else:
947
- self.spot_viewer_btn.setEnabled(False)
948
-
949
- def spot_preview(self):
950
- self.locate_image()
951
- if self.test_frame is not None:
952
- self.locate_mask()
953
- if self.test_mask is not None:
954
-
955
- # invert_value = self.invert_value_le.text().replace(',','.')
956
- # if invert_value != '':
957
- # invert_value = float(invert_value)
958
- # else:
959
- # invert_value = None
960
-
961
- self.spot_visual = SpotDetectionVisualizer(frame_slider=True,
962
- contrast_slider=True,
963
- cell_type=self.mode,
964
- channel_cb=True,
965
- channel_names = self.channel_names,
966
- stack_path=self.current_stack,
967
- n_channels=len(self.channel_names),
968
- target_channel=self.spot_channel.currentIndex(),
969
- window_title='Detect spots',
970
- parent_channel_cb=self.spot_channel,
971
- parent_diameter_le=self.diameter_value,
972
- parent_threshold_le=self.threshold_value,
973
- parent_preprocessing_list=self.spot_preprocessing.list,
974
- #parent_invert_check=self.invert_check,
975
- #invert = self.invert_check.isChecked(),
976
- #invert_value = self.invert_value_le.text().replace(',','.'),
977
- PxToUm = 1,)
978
- self.spot_visual.show()
979
- #self.spot_visual = ThresholdSpot(current_channel=self.spot_channel.currentIndex(), img=self.test_frame,
980
- # mask=self.test_mask, parent_window=self)
981
-
982
- def enable_spot_detection(self):
983
-
984
- if self.spot_check.isChecked():
985
- for wg in self.spot_detection_widgets:
986
- wg.setEnabled(True)
987
- else:
988
- for wg in self.spot_detection_widgets:
989
- wg.setEnabled(False)
163
+ """
164
+ )
165
+ grid.addWidget(self.feature_lbl, 0, 0, 1, 4, alignment=Qt.AlignCenter)
166
+
167
+ self.generate_feature_panel_contents()
168
+ grid.addWidget(self.ContentsFeatures, 1, 0, 1, 4, alignment=Qt.AlignTop)
169
+
170
+ def generate_iso_contents(self):
171
+
172
+ self.ContentsIso = QFrame()
173
+ layout = QVBoxLayout(self.ContentsIso)
174
+ layout.setContentsMargins(0, 0, 0, 0)
175
+
176
+ radii_layout = QHBoxLayout()
177
+ self.radii_lbl.setToolTip(
178
+ "Define radii or donughts for intensity measurements."
179
+ )
180
+ radii_layout.addWidget(self.radii_lbl, 90)
181
+
182
+ self.del_radius_btn = QPushButton("")
183
+ self.del_radius_btn.setStyleSheet(self.button_select_all)
184
+ self.del_radius_btn.setIcon(icon(MDI6.trash_can, color="black"))
185
+ self.del_radius_btn.setToolTip("Remove radius")
186
+ self.del_radius_btn.setIconSize(QSize(20, 20))
187
+ radii_layout.addWidget(self.del_radius_btn, 5)
188
+
189
+ self.add_radius_btn = QPushButton("")
190
+ self.add_radius_btn.setStyleSheet(self.button_select_all)
191
+ self.add_radius_btn.setIcon(icon(MDI6.plus, color="black"))
192
+ self.add_radius_btn.setToolTip("Add radius")
193
+ self.add_radius_btn.setIconSize(QSize(20, 20))
194
+ radii_layout.addWidget(self.add_radius_btn, 5)
195
+ layout.addLayout(radii_layout)
196
+
197
+ self.radii_list = ListWidget(GeometryChoice, initial_features=["10"], dtype=int)
198
+ layout.addWidget(self.radii_list)
199
+
200
+ self.del_radius_btn.clicked.connect(self.radii_list.removeSel)
201
+ self.add_radius_btn.clicked.connect(self.radii_list.addItem)
202
+
203
+ # Operation
204
+ operation_layout = QHBoxLayout()
205
+ self.op_lbl.setToolTip("Set the operations to perform inside the ROI.")
206
+ operation_layout.addWidget(self.op_lbl, 90)
207
+
208
+ self.del_op_btn = QPushButton("")
209
+ self.del_op_btn.setStyleSheet(self.button_select_all)
210
+ self.del_op_btn.setIcon(icon(MDI6.trash_can, color="black"))
211
+ self.del_op_btn.setToolTip("Remove operation")
212
+ self.del_op_btn.setIconSize(QSize(20, 20))
213
+ operation_layout.addWidget(self.del_op_btn, 5)
214
+
215
+ self.add_op_btn = QPushButton("")
216
+ self.add_op_btn.setStyleSheet(self.button_select_all)
217
+ self.add_op_btn.setIcon(icon(MDI6.plus, color="black"))
218
+ self.add_op_btn.setToolTip("Add operation")
219
+ self.add_op_btn.setIconSize(QSize(20, 20))
220
+ operation_layout.addWidget(self.add_op_btn, 5)
221
+ layout.addLayout(operation_layout)
222
+
223
+ self.operations_list = ListWidget(OperationChoice, initial_features=["mean"])
224
+ layout.addWidget(self.operations_list)
225
+
226
+ self.del_op_btn.clicked.connect(self.operations_list.removeSel)
227
+ self.add_op_btn.clicked.connect(self.operations_list.addItem)
228
+
229
+ def generate_feature_panel_contents(self):
230
+
231
+ self.ContentsFeatures = QFrame()
232
+ layout = QVBoxLayout(self.ContentsFeatures)
233
+ layout.setContentsMargins(0, 0, 0, 0)
234
+
235
+ feature_layout = QHBoxLayout()
236
+ feature_layout.setContentsMargins(0, 0, 0, 0)
237
+
238
+ self.feature_lbl = QLabel("Add features:")
239
+ self.del_feature_btn = QPushButton("")
240
+ self.del_feature_btn.setStyleSheet(self.button_select_all)
241
+ self.del_feature_btn.setIcon(icon(MDI6.trash_can, color="black"))
242
+ self.del_feature_btn.setToolTip("Remove feature")
243
+ self.del_feature_btn.setIconSize(QSize(20, 20))
244
+
245
+ self.add_feature_btn = QPushButton("")
246
+ self.add_feature_btn.setStyleSheet(self.button_select_all)
247
+ self.add_feature_btn.setIcon(icon(MDI6.filter_plus, color="black"))
248
+ self.add_feature_btn.setToolTip("Add feature")
249
+ self.add_feature_btn.setIconSize(QSize(20, 20))
250
+
251
+ self.features_list = ListWidget(
252
+ FeatureChoice,
253
+ initial_features=[
254
+ "area",
255
+ "intensity_mean",
256
+ ],
257
+ )
258
+
259
+ self.create_feature_btn = QPushButton("")
260
+ self.create_feature_btn.setStyleSheet(self.button_select_all)
261
+ self.create_feature_btn.setIcon(icon(MDI6.file_cog, color="black"))
262
+ self.create_feature_btn.setToolTip("Create new feature")
263
+ self.create_feature_btn.setIconSize(QSize(20, 20))
264
+
265
+ self.del_feature_btn.clicked.connect(self.features_list.removeSel)
266
+ self.add_feature_btn.clicked.connect(self.features_list.addItem)
267
+ self.create_feature_btn.clicked.connect(self.go_to_extraprops)
268
+
269
+ feature_layout.addWidget(self.feature_lbl, 90)
270
+ feature_layout.addWidget(self.del_feature_btn, 5)
271
+ feature_layout.addWidget(self.add_feature_btn, 5)
272
+ feature_layout.addWidget(self.create_feature_btn, 5)
273
+
274
+ layout.addLayout(feature_layout)
275
+ layout.addWidget(self.features_list)
276
+
277
+ self.feat_sep2 = QHSeperationLine()
278
+ layout.addWidget(self.feat_sep2)
279
+
280
+ contour_layout = QHBoxLayout()
281
+ self.border_dist_lbl = QLabel("Contour measurements (from edge of mask):")
282
+ self.border_dist_lbl.setToolTip(
283
+ "Apply the intensity measurements defined above\nto a slice of each cell mask, defined as distance\nfrom the edge."
284
+ )
285
+ contour_layout.addWidget(self.border_dist_lbl, 90)
286
+
287
+ self.del_contour_btn = QPushButton("")
288
+ self.del_contour_btn.setStyleSheet(self.button_select_all)
289
+ self.del_contour_btn.setIcon(icon(MDI6.trash_can, color="black"))
290
+ self.del_contour_btn.setToolTip("Remove distance")
291
+ self.del_contour_btn.setIconSize(QSize(20, 20))
292
+ contour_layout.addWidget(self.del_contour_btn, 5)
293
+
294
+ self.add_contour_btn = QPushButton("")
295
+ self.add_contour_btn.setStyleSheet(self.button_select_all)
296
+ self.add_contour_btn.setIcon(icon(MDI6.plus, color="black"))
297
+ self.add_contour_btn.setToolTip("Add distance")
298
+ self.add_contour_btn.setIconSize(QSize(20, 20))
299
+ contour_layout.addWidget(self.add_contour_btn, 5)
300
+
301
+ self.view_contour_btn = QPushButton("")
302
+ self.view_contour_btn.setStyleSheet(self.button_select_all)
303
+ self.view_contour_btn.setIcon(icon(MDI6.eye_plus_outline, color="black"))
304
+ self.view_contour_btn.setToolTip("View contour")
305
+ self.view_contour_btn.setIconSize(QSize(20, 20))
306
+ contour_layout.addWidget(self.view_contour_btn, 5)
307
+
308
+ layout.addLayout(contour_layout)
309
+
310
+ self.contours_list = ListWidget(GeometryChoice, initial_features=[], dtype=int)
311
+ layout.addWidget(self.contours_list)
312
+
313
+ self.del_contour_btn.clicked.connect(self.contours_list.removeSel)
314
+ self.add_contour_btn.clicked.connect(self.contours_list.addItem)
315
+ self.view_contour_btn.clicked.connect(self.view_selected_contour)
316
+
317
+ self.feat_sep3 = QHSeperationLine()
318
+ layout.addWidget(self.feat_sep3)
319
+
320
+ # Haralick features parameters
321
+ self.activate_haralick_btn = QCheckBox("Measure Haralick texture features")
322
+ self.activate_haralick_btn.toggled.connect(self.show_haralick_options)
323
+
324
+ self.haralick_channel_choice = QComboBox()
325
+ self.haralick_channel_choice.addItems(self.channel_names)
326
+ self.haralick_channel_lbl = QLabel("Target channel: ")
327
+
328
+ self.haralick_distance_le = QLineEdit("1")
329
+ self.haralick_distance_lbl = QLabel("Distance: ")
330
+
331
+ self.haralick_n_gray_levels_le = QLineEdit("256")
332
+ self.haralick_n_gray_levels_lbl = QLabel("# gray levels: ")
333
+
334
+ # Slider to set vmin & vmax
335
+ self.haralick_scale_slider = QLabeledDoubleSlider()
336
+ self.haralick_scale_slider.setSingleStep(0.05)
337
+ self.haralick_scale_slider.setTickInterval(0.05)
338
+ self.haralick_scale_slider.setSingleStep(1)
339
+ self.haralick_scale_slider.setOrientation(Qt.Horizontal)
340
+ self.haralick_scale_slider.setRange(0, 1)
341
+ self.haralick_scale_slider.setValue(0.5)
342
+ self.haralick_scale_lbl = QLabel("Scale: ")
343
+
344
+ self.haralick_percentile_min_le = QLineEdit("0.01")
345
+ self.haralick_percentile_max_le = QLineEdit("99.9")
346
+ self.haralick_normalization_mode_btn = QPushButton()
347
+ self.haralick_normalization_mode_btn.setStyleSheet(self.button_select_all)
348
+ self.haralick_normalization_mode_btn.setIcon(
349
+ icon(MDI6.percent_circle, color="black")
350
+ )
351
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
352
+ self.haralick_normalization_mode_btn.setToolTip(
353
+ "Switch to absolute normalization values."
354
+ )
355
+ self.percentile_mode = True
356
+
357
+ min_percentile_hbox = QHBoxLayout()
358
+ min_percentile_hbox.addWidget(self.haralick_percentile_min_le, 90)
359
+ min_percentile_hbox.addWidget(self.haralick_normalization_mode_btn, 10)
360
+ min_percentile_hbox.setContentsMargins(0, 0, 0, 0)
361
+
362
+ self.haralick_percentile_min_lbl = QLabel("Min percentile: ")
363
+ self.haralick_percentile_max_lbl = QLabel("Max percentile: ")
364
+
365
+ self.haralick_hist_btn = QPushButton()
366
+ self.haralick_hist_btn.clicked.connect(
367
+ self.control_haralick_intensity_histogram
368
+ )
369
+ self.haralick_hist_btn.setIcon(icon(MDI6.poll, color="k"))
370
+ self.haralick_hist_btn.setStyleSheet(self.button_select_all)
371
+
372
+ self.haralick_digit_btn = QPushButton()
373
+ self.haralick_digit_btn.clicked.connect(self.control_haralick_digitalization)
374
+ self.haralick_digit_btn.setIcon(icon(MDI6.image_check, color="k"))
375
+ self.haralick_digit_btn.setStyleSheet(self.button_select_all)
376
+
377
+ self.haralick_layout = QVBoxLayout()
378
+ self.haralick_layout.setContentsMargins(20, 20, 20, 20)
379
+
380
+ activate_layout = QHBoxLayout()
381
+ activate_layout.addWidget(self.activate_haralick_btn, 80)
382
+ activate_layout.addWidget(self.haralick_hist_btn, 10)
383
+ activate_layout.addWidget(self.haralick_digit_btn, 10)
384
+ self.haralick_layout.addLayout(activate_layout)
385
+
386
+ channel_layout = QHBoxLayout()
387
+ channel_layout.addWidget(self.haralick_channel_lbl, 40)
388
+ channel_layout.addWidget(self.haralick_channel_choice, 60)
389
+ self.haralick_layout.addLayout(channel_layout)
390
+
391
+ distance_layout = QHBoxLayout()
392
+ distance_layout.addWidget(self.haralick_distance_lbl, 40)
393
+ distance_layout.addWidget(self.haralick_distance_le, 60)
394
+ self.haralick_layout.addLayout(distance_layout)
395
+
396
+ gl_layout = QHBoxLayout()
397
+ gl_layout.addWidget(self.haralick_n_gray_levels_lbl, 40)
398
+ gl_layout.addWidget(self.haralick_n_gray_levels_le, 60)
399
+ self.haralick_layout.addLayout(gl_layout)
400
+
401
+ slider_layout = QHBoxLayout()
402
+ slider_layout.addWidget(self.haralick_scale_lbl, 40)
403
+ slider_layout.addWidget(self.haralick_scale_slider, 60)
404
+ self.haralick_layout.addLayout(slider_layout)
405
+
406
+ slider_min_percentile_layout = QHBoxLayout()
407
+ slider_min_percentile_layout.addWidget(self.haralick_percentile_min_lbl, 40)
408
+ # slider_min_percentile_layout.addWidget(self.haralick_percentile_min_le,55)
409
+ slider_min_percentile_layout.addLayout(min_percentile_hbox, 60)
410
+ # slider_min_percentile_layout.addWidget(self.haralick_normalization_mode_btn, 5)
411
+ self.haralick_layout.addLayout(slider_min_percentile_layout)
412
+
413
+ slider_max_percentile_layout = QHBoxLayout()
414
+ slider_max_percentile_layout.addWidget(self.haralick_percentile_max_lbl, 40)
415
+ slider_max_percentile_layout.addWidget(self.haralick_percentile_max_le, 60)
416
+ self.haralick_layout.addLayout(slider_max_percentile_layout)
417
+
418
+ self.haralick_to_hide = [
419
+ self.haralick_hist_btn,
420
+ self.haralick_digit_btn,
421
+ self.haralick_channel_lbl,
422
+ self.haralick_channel_choice,
423
+ self.haralick_distance_le,
424
+ self.haralick_distance_lbl,
425
+ self.haralick_n_gray_levels_le,
426
+ self.haralick_n_gray_levels_lbl,
427
+ self.haralick_scale_lbl,
428
+ self.haralick_scale_slider,
429
+ self.haralick_percentile_min_lbl,
430
+ self.haralick_percentile_min_le,
431
+ self.haralick_percentile_max_lbl,
432
+ self.haralick_percentile_max_le,
433
+ self.haralick_normalization_mode_btn,
434
+ ]
435
+
436
+ self.features_to_disable = [
437
+ self.feature_lbl,
438
+ self.del_feature_btn,
439
+ self.add_feature_btn,
440
+ self.features_list,
441
+ self.activate_haralick_btn,
442
+ ]
443
+
444
+ self.activate_haralick_btn.setChecked(False)
445
+ for f in self.haralick_to_hide:
446
+ f.setEnabled(False)
447
+
448
+ self.haralick_normalization_mode_btn.clicked.connect(
449
+ self.switch_to_absolute_normalization_mode
450
+ )
451
+ layout.addLayout(self.haralick_layout)
452
+
453
+ def go_to_extraprops(self):
454
+
455
+ path = os.sep.join(
456
+ [self._software_path, "celldetective", os.sep, "extra_properties.py"]
457
+ )
458
+ try:
459
+ Popen(f"explorer {os.path.realpath(path)}")
460
+ except:
461
+
462
+ try:
463
+ os.system('xdg-open "%s"' % path)
464
+ except:
465
+ return None
466
+
467
+ def switch_to_absolute_normalization_mode(self):
468
+
469
+ if self.percentile_mode:
470
+ self.percentile_mode = False
471
+ self.haralick_normalization_mode_btn.setIcon(
472
+ icon(MDI6.percent_circle_outline, color="black")
473
+ )
474
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
475
+ self.haralick_normalization_mode_btn.setToolTip(
476
+ "Switch to percentile normalization values."
477
+ )
478
+ self.haralick_percentile_min_lbl.setText("Min value: ")
479
+ self.haralick_percentile_max_lbl.setText("Max value: ")
480
+ self.haralick_percentile_min_le.setText("0")
481
+ self.haralick_percentile_max_le.setText("10000")
482
+
483
+ else:
484
+ self.percentile_mode = True
485
+ self.haralick_normalization_mode_btn.setIcon(
486
+ icon(MDI6.percent_circle, color="black")
487
+ )
488
+ self.haralick_normalization_mode_btn.setIconSize(QSize(20, 20))
489
+ self.haralick_normalization_mode_btn.setToolTip(
490
+ "Switch to absolute normalization values."
491
+ )
492
+ self.haralick_percentile_min_lbl.setText("Min percentile: ")
493
+ self.haralick_percentile_max_lbl.setText("Max percentile: ")
494
+ self.haralick_percentile_min_le.setText("0.01")
495
+ self.haralick_percentile_max_le.setText("99.99")
496
+
497
+ def show_haralick_options(self):
498
+ """
499
+ Show the Haralick texture options.
500
+ """
501
+
502
+ if self.activate_haralick_btn.isChecked():
503
+ for element in self.haralick_to_hide:
504
+ element.setEnabled(True)
505
+ else:
506
+ for element in self.haralick_to_hide:
507
+ element.setEnabled(False)
508
+
509
+ def adjustScrollArea(self):
510
+ """
511
+ Auto-adjust scroll area to fill space
512
+ (from https://stackoverflow.com/questions/66417576/make-qscrollarea-use-all-available-space-of-qmainwindow-height-axis)
513
+ """
514
+
515
+ step = 5
516
+ while (
517
+ self.scroll_area.verticalScrollBar().isVisible()
518
+ and self.height() < self.maximumHeight()
519
+ ):
520
+ self.resize(self.width(), self.height() + step)
521
+
522
+ def _write_instructions(self):
523
+ """
524
+ Write the selected options in a json file for later reading by the software.
525
+ """
526
+
527
+ print(f"{self.spot_preprocessing.list.items=}")
528
+
529
+ print("Writing instructions...")
530
+ measurement_options = {}
531
+ background_correction = self.protocol_layout.protocols
532
+ if not background_correction:
533
+ background_correction = None
534
+ measurement_options.update({"background_correction": background_correction})
535
+ features = self.features_list.getItems()
536
+ if not features:
537
+ features = None
538
+ measurement_options.update({"features": features})
539
+
540
+ border_distances = self.contours_list.getItems()
541
+ if not border_distances:
542
+ border_distances = None
543
+ measurement_options.update({"border_distances": border_distances})
544
+
545
+ self.extract_haralick_options()
546
+ measurement_options.update({"haralick_options": self.haralick_options})
547
+
548
+ intensity_measurement_radii = self.radii_list.getItems()
549
+ if not intensity_measurement_radii:
550
+ intensity_measurement_radii = None
551
+
552
+ isotropic_operations = self.operations_list.getItems()
553
+ if not isotropic_operations:
554
+ isotropic_operations = None
555
+ intensity_measurement_radii = None
556
+ measurement_options.update(
557
+ {
558
+ "intensity_measurement_radii": intensity_measurement_radii,
559
+ "isotropic_operations": isotropic_operations,
560
+ }
561
+ )
562
+ spot_detection = None
563
+ if self.spot_check.isChecked():
564
+ image_preprocessing = self.spot_preprocessing.list.items
565
+ if image_preprocessing == []:
566
+ image_preprocessing = None
567
+ spot_detection = {
568
+ "channel": self.spot_channel.currentText(),
569
+ "diameter": float(self.diameter_value.text().replace(",", ".")),
570
+ "threshold": float(self.threshold_value.text().replace(",", ".")),
571
+ "image_preprocessing": image_preprocessing,
572
+ }
573
+ measurement_options.update({"spot_detection": spot_detection})
574
+ if self.clear_previous_btn.isChecked():
575
+ self.clear_previous = True
576
+ else:
577
+ self.clear_previous = False
578
+ measurement_options.update({"clear_previous": self.clear_previous})
579
+
580
+ print("Measurement instructions: ", measurement_options)
581
+ file_name = self.measure_instructions_path
582
+ with open(file_name, "w") as f:
583
+ json.dump(measurement_options, f, indent=4)
584
+
585
+ print("Done.")
586
+ self.close()
587
+
588
+ def extract_haralick_options(self):
589
+
590
+ if self.activate_haralick_btn.isChecked():
591
+ self.haralick_options = {
592
+ "target_channel": self.haralick_channel_choice.currentIndex(),
593
+ "scale_factor": float(self.haralick_scale_slider.value()),
594
+ "n_intensity_bins": int(self.haralick_n_gray_levels_le.text()),
595
+ "distance": int(self.haralick_distance_le.text()),
596
+ }
597
+ if self.percentile_mode:
598
+ self.haralick_options.update(
599
+ {
600
+ "percentiles": (
601
+ float(self.haralick_percentile_min_le.text()),
602
+ float(self.haralick_percentile_max_le.text()),
603
+ ),
604
+ "clip_values": None,
605
+ }
606
+ )
607
+ else:
608
+ self.haralick_options.update(
609
+ {
610
+ "percentiles": None,
611
+ "clip_values": (
612
+ float(self.haralick_percentile_min_le.text()),
613
+ float(self.haralick_percentile_max_le.text()),
614
+ ),
615
+ }
616
+ )
617
+
618
+ else:
619
+ self.haralick_options = None
620
+
621
+ def _load_previous_instructions(self):
622
+ """
623
+ Read the measurmeent options from a previously written json file and format properly for the UI.
624
+ """
625
+
626
+ print("Reading instructions..")
627
+ if os.path.exists(self.measure_instructions_path):
628
+ with open(self.measure_instructions_path, "r") as f:
629
+ measurement_instructions = json.load(f)
630
+ print(measurement_instructions)
631
+ if "background_correction" in measurement_instructions:
632
+ self.protocol_layout.protocols = measurement_instructions[
633
+ "background_correction"
634
+ ]
635
+ if self.protocol_layout.protocols is None:
636
+ self.protocol_layout.protocols = []
637
+ if (self.protocol_layout.protocols is not None) and len(
638
+ self.protocol_layout.protocols
639
+ ) > 0:
640
+ self.protocol_layout.protocol_list.clear()
641
+ for norm_params in self.protocol_layout.protocols:
642
+ normalisation_description = ""
643
+ for index, (key, value) in enumerate(norm_params.items()):
644
+ if index > 0:
645
+ normalisation_description += ", "
646
+ normalisation_description += (
647
+ str(key) + " : " + str(value)
648
+ )
649
+ self.protocol_layout.protocol_list.addItem(
650
+ normalisation_description
651
+ )
652
+ else:
653
+ self.protocol_layout.protocol_list.clear()
654
+ if "features" in measurement_instructions:
655
+ features = measurement_instructions["features"]
656
+ if (features is not None) and len(features) > 0:
657
+ self.features_list.list_widget.clear()
658
+ self.features_list.list_widget.addItems(features)
659
+ else:
660
+ self.features_list.list_widget.clear()
661
+
662
+ if "spot_detection" in measurement_instructions:
663
+ spot_detection = measurement_instructions["spot_detection"]
664
+ if spot_detection is not None:
665
+ self.spot_check.setChecked(True)
666
+ if "channel" in spot_detection:
667
+ idx = spot_detection["channel"]
668
+ self.spot_channel.setCurrentText(idx)
669
+ self.diameter_value.setText(str(spot_detection["diameter"]))
670
+ self.threshold_value.setText(str(spot_detection["threshold"]))
671
+ if "image_preprocessing" in spot_detection:
672
+ items = spot_detection["image_preprocessing"]
673
+ if items is not None:
674
+ items_for_list = [a[0] for a in items]
675
+ for it in items_for_list:
676
+ self.spot_preprocessing.list.addItemToList(it)
677
+ self.spot_preprocessing.list.items = items
678
+
679
+ if "border_distances" in measurement_instructions:
680
+ border_distances = measurement_instructions["border_distances"]
681
+ if border_distances is not None:
682
+ if isinstance(border_distances, int):
683
+ distances = [border_distances]
684
+ elif isinstance(border_distances, list):
685
+ distances = []
686
+ for d in border_distances:
687
+ if isinstance(d, int) | isinstance(d, float):
688
+ distances.append(str(int(d)))
689
+ elif isinstance(d, list):
690
+ distances.append(
691
+ str(int(d[0])) + "-" + str(int(d[1]))
692
+ )
693
+ self.contours_list.list_widget.clear()
694
+ self.contours_list.list_widget.addItems(distances)
695
+
696
+ if "haralick_options" in measurement_instructions:
697
+ haralick_options = measurement_instructions["haralick_options"]
698
+ if haralick_options is None:
699
+ self.activate_haralick_btn.setChecked(False)
700
+ self.show_haralick_options()
701
+ else:
702
+ self.activate_haralick_btn.setChecked(True)
703
+ self.show_haralick_options()
704
+ if "target_channel" in haralick_options:
705
+ idx = haralick_options["target_channel"]
706
+ self.haralick_channel_choice.setCurrentIndex(idx)
707
+ if "scale_factor" in haralick_options:
708
+ self.haralick_scale_slider.setValue(
709
+ float(haralick_options["scale_factor"])
710
+ )
711
+ if ("percentiles" in haralick_options) and (
712
+ haralick_options["percentiles"] is not None
713
+ ):
714
+ perc = list(haralick_options["percentiles"])
715
+ self.haralick_percentile_min_le.setText(str(perc[0]))
716
+ self.haralick_percentile_max_le.setText(str(perc[1]))
717
+ if ("clip_values" in haralick_options) and (
718
+ haralick_options["clip_values"] is not None
719
+ ):
720
+ values = list(haralick_options["clip_values"])
721
+ self.percentile_mode = True
722
+ self.switch_to_absolute_normalization_mode()
723
+ self.haralick_percentile_min_le.setText(str(values[0]))
724
+ self.haralick_percentile_max_le.setText(str(values[1]))
725
+ if "n_intensity_bins" in haralick_options:
726
+ self.haralick_n_gray_levels_le.setText(
727
+ str(haralick_options["n_intensity_bins"])
728
+ )
729
+ if "distance" in haralick_options:
730
+ self.haralick_distance_le.setText(
731
+ str(haralick_options["distance"])
732
+ )
733
+
734
+ if "intensity_measurement_radii" in measurement_instructions:
735
+ intensity_measurement_radii = measurement_instructions[
736
+ "intensity_measurement_radii"
737
+ ]
738
+ if intensity_measurement_radii is not None:
739
+ if isinstance(intensity_measurement_radii, int):
740
+ radii = [intensity_measurement_radii]
741
+ elif isinstance(intensity_measurement_radii, list):
742
+ radii = []
743
+ for r in intensity_measurement_radii:
744
+ if isinstance(r, int) | isinstance(r, float):
745
+ radii.append(str(int(r)))
746
+ elif isinstance(r, list):
747
+ radii.append(str(int(r[0])) + "-" + str(int(r[1])))
748
+ self.radii_list.list_widget.clear()
749
+ self.radii_list.list_widget.addItems(radii)
750
+ else:
751
+ self.radii_list.list_widget.clear()
752
+
753
+ if "isotropic_operations" in measurement_instructions:
754
+ isotropic_operations = measurement_instructions[
755
+ "isotropic_operations"
756
+ ]
757
+ if (isotropic_operations is not None) and len(
758
+ isotropic_operations
759
+ ) > 0:
760
+ self.operations_list.list_widget.clear()
761
+ self.operations_list.list_widget.addItems(isotropic_operations)
762
+ else:
763
+ self.operations_list.list_widget.clear()
764
+
765
+ if "clear_previous" in measurement_instructions:
766
+ self.clear_previous = measurement_instructions["clear_previous"]
767
+ self.clear_previous_btn.setChecked(self.clear_previous)
768
+
769
+ def locate_image(self):
770
+ """
771
+ Load the first frame of the first movie found in the experiment folder as a sample.
772
+ """
773
+
774
+ movies = glob(
775
+ self.parent_window.parent_window.pos
776
+ + os.sep.join(["movie", f"{self.parent_window.movie_prefix}*.tif"])
777
+ )
778
+
779
+ if len(movies) == 0:
780
+ msgBox = QMessageBox()
781
+ msgBox.setIcon(QMessageBox.Warning)
782
+ msgBox.setText("Please select a position containing a movie...")
783
+ msgBox.setWindowTitle("Warning")
784
+ msgBox.setStandardButtons(QMessageBox.Ok)
785
+ returnValue = msgBox.exec()
786
+ if returnValue == QMessageBox.Ok:
787
+ self.current_stack = None
788
+ self.test_frame = None
789
+ return None
790
+ else:
791
+ self.current_stack = movies[0]
792
+ self.stack_length = auto_load_number_of_frames(self.current_stack)
793
+ self.mid_time = self.stack_length // 2
794
+ indices = len(self.channel_names) * self.mid_time + np.arange(
795
+ len(self.channel_names)
796
+ )
797
+ self.test_frame = load_frames(
798
+ list(indices.astype(int)), self.current_stack, normalize_input=False
799
+ )
800
+
801
+ def control_haralick_digitalization(self):
802
+ """
803
+ Load an image for the first experiment movie found.
804
+ Apply the Haralick parameters and check the result of the digitization (normalization + binning of intensities).
805
+
806
+ """
807
+
808
+ self.locate_image() # pb here
809
+ self.extract_haralick_options()
810
+ if self.test_frame is not None:
811
+ import matplotlib.pyplot as plt
812
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
813
+ from celldetective.gui.base.figure_canvas import FigureCanvas
814
+ from celldetective.measure import compute_haralick_features
815
+
816
+ digitized_img = compute_haralick_features(
817
+ self.test_frame,
818
+ np.zeros(self.test_frame.shape[:2]),
819
+ channels=self.channel_names,
820
+ return_digit_image_only=True,
821
+ **self.haralick_options,
822
+ )
823
+
824
+ self.fig, self.ax = plt.subplots()
825
+ divider = make_axes_locatable(self.ax)
826
+ cax = divider.append_axes("right", size="5%", pad=0.05)
827
+
828
+ self.imshow_digit_window = FigureCanvas(
829
+ self.fig, title="Haralick: control digitization"
830
+ )
831
+ self.ax.clear()
832
+ im = self.ax.imshow(digitized_img, cmap="gray")
833
+ self.fig.colorbar(im, cax=cax, orientation="vertical")
834
+ self.ax.set_xticks([])
835
+ self.ax.set_yticks([])
836
+ self.fig.set_facecolor("none") # or 'None'
837
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
838
+ self.imshow_digit_window.canvas.draw()
839
+ self.imshow_digit_window.show()
840
+
841
+
842
+ def control_haralick_intensity_histogram(self):
843
+ """
844
+ Load an image for the first experiment movie found.
845
+ Apply the Haralick normalization parameters and check the normalized intensity histogram.
846
+
847
+ """
848
+
849
+ self.locate_image()
850
+ self.extract_haralick_options()
851
+ if self.test_frame is not None:
852
+ import matplotlib.pyplot as plt
853
+ from celldetective.gui.base.figure_canvas import FigureCanvas
854
+ from celldetective.measure import compute_haralick_features
855
+
856
+ norm_img = compute_haralick_features(
857
+ self.test_frame,
858
+ np.zeros(self.test_frame.shape[:2]),
859
+ channels=self.channel_names,
860
+ return_norm_image_only=True,
861
+ **self.haralick_options,
862
+ )
863
+ self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
864
+ self.hist_window = FigureCanvas(
865
+ self.fig, title="Haralick: control digitized histogram"
866
+ )
867
+ self.ax.clear()
868
+ flat = norm_img.flatten()
869
+ self.ax.hist(
870
+ flat[flat == flat], bins=self.haralick_options["n_intensity_bins"]
871
+ )
872
+ self.ax.set_xlabel("gray level value")
873
+ self.ax.set_ylabel("#")
874
+ plt.tight_layout()
875
+ self.fig.set_facecolor("none") # or 'None'
876
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
877
+ self.hist_window.canvas.draw()
878
+ self.hist_window.show()
879
+
880
+ try:
881
+ QTimer.singleShot(100, lambda: center_window(self.hist_window))
882
+ except Exception as e:
883
+ pass
884
+
885
+ def view_selected_contour(self):
886
+ """
887
+ Show the ROI for the selected contour measurement on experimental data.
888
+
889
+ """
890
+ self.locate_image()
891
+
892
+ if self.current_stack is not None:
893
+
894
+ from celldetective.gui.viewers.contour_viewer import CellEdgeVisualizer
895
+
896
+ self.viewer = CellEdgeVisualizer(
897
+ cell_type=self.mode,
898
+ stack_path=self.current_stack,
899
+ parent_list_widget=self.contours_list.list_widget,
900
+ n_channels=len(self.channel_names),
901
+ target_channel=0,
902
+ window_title="Set an edge measurement",
903
+ channel_cb=True,
904
+ channel_names=self.channel_names,
905
+ PxToUm=1,
906
+ )
907
+ self.viewer.show()
908
+
909
+ def locate_mask(self):
910
+ """
911
+ Load the first mask of the detected movie.
912
+ """
913
+
914
+ labels_path = (
915
+ str(Path(self.current_stack).parent.parent)
916
+ + os.sep
917
+ + f"labels_{self.mode}"
918
+ + os.sep
919
+ )
920
+ masks = natsorted(glob(labels_path + "*.tif"))
921
+ if len(masks) == 0:
922
+ print("no mask found")
923
+ self.test_mask = None
924
+ else:
925
+ from tifffile import imread
926
+
927
+ self.test_mask = imread(masks[self.mid_time])
928
+
929
+ def switch_channel_contour(self, value):
930
+ """
931
+ Adjust intensity values when changing channels in the contour visualizer.
932
+
933
+ """
934
+
935
+ self.im_contour.set_array(self.test_frame[:, :, value])
936
+ self.im_contour.set_clim(
937
+ vmin=np.percentile(self.test_frame[:, :, value].flatten(), 1),
938
+ vmax=np.percentile(self.test_frame[:, :, value].flatten(), 99.99),
939
+ )
940
+ self.contrast_slider_contour.setRange(
941
+ np.amin(self.test_frame[:, :, value]), np.amax(self.test_frame[:, :, value])
942
+ )
943
+ self.contrast_slider_contour.setValue(
944
+ [
945
+ np.percentile(self.test_frame[:, :, value].flatten(), 1),
946
+ np.percentile(self.test_frame[:, :, value].flatten(), 99.99),
947
+ ]
948
+ )
949
+ self.fig_contour.canvas.draw_idle()
950
+
951
+ def contrast_im_contour(self, value):
952
+ vmin = value[0]
953
+ vmax = value[1]
954
+ self.im_contour.set_clim(vmin=vmin, vmax=vmax)
955
+ self.fig_contour.canvas.draw_idle()
956
+
957
+ def make_contour_transparent(self, value):
958
+
959
+ self.im_mask.set_alpha(value)
960
+ self.fig_contour.canvas.draw_idle()
961
+
962
+ def remove_item_from_list(self):
963
+ current_item = self.normalisation_list.currentRow()
964
+ if current_item > -1:
965
+ del self.background_correction[current_item]
966
+ self.normalisation_list.takeItem(current_item)
967
+
968
+ def check_the_information(self):
969
+
970
+ if self.tabs.currentIndex() == 0:
971
+ if self.background_correction is None:
972
+ self.background_correction = []
973
+ for index, normalisation_opt in enumerate(self.background_correction):
974
+ if (
975
+ self.tab1_channel_dropdown.currentText()
976
+ in normalisation_opt["target channel"]
977
+ ):
978
+ result = self.channel_already_in_list()
979
+ if result != QMessageBox.Yes:
980
+ return False
981
+ else:
982
+ self.background_correction.remove(normalisation_opt)
983
+ self.normalisation_list.takeItem(index)
984
+ return True
985
+
986
+ def display_message_box(self, missing_info):
987
+ QMessageBox.about(
988
+ self,
989
+ "Message Box Title",
990
+ "Please " + missing_info + " for background correction",
991
+ )
992
+
993
+ def channel_already_in_list(self):
994
+ response = QMessageBox.question(
995
+ self,
996
+ "Message Box Title",
997
+ "The background correction parameters for this channel already exist, "
998
+ "continuing will erase the previous configurations. Are you sure you want to "
999
+ "proceed?",
1000
+ QMessageBox.No | QMessageBox.Yes,
1001
+ QMessageBox.No,
1002
+ )
1003
+ return response
1004
+
1005
+ def view_normalisation_contour(self):
1006
+ """
1007
+ Show the ROI for the selected contour measurement on experimental data.
1008
+
1009
+ """
1010
+
1011
+ self.locate_image()
1012
+
1013
+ if self.current_stack is not None:
1014
+
1015
+ from celldetective.gui.viewers.contour_viewer import CellEdgeVisualizer
1016
+
1017
+ self.viewer = CellEdgeVisualizer(
1018
+ cell_type=self.mode,
1019
+ stack_path=self.current_stack,
1020
+ parent_le=self.tab1_txt_distance,
1021
+ n_channels=len(self.channel_names),
1022
+ target_channel=self.tab1_channel_dropdown.currentIndex(),
1023
+ edge_range=(0, 30),
1024
+ initial_edge=self.tab1_txt_distance.get_threshold(),
1025
+ invert=True,
1026
+ window_title="Set an edge distance to estimate local intensity",
1027
+ channel_cb=False,
1028
+ PxToUm=1,
1029
+ )
1030
+ self.viewer.show()
1031
+
1032
+ def populate_spot_detection(self):
1033
+
1034
+ layout = QVBoxLayout(self.spot_detection_frame)
1035
+
1036
+ self.spot_detection_lbl = QLabel("SPOT DETECTION")
1037
+ self.spot_detection_lbl.setStyleSheet("""font-weight: bold;padding: 0px;""")
1038
+ layout.addWidget(self.spot_detection_lbl, alignment=Qt.AlignCenter)
1039
+
1040
+ perform_hbox = QHBoxLayout()
1041
+ self.spot_check = QCheckBox("Perform spot detection")
1042
+ self.spot_check.toggled.connect(self.enable_spot_detection)
1043
+ perform_hbox.addWidget(self.spot_check, 95)
1044
+
1045
+ self.spot_viewer_btn = QPushButton()
1046
+ self.spot_viewer_btn.clicked.connect(self.spot_preview)
1047
+ self.spot_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
1048
+ self.spot_viewer_btn.setStyleSheet(self.button_select_all)
1049
+ self.spot_viewer_btn.setToolTip("Set detection parameters visually.")
1050
+ perform_hbox.addWidget(self.spot_viewer_btn, 5)
1051
+ layout.addLayout(perform_hbox)
1052
+
1053
+ channel_hbox = QHBoxLayout()
1054
+ self.spot_channel_lbl = QLabel("Channel: ")
1055
+ self.spot_channel = QComboBox()
1056
+ self.spot_channel.addItems(self.channel_names)
1057
+ channel_hbox.addWidget(self.spot_channel_lbl, 30)
1058
+ channel_hbox.addWidget(self.spot_channel, 70)
1059
+ layout.addLayout(channel_hbox)
1060
+
1061
+ self.spot_preprocessing = PreprocessingLayout2(fraction=30, parent_window=self)
1062
+ layout.addLayout(self.spot_preprocessing)
1063
+
1064
+ # continue switching to VBox + HBox down
1065
+ diam_hbox = QHBoxLayout()
1066
+ self.diameter_lbl = QLabel("Spot diameter: ")
1067
+ self.diameter_value = QLineEdit()
1068
+ self.diameter_value.setValidator(self._floatValidator)
1069
+ self.diameter_value.setText("7")
1070
+ self.diameter_value.textChanged.connect(self.enable_spot_preview)
1071
+
1072
+ diam_hbox.addWidget(self.diameter_lbl, 30)
1073
+ diam_hbox.addWidget(self.diameter_value, 70)
1074
+ layout.addLayout(diam_hbox)
1075
+
1076
+ thresh_hbox = QHBoxLayout()
1077
+ self.threshold_lbl = QLabel("Spot threshold: ")
1078
+ self.threshold_value = QLineEdit()
1079
+ self.threshold_value.setValidator(self._floatValidator)
1080
+ self.threshold_value.setText("0")
1081
+ self.threshold_value.textChanged.connect(self.enable_spot_preview)
1082
+
1083
+ thresh_hbox.addWidget(self.threshold_lbl, 30)
1084
+ thresh_hbox.addWidget(self.threshold_value, 70)
1085
+ layout.addLayout(thresh_hbox)
1086
+
1087
+ self.spot_detection_widgets = [
1088
+ self.spot_channel,
1089
+ self.spot_channel_lbl,
1090
+ self.diameter_value,
1091
+ self.diameter_lbl,
1092
+ self.threshold_value,
1093
+ self.threshold_lbl,
1094
+ self.spot_viewer_btn,
1095
+ # self.invert_check,
1096
+ # self.invert_value_le,
1097
+ self.spot_preprocessing.list,
1098
+ self.spot_preprocessing.add_filter_btn,
1099
+ self.spot_preprocessing.delete_filter_btn,
1100
+ self.spot_preprocessing.preprocess_lbl,
1101
+ ]
1102
+ for wg in self.spot_detection_widgets:
1103
+ wg.setEnabled(False)
1104
+
1105
+ def enable_spot_preview(self):
1106
+
1107
+ diam = self.diameter_value.text().replace(",", "").replace(".", "")
1108
+ thresh = self.threshold_value.text().replace(",", "").replace(".", "")
1109
+ if diam.isnumeric() and thresh.isnumeric():
1110
+ self.spot_viewer_btn.setEnabled(True)
1111
+ else:
1112
+ self.spot_viewer_btn.setEnabled(False)
1113
+
1114
+ def spot_preview(self):
1115
+ self.locate_image()
1116
+ if self.test_frame is not None:
1117
+ self.locate_mask()
1118
+ if self.test_mask is not None:
1119
+
1120
+ # invert_value = self.invert_value_le.text().replace(',','.')
1121
+ # if invert_value != '':
1122
+ # invert_value = float(invert_value)
1123
+ # else:
1124
+ # invert_value = None
1125
+
1126
+ from celldetective.gui.viewers.spot_detection_viewer import SpotDetectionVisualizer
1127
+
1128
+ self.spot_visual = SpotDetectionVisualizer(
1129
+ frame_slider=True,
1130
+ contrast_slider=True,
1131
+ cell_type=self.mode,
1132
+ channel_cb=True,
1133
+ channel_names=self.channel_names,
1134
+ stack_path=self.current_stack,
1135
+ n_channels=len(self.channel_names),
1136
+ target_channel=self.spot_channel.currentIndex(),
1137
+ window_title="Detect spots",
1138
+ parent_channel_cb=self.spot_channel,
1139
+ parent_diameter_le=self.diameter_value,
1140
+ parent_threshold_le=self.threshold_value,
1141
+ parent_preprocessing_list=self.spot_preprocessing.list,
1142
+ # parent_invert_check=self.invert_check,
1143
+ # invert = self.invert_check.isChecked(),
1144
+ # invert_value = self.invert_value_le.text().replace(',','.'),
1145
+ PxToUm=1,
1146
+ )
1147
+ self.spot_visual.show()
1148
+ # self.spot_visual = ThresholdSpot(current_channel=self.spot_channel.currentIndex(), img=self.test_frame,
1149
+ # mask=self.test_mask, parent_window=self)
1150
+
1151
+ def enable_spot_detection(self):
1152
+
1153
+ if self.spot_check.isChecked():
1154
+ for wg in self.spot_detection_widgets:
1155
+ wg.setEnabled(True)
1156
+ else:
1157
+ for wg in self.spot_detection_widgets:
1158
+ wg.setEnabled(False)