celldetective 1.4.2__py3-none-any.whl → 1.5.0b0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- celldetective/__init__.py +25 -0
- celldetective/__main__.py +62 -43
- celldetective/_version.py +1 -1
- celldetective/extra_properties.py +477 -399
- celldetective/filters.py +192 -97
- celldetective/gui/InitWindow.py +541 -411
- celldetective/gui/__init__.py +0 -15
- celldetective/gui/about.py +44 -39
- celldetective/gui/analyze_block.py +120 -84
- celldetective/gui/base/__init__.py +0 -0
- celldetective/gui/base/channel_norm_generator.py +335 -0
- celldetective/gui/base/components.py +249 -0
- celldetective/gui/base/feature_choice.py +92 -0
- celldetective/gui/base/figure_canvas.py +52 -0
- celldetective/gui/base/list_widget.py +133 -0
- celldetective/gui/{styles.py → base/styles.py} +92 -36
- celldetective/gui/base/utils.py +33 -0
- celldetective/gui/base_annotator.py +900 -767
- celldetective/gui/classifier_widget.py +6 -22
- celldetective/gui/configure_new_exp.py +777 -671
- celldetective/gui/control_panel.py +635 -524
- celldetective/gui/dynamic_progress.py +449 -0
- celldetective/gui/event_annotator.py +2023 -1662
- celldetective/gui/generic_signal_plot.py +1292 -944
- celldetective/gui/gui_utils.py +899 -1289
- celldetective/gui/interactions_block.py +658 -0
- celldetective/gui/interactive_timeseries_viewer.py +447 -0
- celldetective/gui/json_readers.py +48 -15
- celldetective/gui/layouts/__init__.py +5 -0
- celldetective/gui/layouts/background_model_free_layout.py +537 -0
- celldetective/gui/layouts/channel_offset_layout.py +134 -0
- celldetective/gui/layouts/local_correction_layout.py +91 -0
- celldetective/gui/layouts/model_fit_layout.py +372 -0
- celldetective/gui/layouts/operation_layout.py +68 -0
- celldetective/gui/layouts/protocol_designer_layout.py +96 -0
- celldetective/gui/pair_event_annotator.py +3130 -2435
- celldetective/gui/plot_measurements.py +586 -267
- celldetective/gui/plot_signals_ui.py +724 -506
- celldetective/gui/preprocessing_block.py +395 -0
- celldetective/gui/process_block.py +1678 -1831
- celldetective/gui/seg_model_loader.py +580 -473
- celldetective/gui/settings/__init__.py +0 -7
- celldetective/gui/settings/_cellpose_model_params.py +181 -0
- celldetective/gui/settings/_event_detection_model_params.py +95 -0
- celldetective/gui/settings/_segmentation_model_params.py +159 -0
- celldetective/gui/settings/_settings_base.py +77 -65
- celldetective/gui/settings/_settings_event_model_training.py +752 -526
- celldetective/gui/settings/_settings_measurements.py +1133 -964
- celldetective/gui/settings/_settings_neighborhood.py +574 -488
- celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
- celldetective/gui/settings/_settings_signal_annotator.py +329 -305
- celldetective/gui/settings/_settings_tracking.py +1304 -1094
- celldetective/gui/settings/_stardist_model_params.py +98 -0
- celldetective/gui/survival_ui.py +422 -312
- celldetective/gui/tableUI.py +1665 -1701
- celldetective/gui/table_ops/_maths.py +295 -0
- celldetective/gui/table_ops/_merge_groups.py +140 -0
- celldetective/gui/table_ops/_merge_one_hot.py +95 -0
- celldetective/gui/table_ops/_query_table.py +43 -0
- celldetective/gui/table_ops/_rename_col.py +44 -0
- celldetective/gui/thresholds_gui.py +382 -179
- celldetective/gui/viewers/__init__.py +0 -0
- celldetective/gui/viewers/base_viewer.py +700 -0
- celldetective/gui/viewers/channel_offset_viewer.py +331 -0
- celldetective/gui/viewers/contour_viewer.py +394 -0
- celldetective/gui/viewers/size_viewer.py +153 -0
- celldetective/gui/viewers/spot_detection_viewer.py +341 -0
- celldetective/gui/viewers/threshold_viewer.py +309 -0
- celldetective/gui/workers.py +304 -126
- celldetective/log_manager.py +92 -0
- celldetective/measure.py +1895 -1478
- celldetective/napari/__init__.py +0 -0
- celldetective/napari/utils.py +1025 -0
- celldetective/neighborhood.py +1914 -1448
- celldetective/preprocessing.py +1620 -1220
- celldetective/processes/__init__.py +0 -0
- celldetective/processes/background_correction.py +271 -0
- celldetective/processes/compute_neighborhood.py +894 -0
- celldetective/processes/detect_events.py +246 -0
- celldetective/processes/measure_cells.py +565 -0
- celldetective/processes/segment_cells.py +760 -0
- celldetective/processes/track_cells.py +435 -0
- celldetective/processes/train_segmentation_model.py +694 -0
- celldetective/processes/train_signal_model.py +265 -0
- celldetective/processes/unified_process.py +292 -0
- celldetective/regionprops/_regionprops.py +358 -317
- celldetective/relative_measurements.py +987 -710
- celldetective/scripts/measure_cells.py +313 -212
- celldetective/scripts/measure_relative.py +90 -46
- celldetective/scripts/segment_cells.py +165 -104
- celldetective/scripts/segment_cells_thresholds.py +96 -68
- celldetective/scripts/track_cells.py +198 -149
- celldetective/scripts/train_segmentation_model.py +324 -201
- celldetective/scripts/train_signal_model.py +87 -45
- celldetective/segmentation.py +844 -749
- celldetective/signals.py +3514 -2861
- celldetective/tracking.py +30 -15
- celldetective/utils/__init__.py +0 -0
- celldetective/utils/cellpose_utils/__init__.py +133 -0
- celldetective/utils/color_mappings.py +42 -0
- celldetective/utils/data_cleaning.py +630 -0
- celldetective/utils/data_loaders.py +450 -0
- celldetective/utils/dataset_helpers.py +207 -0
- celldetective/utils/downloaders.py +197 -0
- celldetective/utils/event_detection/__init__.py +8 -0
- celldetective/utils/experiment.py +1782 -0
- celldetective/utils/image_augmenters.py +308 -0
- celldetective/utils/image_cleaning.py +74 -0
- celldetective/utils/image_loaders.py +926 -0
- celldetective/utils/image_transforms.py +335 -0
- celldetective/utils/io.py +62 -0
- celldetective/utils/mask_cleaning.py +348 -0
- celldetective/utils/mask_transforms.py +5 -0
- celldetective/utils/masks.py +184 -0
- celldetective/utils/maths.py +351 -0
- celldetective/utils/model_getters.py +325 -0
- celldetective/utils/model_loaders.py +296 -0
- celldetective/utils/normalization.py +380 -0
- celldetective/utils/parsing.py +465 -0
- celldetective/utils/plots/__init__.py +0 -0
- celldetective/utils/plots/regression.py +53 -0
- celldetective/utils/resources.py +34 -0
- celldetective/utils/stardist_utils/__init__.py +104 -0
- celldetective/utils/stats.py +90 -0
- celldetective/utils/types.py +21 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
- celldetective-1.5.0b0.dist-info/RECORD +187 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
- tests/gui/test_new_project.py +129 -117
- tests/gui/test_project.py +127 -79
- tests/test_filters.py +39 -15
- tests/test_notebooks.py +8 -0
- tests/test_tracking.py +232 -13
- tests/test_utils.py +123 -77
- celldetective/gui/base_components.py +0 -23
- celldetective/gui/layouts.py +0 -1602
- celldetective/gui/processes/compute_neighborhood.py +0 -594
- celldetective/gui/processes/measure_cells.py +0 -360
- celldetective/gui/processes/segment_cells.py +0 -499
- celldetective/gui/processes/track_cells.py +0 -303
- celldetective/gui/processes/train_segmentation_model.py +0 -270
- celldetective/gui/processes/train_signal_model.py +0 -108
- celldetective/gui/table_ops/merge_groups.py +0 -118
- celldetective/gui/viewers.py +0 -1354
- celldetective/io.py +0 -3663
- celldetective/utils.py +0 -3108
- celldetective-1.4.2.dist-info/RECORD +0 -123
- /celldetective/{gui/processes → processes}/downloader.py +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PyQt5.QtCore import QSize, Qt, QThread, pyqtSignal
|
|
4
|
+
from PyQt5.QtGui import QIntValidator, QDoubleValidator
|
|
5
|
+
from PyQt5.QtWidgets import QGridLayout, QLabel, QComboBox, QButtonGroup, QRadioButton, QPushButton, QCheckBox, \
|
|
6
|
+
QLineEdit, QHBoxLayout, QMessageBox, QDialog
|
|
7
|
+
from fonticon_mdi6 import MDI6
|
|
8
|
+
from superqt import QLabeledRangeSlider, QLabeledSlider, QLabeledDoubleRangeSlider
|
|
9
|
+
from superqt.fonticon import icon
|
|
10
|
+
from tifffile import imread
|
|
11
|
+
|
|
12
|
+
from celldetective.gui.base.components import CelldetectiveProgressDialog
|
|
13
|
+
from celldetective.gui.base.styles import Styles
|
|
14
|
+
from celldetective.gui.gui_utils import ThresholdLineEdit, QuickSliderLayout
|
|
15
|
+
from celldetective.gui.layouts.operation_layout import OperationLayout
|
|
16
|
+
from celldetective.processes.background_correction import BackgroundCorrectionProcess
|
|
17
|
+
from celldetective.utils.parsing import _extract_channel_indices_from_config
|
|
18
|
+
from celldetective import get_logger
|
|
19
|
+
|
|
20
|
+
logger = get_logger(__name__)
|
|
21
|
+
|
|
22
|
+
class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
|
|
23
|
+
"""docstring for ClassName"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, parent_window=None, *args):
|
|
26
|
+
super().__init__(*args)
|
|
27
|
+
|
|
28
|
+
self.parent_window = parent_window
|
|
29
|
+
|
|
30
|
+
if hasattr(self.parent_window.parent_window, "exp_config"):
|
|
31
|
+
self.attr_parent = self.parent_window.parent_window
|
|
32
|
+
else:
|
|
33
|
+
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
34
|
+
|
|
35
|
+
self.channel_names = self.attr_parent.exp_channels
|
|
36
|
+
|
|
37
|
+
self.setContentsMargins(15, 15, 15, 15)
|
|
38
|
+
self.generate_widgets()
|
|
39
|
+
self.add_to_layout()
|
|
40
|
+
|
|
41
|
+
def generate_widgets(self):
|
|
42
|
+
|
|
43
|
+
self.channel_lbl = QLabel("Channel: ")
|
|
44
|
+
self.channels_cb = QComboBox()
|
|
45
|
+
self.channels_cb.addItems(self.channel_names)
|
|
46
|
+
|
|
47
|
+
self.acquistion_lbl = QLabel("Stack mode: ")
|
|
48
|
+
self.acq_mode_group = QButtonGroup()
|
|
49
|
+
self.timeseries_rb = QRadioButton("timeseries")
|
|
50
|
+
self.timeseries_rb.setChecked(True)
|
|
51
|
+
self.tiles_rb = QRadioButton("tiles")
|
|
52
|
+
self.acq_mode_group.addButton(self.timeseries_rb, 0)
|
|
53
|
+
self.acq_mode_group.addButton(self.tiles_rb, 1)
|
|
54
|
+
|
|
55
|
+
self.frame_range_slider = QLabeledRangeSlider(parent=None)
|
|
56
|
+
|
|
57
|
+
self.timeseries_rb.toggled.connect(self.activate_time_range)
|
|
58
|
+
self.tiles_rb.toggled.connect(self.activate_time_range)
|
|
59
|
+
|
|
60
|
+
self.thresh_lbl = QLabel("Threshold: ")
|
|
61
|
+
self.thresh_lbl.setToolTip(
|
|
62
|
+
"Threshold on the STD-filtered image.\nPixel values above the threshold are\nconsidered as non-background and are\nmasked prior to background estimation."
|
|
63
|
+
)
|
|
64
|
+
self.threshold_viewer_btn = QPushButton()
|
|
65
|
+
self.threshold_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
66
|
+
self.threshold_viewer_btn.setStyleSheet(self.button_select_all)
|
|
67
|
+
self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
|
|
68
|
+
|
|
69
|
+
self.background_viewer_btn = QPushButton()
|
|
70
|
+
self.background_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
71
|
+
self.background_viewer_btn.setStyleSheet(self.button_select_all)
|
|
72
|
+
self.background_viewer_btn.setToolTip("View reconstructed background.")
|
|
73
|
+
|
|
74
|
+
self.corrected_stack_viewer_btn = QPushButton("")
|
|
75
|
+
self.corrected_stack_viewer_btn.setStyleSheet(self.button_select_all)
|
|
76
|
+
self.corrected_stack_viewer_btn.setIcon(icon(MDI6.eye_outline, color="black"))
|
|
77
|
+
self.corrected_stack_viewer_btn.setToolTip("View corrected image")
|
|
78
|
+
self.corrected_stack_viewer_btn.clicked.connect(self.preview_correction)
|
|
79
|
+
self.corrected_stack_viewer_btn.setIconSize(QSize(20, 20))
|
|
80
|
+
|
|
81
|
+
self.add_correction_btn = QPushButton("Add correction")
|
|
82
|
+
self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
|
|
83
|
+
self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
|
|
84
|
+
self.add_correction_btn.setToolTip("Add correction.")
|
|
85
|
+
self.add_correction_btn.setIconSize(QSize(25, 25))
|
|
86
|
+
self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
|
|
87
|
+
|
|
88
|
+
self.threshold_le = ThresholdLineEdit(
|
|
89
|
+
init_value=2,
|
|
90
|
+
connected_buttons=[
|
|
91
|
+
self.threshold_viewer_btn,
|
|
92
|
+
self.background_viewer_btn,
|
|
93
|
+
self.corrected_stack_viewer_btn,
|
|
94
|
+
self.add_correction_btn,
|
|
95
|
+
],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
self.well_slider = QLabeledSlider(parent=None)
|
|
99
|
+
|
|
100
|
+
self.background_viewer_btn.clicked.connect(self.estimate_bg)
|
|
101
|
+
|
|
102
|
+
self.regress_cb = QCheckBox("Optimize for each frame?")
|
|
103
|
+
self.regress_cb.toggled.connect(self.activate_coef_options)
|
|
104
|
+
self.regress_cb.setChecked(False)
|
|
105
|
+
|
|
106
|
+
self.coef_range_slider = QLabeledDoubleRangeSlider(parent=None)
|
|
107
|
+
self.coef_range_layout = QuickSliderLayout(
|
|
108
|
+
label="Coef. range: ",
|
|
109
|
+
slider=self.coef_range_slider,
|
|
110
|
+
slider_initial_value=(0.95, 1.05),
|
|
111
|
+
slider_range=(0.75, 1.25),
|
|
112
|
+
slider_tooltip="Coefficient range to increase or decrease the background intensity level...",
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
self.nbr_coefs_lbl = QLabel("Nbr of coefs: ")
|
|
116
|
+
self.nbr_coefs_lbl.setToolTip(
|
|
117
|
+
"Number of coefficients to be tested within range.\nThe more, the slower."
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
self.nbr_coef_le = QLineEdit()
|
|
121
|
+
self.nbr_coef_le.setText("100")
|
|
122
|
+
self.nbr_coef_le.setValidator(QIntValidator())
|
|
123
|
+
self.nbr_coef_le.setPlaceholderText("nbr of coefs")
|
|
124
|
+
|
|
125
|
+
self.coef_widgets = [
|
|
126
|
+
self.coef_range_layout.qlabel,
|
|
127
|
+
self.coef_range_slider,
|
|
128
|
+
self.nbr_coefs_lbl,
|
|
129
|
+
self.nbr_coef_le,
|
|
130
|
+
]
|
|
131
|
+
for c in self.coef_widgets:
|
|
132
|
+
c.setEnabled(False)
|
|
133
|
+
|
|
134
|
+
self.interpolate_check = QCheckBox("interpolate NaNs")
|
|
135
|
+
|
|
136
|
+
def add_to_layout(self):
|
|
137
|
+
|
|
138
|
+
channel_layout = QHBoxLayout()
|
|
139
|
+
channel_layout.addWidget(self.channel_lbl, 25)
|
|
140
|
+
channel_layout.addWidget(self.channels_cb, 75)
|
|
141
|
+
self.addLayout(channel_layout, 0, 0, 1, 3)
|
|
142
|
+
|
|
143
|
+
acquisition_layout = QHBoxLayout()
|
|
144
|
+
acquisition_layout.addWidget(self.acquistion_lbl, 25)
|
|
145
|
+
acquisition_layout.addWidget(
|
|
146
|
+
self.timeseries_rb, 75 // 2, alignment=Qt.AlignCenter
|
|
147
|
+
)
|
|
148
|
+
acquisition_layout.addWidget(self.tiles_rb, 75 // 2, alignment=Qt.AlignCenter)
|
|
149
|
+
self.addLayout(acquisition_layout, 1, 0, 1, 3)
|
|
150
|
+
|
|
151
|
+
frame_selection_layout = QuickSliderLayout(
|
|
152
|
+
label="Time range: ",
|
|
153
|
+
slider=self.frame_range_slider,
|
|
154
|
+
slider_initial_value=(0, 5),
|
|
155
|
+
slider_range=(0, self.attr_parent.len_movie),
|
|
156
|
+
slider_tooltip="frame [#]",
|
|
157
|
+
decimal_option=False,
|
|
158
|
+
)
|
|
159
|
+
frame_selection_layout.qlabel.setToolTip(
|
|
160
|
+
"Frame range for which the background\nis most likely to be observed."
|
|
161
|
+
)
|
|
162
|
+
self.time_range_options = [
|
|
163
|
+
self.frame_range_slider,
|
|
164
|
+
frame_selection_layout.qlabel,
|
|
165
|
+
]
|
|
166
|
+
self.addLayout(frame_selection_layout, 2, 0, 1, 3)
|
|
167
|
+
|
|
168
|
+
threshold_layout = QHBoxLayout()
|
|
169
|
+
threshold_layout.addWidget(self.thresh_lbl, 25)
|
|
170
|
+
subthreshold_layout = QHBoxLayout()
|
|
171
|
+
subthreshold_layout.addWidget(self.threshold_le, 95)
|
|
172
|
+
subthreshold_layout.addWidget(self.threshold_viewer_btn, 5)
|
|
173
|
+
threshold_layout.addLayout(subthreshold_layout, 75)
|
|
174
|
+
self.addLayout(threshold_layout, 3, 0, 1, 3)
|
|
175
|
+
|
|
176
|
+
background_layout = QuickSliderLayout(
|
|
177
|
+
label="QC for well: ",
|
|
178
|
+
slider=self.well_slider,
|
|
179
|
+
slider_initial_value=1,
|
|
180
|
+
slider_range=(1, len(self.attr_parent.wells)),
|
|
181
|
+
slider_tooltip="well [#]",
|
|
182
|
+
decimal_option=False,
|
|
183
|
+
layout_ratio=(0.25, 0.70),
|
|
184
|
+
)
|
|
185
|
+
background_layout.addWidget(self.background_viewer_btn, 5)
|
|
186
|
+
self.addLayout(background_layout, 4, 0, 1, 3)
|
|
187
|
+
|
|
188
|
+
self.addWidget(self.regress_cb, 5, 0, 1, 3)
|
|
189
|
+
|
|
190
|
+
self.addLayout(self.coef_range_layout, 6, 0, 1, 3)
|
|
191
|
+
|
|
192
|
+
coef_nbr_layout = QHBoxLayout()
|
|
193
|
+
coef_nbr_layout.addWidget(self.nbr_coefs_lbl, 25)
|
|
194
|
+
coef_nbr_layout.addWidget(self.nbr_coef_le, 75)
|
|
195
|
+
self.addLayout(coef_nbr_layout, 7, 0, 1, 3)
|
|
196
|
+
|
|
197
|
+
offset_layout = QHBoxLayout()
|
|
198
|
+
offset_layout.addWidget(QLabel("Offset: "), 25)
|
|
199
|
+
self.camera_offset_le = QLineEdit("0")
|
|
200
|
+
self.camera_offset_le.setPlaceholderText("camera black level")
|
|
201
|
+
self.camera_offset_le.setValidator(QDoubleValidator())
|
|
202
|
+
offset_layout.addWidget(self.camera_offset_le, 75)
|
|
203
|
+
self.addLayout(offset_layout, 8, 0, 1, 3)
|
|
204
|
+
|
|
205
|
+
self.operation_layout = OperationLayout()
|
|
206
|
+
self.addLayout(self.operation_layout, 9, 0, 1, 3)
|
|
207
|
+
|
|
208
|
+
self.addWidget(self.interpolate_check, 10, 0, 1, 1)
|
|
209
|
+
|
|
210
|
+
correction_layout = QHBoxLayout()
|
|
211
|
+
correction_layout.addWidget(self.add_correction_btn, 95)
|
|
212
|
+
correction_layout.addWidget(self.corrected_stack_viewer_btn, 5)
|
|
213
|
+
self.addLayout(correction_layout, 11, 0, 1, 3)
|
|
214
|
+
|
|
215
|
+
# verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
216
|
+
# self.addItem(verticalSpacer, 5, 0, 1, 3)
|
|
217
|
+
|
|
218
|
+
def add_instructions_to_parent_list(self):
|
|
219
|
+
|
|
220
|
+
self.generate_instructions()
|
|
221
|
+
self.parent_window.protocols.append(self.instructions)
|
|
222
|
+
correction_description = ""
|
|
223
|
+
for index, (key, value) in enumerate(self.instructions.items()):
|
|
224
|
+
if index > 0:
|
|
225
|
+
correction_description += ", "
|
|
226
|
+
correction_description += str(key) + " : " + str(value)
|
|
227
|
+
self.parent_window.protocol_list.addItem(correction_description)
|
|
228
|
+
|
|
229
|
+
def generate_instructions(self):
|
|
230
|
+
|
|
231
|
+
if self.timeseries_rb.isChecked():
|
|
232
|
+
mode = "timeseries"
|
|
233
|
+
elif self.tiles_rb.isChecked():
|
|
234
|
+
mode = "tiles"
|
|
235
|
+
|
|
236
|
+
if self.regress_cb.isChecked():
|
|
237
|
+
optimize_option = True
|
|
238
|
+
opt_coef_range = self.coef_range_slider.value()
|
|
239
|
+
opt_coef_nbr = int(self.nbr_coef_le.text())
|
|
240
|
+
else:
|
|
241
|
+
optimize_option = False
|
|
242
|
+
opt_coef_range = None
|
|
243
|
+
opt_coef_nbr = None
|
|
244
|
+
|
|
245
|
+
if self.operation_layout.subtract_btn.isChecked():
|
|
246
|
+
operation = "subtract"
|
|
247
|
+
else:
|
|
248
|
+
operation = "divide"
|
|
249
|
+
clip = None
|
|
250
|
+
|
|
251
|
+
if (
|
|
252
|
+
self.operation_layout.clip_btn.isChecked()
|
|
253
|
+
and self.operation_layout.subtract_btn.isChecked()
|
|
254
|
+
):
|
|
255
|
+
clip = True
|
|
256
|
+
else:
|
|
257
|
+
clip = False
|
|
258
|
+
|
|
259
|
+
if self.camera_offset_le.text() == "":
|
|
260
|
+
offset = None
|
|
261
|
+
else:
|
|
262
|
+
offset = float(self.camera_offset_le.text().replace(",", "."))
|
|
263
|
+
|
|
264
|
+
self.instructions = {
|
|
265
|
+
"target_channel": self.channels_cb.currentText(),
|
|
266
|
+
"correction_type": "model-free",
|
|
267
|
+
"threshold_on_std": self.threshold_le.get_threshold(),
|
|
268
|
+
"frame_range": self.frame_range_slider.value(),
|
|
269
|
+
"mode": mode,
|
|
270
|
+
"optimize_option": optimize_option,
|
|
271
|
+
"opt_coef_range": opt_coef_range,
|
|
272
|
+
"opt_coef_nbr": opt_coef_nbr,
|
|
273
|
+
"operation": operation,
|
|
274
|
+
"clip": clip,
|
|
275
|
+
"offset": offset,
|
|
276
|
+
"fix_nan": self.interpolate_check.isChecked(),
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
def set_target_channel(self):
|
|
280
|
+
|
|
281
|
+
channel_indices = _extract_channel_indices_from_config(
|
|
282
|
+
self.attr_parent.exp_config, [self.channels_cb.currentText()]
|
|
283
|
+
)
|
|
284
|
+
self.target_channel = channel_indices[0]
|
|
285
|
+
|
|
286
|
+
def set_threshold_graphically(self):
|
|
287
|
+
from celldetective.gui.viewers.threshold_viewer import (
|
|
288
|
+
ThresholdedStackVisualizer,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
self.attr_parent.locate_image()
|
|
292
|
+
self.set_target_channel()
|
|
293
|
+
thresh = self.threshold_le.get_threshold()
|
|
294
|
+
|
|
295
|
+
if self.attr_parent.current_stack is not None and thresh is not None:
|
|
296
|
+
self.viewer = ThresholdedStackVisualizer(
|
|
297
|
+
initial_threshold=thresh,
|
|
298
|
+
parent_le=self.threshold_le,
|
|
299
|
+
preprocessing=[["gauss", 2], ["std", 4]],
|
|
300
|
+
stack_path=self.attr_parent.current_stack,
|
|
301
|
+
n_channels=len(self.channel_names),
|
|
302
|
+
target_channel=self.target_channel,
|
|
303
|
+
window_title="Set the exclusion threshold",
|
|
304
|
+
)
|
|
305
|
+
self.viewer.show()
|
|
306
|
+
|
|
307
|
+
def preview_correction(self):
|
|
308
|
+
from celldetective.gui.viewers.base_viewer import StackVisualizer
|
|
309
|
+
|
|
310
|
+
if (
|
|
311
|
+
self.attr_parent.well_list.isMultipleSelection()
|
|
312
|
+
or not self.attr_parent.well_list.isAnySelected()
|
|
313
|
+
or self.attr_parent.position_list.isMultipleSelection()
|
|
314
|
+
or not self.attr_parent.position_list.isAnySelected()
|
|
315
|
+
):
|
|
316
|
+
msgBox = QMessageBox()
|
|
317
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
318
|
+
msgBox.setText("Please select a single position...")
|
|
319
|
+
msgBox.setWindowTitle("Warning")
|
|
320
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
321
|
+
returnValue = msgBox.exec()
|
|
322
|
+
if returnValue == QMessageBox.Ok:
|
|
323
|
+
return None
|
|
324
|
+
|
|
325
|
+
if self.timeseries_rb.isChecked():
|
|
326
|
+
mode = "timeseries"
|
|
327
|
+
elif self.tiles_rb.isChecked():
|
|
328
|
+
mode = "tiles"
|
|
329
|
+
else:
|
|
330
|
+
mode = "tiles"
|
|
331
|
+
|
|
332
|
+
if self.regress_cb.isChecked():
|
|
333
|
+
optimize_option = True
|
|
334
|
+
opt_coef_range = self.coef_range_slider.value()
|
|
335
|
+
opt_coef_nbr = int(self.nbr_coef_le.text())
|
|
336
|
+
else:
|
|
337
|
+
optimize_option = False
|
|
338
|
+
opt_coef_range = None
|
|
339
|
+
opt_coef_nbr = None
|
|
340
|
+
|
|
341
|
+
if self.operation_layout.subtract_btn.isChecked():
|
|
342
|
+
operation = "subtract"
|
|
343
|
+
else:
|
|
344
|
+
operation = "divide"
|
|
345
|
+
clip = None
|
|
346
|
+
|
|
347
|
+
if (
|
|
348
|
+
self.operation_layout.clip_btn.isChecked()
|
|
349
|
+
and self.operation_layout.subtract_btn.isChecked()
|
|
350
|
+
):
|
|
351
|
+
clip = True
|
|
352
|
+
else:
|
|
353
|
+
clip = False
|
|
354
|
+
|
|
355
|
+
process_args = {
|
|
356
|
+
"exp_dir": self.attr_parent.exp_dir,
|
|
357
|
+
"well_option": self.attr_parent.well_list.getSelectedIndices(),
|
|
358
|
+
"position_option": self.attr_parent.position_list.getSelectedIndices(),
|
|
359
|
+
"target_channel": self.channels_cb.currentText(),
|
|
360
|
+
"mode": mode,
|
|
361
|
+
"threshold_on_std": self.threshold_le.get_threshold(),
|
|
362
|
+
"frame_range": self.frame_range_slider.value(),
|
|
363
|
+
"optimize_option": optimize_option,
|
|
364
|
+
"opt_coef_range": opt_coef_range,
|
|
365
|
+
"opt_coef_nbr": opt_coef_nbr,
|
|
366
|
+
"operation": operation,
|
|
367
|
+
"clip": clip,
|
|
368
|
+
"fix_nan": self.interpolate_check.isChecked(),
|
|
369
|
+
"activation_protocol": [["gauss", 2], ["std", 4]],
|
|
370
|
+
"correction_type": "model-free",
|
|
371
|
+
}
|
|
372
|
+
from celldetective.gui.workers import ProgressWindow
|
|
373
|
+
|
|
374
|
+
self.job = ProgressWindow(
|
|
375
|
+
BackgroundCorrectionProcess,
|
|
376
|
+
parent_window=self,
|
|
377
|
+
title="Background Correction",
|
|
378
|
+
position_info=False,
|
|
379
|
+
process_args=process_args,
|
|
380
|
+
)
|
|
381
|
+
result = self.job.exec_()
|
|
382
|
+
|
|
383
|
+
if result == QDialog.Accepted:
|
|
384
|
+
temp_path = os.path.join(
|
|
385
|
+
self.attr_parent.exp_dir, "temp_corrected_stack.tif"
|
|
386
|
+
)
|
|
387
|
+
if os.path.exists(temp_path):
|
|
388
|
+
corrected_stack = imread(temp_path)
|
|
389
|
+
os.remove(temp_path)
|
|
390
|
+
|
|
391
|
+
self.viewer = StackVisualizer(
|
|
392
|
+
stack=corrected_stack,
|
|
393
|
+
window_title="Corrected channel",
|
|
394
|
+
frame_slider=True,
|
|
395
|
+
contrast_slider=True,
|
|
396
|
+
target_channel=self.channels_cb.currentIndex(),
|
|
397
|
+
)
|
|
398
|
+
self.viewer.show()
|
|
399
|
+
else:
|
|
400
|
+
print("Corrected stack could not be generated... No stack available...")
|
|
401
|
+
else:
|
|
402
|
+
print("Background correction cancelled.")
|
|
403
|
+
|
|
404
|
+
def activate_time_range(self):
|
|
405
|
+
|
|
406
|
+
if self.timeseries_rb.isChecked():
|
|
407
|
+
for wg in self.time_range_options:
|
|
408
|
+
wg.setEnabled(True)
|
|
409
|
+
elif self.tiles_rb.isChecked():
|
|
410
|
+
for wg in self.time_range_options:
|
|
411
|
+
wg.setEnabled(False)
|
|
412
|
+
|
|
413
|
+
def activate_coef_options(self):
|
|
414
|
+
|
|
415
|
+
if self.regress_cb.isChecked():
|
|
416
|
+
for c in self.coef_widgets:
|
|
417
|
+
c.setEnabled(True)
|
|
418
|
+
else:
|
|
419
|
+
for c in self.coef_widgets:
|
|
420
|
+
c.setEnabled(False)
|
|
421
|
+
|
|
422
|
+
def estimate_bg(self):
|
|
423
|
+
|
|
424
|
+
if self.timeseries_rb.isChecked():
|
|
425
|
+
mode = "timeseries"
|
|
426
|
+
elif self.tiles_rb.isChecked():
|
|
427
|
+
mode = "tiles"
|
|
428
|
+
else:
|
|
429
|
+
mode = "tiles"
|
|
430
|
+
|
|
431
|
+
# Create progress dialog
|
|
432
|
+
window_title = "Background reconstruction"
|
|
433
|
+
self.bg_progress = CelldetectiveProgressDialog(
|
|
434
|
+
"Loading libraries...", "Cancel", 0, 100, None, window_title=window_title
|
|
435
|
+
)
|
|
436
|
+
|
|
437
|
+
self.bg_worker = BackgroundEstimatorThread(
|
|
438
|
+
self.attr_parent.exp_dir,
|
|
439
|
+
self.well_slider.value() - 1,
|
|
440
|
+
self.frame_range_slider.value(),
|
|
441
|
+
self.channels_cb.currentText(),
|
|
442
|
+
self.threshold_le.get_threshold(),
|
|
443
|
+
mode,
|
|
444
|
+
)
|
|
445
|
+
from celldetective.gui.viewers.base_viewer import StackVisualizer
|
|
446
|
+
|
|
447
|
+
self.bg_worker.progress.connect(self.bg_progress.setValue)
|
|
448
|
+
self.bg_worker.status_update.connect(self.bg_progress.setLabelText)
|
|
449
|
+
|
|
450
|
+
def on_finished(bg):
|
|
451
|
+
self.bg_progress.blockSignals(True)
|
|
452
|
+
self.bg_progress.close()
|
|
453
|
+
if self.bg_worker._is_cancelled:
|
|
454
|
+
logger.info("Background estimation cancelled.")
|
|
455
|
+
return
|
|
456
|
+
|
|
457
|
+
if bg and len(bg) > 0:
|
|
458
|
+
bg_img = bg[0]["bg"]
|
|
459
|
+
if len(bg_img) > 0:
|
|
460
|
+
self.viewer = StackVisualizer(
|
|
461
|
+
stack=[bg_img],
|
|
462
|
+
window_title="Reconstructed background",
|
|
463
|
+
frame_slider=False,
|
|
464
|
+
)
|
|
465
|
+
self.viewer.show()
|
|
466
|
+
else:
|
|
467
|
+
QMessageBox.warning(
|
|
468
|
+
None, "Warning", "Background estimation returned empty image."
|
|
469
|
+
)
|
|
470
|
+
elif bg is None:
|
|
471
|
+
QMessageBox.critical(None, "Error", "Background estimation failed.")
|
|
472
|
+
|
|
473
|
+
self.bg_worker.finished_with_result.connect(on_finished)
|
|
474
|
+
self.bg_progress.canceled.connect(self.bg_worker.stop)
|
|
475
|
+
|
|
476
|
+
self.bg_worker.start()
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
class BackgroundEstimatorThread(QThread):
|
|
480
|
+
progress = pyqtSignal(int)
|
|
481
|
+
finished_with_result = pyqtSignal(object)
|
|
482
|
+
status_update = pyqtSignal(str)
|
|
483
|
+
|
|
484
|
+
def __init__(self, exp_dir, well_idx, frame_range, channel, threshold, mode):
|
|
485
|
+
super().__init__()
|
|
486
|
+
self.exp_dir = exp_dir
|
|
487
|
+
self.well_idx = well_idx
|
|
488
|
+
self.frame_range = frame_range
|
|
489
|
+
self.channel = channel
|
|
490
|
+
self.threshold = threshold
|
|
491
|
+
self.mode = mode
|
|
492
|
+
self._is_cancelled = False
|
|
493
|
+
|
|
494
|
+
def stop(self):
|
|
495
|
+
self._is_cancelled = True
|
|
496
|
+
|
|
497
|
+
def run(self):
|
|
498
|
+
from celldetective.preprocessing import estimate_background_per_condition
|
|
499
|
+
|
|
500
|
+
self.first_update = True
|
|
501
|
+
|
|
502
|
+
def callback(**kwargs):
|
|
503
|
+
if self._is_cancelled:
|
|
504
|
+
return False
|
|
505
|
+
|
|
506
|
+
if self.first_update:
|
|
507
|
+
self.status_update.emit("Estimating background...")
|
|
508
|
+
self.first_update = False
|
|
509
|
+
|
|
510
|
+
if kwargs.get("level") == "position":
|
|
511
|
+
|
|
512
|
+
iter_ = kwargs.get("iter", 0)
|
|
513
|
+
total = kwargs.get("total", 1)
|
|
514
|
+
# Avoid division by zero
|
|
515
|
+
if total > 0:
|
|
516
|
+
p = int((iter_ / total) * 100)
|
|
517
|
+
self.progress.emit(p)
|
|
518
|
+
return True
|
|
519
|
+
|
|
520
|
+
try:
|
|
521
|
+
bg = estimate_background_per_condition(
|
|
522
|
+
self.exp_dir,
|
|
523
|
+
well_option=self.well_idx,
|
|
524
|
+
frame_range=self.frame_range,
|
|
525
|
+
target_channel=self.channel,
|
|
526
|
+
show_progress_per_pos=False,
|
|
527
|
+
threshold_on_std=self.threshold,
|
|
528
|
+
mode=self.mode,
|
|
529
|
+
progress_callback=callback,
|
|
530
|
+
)
|
|
531
|
+
if not self._is_cancelled:
|
|
532
|
+
self.finished_with_result.emit(bg)
|
|
533
|
+
else:
|
|
534
|
+
self.finished_with_result.emit(None)
|
|
535
|
+
except Exception as e:
|
|
536
|
+
print(f"Error in background estimation thread: {e}")
|
|
537
|
+
self.finished_with_result.emit(None)
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
from PyQt5.QtCore import QSize
|
|
2
|
+
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QComboBox, QPushButton, QHBoxLayout
|
|
3
|
+
from fonticon_mdi6 import MDI6
|
|
4
|
+
from superqt.fonticon import icon
|
|
5
|
+
|
|
6
|
+
from celldetective.gui.base.styles import Styles
|
|
7
|
+
from celldetective.gui.gui_utils import ThresholdLineEdit
|
|
8
|
+
from celldetective.utils.parsing import _extract_channel_indices_from_config
|
|
9
|
+
from celldetective import get_logger
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
class ChannelOffsetOptionsLayout(QVBoxLayout, Styles):
|
|
14
|
+
|
|
15
|
+
def __init__(self, parent_window=None, *args, **kwargs):
|
|
16
|
+
|
|
17
|
+
super().__init__(*args, **kwargs)
|
|
18
|
+
|
|
19
|
+
self.parent_window = parent_window
|
|
20
|
+
if hasattr(self.parent_window.parent_window, "exp_config"):
|
|
21
|
+
self.attr_parent = self.parent_window.parent_window
|
|
22
|
+
else:
|
|
23
|
+
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
24
|
+
|
|
25
|
+
self.channel_names = self.attr_parent.exp_channels
|
|
26
|
+
|
|
27
|
+
self.setContentsMargins(15, 15, 15, 15)
|
|
28
|
+
self.generate_widgets()
|
|
29
|
+
self.add_to_layout()
|
|
30
|
+
|
|
31
|
+
def generate_widgets(self):
|
|
32
|
+
|
|
33
|
+
self.channel_lbl = QLabel("Channel: ")
|
|
34
|
+
self.channels_cb = QComboBox()
|
|
35
|
+
self.channels_cb.addItems(self.channel_names)
|
|
36
|
+
|
|
37
|
+
self.shift_lbl = QLabel("Shift: ")
|
|
38
|
+
self.shift_h_lbl = QLabel("(h): ")
|
|
39
|
+
self.shift_v_lbl = QLabel("(v): ")
|
|
40
|
+
|
|
41
|
+
self.set_shift_btn = QPushButton()
|
|
42
|
+
self.set_shift_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
43
|
+
self.set_shift_btn.setStyleSheet(self.button_select_all)
|
|
44
|
+
self.set_shift_btn.setToolTip("Set the channel shift.")
|
|
45
|
+
self.set_shift_btn.clicked.connect(self.open_offset_viewer)
|
|
46
|
+
|
|
47
|
+
self.add_correction_btn = QPushButton("Add correction")
|
|
48
|
+
self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
|
|
49
|
+
self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
|
|
50
|
+
self.add_correction_btn.setToolTip("Add correction.")
|
|
51
|
+
self.add_correction_btn.setIconSize(QSize(25, 25))
|
|
52
|
+
self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
|
|
53
|
+
|
|
54
|
+
self.vertical_shift_le = ThresholdLineEdit(
|
|
55
|
+
init_value=0,
|
|
56
|
+
connected_buttons=[self.add_correction_btn],
|
|
57
|
+
placeholder="vertical shift [pixels]",
|
|
58
|
+
value_type="float",
|
|
59
|
+
)
|
|
60
|
+
self.horizontal_shift_le = ThresholdLineEdit(
|
|
61
|
+
init_value=0,
|
|
62
|
+
connected_buttons=[self.add_correction_btn],
|
|
63
|
+
placeholder="vertical shift [pixels]",
|
|
64
|
+
value_type="float",
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def add_to_layout(self):
|
|
68
|
+
|
|
69
|
+
channel_ch_hbox = QHBoxLayout()
|
|
70
|
+
channel_ch_hbox.addWidget(self.channel_lbl, 25)
|
|
71
|
+
channel_ch_hbox.addWidget(self.channels_cb, 75)
|
|
72
|
+
self.addLayout(channel_ch_hbox)
|
|
73
|
+
|
|
74
|
+
shift_hbox = QHBoxLayout()
|
|
75
|
+
shift_hbox.addWidget(self.shift_lbl, 25)
|
|
76
|
+
|
|
77
|
+
shift_subhbox = QHBoxLayout()
|
|
78
|
+
shift_subhbox.addWidget(self.shift_h_lbl, 10)
|
|
79
|
+
shift_subhbox.addWidget(self.horizontal_shift_le, 75 // 2)
|
|
80
|
+
shift_subhbox.addWidget(self.shift_v_lbl, 10)
|
|
81
|
+
shift_subhbox.addWidget(self.vertical_shift_le, 75 // 2)
|
|
82
|
+
shift_subhbox.addWidget(self.set_shift_btn, 5)
|
|
83
|
+
|
|
84
|
+
shift_hbox.addLayout(shift_subhbox, 75)
|
|
85
|
+
self.addLayout(shift_hbox)
|
|
86
|
+
|
|
87
|
+
btn_hbox = QHBoxLayout()
|
|
88
|
+
btn_hbox.addWidget(self.add_correction_btn, 95)
|
|
89
|
+
self.addLayout(btn_hbox)
|
|
90
|
+
|
|
91
|
+
def add_instructions_to_parent_list(self):
|
|
92
|
+
|
|
93
|
+
self.generate_instructions()
|
|
94
|
+
self.parent_window.protocol_layout.protocols.append(self.instructions)
|
|
95
|
+
correction_description = ""
|
|
96
|
+
for index, (key, value) in enumerate(self.instructions.items()):
|
|
97
|
+
if index > 0:
|
|
98
|
+
correction_description += ", "
|
|
99
|
+
correction_description += str(key) + " : " + str(value)
|
|
100
|
+
self.parent_window.protocol_layout.protocol_list.addItem(correction_description)
|
|
101
|
+
|
|
102
|
+
def generate_instructions(self):
|
|
103
|
+
|
|
104
|
+
self.instructions = {
|
|
105
|
+
"correction_type": "offset",
|
|
106
|
+
"target_channel": self.channels_cb.currentText(),
|
|
107
|
+
"correction_horizontal": self.horizontal_shift_le.get_threshold(),
|
|
108
|
+
"correction_vertical": self.vertical_shift_le.get_threshold(),
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
def set_target_channel(self):
|
|
112
|
+
|
|
113
|
+
channel_indices = _extract_channel_indices_from_config(
|
|
114
|
+
self.attr_parent.exp_config, [self.channels_cb.currentText()]
|
|
115
|
+
)
|
|
116
|
+
self.target_channel = channel_indices[0]
|
|
117
|
+
|
|
118
|
+
def open_offset_viewer(self):
|
|
119
|
+
from celldetective.gui.viewers.channel_offset_viewer import ChannelOffsetViewer
|
|
120
|
+
|
|
121
|
+
self.attr_parent.locate_image()
|
|
122
|
+
self.set_target_channel()
|
|
123
|
+
|
|
124
|
+
if self.attr_parent.current_stack is not None:
|
|
125
|
+
self.viewer = ChannelOffsetViewer(
|
|
126
|
+
parent_window=self,
|
|
127
|
+
stack_path=self.attr_parent.current_stack,
|
|
128
|
+
channel_names=self.attr_parent.exp_channels,
|
|
129
|
+
n_channels=len(self.channel_names),
|
|
130
|
+
channel_cb=True,
|
|
131
|
+
target_channel=self.target_channel,
|
|
132
|
+
window_title="offset viewer",
|
|
133
|
+
)
|
|
134
|
+
self.viewer.show()
|