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,91 @@
|
|
|
1
|
+
from PyQt5.QtGui import QIntValidator
|
|
2
|
+
|
|
3
|
+
from celldetective.gui.layouts.model_fit_layout import BackgroundFitCorrectionLayout
|
|
4
|
+
from celldetective import get_logger
|
|
5
|
+
|
|
6
|
+
logger = get_logger(__name__)
|
|
7
|
+
|
|
8
|
+
class LocalCorrectionLayout(BackgroundFitCorrectionLayout):
|
|
9
|
+
"""docstring for ClassName"""
|
|
10
|
+
|
|
11
|
+
def __init__(self, *args):
|
|
12
|
+
|
|
13
|
+
super().__init__(*args)
|
|
14
|
+
|
|
15
|
+
if hasattr(self.parent_window.parent_window, "locate_image"):
|
|
16
|
+
self.attr_parent = self.parent_window.parent_window
|
|
17
|
+
elif hasattr(self.parent_window.parent_window.parent_window, "locate_image"):
|
|
18
|
+
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
19
|
+
else:
|
|
20
|
+
self.attr_parent = (
|
|
21
|
+
self.parent_window.parent_window.parent_window.parent_window
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
self.thresh_lbl.setText("Distance: ")
|
|
25
|
+
self.thresh_lbl.setToolTip(
|
|
26
|
+
"Distance from the cell mask over which to estimate local intensity."
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
self.models_cb.clear()
|
|
30
|
+
self.models_cb.addItems(["mean", "median"])
|
|
31
|
+
|
|
32
|
+
self.threshold_le.set_threshold(5)
|
|
33
|
+
self.threshold_le.connected_buttons = [
|
|
34
|
+
self.threshold_viewer_btn,
|
|
35
|
+
self.add_correction_btn,
|
|
36
|
+
]
|
|
37
|
+
self.threshold_le.setValidator(QIntValidator())
|
|
38
|
+
|
|
39
|
+
self.threshold_viewer_btn.disconnect()
|
|
40
|
+
self.threshold_viewer_btn.clicked.connect(self.set_distance_graphically)
|
|
41
|
+
|
|
42
|
+
self.corrected_stack_viewer.hide()
|
|
43
|
+
|
|
44
|
+
def set_distance_graphically(self):
|
|
45
|
+
from celldetective.gui.viewers.contour_viewer import CellEdgeVisualizer
|
|
46
|
+
|
|
47
|
+
self.attr_parent.locate_image()
|
|
48
|
+
self.set_target_channel()
|
|
49
|
+
thresh = self.threshold_le.get_threshold()
|
|
50
|
+
|
|
51
|
+
if self.attr_parent.current_stack is not None and thresh is not None:
|
|
52
|
+
|
|
53
|
+
self.viewer = CellEdgeVisualizer(
|
|
54
|
+
cell_type=self.parent_window.parent_window.mode,
|
|
55
|
+
stack_path=self.attr_parent.current_stack,
|
|
56
|
+
parent_le=self.threshold_le,
|
|
57
|
+
n_channels=len(self.channel_names),
|
|
58
|
+
target_channel=self.channels_cb.currentIndex(),
|
|
59
|
+
edge_range=(0, 30),
|
|
60
|
+
initial_edge=int(thresh),
|
|
61
|
+
invert=True,
|
|
62
|
+
window_title="Set an edge distance to estimate local intensity",
|
|
63
|
+
channel_cb=False,
|
|
64
|
+
PxToUm=1,
|
|
65
|
+
)
|
|
66
|
+
self.viewer.show()
|
|
67
|
+
|
|
68
|
+
def generate_instructions(self):
|
|
69
|
+
|
|
70
|
+
if self.operation_layout.subtract_btn.isChecked():
|
|
71
|
+
operation = "subtract"
|
|
72
|
+
else:
|
|
73
|
+
operation = "divide"
|
|
74
|
+
clip = None
|
|
75
|
+
|
|
76
|
+
if (
|
|
77
|
+
self.operation_layout.clip_btn.isChecked()
|
|
78
|
+
and self.operation_layout.subtract_btn.isChecked()
|
|
79
|
+
):
|
|
80
|
+
clip = True
|
|
81
|
+
else:
|
|
82
|
+
clip = False
|
|
83
|
+
|
|
84
|
+
self.instructions = {
|
|
85
|
+
"target_channel": self.channels_cb.currentText(),
|
|
86
|
+
"correction_type": "local",
|
|
87
|
+
"model": self.models_cb.currentText(),
|
|
88
|
+
"distance": int(self.threshold_le.get_threshold()),
|
|
89
|
+
"operation": operation,
|
|
90
|
+
"clip": clip,
|
|
91
|
+
}
|
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from PyQt5.QtCore import QSize, QThread, pyqtSignal
|
|
3
|
+
from PyQt5.QtGui import QIntValidator
|
|
4
|
+
from PyQt5.QtWidgets import (
|
|
5
|
+
QGridLayout,
|
|
6
|
+
QLabel,
|
|
7
|
+
QComboBox,
|
|
8
|
+
QPushButton,
|
|
9
|
+
QLineEdit,
|
|
10
|
+
QHBoxLayout,
|
|
11
|
+
QSpacerItem,
|
|
12
|
+
QSizePolicy,
|
|
13
|
+
QMessageBox,
|
|
14
|
+
)
|
|
15
|
+
from fonticon_mdi6 import MDI6
|
|
16
|
+
from superqt.fonticon import icon
|
|
17
|
+
|
|
18
|
+
from celldetective.gui.base.components import CelldetectiveProgressDialog
|
|
19
|
+
from celldetective.gui.base.styles import Styles
|
|
20
|
+
from celldetective.gui.gui_utils import ThresholdLineEdit
|
|
21
|
+
from celldetective.gui.layouts.operation_layout import OperationLayout
|
|
22
|
+
from celldetective.preprocessing import correct_background_model
|
|
23
|
+
from celldetective.processes.background_correction import BackgroundCorrectionProcess
|
|
24
|
+
from celldetective.utils.image_loaders import auto_load_number_of_frames
|
|
25
|
+
from celldetective.utils.parsing import _extract_channel_indices_from_config
|
|
26
|
+
from celldetective import get_logger
|
|
27
|
+
from celldetective.gui.viewers.base_viewer import StackVisualizer
|
|
28
|
+
from celldetective.gui.viewers.threshold_viewer import ThresholdedStackVisualizer
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class BackgroundFitCorrectionLayout(QGridLayout, Styles):
|
|
34
|
+
"""docstring for ClassName"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, parent_window=None, *args):
|
|
37
|
+
super().__init__(*args)
|
|
38
|
+
|
|
39
|
+
self.parent_window = parent_window
|
|
40
|
+
|
|
41
|
+
if hasattr(self.parent_window.parent_window, "locate_image"):
|
|
42
|
+
self.attr_parent = self.parent_window.parent_window
|
|
43
|
+
elif hasattr(self.parent_window.parent_window.parent_window, "locate_image"):
|
|
44
|
+
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
45
|
+
else:
|
|
46
|
+
self.attr_parent = (
|
|
47
|
+
self.parent_window.parent_window.parent_window.parent_window
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
self.channel_names = self.attr_parent.exp_channels
|
|
51
|
+
self.setContentsMargins(15, 15, 15, 15)
|
|
52
|
+
self.generate_widgets()
|
|
53
|
+
self.add_to_layout()
|
|
54
|
+
|
|
55
|
+
def generate_widgets(self):
|
|
56
|
+
|
|
57
|
+
self.channel_lbl = QLabel("Channel: ")
|
|
58
|
+
self.channels_cb = QComboBox()
|
|
59
|
+
self.channels_cb.addItems(self.channel_names)
|
|
60
|
+
|
|
61
|
+
self.thresh_lbl = QLabel("Threshold: ")
|
|
62
|
+
self.thresh_lbl.setToolTip(
|
|
63
|
+
"Threshold on the STD-filtered image.\nPixel values above the threshold are\nconsidered as non-background and are\nmasked prior to background estimation."
|
|
64
|
+
)
|
|
65
|
+
self.threshold_viewer_btn = QPushButton()
|
|
66
|
+
self.threshold_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
67
|
+
self.threshold_viewer_btn.setStyleSheet(self.button_select_all)
|
|
68
|
+
self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
|
|
69
|
+
self.threshold_viewer_btn.setToolTip("Set the threshold graphically.")
|
|
70
|
+
|
|
71
|
+
self.model_lbl = QLabel("Model: ")
|
|
72
|
+
self.model_lbl.setToolTip("2D model to fit the background with.")
|
|
73
|
+
self.models_cb = QComboBox()
|
|
74
|
+
self.models_cb.addItems(["paraboloid", "plane"])
|
|
75
|
+
self.models_cb.setToolTip("2D model to fit the background with.")
|
|
76
|
+
|
|
77
|
+
self.corrected_stack_viewer = QPushButton("")
|
|
78
|
+
self.corrected_stack_viewer.setStyleSheet(self.button_select_all)
|
|
79
|
+
self.corrected_stack_viewer.setIcon(icon(MDI6.eye_outline, color="black"))
|
|
80
|
+
self.corrected_stack_viewer.setToolTip("View corrected image")
|
|
81
|
+
self.corrected_stack_viewer.clicked.connect(self.preview_correction)
|
|
82
|
+
self.corrected_stack_viewer.setIconSize(QSize(20, 20))
|
|
83
|
+
|
|
84
|
+
self.add_correction_btn = QPushButton("Add correction")
|
|
85
|
+
self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
|
|
86
|
+
self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
|
|
87
|
+
self.add_correction_btn.setToolTip("Add correction.")
|
|
88
|
+
self.add_correction_btn.setIconSize(QSize(25, 25))
|
|
89
|
+
self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
|
|
90
|
+
|
|
91
|
+
self.threshold_le = ThresholdLineEdit(
|
|
92
|
+
init_value=2,
|
|
93
|
+
connected_buttons=[
|
|
94
|
+
self.threshold_viewer_btn,
|
|
95
|
+
self.corrected_stack_viewer,
|
|
96
|
+
self.add_correction_btn,
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
self.downsample_lbl = QLabel("Downsample: ")
|
|
101
|
+
self.downsample_lbl.setToolTip(
|
|
102
|
+
"Factor by which to downsample the image for fitting (for speed)."
|
|
103
|
+
)
|
|
104
|
+
self.downsample_le = QLineEdit("10")
|
|
105
|
+
self.downsample_le.setValidator(QIntValidator())
|
|
106
|
+
|
|
107
|
+
def add_to_layout(self):
|
|
108
|
+
|
|
109
|
+
channel_layout = QHBoxLayout()
|
|
110
|
+
channel_layout.addWidget(self.channel_lbl, 25)
|
|
111
|
+
channel_layout.addWidget(self.channels_cb, 75)
|
|
112
|
+
self.addLayout(channel_layout, 0, 0, 1, 3)
|
|
113
|
+
|
|
114
|
+
threshold_layout = QHBoxLayout()
|
|
115
|
+
threshold_layout.addWidget(self.thresh_lbl, 25)
|
|
116
|
+
subthreshold_layout = QHBoxLayout()
|
|
117
|
+
subthreshold_layout.addWidget(self.threshold_le, 95)
|
|
118
|
+
subthreshold_layout.addWidget(self.threshold_viewer_btn, 5)
|
|
119
|
+
|
|
120
|
+
threshold_layout.addLayout(subthreshold_layout, 75)
|
|
121
|
+
self.addLayout(threshold_layout, 1, 0, 1, 3)
|
|
122
|
+
|
|
123
|
+
model_layout = QHBoxLayout()
|
|
124
|
+
model_layout.addWidget(self.model_lbl, 25)
|
|
125
|
+
model_layout.addWidget(self.models_cb, 75)
|
|
126
|
+
self.addLayout(model_layout, 2, 0, 1, 3)
|
|
127
|
+
|
|
128
|
+
downsample_layout = QHBoxLayout()
|
|
129
|
+
downsample_layout.addWidget(self.downsample_lbl, 25)
|
|
130
|
+
downsample_layout.addWidget(self.downsample_le, 75)
|
|
131
|
+
self.addLayout(downsample_layout, 3, 0, 1, 3)
|
|
132
|
+
|
|
133
|
+
self.operation_layout = OperationLayout()
|
|
134
|
+
self.addLayout(self.operation_layout, 4, 0, 1, 3)
|
|
135
|
+
|
|
136
|
+
correction_layout = QHBoxLayout()
|
|
137
|
+
correction_layout.addWidget(self.add_correction_btn, 95)
|
|
138
|
+
correction_layout.addWidget(self.corrected_stack_viewer, 5)
|
|
139
|
+
self.addLayout(correction_layout, 5, 0, 1, 3)
|
|
140
|
+
|
|
141
|
+
verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
142
|
+
self.addItem(verticalSpacer, 6, 0, 1, 3)
|
|
143
|
+
|
|
144
|
+
def add_instructions_to_parent_list(self):
|
|
145
|
+
|
|
146
|
+
self.generate_instructions()
|
|
147
|
+
self.parent_window.protocols.append(self.instructions)
|
|
148
|
+
correction_description = ""
|
|
149
|
+
for index, (key, value) in enumerate(self.instructions.items()):
|
|
150
|
+
if index > 0:
|
|
151
|
+
correction_description += ", "
|
|
152
|
+
correction_description += str(key) + " : " + str(value)
|
|
153
|
+
self.parent_window.protocol_list.addItem(correction_description)
|
|
154
|
+
|
|
155
|
+
def generate_instructions(self):
|
|
156
|
+
|
|
157
|
+
if self.operation_layout.subtract_btn.isChecked():
|
|
158
|
+
operation = "subtract"
|
|
159
|
+
else:
|
|
160
|
+
operation = "divide"
|
|
161
|
+
|
|
162
|
+
if (
|
|
163
|
+
self.operation_layout.clip_btn.isChecked()
|
|
164
|
+
and self.operation_layout.subtract_btn.isChecked()
|
|
165
|
+
):
|
|
166
|
+
clip = True
|
|
167
|
+
else:
|
|
168
|
+
clip = False
|
|
169
|
+
|
|
170
|
+
self.instructions = {
|
|
171
|
+
"target_channel": self.channels_cb.currentText(),
|
|
172
|
+
"correction_type": "fit",
|
|
173
|
+
"model": self.models_cb.currentText(),
|
|
174
|
+
"threshold_on_std": self.threshold_le.get_threshold(),
|
|
175
|
+
"operation": operation,
|
|
176
|
+
"clip": clip,
|
|
177
|
+
"downsample": int(self.downsample_le.text()),
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
def set_target_channel(self):
|
|
181
|
+
|
|
182
|
+
channel_indices = _extract_channel_indices_from_config(
|
|
183
|
+
self.attr_parent.exp_config, [self.channels_cb.currentText()]
|
|
184
|
+
)
|
|
185
|
+
self.target_channel = channel_indices[0]
|
|
186
|
+
|
|
187
|
+
def set_threshold_graphically(self):
|
|
188
|
+
|
|
189
|
+
self.attr_parent.locate_image()
|
|
190
|
+
self.set_target_channel()
|
|
191
|
+
thresh = self.threshold_le.get_threshold()
|
|
192
|
+
|
|
193
|
+
if self.attr_parent.current_stack is not None and thresh is not None:
|
|
194
|
+
self.viewer = ThresholdedStackVisualizer(
|
|
195
|
+
initial_threshold=thresh,
|
|
196
|
+
parent_le=self.threshold_le,
|
|
197
|
+
preprocessing=[["gauss", 2], ["std", 4]],
|
|
198
|
+
stack_path=self.attr_parent.current_stack,
|
|
199
|
+
n_channels=len(self.channel_names),
|
|
200
|
+
target_channel=self.target_channel,
|
|
201
|
+
window_title="Set the exclusion threshold",
|
|
202
|
+
)
|
|
203
|
+
self.viewer.show()
|
|
204
|
+
|
|
205
|
+
def preview_correction(self):
|
|
206
|
+
|
|
207
|
+
if (
|
|
208
|
+
self.attr_parent.well_list.isMultipleSelection()
|
|
209
|
+
or not self.attr_parent.well_list.isAnySelected()
|
|
210
|
+
or self.attr_parent.position_list.isMultipleSelection()
|
|
211
|
+
or not self.attr_parent.position_list.isAnySelected()
|
|
212
|
+
):
|
|
213
|
+
|
|
214
|
+
msgBox = QMessageBox()
|
|
215
|
+
msgBox.setIcon(QMessageBox.Critical)
|
|
216
|
+
msgBox.setText("Please select a single position...")
|
|
217
|
+
msgBox.setWindowTitle("Critical")
|
|
218
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
219
|
+
returnValue = msgBox.exec()
|
|
220
|
+
if returnValue == QMessageBox.Ok:
|
|
221
|
+
return None
|
|
222
|
+
|
|
223
|
+
if self.operation_layout.subtract_btn.isChecked():
|
|
224
|
+
operation = "subtract"
|
|
225
|
+
else:
|
|
226
|
+
operation = "divide"
|
|
227
|
+
|
|
228
|
+
if (
|
|
229
|
+
self.operation_layout.clip_btn.isChecked()
|
|
230
|
+
and self.operation_layout.subtract_btn.isChecked()
|
|
231
|
+
):
|
|
232
|
+
clip = True
|
|
233
|
+
else:
|
|
234
|
+
clip = False
|
|
235
|
+
|
|
236
|
+
self.attr_parent.locate_image()
|
|
237
|
+
self.set_target_channel()
|
|
238
|
+
|
|
239
|
+
subset_indices = None
|
|
240
|
+
if (
|
|
241
|
+
hasattr(self.attr_parent, "current_stack")
|
|
242
|
+
and self.attr_parent.current_stack is not None
|
|
243
|
+
):
|
|
244
|
+
|
|
245
|
+
n_frames = auto_load_number_of_frames(self.attr_parent.current_stack)
|
|
246
|
+
if n_frames is not None:
|
|
247
|
+
midpoint = int(n_frames // 2)
|
|
248
|
+
n_channels = len(self.attr_parent.exp_channels)
|
|
249
|
+
subset_indices = [midpoint * n_channels]
|
|
250
|
+
|
|
251
|
+
process_args = {
|
|
252
|
+
"exp_dir": self.attr_parent.exp_dir,
|
|
253
|
+
"well_option": self.attr_parent.well_list.getSelectedIndices(),
|
|
254
|
+
"position_option": self.attr_parent.position_list.getSelectedIndices(),
|
|
255
|
+
"target_channel": self.channels_cb.currentText(),
|
|
256
|
+
"model": self.models_cb.currentText(),
|
|
257
|
+
"threshold_on_std": self.threshold_le.get_threshold(),
|
|
258
|
+
"operation": operation,
|
|
259
|
+
"clip": clip,
|
|
260
|
+
"activation_protocol": [["gauss", 2], ["std", 4]],
|
|
261
|
+
"downsample": int(self.downsample_le.text()),
|
|
262
|
+
"subset_indices": subset_indices,
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
self.bg_progress = CelldetectiveProgressDialog(
|
|
266
|
+
"Correcting background (Preview)...",
|
|
267
|
+
"Cancel",
|
|
268
|
+
0,
|
|
269
|
+
0,
|
|
270
|
+
None,
|
|
271
|
+
window_title="Processing",
|
|
272
|
+
)
|
|
273
|
+
self.bg_progress.setRange(0, 0)
|
|
274
|
+
|
|
275
|
+
self.preview_worker = PreviewWorker(
|
|
276
|
+
BackgroundCorrectionProcess, process_args=process_args
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
def on_result(corrected_stack):
|
|
280
|
+
|
|
281
|
+
if corrected_stack is not None:
|
|
282
|
+
if subset_indices is not None and len(self.channel_names) > 0:
|
|
283
|
+
# Logic to extract the specific channel
|
|
284
|
+
if corrected_stack.ndim == 3 and corrected_stack.shape[0] == len(
|
|
285
|
+
self.channel_names
|
|
286
|
+
):
|
|
287
|
+
# Shape likely (C, Y, X)
|
|
288
|
+
corrected_stack = corrected_stack[
|
|
289
|
+
self.channels_cb.currentIndex()
|
|
290
|
+
]
|
|
291
|
+
elif corrected_stack.ndim == 4 and corrected_stack.shape[-1] == len(
|
|
292
|
+
self.channel_names
|
|
293
|
+
):
|
|
294
|
+
# Shape likely (T, Y, X, C)
|
|
295
|
+
corrected_stack = corrected_stack[
|
|
296
|
+
..., self.channels_cb.currentIndex()
|
|
297
|
+
]
|
|
298
|
+
|
|
299
|
+
# Ensure (T=1, Y, X, C=1) for display or similar that StackVisualizer likes for single channel
|
|
300
|
+
if corrected_stack.ndim == 2:
|
|
301
|
+
corrected_stack = corrected_stack[np.newaxis, :, :, np.newaxis]
|
|
302
|
+
elif corrected_stack.ndim == 3:
|
|
303
|
+
# (1, Y, X)
|
|
304
|
+
corrected_stack = corrected_stack[:, :, :, np.newaxis]
|
|
305
|
+
|
|
306
|
+
self.viewer = StackVisualizer(
|
|
307
|
+
stack=corrected_stack,
|
|
308
|
+
window_title="Corrected channel",
|
|
309
|
+
target_channel=0,
|
|
310
|
+
frame_slider=True,
|
|
311
|
+
contrast_slider=True,
|
|
312
|
+
)
|
|
313
|
+
self.viewer.show()
|
|
314
|
+
else:
|
|
315
|
+
print("Corrected stack could not be generated...")
|
|
316
|
+
|
|
317
|
+
def on_finished():
|
|
318
|
+
self.bg_progress.close()
|
|
319
|
+
|
|
320
|
+
def on_error(msg):
|
|
321
|
+
self.bg_progress.close()
|
|
322
|
+
QMessageBox.critical(None, "Error", f"Correction failed: {msg}")
|
|
323
|
+
|
|
324
|
+
self.preview_worker.result_ready.connect(on_result)
|
|
325
|
+
self.preview_worker.finished.connect(on_finished)
|
|
326
|
+
self.preview_worker.error.connect(on_error)
|
|
327
|
+
self.bg_progress.canceled.connect(self.preview_worker.stop)
|
|
328
|
+
|
|
329
|
+
self.preview_worker.start()
|
|
330
|
+
self.bg_progress.exec_()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
class PreviewWorker(QThread):
|
|
334
|
+
finished = pyqtSignal()
|
|
335
|
+
result_ready = pyqtSignal(object)
|
|
336
|
+
error = pyqtSignal(str)
|
|
337
|
+
|
|
338
|
+
def __init__(self, process_class, process_args):
|
|
339
|
+
super().__init__()
|
|
340
|
+
# process_class is unused now as we call function directly
|
|
341
|
+
self.process_args = process_args
|
|
342
|
+
|
|
343
|
+
def run(self):
|
|
344
|
+
try:
|
|
345
|
+
result = correct_background_model(
|
|
346
|
+
experiment=self.process_args["exp_dir"],
|
|
347
|
+
well_option=self.process_args["well_option"],
|
|
348
|
+
position_option=self.process_args["position_option"],
|
|
349
|
+
target_channel=self.process_args["target_channel"],
|
|
350
|
+
model=self.process_args["model"],
|
|
351
|
+
threshold_on_std=self.process_args["threshold_on_std"],
|
|
352
|
+
operation=self.process_args["operation"],
|
|
353
|
+
clip=self.process_args["clip"],
|
|
354
|
+
export=False,
|
|
355
|
+
return_stacks=True,
|
|
356
|
+
activation_protocol=self.process_args["activation_protocol"],
|
|
357
|
+
downsample=self.process_args["downsample"],
|
|
358
|
+
subset_indices=self.process_args["subset_indices"],
|
|
359
|
+
show_progress_per_well=False,
|
|
360
|
+
show_progress_per_pos=False,
|
|
361
|
+
)
|
|
362
|
+
|
|
363
|
+
if result is not None and len(result) > 0:
|
|
364
|
+
self.result_ready.emit(result[0])
|
|
365
|
+
|
|
366
|
+
except Exception as e:
|
|
367
|
+
self.error.emit(str(e))
|
|
368
|
+
|
|
369
|
+
self.finished.emit()
|
|
370
|
+
|
|
371
|
+
def stop(self):
|
|
372
|
+
self.quit()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from PyQt5.QtCore import Qt
|
|
2
|
+
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QButtonGroup, QRadioButton, QHBoxLayout
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OperationLayout(QVBoxLayout):
|
|
6
|
+
"""docstring for ClassName"""
|
|
7
|
+
|
|
8
|
+
def __init__(self, ratio=(0.25, 0.75), *args):
|
|
9
|
+
|
|
10
|
+
super().__init__(*args)
|
|
11
|
+
|
|
12
|
+
self.ratio = ratio
|
|
13
|
+
self.generate_widgets()
|
|
14
|
+
self.generate_layout()
|
|
15
|
+
|
|
16
|
+
def generate_widgets(self):
|
|
17
|
+
|
|
18
|
+
self.operation_lbl = QLabel("Operation: ")
|
|
19
|
+
self.operation_group = QButtonGroup()
|
|
20
|
+
self.subtract_btn = QRadioButton("Subtract")
|
|
21
|
+
self.divide_btn = QRadioButton("Divide")
|
|
22
|
+
self.subtract_btn.toggled.connect(self.activate_clipping_options)
|
|
23
|
+
self.divide_btn.toggled.connect(self.activate_clipping_options)
|
|
24
|
+
|
|
25
|
+
self.operation_group.addButton(self.subtract_btn)
|
|
26
|
+
self.operation_group.addButton(self.divide_btn)
|
|
27
|
+
|
|
28
|
+
self.clip_group = QButtonGroup()
|
|
29
|
+
self.clip_btn = QRadioButton("Clip")
|
|
30
|
+
self.clip_not_btn = QRadioButton("Do not clip")
|
|
31
|
+
|
|
32
|
+
self.clip_group.addButton(self.clip_btn)
|
|
33
|
+
self.clip_group.addButton(self.clip_not_btn)
|
|
34
|
+
|
|
35
|
+
def generate_layout(self):
|
|
36
|
+
|
|
37
|
+
operation_layout = QHBoxLayout()
|
|
38
|
+
operation_layout.addWidget(self.operation_lbl, 100 * int(self.ratio[0]))
|
|
39
|
+
operation_layout.addWidget(
|
|
40
|
+
self.subtract_btn, 100 * int(self.ratio[1]) // 2, alignment=Qt.AlignCenter
|
|
41
|
+
)
|
|
42
|
+
operation_layout.addWidget(
|
|
43
|
+
self.divide_btn, 100 * int(self.ratio[1]) // 2, alignment=Qt.AlignCenter
|
|
44
|
+
)
|
|
45
|
+
self.addLayout(operation_layout)
|
|
46
|
+
|
|
47
|
+
clip_layout = QHBoxLayout()
|
|
48
|
+
clip_layout.addWidget(QLabel(""), 100 * int(self.ratio[0]))
|
|
49
|
+
clip_layout.addWidget(
|
|
50
|
+
self.clip_btn, 100 * int(self.ratio[1]) // 4, alignment=Qt.AlignCenter
|
|
51
|
+
)
|
|
52
|
+
clip_layout.addWidget(
|
|
53
|
+
self.clip_not_btn, 100 * int(self.ratio[1]) // 4, alignment=Qt.AlignCenter
|
|
54
|
+
)
|
|
55
|
+
clip_layout.addWidget(QLabel(""), 100 * int(self.ratio[1]) // 2)
|
|
56
|
+
self.addLayout(clip_layout)
|
|
57
|
+
|
|
58
|
+
self.subtract_btn.click()
|
|
59
|
+
self.clip_not_btn.click()
|
|
60
|
+
|
|
61
|
+
def activate_clipping_options(self):
|
|
62
|
+
|
|
63
|
+
if self.subtract_btn.isChecked():
|
|
64
|
+
self.clip_btn.setEnabled(True)
|
|
65
|
+
self.clip_not_btn.setEnabled(True)
|
|
66
|
+
else:
|
|
67
|
+
self.clip_btn.setEnabled(False)
|
|
68
|
+
self.clip_not_btn.setEnabled(False)
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from PyQt5.QtCore import QSize, Qt
|
|
2
|
+
from PyQt5.QtWidgets import QVBoxLayout, QLabel, QTabWidget, QSizePolicy, QListWidget, QPushButton, QHBoxLayout
|
|
3
|
+
from fonticon_mdi6 import MDI6
|
|
4
|
+
from superqt.fonticon import icon
|
|
5
|
+
|
|
6
|
+
from celldetective.gui.base.components import CelldetectiveWidget
|
|
7
|
+
from celldetective.gui.base.styles import Styles
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ProtocolDesignerLayout(QVBoxLayout, Styles):
|
|
11
|
+
"""Multi tabs and list widget configuration for background correction
|
|
12
|
+
in preprocessing and measurements
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
parent_window=None,
|
|
18
|
+
tab_layouts=[],
|
|
19
|
+
tab_names=[],
|
|
20
|
+
title="",
|
|
21
|
+
list_title="",
|
|
22
|
+
*args,
|
|
23
|
+
):
|
|
24
|
+
|
|
25
|
+
super().__init__(*args)
|
|
26
|
+
|
|
27
|
+
self.title = title
|
|
28
|
+
self.parent_window = parent_window
|
|
29
|
+
self.channel_names = self.parent_window.channel_names
|
|
30
|
+
self.tab_layouts = tab_layouts
|
|
31
|
+
self.tab_names = tab_names
|
|
32
|
+
self.list_title = list_title
|
|
33
|
+
self.protocols = []
|
|
34
|
+
assert len(self.tab_layouts) == len(self.tab_names)
|
|
35
|
+
|
|
36
|
+
self.generate_widgets()
|
|
37
|
+
self.generate_layout()
|
|
38
|
+
|
|
39
|
+
def generate_widgets(self):
|
|
40
|
+
|
|
41
|
+
self.title_lbl = QLabel(self.title)
|
|
42
|
+
self.title_lbl.setStyleSheet(
|
|
43
|
+
"""
|
|
44
|
+
font-weight: bold;
|
|
45
|
+
padding: 0px;
|
|
46
|
+
"""
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
self.tabs = QTabWidget()
|
|
50
|
+
self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
51
|
+
|
|
52
|
+
for k in range(len(self.tab_layouts)):
|
|
53
|
+
wg = CelldetectiveWidget()
|
|
54
|
+
self.tab_layouts[k].parent_window = self
|
|
55
|
+
wg.setLayout(self.tab_layouts[k])
|
|
56
|
+
self.tabs.addTab(wg, self.tab_names[k])
|
|
57
|
+
|
|
58
|
+
self.protocol_list_lbl = QLabel(self.list_title)
|
|
59
|
+
self.protocol_list = QListWidget()
|
|
60
|
+
|
|
61
|
+
self.delete_protocol_btn = QPushButton("")
|
|
62
|
+
self.delete_protocol_btn.setStyleSheet(self.button_select_all)
|
|
63
|
+
self.delete_protocol_btn.setIcon(icon(MDI6.trash_can, color="black"))
|
|
64
|
+
self.delete_protocol_btn.setToolTip("Remove.")
|
|
65
|
+
self.delete_protocol_btn.setIconSize(QSize(20, 20))
|
|
66
|
+
self.delete_protocol_btn.clicked.connect(self.remove_protocol_from_list)
|
|
67
|
+
|
|
68
|
+
def generate_layout(self):
|
|
69
|
+
|
|
70
|
+
self.correction_layout = QVBoxLayout()
|
|
71
|
+
|
|
72
|
+
self.background_correction_layout = QVBoxLayout()
|
|
73
|
+
self.background_correction_layout.setContentsMargins(0, 0, 0, 0)
|
|
74
|
+
self.title_layout = QHBoxLayout()
|
|
75
|
+
self.title_layout.addWidget(self.title_lbl, 100, alignment=Qt.AlignCenter)
|
|
76
|
+
self.background_correction_layout.addLayout(self.title_layout)
|
|
77
|
+
self.background_correction_layout.addWidget(self.tabs)
|
|
78
|
+
self.correction_layout.addLayout(self.background_correction_layout)
|
|
79
|
+
|
|
80
|
+
self.addLayout(self.correction_layout)
|
|
81
|
+
|
|
82
|
+
self.list_layout = QVBoxLayout()
|
|
83
|
+
list_header_layout = QHBoxLayout()
|
|
84
|
+
list_header_layout.addWidget(self.protocol_list_lbl)
|
|
85
|
+
list_header_layout.addWidget(self.delete_protocol_btn, alignment=Qt.AlignRight)
|
|
86
|
+
self.list_layout.addLayout(list_header_layout)
|
|
87
|
+
self.list_layout.addWidget(self.protocol_list)
|
|
88
|
+
|
|
89
|
+
self.addLayout(self.list_layout)
|
|
90
|
+
|
|
91
|
+
def remove_protocol_from_list(self):
|
|
92
|
+
|
|
93
|
+
current_item = self.protocol_list.currentRow()
|
|
94
|
+
if current_item > -1:
|
|
95
|
+
del self.protocols[current_item]
|
|
96
|
+
self.protocol_list.takeItem(current_item)
|