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
celldetective/gui/layouts.py
DELETED
|
@@ -1,1602 +0,0 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import QCheckBox, QLineEdit, QListWidget, QTabWidget, QHBoxLayout,QMessageBox, QPushButton, QVBoxLayout, QRadioButton, QLabel, QButtonGroup, QSizePolicy, QComboBox,QSpacerItem, QGridLayout
|
|
2
|
-
from celldetective.gui.gui_utils import ThresholdLineEdit, QuickSliderLayout, center_window
|
|
3
|
-
from PyQt5.QtCore import Qt, QSize
|
|
4
|
-
from PyQt5.QtGui import QIntValidator, QDoubleValidator
|
|
5
|
-
|
|
6
|
-
from superqt import QLabeledRangeSlider, QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider, QSearchableComboBox
|
|
7
|
-
|
|
8
|
-
from superqt.fonticon import icon
|
|
9
|
-
from fonticon_mdi6 import MDI6
|
|
10
|
-
from celldetective.utils import _extract_channel_indices_from_config
|
|
11
|
-
from celldetective.gui.viewers import ThresholdedStackVisualizer, CellEdgeVisualizer, StackVisualizer, CellSizeViewer, ChannelOffsetViewer
|
|
12
|
-
from celldetective.gui import Styles, CelldetectiveWidget
|
|
13
|
-
from celldetective.preprocessing import correct_background_model, correct_background_model_free, estimate_background_per_condition
|
|
14
|
-
from functools import partial
|
|
15
|
-
from glob import glob
|
|
16
|
-
import os
|
|
17
|
-
import pandas as pd
|
|
18
|
-
import numpy as np
|
|
19
|
-
from celldetective.io import locate_segmentation_model, locate_signal_model
|
|
20
|
-
import json
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
class SignalModelParamsWidget(CelldetectiveWidget):
|
|
24
|
-
|
|
25
|
-
def __init__(self, parent_window=None, model_name=None, *args, **kwargs):
|
|
26
|
-
|
|
27
|
-
super().__init__(*args)
|
|
28
|
-
self.setWindowTitle('Signals')
|
|
29
|
-
self.parent_window = parent_window
|
|
30
|
-
self.model_name = model_name
|
|
31
|
-
self.locate_model_path()
|
|
32
|
-
self.required_channels = self.input_config["channels"]
|
|
33
|
-
self.onlyFloat = QDoubleValidator()
|
|
34
|
-
|
|
35
|
-
# Setting up references to parent window attributes
|
|
36
|
-
if hasattr(self.parent_window.parent_window, 'locate_image'):
|
|
37
|
-
self.attr_parent = self.parent_window.parent_window
|
|
38
|
-
elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
|
|
39
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
40
|
-
else:
|
|
41
|
-
self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
|
|
42
|
-
|
|
43
|
-
# Set up layout and widgets
|
|
44
|
-
self.layout = QVBoxLayout()
|
|
45
|
-
self.populate_widgets()
|
|
46
|
-
self.setLayout(self.layout)
|
|
47
|
-
center_window(self)
|
|
48
|
-
|
|
49
|
-
def locate_model_path(self):
|
|
50
|
-
|
|
51
|
-
self.model_complete_path = locate_signal_model(self.model_name)
|
|
52
|
-
if self.model_complete_path is None:
|
|
53
|
-
print('Model could not be found. Abort.')
|
|
54
|
-
self.abort_process()
|
|
55
|
-
else:
|
|
56
|
-
print(f'Model path: {self.model_complete_path}...')
|
|
57
|
-
|
|
58
|
-
if not os.path.exists(self.model_complete_path+"config_input.json"):
|
|
59
|
-
print('The configuration for the inputs to the model could not be located. Abort.')
|
|
60
|
-
self.abort_process()
|
|
61
|
-
|
|
62
|
-
with open(self.model_complete_path+"config_input.json") as config_file:
|
|
63
|
-
self.input_config = json.load(config_file)
|
|
64
|
-
|
|
65
|
-
def populate_widgets(self):
|
|
66
|
-
|
|
67
|
-
self.n_channels = len(self.required_channels)
|
|
68
|
-
self.channel_cbs = [QComboBox() for i in range(self.n_channels)]
|
|
69
|
-
|
|
70
|
-
self.parent_window.load_available_tables()
|
|
71
|
-
available_channels = list(self.parent_window.signals)+['None']
|
|
72
|
-
# Populate the comboboxes with available channels from the experiment
|
|
73
|
-
for k in range(self.n_channels):
|
|
74
|
-
hbox_channel = QHBoxLayout()
|
|
75
|
-
hbox_channel.addWidget(QLabel(f'channel {k+1}: '), 33)
|
|
76
|
-
|
|
77
|
-
ch_vbox = QVBoxLayout()
|
|
78
|
-
ch_vbox.addWidget(QLabel(f'Req: {self.required_channels[k]}'), alignment=Qt.AlignLeft)
|
|
79
|
-
ch_vbox.addWidget(self.channel_cbs[k])
|
|
80
|
-
|
|
81
|
-
self.channel_cbs[k].addItems(available_channels) #Give none option for more than one channel input
|
|
82
|
-
idx = self.channel_cbs[k].findText(self.required_channels[k])
|
|
83
|
-
|
|
84
|
-
if idx>=0:
|
|
85
|
-
self.channel_cbs[k].setCurrentIndex(idx)
|
|
86
|
-
else:
|
|
87
|
-
self.channel_cbs[k].setCurrentIndex(len(available_channels)-1)
|
|
88
|
-
|
|
89
|
-
hbox_channel.addLayout(ch_vbox, 66)
|
|
90
|
-
self.layout.addLayout(hbox_channel)
|
|
91
|
-
|
|
92
|
-
# Button to apply the StarDist settings
|
|
93
|
-
self.set_btn = QPushButton('set')
|
|
94
|
-
self.set_btn.setStyleSheet(self.button_style_sheet)
|
|
95
|
-
self.set_btn.clicked.connect(self.parent_window.set_selected_signals_for_event_detection)
|
|
96
|
-
self.layout.addWidget(self.set_btn)
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
class SegModelParamsWidget(CelldetectiveWidget):
|
|
101
|
-
|
|
102
|
-
def __init__(self, parent_window=None, model_name='SD_versatile_fluo', *args, **kwargs):
|
|
103
|
-
|
|
104
|
-
super().__init__(*args)
|
|
105
|
-
self.setWindowTitle('Channels')
|
|
106
|
-
self.parent_window = parent_window
|
|
107
|
-
self.model_name = model_name
|
|
108
|
-
self.locate_model_path()
|
|
109
|
-
self.required_channels = self.input_config["channels"]
|
|
110
|
-
self.onlyFloat = QDoubleValidator()
|
|
111
|
-
|
|
112
|
-
# Setting up references to parent window attributes
|
|
113
|
-
if hasattr(self.parent_window.parent_window, 'locate_image'):
|
|
114
|
-
self.attr_parent = self.parent_window.parent_window
|
|
115
|
-
elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
|
|
116
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
117
|
-
else:
|
|
118
|
-
self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
|
|
119
|
-
|
|
120
|
-
# Set up layout and widgets
|
|
121
|
-
self.layout = QVBoxLayout()
|
|
122
|
-
self.populate_widgets()
|
|
123
|
-
self.setLayout(self.layout)
|
|
124
|
-
center_window(self)
|
|
125
|
-
|
|
126
|
-
def locate_model_path(self):
|
|
127
|
-
|
|
128
|
-
self.model_complete_path = locate_segmentation_model(self.model_name)
|
|
129
|
-
if self.model_complete_path is None:
|
|
130
|
-
print('Model could not be found. Abort.')
|
|
131
|
-
self.abort_process()
|
|
132
|
-
else:
|
|
133
|
-
print(f'Model path: {self.model_complete_path}...')
|
|
134
|
-
|
|
135
|
-
if not os.path.exists(self.model_complete_path+"config_input.json"):
|
|
136
|
-
print('The configuration for the inputs to the model could not be located. Abort.')
|
|
137
|
-
self.abort_process()
|
|
138
|
-
|
|
139
|
-
with open(self.model_complete_path+"config_input.json") as config_file:
|
|
140
|
-
self.input_config = json.load(config_file)
|
|
141
|
-
|
|
142
|
-
def populate_widgets(self):
|
|
143
|
-
|
|
144
|
-
self.n_channels = len(self.required_channels)
|
|
145
|
-
self.channel_cbs = [QComboBox() for i in range(self.n_channels)]
|
|
146
|
-
|
|
147
|
-
# Button to view the current stack with a scale bar
|
|
148
|
-
self.view_diameter_btn = QPushButton()
|
|
149
|
-
self.view_diameter_btn.setStyleSheet(self.button_select_all)
|
|
150
|
-
self.view_diameter_btn.setIcon(icon(MDI6.image_check, color="black"))
|
|
151
|
-
self.view_diameter_btn.setToolTip("View stack.")
|
|
152
|
-
self.view_diameter_btn.setIconSize(QSize(20, 20))
|
|
153
|
-
self.view_diameter_btn.clicked.connect(self.view_current_stack_with_scale_bar)
|
|
154
|
-
|
|
155
|
-
# Line edit for entering cell diameter
|
|
156
|
-
self.diameter_le = ThresholdLineEdit(init_value=40, connected_buttons=[self.view_diameter_btn],placeholder='cell diameter in µm', value_type='float')
|
|
157
|
-
|
|
158
|
-
available_channels = list(self.attr_parent.exp_channels)+['None']
|
|
159
|
-
# Populate the comboboxes with available channels from the experiment
|
|
160
|
-
for k in range(self.n_channels):
|
|
161
|
-
hbox_channel = QHBoxLayout()
|
|
162
|
-
hbox_channel.addWidget(QLabel(f'channel {k+1}: '), 33)
|
|
163
|
-
|
|
164
|
-
ch_vbox = QVBoxLayout()
|
|
165
|
-
ch_vbox.addWidget(QLabel(f'Req: {self.required_channels[k]}'), alignment=Qt.AlignLeft)
|
|
166
|
-
ch_vbox.addWidget(self.channel_cbs[k])
|
|
167
|
-
|
|
168
|
-
self.channel_cbs[k].addItems(available_channels) #Give none option for more than one channel input
|
|
169
|
-
idx = self.channel_cbs[k].findText(self.required_channels[k])
|
|
170
|
-
|
|
171
|
-
if idx>=0:
|
|
172
|
-
self.channel_cbs[k].setCurrentIndex(idx)
|
|
173
|
-
else:
|
|
174
|
-
self.channel_cbs[k].setCurrentIndex(len(available_channels)-1)
|
|
175
|
-
|
|
176
|
-
hbox_channel.addLayout(ch_vbox, 66)
|
|
177
|
-
self.layout.addLayout(hbox_channel)
|
|
178
|
-
|
|
179
|
-
if 'cell_size_um' in self.input_config:
|
|
180
|
-
|
|
181
|
-
# Layout for diameter input and button
|
|
182
|
-
hbox = QHBoxLayout()
|
|
183
|
-
hbox.addWidget(QLabel('cell size [µm]: '), 33)
|
|
184
|
-
hbox.addWidget(self.diameter_le, 61)
|
|
185
|
-
hbox.addWidget(self.view_diameter_btn)
|
|
186
|
-
self.layout.addLayout(hbox)
|
|
187
|
-
|
|
188
|
-
self.diameter_le.set_threshold(self.input_config['cell_size_um'])
|
|
189
|
-
|
|
190
|
-
# size_hbox = QHBoxLayout()
|
|
191
|
-
# size_hbox.addWidget(QLabel('cell size [µm]: '), 33)
|
|
192
|
-
# self.size_le = QLineEdit(str(self.input_config['cell_size_um']).replace('.',','))
|
|
193
|
-
# self.size_le.setValidator(self.onlyFloat)
|
|
194
|
-
# size_hbox.addWidget(self.size_le, 66)
|
|
195
|
-
# self.layout.addLayout(size_hbox)
|
|
196
|
-
|
|
197
|
-
# Button to apply the StarDist settings
|
|
198
|
-
self.set_btn = QPushButton('set')
|
|
199
|
-
self.set_btn.setStyleSheet(self.button_style_sheet)
|
|
200
|
-
self.set_btn.clicked.connect(self.parent_window.set_selected_channels_for_segmentation)
|
|
201
|
-
self.layout.addWidget(self.set_btn)
|
|
202
|
-
|
|
203
|
-
def view_current_stack_with_scale_bar(self):
|
|
204
|
-
|
|
205
|
-
"""
|
|
206
|
-
Displays the current image stack with a scale bar, allowing users to visually estimate cell diameters.
|
|
207
|
-
"""
|
|
208
|
-
|
|
209
|
-
self.attr_parent.locate_image()
|
|
210
|
-
if self.attr_parent.current_stack is not None:
|
|
211
|
-
max_size = np.amax([self.attr_parent.shape_x, self.attr_parent.shape_y])
|
|
212
|
-
self.viewer = CellSizeViewer(
|
|
213
|
-
initial_diameter = float(self.diameter_le.text().replace(',', '.')),
|
|
214
|
-
parent_le = self.diameter_le,
|
|
215
|
-
stack_path=self.attr_parent.current_stack,
|
|
216
|
-
window_title=f'Position {self.attr_parent.position_list.currentText()}',
|
|
217
|
-
diameter_slider_range=(0,max_size*self.attr_parent.PxToUm),
|
|
218
|
-
frame_slider = True,
|
|
219
|
-
contrast_slider = True,
|
|
220
|
-
channel_cb = True,
|
|
221
|
-
channel_names = self.attr_parent.exp_channels,
|
|
222
|
-
n_channels = self.attr_parent.nbr_channels,
|
|
223
|
-
PxToUm = self.attr_parent.PxToUm,
|
|
224
|
-
)
|
|
225
|
-
self.viewer.show()
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
class StarDistParamsWidget(CelldetectiveWidget):
|
|
229
|
-
|
|
230
|
-
"""
|
|
231
|
-
A widget to configure parameters for StarDist segmentation.
|
|
232
|
-
|
|
233
|
-
This widget allows the user to select specific imaging channels for segmentation and adjust
|
|
234
|
-
parameters for StarDist, a neural network-based image segmentation tool designed to segment
|
|
235
|
-
star-convex shapes (typically nuclei).
|
|
236
|
-
|
|
237
|
-
Parameters
|
|
238
|
-
----------
|
|
239
|
-
parent_window : QWidget, optional
|
|
240
|
-
The parent window hosting this widget (default is None).
|
|
241
|
-
model_name : str, optional
|
|
242
|
-
The name of the StarDist model being used, typically 'SD_versatile_fluo' for versatile
|
|
243
|
-
fluorescence or 'SD_versatile_he' for H&E-stained images (default is 'SD_versatile_fluo').
|
|
244
|
-
"""
|
|
245
|
-
|
|
246
|
-
def __init__(self, parent_window=None, model_name='SD_versatile_fluo', *args, **kwargs):
|
|
247
|
-
|
|
248
|
-
super().__init__(*args)
|
|
249
|
-
self.setWindowTitle('Channels')
|
|
250
|
-
self.parent_window = parent_window
|
|
251
|
-
self.model_name = model_name
|
|
252
|
-
|
|
253
|
-
# Setting up references to parent window attributes
|
|
254
|
-
if hasattr(self.parent_window.parent_window, 'locate_image'):
|
|
255
|
-
self.attr_parent = self.parent_window.parent_window
|
|
256
|
-
elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
|
|
257
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
258
|
-
else:
|
|
259
|
-
self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
|
|
260
|
-
|
|
261
|
-
# Set up layout and widgets
|
|
262
|
-
self.layout = QVBoxLayout()
|
|
263
|
-
self.populate_widgets()
|
|
264
|
-
self.setLayout(self.layout)
|
|
265
|
-
center_window(self)
|
|
266
|
-
|
|
267
|
-
def populate_widgets(self):
|
|
268
|
-
|
|
269
|
-
"""
|
|
270
|
-
Populates the widget with channel selection comboboxes and a 'set' button to configure
|
|
271
|
-
the StarDist segmentation settings. Handles different models by adjusting the number of
|
|
272
|
-
available channels.
|
|
273
|
-
"""
|
|
274
|
-
|
|
275
|
-
# Initialize comboboxes based on the selected model
|
|
276
|
-
self.stardist_channel_cb = [QComboBox() for i in range(1)]
|
|
277
|
-
self.stardist_channel_template = ['live_nuclei_channel']
|
|
278
|
-
max_i = 1
|
|
279
|
-
|
|
280
|
-
# If the H&E model is selected, update the combobox configuration
|
|
281
|
-
if self.model_name=="SD_versatile_he":
|
|
282
|
-
self.stardist_channel_template = ["H&E_1","H&E_2","H&E_3"]
|
|
283
|
-
self.stardist_channel_cb = [QComboBox() for i in range(3)]
|
|
284
|
-
max_i = 3
|
|
285
|
-
|
|
286
|
-
# Populate the comboboxes with available channels from the experiment
|
|
287
|
-
for k in range(max_i):
|
|
288
|
-
hbox_channel = QHBoxLayout()
|
|
289
|
-
hbox_channel.addWidget(QLabel(f'channel {k+1}: '))
|
|
290
|
-
hbox_channel.addWidget(self.stardist_channel_cb[k])
|
|
291
|
-
if k==1:
|
|
292
|
-
self.stardist_channel_cb[k].addItems(list(self.attr_parent.exp_channels)+['None'])
|
|
293
|
-
else:
|
|
294
|
-
self.stardist_channel_cb[k].addItems(list(self.attr_parent.exp_channels))
|
|
295
|
-
|
|
296
|
-
# Set the default channel based on the template or fallback to the first option
|
|
297
|
-
idx = self.stardist_channel_cb[k].findText(self.stardist_channel_template[k])
|
|
298
|
-
if idx>0:
|
|
299
|
-
self.stardist_channel_cb[k].setCurrentIndex(idx)
|
|
300
|
-
else:
|
|
301
|
-
self.stardist_channel_cb[k].setCurrentIndex(0)
|
|
302
|
-
|
|
303
|
-
self.layout.addLayout(hbox_channel)
|
|
304
|
-
|
|
305
|
-
# Button to apply the StarDist settings
|
|
306
|
-
self.set_stardist_scale_btn = QPushButton('set')
|
|
307
|
-
self.set_stardist_scale_btn.setStyleSheet(self.button_style_sheet)
|
|
308
|
-
self.set_stardist_scale_btn.clicked.connect(self.parent_window.set_stardist_scale)
|
|
309
|
-
self.layout.addWidget(self.set_stardist_scale_btn)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
class CellposeParamsWidget(CelldetectiveWidget):
|
|
313
|
-
|
|
314
|
-
"""
|
|
315
|
-
A widget to configure parameters for Cellpose segmentation, allowing users to set the cell diameter,
|
|
316
|
-
select imaging channels, and adjust flow and cell probability thresholds for cell detection.
|
|
317
|
-
|
|
318
|
-
This widget is designed for estimating cell diameters and configuring parameters for Cellpose,
|
|
319
|
-
a deep learning-based segmentation tool. It also provides functionality to preview the image stack with a scale bar.
|
|
320
|
-
|
|
321
|
-
Parameters
|
|
322
|
-
----------
|
|
323
|
-
parent_window : QWidget, optional
|
|
324
|
-
The parent window that hosts the widget (default is None).
|
|
325
|
-
model_name : str, optional
|
|
326
|
-
The name of the Cellpose model being used, typically 'CP_cyto2' for cytoplasm or 'CP_nuclei' for nuclei segmentation
|
|
327
|
-
(default is 'CP_cyto2').
|
|
328
|
-
|
|
329
|
-
Notes
|
|
330
|
-
-----
|
|
331
|
-
- This widget assumes that the parent window or one of its ancestor windows has access to the experiment channels
|
|
332
|
-
and can locate the current image stack via `locate_image()`.
|
|
333
|
-
- This class integrates sliders for flow and cell probability thresholds, as well as a channel selection for running
|
|
334
|
-
Cellpose segmentation.
|
|
335
|
-
- The `view_current_stack_with_scale_bar()` method opens a new window where the user can visually inspect the
|
|
336
|
-
image stack with a superimposed scale bar, to better estimate the cell diameter.
|
|
337
|
-
|
|
338
|
-
"""
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
def __init__(self, parent_window=None, model_name='CP_cyto2', *args, **kwargs):
|
|
342
|
-
|
|
343
|
-
super().__init__(*args)
|
|
344
|
-
self.setWindowTitle('Estimate diameter')
|
|
345
|
-
self.parent_window = parent_window
|
|
346
|
-
self.model_name = model_name
|
|
347
|
-
|
|
348
|
-
# Setting up references to parent window attributes
|
|
349
|
-
if hasattr(self.parent_window.parent_window, 'locate_image'):
|
|
350
|
-
self.attr_parent = self.parent_window.parent_window
|
|
351
|
-
elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
|
|
352
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
353
|
-
else:
|
|
354
|
-
self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
|
|
355
|
-
|
|
356
|
-
# Layout and widgets setup
|
|
357
|
-
self.layout = QVBoxLayout()
|
|
358
|
-
self.populate_widgets()
|
|
359
|
-
self.setLayout(self.layout)
|
|
360
|
-
center_window(self)
|
|
361
|
-
|
|
362
|
-
def populate_widgets(self):
|
|
363
|
-
|
|
364
|
-
"""
|
|
365
|
-
Populates the widget with UI elements such as buttons, sliders, and comboboxes to allow configuration
|
|
366
|
-
of Cellpose segmentation parameters.
|
|
367
|
-
"""
|
|
368
|
-
|
|
369
|
-
# Button to view the current stack with a scale bar
|
|
370
|
-
self.view_diameter_btn = QPushButton()
|
|
371
|
-
self.view_diameter_btn.setStyleSheet(self.button_select_all)
|
|
372
|
-
self.view_diameter_btn.setIcon(icon(MDI6.image_check, color="black"))
|
|
373
|
-
self.view_diameter_btn.setToolTip("View stack.")
|
|
374
|
-
self.view_diameter_btn.setIconSize(QSize(20, 20))
|
|
375
|
-
self.view_diameter_btn.clicked.connect(self.view_current_stack_with_scale_bar)
|
|
376
|
-
|
|
377
|
-
# Line edit for entering cell diameter
|
|
378
|
-
self.diameter_le = ThresholdLineEdit(init_value=40, connected_buttons=[self.view_diameter_btn],placeholder='cell diameter in pixels', value_type='float')
|
|
379
|
-
|
|
380
|
-
# Comboboxes for selecting imaging channels
|
|
381
|
-
self.cellpose_channel_cb = [QComboBox() for i in range(2)]
|
|
382
|
-
self.cellpose_channel_template = ['brightfield_channel', 'live_nuclei_channel']
|
|
383
|
-
if self.model_name=="CP_nuclei":
|
|
384
|
-
self.cellpose_channel_template = ['live_nuclei_channel', 'None']
|
|
385
|
-
|
|
386
|
-
for k in range(2):
|
|
387
|
-
hbox_channel = QHBoxLayout()
|
|
388
|
-
hbox_channel.addWidget(QLabel(f'channel {k+1}: '))
|
|
389
|
-
hbox_channel.addWidget(self.cellpose_channel_cb[k])
|
|
390
|
-
if k==1:
|
|
391
|
-
self.cellpose_channel_cb[k].addItems(list(self.attr_parent.exp_channels)+['None'])
|
|
392
|
-
else:
|
|
393
|
-
self.cellpose_channel_cb[k].addItems(list(self.attr_parent.exp_channels))
|
|
394
|
-
idx = self.cellpose_channel_cb[k].findText(self.cellpose_channel_template[k])
|
|
395
|
-
if idx>0:
|
|
396
|
-
self.cellpose_channel_cb[k].setCurrentIndex(idx)
|
|
397
|
-
else:
|
|
398
|
-
self.cellpose_channel_cb[k].setCurrentIndex(0)
|
|
399
|
-
|
|
400
|
-
if k==1:
|
|
401
|
-
idx = self.cellpose_channel_cb[k].findText('None')
|
|
402
|
-
self.cellpose_channel_cb[k].setCurrentIndex(idx)
|
|
403
|
-
|
|
404
|
-
self.layout.addLayout(hbox_channel)
|
|
405
|
-
|
|
406
|
-
# Layout for diameter input and button
|
|
407
|
-
hbox = QHBoxLayout()
|
|
408
|
-
hbox.addWidget(QLabel('diameter [px]: '), 33)
|
|
409
|
-
hbox.addWidget(self.diameter_le, 61)
|
|
410
|
-
hbox.addWidget(self.view_diameter_btn)
|
|
411
|
-
self.layout.addLayout(hbox)
|
|
412
|
-
|
|
413
|
-
# Flow threshold slider
|
|
414
|
-
self.flow_slider = QLabeledDoubleSlider()
|
|
415
|
-
self.flow_slider.setOrientation(Qt.Horizontal)
|
|
416
|
-
self.flow_slider.setRange(-6,6)
|
|
417
|
-
self.flow_slider.setValue(0.4)
|
|
418
|
-
hbox = QHBoxLayout()
|
|
419
|
-
hbox.addWidget(QLabel('flow threshold: '), 33)
|
|
420
|
-
hbox.addWidget(self.flow_slider, 66)
|
|
421
|
-
self.layout.addLayout(hbox)
|
|
422
|
-
|
|
423
|
-
# Cell probability threshold slider
|
|
424
|
-
self.cellprob_slider = QLabeledDoubleSlider()
|
|
425
|
-
self.cellprob_slider.setOrientation(Qt.Horizontal)
|
|
426
|
-
self.cellprob_slider.setRange(-6,6)
|
|
427
|
-
self.cellprob_slider.setValue(0.)
|
|
428
|
-
hbox = QHBoxLayout()
|
|
429
|
-
hbox.addWidget(QLabel('cellprob threshold: '), 33)
|
|
430
|
-
hbox.addWidget(self.cellprob_slider, 66)
|
|
431
|
-
self.layout.addLayout(hbox)
|
|
432
|
-
|
|
433
|
-
# Button to set the scale for Cellpose segmentation
|
|
434
|
-
self.set_cellpose_scale_btn = QPushButton('set')
|
|
435
|
-
self.set_cellpose_scale_btn.setStyleSheet(self.button_style_sheet)
|
|
436
|
-
self.set_cellpose_scale_btn.clicked.connect(self.parent_window.set_cellpose_scale)
|
|
437
|
-
self.layout.addWidget(self.set_cellpose_scale_btn)
|
|
438
|
-
|
|
439
|
-
def view_current_stack_with_scale_bar(self):
|
|
440
|
-
|
|
441
|
-
"""
|
|
442
|
-
Displays the current image stack with a scale bar, allowing users to visually estimate cell diameters.
|
|
443
|
-
"""
|
|
444
|
-
|
|
445
|
-
self.attr_parent.locate_image()
|
|
446
|
-
if self.attr_parent.current_stack is not None:
|
|
447
|
-
max_size = np.amax([self.attr_parent.shape_x, self.attr_parent.shape_y])
|
|
448
|
-
self.viewer = CellSizeViewer(
|
|
449
|
-
initial_diameter = float(self.diameter_le.text().replace(',', '.')),
|
|
450
|
-
parent_le = self.diameter_le,
|
|
451
|
-
stack_path=self.attr_parent.current_stack,
|
|
452
|
-
window_title=f'Position {self.attr_parent.position_list.currentText()}',
|
|
453
|
-
diameter_slider_range=(0, max_size),
|
|
454
|
-
frame_slider = True,
|
|
455
|
-
contrast_slider = True,
|
|
456
|
-
channel_cb = True,
|
|
457
|
-
channel_names = self.attr_parent.exp_channels,
|
|
458
|
-
n_channels = self.attr_parent.nbr_channels,
|
|
459
|
-
PxToUm = 1,
|
|
460
|
-
)
|
|
461
|
-
self.viewer.show()
|
|
462
|
-
|
|
463
|
-
class ChannelNormGenerator(QVBoxLayout, Styles):
|
|
464
|
-
|
|
465
|
-
"""Generator for list of channels"""
|
|
466
|
-
|
|
467
|
-
def __init__(self, parent_window=None, init_n_channels=4, mode='signals', *args):
|
|
468
|
-
super().__init__(*args)
|
|
469
|
-
|
|
470
|
-
self.parent_window = parent_window
|
|
471
|
-
self.mode = mode
|
|
472
|
-
self.init_n_channels = init_n_channels
|
|
473
|
-
|
|
474
|
-
if hasattr(self.parent_window.parent_window, 'locate_image'):
|
|
475
|
-
self.attr_parent = self.parent_window.parent_window
|
|
476
|
-
elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
|
|
477
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
478
|
-
else:
|
|
479
|
-
self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
|
|
480
|
-
|
|
481
|
-
self.channel_names = self.attr_parent.exp_channels
|
|
482
|
-
self.setContentsMargins(15,15,15,15)
|
|
483
|
-
self.generate_widgets()
|
|
484
|
-
self.add_to_layout()
|
|
485
|
-
|
|
486
|
-
def generate_widgets(self):
|
|
487
|
-
|
|
488
|
-
self.channel_cbs = [QSearchableComboBox() for i in range(self.init_n_channels)]
|
|
489
|
-
self.channel_labels = [QLabel() for i in range(self.init_n_channels)]
|
|
490
|
-
|
|
491
|
-
self.normalization_mode_btns = [QPushButton('') for i in range(self.init_n_channels)]
|
|
492
|
-
self.normalization_mode = [True for i in range(self.init_n_channels)]
|
|
493
|
-
self.normalization_clip_btns = [QPushButton('') for i in range(self.init_n_channels)]
|
|
494
|
-
self.clip_option = [False for i in range(self.init_n_channels)]
|
|
495
|
-
|
|
496
|
-
for i in range(self.init_n_channels):
|
|
497
|
-
self.normalization_mode_btns[i].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
|
|
498
|
-
self.normalization_mode_btns[i].setIconSize(QSize(20, 20))
|
|
499
|
-
self.normalization_mode_btns[i].setStyleSheet(self.button_select_all)
|
|
500
|
-
self.normalization_mode_btns[i].setToolTip("Switch to absolute normalization values.")
|
|
501
|
-
self.normalization_mode_btns[i].clicked.connect(partial(self.switch_normalization_mode, i))
|
|
502
|
-
|
|
503
|
-
self.normalization_clip_btns[i].setIcon(icon(MDI6.content_cut,color="black"))
|
|
504
|
-
self.normalization_clip_btns[i].setIconSize(QSize(20, 20))
|
|
505
|
-
self.normalization_clip_btns[i].setStyleSheet(self.button_select_all)
|
|
506
|
-
self.normalization_clip_btns[i].clicked.connect(partial(self.switch_clipping_mode, i))
|
|
507
|
-
self.normalization_clip_btns[i].setToolTip('clip')
|
|
508
|
-
|
|
509
|
-
self.normalization_min_value_lbl = [QLabel('Min %: ') for i in range(self.init_n_channels)]
|
|
510
|
-
self.normalization_min_value_le = [QLineEdit('0.1') for i in range(self.init_n_channels)]
|
|
511
|
-
self.normalization_max_value_lbl = [QLabel('Max %: ') for i in range(self.init_n_channels)]
|
|
512
|
-
self.normalization_max_value_le = [QLineEdit('99.99') for i in range(self.init_n_channels)]
|
|
513
|
-
|
|
514
|
-
if self.mode=='signals':
|
|
515
|
-
tables = glob(self.parent_window.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{self.parent_window.mode}.csv']))
|
|
516
|
-
all_measurements = []
|
|
517
|
-
for tab in tables:
|
|
518
|
-
cols = pd.read_csv(tab, nrows=1).columns.tolist()
|
|
519
|
-
all_measurements.extend(cols)
|
|
520
|
-
all_measurements = np.unique(all_measurements)
|
|
521
|
-
|
|
522
|
-
if self.mode=='signals':
|
|
523
|
-
generic_measurements = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel',
|
|
524
|
-
'effector_fluo_channel', 'adhesion_channel', 'fluo_channel_1', 'fluo_channel_2',
|
|
525
|
-
"area", "area_bbox","area_convex","area_filled","major_axis_length",
|
|
526
|
-
"minor_axis_length",
|
|
527
|
-
"eccentricity",
|
|
528
|
-
"equivalent_diameter_area",
|
|
529
|
-
"euler_number",
|
|
530
|
-
"extent",
|
|
531
|
-
"feret_diameter_max",
|
|
532
|
-
"orientation",
|
|
533
|
-
"perimeter",
|
|
534
|
-
"perimeter_crofton",
|
|
535
|
-
"solidity",
|
|
536
|
-
"angular_second_moment",
|
|
537
|
-
"contrast",
|
|
538
|
-
"correlation",
|
|
539
|
-
"sum_of_square_variance",
|
|
540
|
-
"inverse_difference_moment",
|
|
541
|
-
"sum_average",
|
|
542
|
-
"sum_variance",
|
|
543
|
-
"sum_entropy",
|
|
544
|
-
"entropy",
|
|
545
|
-
"difference_variance",
|
|
546
|
-
"difference_entropy",
|
|
547
|
-
"information_measure_of_correlation_1",
|
|
548
|
-
"information_measure_of_correlation_2",
|
|
549
|
-
"maximal_correlation_coefficient",
|
|
550
|
-
"POSITION_X",
|
|
551
|
-
"POSITION_Y",
|
|
552
|
-
]
|
|
553
|
-
elif self.mode=='channels':
|
|
554
|
-
generic_measurements = ['brightfield_channel', 'live_nuclei_channel', 'dead_nuclei_channel',
|
|
555
|
-
'effector_fluo_channel', 'adhesion_channel', 'fluo_channel_1', 'fluo_channel_2', 'None']
|
|
556
|
-
|
|
557
|
-
if self.mode=='channels':
|
|
558
|
-
all_measurements = []
|
|
559
|
-
exp_ch = self.attr_parent.exp_channels
|
|
560
|
-
for c in exp_ch:
|
|
561
|
-
all_measurements.append(c)
|
|
562
|
-
|
|
563
|
-
self.channel_items = np.unique(generic_measurements + list(all_measurements))
|
|
564
|
-
self.channel_items = np.insert(self.channel_items, 0, '--')
|
|
565
|
-
|
|
566
|
-
self.add_col_btn = QPushButton('Add channel')
|
|
567
|
-
self.add_col_btn.clicked.connect(self.add_channel)
|
|
568
|
-
self.add_col_btn.setStyleSheet(self.button_add)
|
|
569
|
-
self.add_col_btn.setIcon(icon(MDI6.plus,color="black"))
|
|
570
|
-
|
|
571
|
-
def add_channel(self):
|
|
572
|
-
|
|
573
|
-
self.channel_cbs.append(QSearchableComboBox())
|
|
574
|
-
self.channel_labels.append(QLabel())
|
|
575
|
-
self.channel_cbs[-1].addItems(self.channel_items)
|
|
576
|
-
self.channel_cbs[-1].currentIndexChanged.connect(self.check_valid_channels)
|
|
577
|
-
self.channel_labels[-1].setText(f'channel {len(self.channel_cbs)-1}: ')
|
|
578
|
-
|
|
579
|
-
self.normalization_mode_btns.append(QPushButton(''))
|
|
580
|
-
self.normalization_mode.append(True)
|
|
581
|
-
self.normalization_clip_btns.append(QPushButton(''))
|
|
582
|
-
self.clip_option.append(False)
|
|
583
|
-
|
|
584
|
-
self.normalization_mode_btns[-1].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
|
|
585
|
-
self.normalization_mode_btns[-1].setIconSize(QSize(20, 20))
|
|
586
|
-
self.normalization_mode_btns[-1].setStyleSheet(self.button_select_all)
|
|
587
|
-
self.normalization_mode_btns[-1].setToolTip("Switch to absolute normalization values.")
|
|
588
|
-
self.normalization_mode_btns[-1].clicked.connect(partial(self.switch_normalization_mode, len(self.channel_cbs)-1))
|
|
589
|
-
|
|
590
|
-
self.normalization_clip_btns[-1].setIcon(icon(MDI6.content_cut,color="black"))
|
|
591
|
-
self.normalization_clip_btns[-1].setIconSize(QSize(20, 20))
|
|
592
|
-
self.normalization_clip_btns[-1].setStyleSheet(self.button_select_all)
|
|
593
|
-
self.normalization_clip_btns[-1].clicked.connect(partial(self.switch_clipping_mode, len(self.channel_cbs)-1))
|
|
594
|
-
self.normalization_clip_btns[-1].setToolTip('clip')
|
|
595
|
-
|
|
596
|
-
self.normalization_min_value_lbl.append(QLabel('Min %: '))
|
|
597
|
-
self.normalization_min_value_le.append(QLineEdit('0.1'))
|
|
598
|
-
self.normalization_max_value_lbl.append(QLabel('Max %: '))
|
|
599
|
-
self.normalization_max_value_le.append(QLineEdit('99.99'))
|
|
600
|
-
|
|
601
|
-
ch_layout = QHBoxLayout()
|
|
602
|
-
ch_layout.addWidget(self.channel_labels[-1], 30)
|
|
603
|
-
ch_layout.addWidget(self.channel_cbs[-1], 70)
|
|
604
|
-
self.channels_vb.addLayout(ch_layout)
|
|
605
|
-
|
|
606
|
-
channel_norm_options_layout = QHBoxLayout()
|
|
607
|
-
channel_norm_options_layout.addWidget(QLabel(''),30)
|
|
608
|
-
ch_norm_sublayout = QHBoxLayout()
|
|
609
|
-
ch_norm_sublayout.addWidget(self.normalization_min_value_lbl[-1])
|
|
610
|
-
ch_norm_sublayout.addWidget(self.normalization_min_value_le[-1])
|
|
611
|
-
ch_norm_sublayout.addWidget(self.normalization_max_value_lbl[-1])
|
|
612
|
-
ch_norm_sublayout.addWidget(self.normalization_max_value_le[-1])
|
|
613
|
-
ch_norm_sublayout.addWidget(self.normalization_clip_btns[-1])
|
|
614
|
-
ch_norm_sublayout.addWidget(self.normalization_mode_btns[-1])
|
|
615
|
-
channel_norm_options_layout.addLayout(ch_norm_sublayout, 70)
|
|
616
|
-
|
|
617
|
-
self.channels_vb.addLayout(channel_norm_options_layout)
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
def add_to_layout(self):
|
|
621
|
-
|
|
622
|
-
self.channels_vb = QVBoxLayout()
|
|
623
|
-
self.channel_option_layouts = []
|
|
624
|
-
for i in range(len(self.channel_cbs)):
|
|
625
|
-
|
|
626
|
-
ch_layout = QHBoxLayout()
|
|
627
|
-
self.channel_labels[i].setText(f'channel {i}: ')
|
|
628
|
-
ch_layout.addWidget(self.channel_labels[i], 30)
|
|
629
|
-
self.channel_cbs[i].addItems(self.channel_items)
|
|
630
|
-
self.channel_cbs[i].currentIndexChanged.connect(self.check_valid_channels)
|
|
631
|
-
ch_layout.addWidget(self.channel_cbs[i], 70)
|
|
632
|
-
self.channels_vb.addLayout(ch_layout)
|
|
633
|
-
|
|
634
|
-
channel_norm_options_layout = QHBoxLayout()
|
|
635
|
-
#channel_norm_options_layout.setContentsMargins(130,0,0,0)
|
|
636
|
-
channel_norm_options_layout.addWidget(QLabel(''),30)
|
|
637
|
-
ch_norm_sublayout = QHBoxLayout()
|
|
638
|
-
ch_norm_sublayout.addWidget(self.normalization_min_value_lbl[i])
|
|
639
|
-
ch_norm_sublayout.addWidget(self.normalization_min_value_le[i])
|
|
640
|
-
ch_norm_sublayout.addWidget(self.normalization_max_value_lbl[i])
|
|
641
|
-
ch_norm_sublayout.addWidget(self.normalization_max_value_le[i])
|
|
642
|
-
ch_norm_sublayout.addWidget(self.normalization_clip_btns[i])
|
|
643
|
-
ch_norm_sublayout.addWidget(self.normalization_mode_btns[i])
|
|
644
|
-
channel_norm_options_layout.addLayout(ch_norm_sublayout, 70)
|
|
645
|
-
self.channels_vb.addLayout(channel_norm_options_layout)
|
|
646
|
-
|
|
647
|
-
self.addLayout(self.channels_vb)
|
|
648
|
-
|
|
649
|
-
add_hbox = QHBoxLayout()
|
|
650
|
-
add_hbox.addWidget(QLabel(''), 66)
|
|
651
|
-
add_hbox.addWidget(self.add_col_btn, 33, alignment=Qt.AlignRight)
|
|
652
|
-
self.addLayout(add_hbox)
|
|
653
|
-
|
|
654
|
-
def switch_normalization_mode(self, index):
|
|
655
|
-
|
|
656
|
-
"""
|
|
657
|
-
Use absolute or percentile values for the normalization of each individual channel.
|
|
658
|
-
|
|
659
|
-
"""
|
|
660
|
-
|
|
661
|
-
currentNormMode = self.normalization_mode[index]
|
|
662
|
-
self.normalization_mode[index] = not currentNormMode
|
|
663
|
-
|
|
664
|
-
if self.normalization_mode[index]:
|
|
665
|
-
self.normalization_mode_btns[index].setIcon(icon(MDI6.percent_circle,color="#1565c0"))
|
|
666
|
-
self.normalization_mode_btns[index].setIconSize(QSize(20, 20))
|
|
667
|
-
self.normalization_mode_btns[index].setStyleSheet(self.button_select_all)
|
|
668
|
-
self.normalization_mode_btns[index].setToolTip("Switch to absolute normalization values.")
|
|
669
|
-
self.normalization_min_value_lbl[index].setText('Min %: ')
|
|
670
|
-
self.normalization_max_value_lbl[index].setText('Max %: ')
|
|
671
|
-
self.normalization_min_value_le[index].setText('0.1')
|
|
672
|
-
self.normalization_max_value_le[index].setText('99.99')
|
|
673
|
-
|
|
674
|
-
else:
|
|
675
|
-
self.normalization_mode_btns[index].setIcon(icon(MDI6.percent_circle_outline,color="black"))
|
|
676
|
-
self.normalization_mode_btns[index].setIconSize(QSize(20, 20))
|
|
677
|
-
self.normalization_mode_btns[index].setStyleSheet(self.button_select_all)
|
|
678
|
-
self.normalization_mode_btns[index].setToolTip("Switch to percentile normalization values.")
|
|
679
|
-
self.normalization_min_value_lbl[index].setText('Min: ')
|
|
680
|
-
self.normalization_min_value_le[index].setText('0')
|
|
681
|
-
self.normalization_max_value_lbl[index].setText('Max: ')
|
|
682
|
-
self.normalization_max_value_le[index].setText('1000')
|
|
683
|
-
|
|
684
|
-
def switch_clipping_mode(self, index):
|
|
685
|
-
|
|
686
|
-
currentClipMode = self.clip_option[index]
|
|
687
|
-
self.clip_option[index] = not currentClipMode
|
|
688
|
-
|
|
689
|
-
if self.clip_option[index]:
|
|
690
|
-
self.normalization_clip_btns[index].setIcon(icon(MDI6.content_cut,color="#1565c0"))
|
|
691
|
-
self.normalization_clip_btns[index].setIconSize(QSize(20, 20))
|
|
692
|
-
self.normalization_clip_btns[index].setStyleSheet(self.button_select_all)
|
|
693
|
-
|
|
694
|
-
else:
|
|
695
|
-
self.normalization_clip_btns[index].setIcon(icon(MDI6.content_cut,color="black"))
|
|
696
|
-
self.normalization_clip_btns[index].setIconSize(QSize(20, 20))
|
|
697
|
-
self.normalization_clip_btns[index].setStyleSheet(self.button_select_all)
|
|
698
|
-
|
|
699
|
-
def check_valid_channels(self):
|
|
700
|
-
|
|
701
|
-
if hasattr(self.parent_window, "submit_btn"):
|
|
702
|
-
if np.all([cb.currentText()=='--' for cb in self.channel_cbs]):
|
|
703
|
-
self.parent_window.submit_btn.setEnabled(False)
|
|
704
|
-
|
|
705
|
-
if hasattr(self.parent_window, "spatial_calib_le") and hasattr(self.parent_window, "submit_btn"):
|
|
706
|
-
if self.parent_window.spatial_calib_le.text()!='--':
|
|
707
|
-
self.parent_window.submit_btn.setEnabled(True)
|
|
708
|
-
elif hasattr(self.parent_window, "submit_btn"):
|
|
709
|
-
self.parent_window.submit_btn.setEnabled(True)
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
class BackgroundFitCorrectionLayout(QGridLayout, Styles):
|
|
714
|
-
|
|
715
|
-
"""docstring for ClassName"""
|
|
716
|
-
|
|
717
|
-
def __init__(self, parent_window=None, *args):
|
|
718
|
-
super().__init__(*args)
|
|
719
|
-
|
|
720
|
-
self.parent_window = parent_window
|
|
721
|
-
|
|
722
|
-
if hasattr(self.parent_window.parent_window, 'locate_image'):
|
|
723
|
-
self.attr_parent = self.parent_window.parent_window
|
|
724
|
-
elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
|
|
725
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
726
|
-
else:
|
|
727
|
-
self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
|
|
728
|
-
|
|
729
|
-
self.channel_names = self.attr_parent.exp_channels
|
|
730
|
-
self.setContentsMargins(15,15,15,15)
|
|
731
|
-
self.generate_widgets()
|
|
732
|
-
self.add_to_layout()
|
|
733
|
-
|
|
734
|
-
def generate_widgets(self):
|
|
735
|
-
|
|
736
|
-
self.channel_lbl = QLabel('Channel: ')
|
|
737
|
-
self.channels_cb = QComboBox()
|
|
738
|
-
self.channels_cb.addItems(self.channel_names)
|
|
739
|
-
|
|
740
|
-
self.thresh_lbl = QLabel('Threshold: ')
|
|
741
|
-
self.thresh_lbl.setToolTip('Threshold on the STD-filtered image.\nPixel values above the threshold are\nconsidered as non-background and are\nmasked prior to background estimation.')
|
|
742
|
-
self.threshold_viewer_btn = QPushButton()
|
|
743
|
-
self.threshold_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
744
|
-
self.threshold_viewer_btn.setStyleSheet(self.button_select_all)
|
|
745
|
-
self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
|
|
746
|
-
self.threshold_viewer_btn.setToolTip('Set the threshold graphically.')
|
|
747
|
-
|
|
748
|
-
self.model_lbl = QLabel('Model: ')
|
|
749
|
-
self.model_lbl.setToolTip('2D model to fit the background with.')
|
|
750
|
-
self.models_cb = QComboBox()
|
|
751
|
-
self.models_cb.addItems(['paraboloid', 'plane'])
|
|
752
|
-
self.models_cb.setToolTip('2D model to fit the background with.')
|
|
753
|
-
|
|
754
|
-
self.corrected_stack_viewer = QPushButton("")
|
|
755
|
-
self.corrected_stack_viewer.setStyleSheet(self.button_select_all)
|
|
756
|
-
self.corrected_stack_viewer.setIcon(icon(MDI6.eye_outline, color="black"))
|
|
757
|
-
self.corrected_stack_viewer.setToolTip("View corrected image")
|
|
758
|
-
self.corrected_stack_viewer.clicked.connect(self.preview_correction)
|
|
759
|
-
self.corrected_stack_viewer.setIconSize(QSize(20, 20))
|
|
760
|
-
|
|
761
|
-
self.add_correction_btn = QPushButton('Add correction')
|
|
762
|
-
self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
|
|
763
|
-
self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
|
|
764
|
-
self.add_correction_btn.setToolTip('Add correction.')
|
|
765
|
-
self.add_correction_btn.setIconSize(QSize(25, 25))
|
|
766
|
-
self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
|
|
767
|
-
|
|
768
|
-
self.threshold_le = ThresholdLineEdit(init_value=2, connected_buttons=[self.threshold_viewer_btn,
|
|
769
|
-
self.corrected_stack_viewer,
|
|
770
|
-
self.add_correction_btn
|
|
771
|
-
])
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
def add_to_layout(self):
|
|
775
|
-
|
|
776
|
-
channel_layout = QHBoxLayout()
|
|
777
|
-
channel_layout.addWidget(self.channel_lbl, 25)
|
|
778
|
-
channel_layout.addWidget(self.channels_cb, 75)
|
|
779
|
-
self.addLayout(channel_layout, 0, 0, 1, 3)
|
|
780
|
-
|
|
781
|
-
threshold_layout = QHBoxLayout()
|
|
782
|
-
threshold_layout.addWidget(self.thresh_lbl, 25)
|
|
783
|
-
subthreshold_layout = QHBoxLayout()
|
|
784
|
-
subthreshold_layout.addWidget(self.threshold_le, 95)
|
|
785
|
-
subthreshold_layout.addWidget(self.threshold_viewer_btn, 5)
|
|
786
|
-
|
|
787
|
-
threshold_layout.addLayout(subthreshold_layout, 75)
|
|
788
|
-
self.addLayout(threshold_layout, 1, 0, 1, 3)
|
|
789
|
-
|
|
790
|
-
model_layout = QHBoxLayout()
|
|
791
|
-
model_layout.addWidget(self.model_lbl, 25)
|
|
792
|
-
model_layout.addWidget(self.models_cb, 75)
|
|
793
|
-
self.addLayout(model_layout, 2, 0, 1, 3)
|
|
794
|
-
|
|
795
|
-
self.operation_layout = OperationLayout()
|
|
796
|
-
self.addLayout(self.operation_layout, 3, 0, 1, 3)
|
|
797
|
-
|
|
798
|
-
correction_layout = QHBoxLayout()
|
|
799
|
-
correction_layout.addWidget(self.add_correction_btn, 95)
|
|
800
|
-
correction_layout.addWidget(self.corrected_stack_viewer, 5)
|
|
801
|
-
self.addLayout(correction_layout, 4, 0, 1, 3)
|
|
802
|
-
|
|
803
|
-
verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
804
|
-
self.addItem(verticalSpacer, 5, 0, 1, 3)
|
|
805
|
-
|
|
806
|
-
def add_instructions_to_parent_list(self):
|
|
807
|
-
|
|
808
|
-
self.generate_instructions()
|
|
809
|
-
self.parent_window.protocols.append(self.instructions)
|
|
810
|
-
correction_description = ""
|
|
811
|
-
for index, (key, value) in enumerate(self.instructions.items()):
|
|
812
|
-
if index > 0:
|
|
813
|
-
correction_description += ", "
|
|
814
|
-
correction_description += str(key) + " : " + str(value)
|
|
815
|
-
self.parent_window.protocol_list.addItem(correction_description)
|
|
816
|
-
|
|
817
|
-
def generate_instructions(self):
|
|
818
|
-
|
|
819
|
-
if self.operation_layout.subtract_btn.isChecked():
|
|
820
|
-
operation = "subtract"
|
|
821
|
-
else:
|
|
822
|
-
operation = "divide"
|
|
823
|
-
clip = None
|
|
824
|
-
|
|
825
|
-
if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
|
|
826
|
-
clip = True
|
|
827
|
-
else:
|
|
828
|
-
clip = False
|
|
829
|
-
|
|
830
|
-
self.instructions = {
|
|
831
|
-
"target_channel": self.channels_cb.currentText(),
|
|
832
|
-
"correction_type": "fit",
|
|
833
|
-
"model": self.models_cb.currentText(),
|
|
834
|
-
"threshold_on_std": self.threshold_le.get_threshold(),
|
|
835
|
-
"operation": operation,
|
|
836
|
-
"clip": clip
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
def set_target_channel(self):
|
|
840
|
-
|
|
841
|
-
channel_indices = _extract_channel_indices_from_config(self.attr_parent.exp_config, [self.channels_cb.currentText()])
|
|
842
|
-
self.target_channel = channel_indices[0]
|
|
843
|
-
|
|
844
|
-
def set_threshold_graphically(self):
|
|
845
|
-
|
|
846
|
-
self.attr_parent.locate_image()
|
|
847
|
-
self.set_target_channel()
|
|
848
|
-
thresh = self.threshold_le.get_threshold()
|
|
849
|
-
|
|
850
|
-
if self.attr_parent.current_stack is not None and thresh is not None:
|
|
851
|
-
self.viewer = ThresholdedStackVisualizer(initial_threshold=thresh,
|
|
852
|
-
parent_le = self.threshold_le,
|
|
853
|
-
preprocessing=[['gauss',2],["std",4]],
|
|
854
|
-
stack_path=self.attr_parent.current_stack,
|
|
855
|
-
n_channels=len(self.channel_names),
|
|
856
|
-
target_channel=self.target_channel,
|
|
857
|
-
window_title='Set the exclusion threshold',
|
|
858
|
-
)
|
|
859
|
-
self.viewer.show()
|
|
860
|
-
|
|
861
|
-
def preview_correction(self):
|
|
862
|
-
|
|
863
|
-
if self.attr_parent.well_list.isMultipleSelection() or not self.attr_parent.well_list.isAnySelected() or self.attr_parent.position_list.isMultipleSelection() or not self.attr_parent.position_list.isAnySelected():
|
|
864
|
-
|
|
865
|
-
msgBox = QMessageBox()
|
|
866
|
-
msgBox.setIcon(QMessageBox.Critical)
|
|
867
|
-
msgBox.setText("Please select a single position...")
|
|
868
|
-
msgBox.setWindowTitle("Critical")
|
|
869
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
870
|
-
returnValue = msgBox.exec()
|
|
871
|
-
if returnValue == QMessageBox.Ok:
|
|
872
|
-
return None
|
|
873
|
-
|
|
874
|
-
if self.operation_layout.subtract_btn.isChecked():
|
|
875
|
-
operation = "subtract"
|
|
876
|
-
else:
|
|
877
|
-
operation = "divide"
|
|
878
|
-
clip = None
|
|
879
|
-
|
|
880
|
-
if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
|
|
881
|
-
clip = True
|
|
882
|
-
else:
|
|
883
|
-
clip = False
|
|
884
|
-
|
|
885
|
-
corrected_stack = correct_background_model(self.attr_parent.exp_dir,
|
|
886
|
-
well_option=self.attr_parent.well_list.getSelectedIndices(), #+1 ??
|
|
887
|
-
position_option=self.attr_parent.position_list.getSelectedIndices(), #+1??
|
|
888
|
-
target_channel=self.channels_cb.currentText(),
|
|
889
|
-
model = self.models_cb.currentText(),
|
|
890
|
-
threshold_on_std = self.threshold_le.get_threshold(),
|
|
891
|
-
operation = operation,
|
|
892
|
-
clip = clip,
|
|
893
|
-
export= False,
|
|
894
|
-
return_stacks=True,
|
|
895
|
-
activation_protocol=[['gauss',2],['std',4]],
|
|
896
|
-
show_progress_per_well = True,
|
|
897
|
-
show_progress_per_pos = False,
|
|
898
|
-
)
|
|
899
|
-
|
|
900
|
-
if corrected_stack:
|
|
901
|
-
self.viewer = StackVisualizer(
|
|
902
|
-
stack=corrected_stack[0],
|
|
903
|
-
window_title='Corrected channel',
|
|
904
|
-
target_channel=self.channels_cb.currentIndex(),
|
|
905
|
-
frame_slider = True,
|
|
906
|
-
contrast_slider = True
|
|
907
|
-
)
|
|
908
|
-
self.viewer.show()
|
|
909
|
-
else:
|
|
910
|
-
print("Corrected stack could not be generated... No stack available...")
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
class LocalCorrectionLayout(BackgroundFitCorrectionLayout):
|
|
915
|
-
|
|
916
|
-
"""docstring for ClassName"""
|
|
917
|
-
|
|
918
|
-
def __init__(self, *args):
|
|
919
|
-
|
|
920
|
-
super().__init__(*args)
|
|
921
|
-
|
|
922
|
-
if hasattr(self.parent_window.parent_window, 'locate_image'):
|
|
923
|
-
self.attr_parent = self.parent_window.parent_window
|
|
924
|
-
elif hasattr(self.parent_window.parent_window.parent_window, 'locate_image'):
|
|
925
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
926
|
-
else:
|
|
927
|
-
self.attr_parent = self.parent_window.parent_window.parent_window.parent_window
|
|
928
|
-
|
|
929
|
-
self.thresh_lbl.setText('Distance: ')
|
|
930
|
-
self.thresh_lbl.setToolTip('Distance from the cell mask over which to estimate local intensity.')
|
|
931
|
-
|
|
932
|
-
self.models_cb.clear()
|
|
933
|
-
self.models_cb.addItems(['mean','median'])
|
|
934
|
-
|
|
935
|
-
self.threshold_le.set_threshold(5)
|
|
936
|
-
self.threshold_le.connected_buttons = [self.threshold_viewer_btn,self.add_correction_btn]
|
|
937
|
-
self.threshold_le.setValidator(QIntValidator())
|
|
938
|
-
|
|
939
|
-
self.threshold_viewer_btn.disconnect()
|
|
940
|
-
self.threshold_viewer_btn.clicked.connect(self.set_distance_graphically)
|
|
941
|
-
|
|
942
|
-
self.corrected_stack_viewer.hide()
|
|
943
|
-
|
|
944
|
-
def set_distance_graphically(self):
|
|
945
|
-
|
|
946
|
-
self.attr_parent.locate_image()
|
|
947
|
-
self.set_target_channel()
|
|
948
|
-
thresh = self.threshold_le.get_threshold()
|
|
949
|
-
|
|
950
|
-
if self.attr_parent.current_stack is not None and thresh is not None:
|
|
951
|
-
|
|
952
|
-
self.viewer = CellEdgeVisualizer(cell_type=self.parent_window.parent_window.mode,
|
|
953
|
-
stack_path=self.attr_parent.current_stack,
|
|
954
|
-
parent_le = self.threshold_le,
|
|
955
|
-
n_channels=len(self.channel_names),
|
|
956
|
-
target_channel=self.channels_cb.currentIndex(),
|
|
957
|
-
edge_range = (0,30),
|
|
958
|
-
initial_edge=int(thresh),
|
|
959
|
-
invert=True,
|
|
960
|
-
window_title='Set an edge distance to estimate local intensity',
|
|
961
|
-
channel_cb=False,
|
|
962
|
-
PxToUm = 1,
|
|
963
|
-
)
|
|
964
|
-
self.viewer.show()
|
|
965
|
-
|
|
966
|
-
def generate_instructions(self):
|
|
967
|
-
|
|
968
|
-
if self.operation_layout.subtract_btn.isChecked():
|
|
969
|
-
operation = "subtract"
|
|
970
|
-
else:
|
|
971
|
-
operation = "divide"
|
|
972
|
-
clip = None
|
|
973
|
-
|
|
974
|
-
if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
|
|
975
|
-
clip = True
|
|
976
|
-
else:
|
|
977
|
-
clip = False
|
|
978
|
-
|
|
979
|
-
self.instructions = {
|
|
980
|
-
"target_channel": self.channels_cb.currentText(),
|
|
981
|
-
"correction_type": "local",
|
|
982
|
-
"model": self.models_cb.currentText(),
|
|
983
|
-
"distance": int(self.threshold_le.get_threshold()),
|
|
984
|
-
"operation": operation,
|
|
985
|
-
"clip": clip,
|
|
986
|
-
}
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
class OperationLayout(QVBoxLayout):
|
|
990
|
-
|
|
991
|
-
"""docstring for ClassName"""
|
|
992
|
-
|
|
993
|
-
def __init__(self, ratio=(0.25,0.75), *args):
|
|
994
|
-
|
|
995
|
-
super().__init__(*args)
|
|
996
|
-
|
|
997
|
-
self.ratio = ratio
|
|
998
|
-
self.generate_widgets()
|
|
999
|
-
self.generate_layout()
|
|
1000
|
-
|
|
1001
|
-
def generate_widgets(self):
|
|
1002
|
-
|
|
1003
|
-
self.operation_lbl = QLabel('Operation: ')
|
|
1004
|
-
self.operation_group = QButtonGroup()
|
|
1005
|
-
self.subtract_btn = QRadioButton('Subtract')
|
|
1006
|
-
self.divide_btn = QRadioButton('Divide')
|
|
1007
|
-
self.subtract_btn.toggled.connect(self.activate_clipping_options)
|
|
1008
|
-
self.divide_btn.toggled.connect(self.activate_clipping_options)
|
|
1009
|
-
|
|
1010
|
-
self.operation_group.addButton(self.subtract_btn)
|
|
1011
|
-
self.operation_group.addButton(self.divide_btn)
|
|
1012
|
-
|
|
1013
|
-
self.clip_group = QButtonGroup()
|
|
1014
|
-
self.clip_btn = QRadioButton('Clip')
|
|
1015
|
-
self.clip_not_btn = QRadioButton('Do not clip')
|
|
1016
|
-
|
|
1017
|
-
self.clip_group.addButton(self.clip_btn)
|
|
1018
|
-
self.clip_group.addButton(self.clip_not_btn)
|
|
1019
|
-
|
|
1020
|
-
def generate_layout(self):
|
|
1021
|
-
|
|
1022
|
-
operation_layout = QHBoxLayout()
|
|
1023
|
-
operation_layout.addWidget(self.operation_lbl, 100*int(self.ratio[0]))
|
|
1024
|
-
operation_layout.addWidget(self.subtract_btn, 100*int(self.ratio[1])//2, alignment=Qt.AlignCenter)
|
|
1025
|
-
operation_layout.addWidget(self.divide_btn, 100*int(self.ratio[1])//2, alignment=Qt.AlignCenter)
|
|
1026
|
-
self.addLayout(operation_layout)
|
|
1027
|
-
|
|
1028
|
-
clip_layout = QHBoxLayout()
|
|
1029
|
-
clip_layout.addWidget(QLabel(''), 100*int(self.ratio[0]))
|
|
1030
|
-
clip_layout.addWidget(self.clip_btn, 100*int(self.ratio[1])//4, alignment=Qt.AlignCenter)
|
|
1031
|
-
clip_layout.addWidget(self.clip_not_btn, 100*int(self.ratio[1])//4, alignment=Qt.AlignCenter)
|
|
1032
|
-
clip_layout.addWidget(QLabel(''), 100*int(self.ratio[1])//2)
|
|
1033
|
-
self.addLayout(clip_layout)
|
|
1034
|
-
|
|
1035
|
-
self.subtract_btn.click()
|
|
1036
|
-
self.clip_not_btn.click()
|
|
1037
|
-
|
|
1038
|
-
def activate_clipping_options(self):
|
|
1039
|
-
|
|
1040
|
-
if self.subtract_btn.isChecked():
|
|
1041
|
-
self.clip_btn.setEnabled(True)
|
|
1042
|
-
self.clip_not_btn.setEnabled(True)
|
|
1043
|
-
else:
|
|
1044
|
-
self.clip_btn.setEnabled(False)
|
|
1045
|
-
self.clip_not_btn.setEnabled(False)
|
|
1046
|
-
|
|
1047
|
-
class ProtocolDesignerLayout(QVBoxLayout, Styles):
|
|
1048
|
-
|
|
1049
|
-
"""Multi tabs and list widget configuration for background correction
|
|
1050
|
-
in preprocessing and measurements
|
|
1051
|
-
"""
|
|
1052
|
-
|
|
1053
|
-
def __init__(self, parent_window=None, tab_layouts=[], tab_names=[], title='',list_title='',*args):
|
|
1054
|
-
|
|
1055
|
-
super().__init__(*args)
|
|
1056
|
-
|
|
1057
|
-
self.title = title
|
|
1058
|
-
self.parent_window = parent_window
|
|
1059
|
-
self.channel_names = self.parent_window.channel_names
|
|
1060
|
-
self.tab_layouts = tab_layouts
|
|
1061
|
-
self.tab_names = tab_names
|
|
1062
|
-
self.list_title = list_title
|
|
1063
|
-
self.protocols = []
|
|
1064
|
-
assert len(self.tab_layouts)==len(self.tab_names)
|
|
1065
|
-
|
|
1066
|
-
self.generate_widgets()
|
|
1067
|
-
self.generate_layout()
|
|
1068
|
-
|
|
1069
|
-
def generate_widgets(self):
|
|
1070
|
-
|
|
1071
|
-
self.title_lbl = QLabel(self.title)
|
|
1072
|
-
self.title_lbl.setStyleSheet("""
|
|
1073
|
-
font-weight: bold;
|
|
1074
|
-
padding: 0px;
|
|
1075
|
-
""")
|
|
1076
|
-
|
|
1077
|
-
self.tabs = QTabWidget()
|
|
1078
|
-
self.tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
1079
|
-
|
|
1080
|
-
for k in range(len(self.tab_layouts)):
|
|
1081
|
-
wg = CelldetectiveWidget()
|
|
1082
|
-
self.tab_layouts[k].parent_window = self
|
|
1083
|
-
wg.setLayout(self.tab_layouts[k])
|
|
1084
|
-
self.tabs.addTab(wg, self.tab_names[k])
|
|
1085
|
-
|
|
1086
|
-
self.protocol_list_lbl = QLabel(self.list_title)
|
|
1087
|
-
self.protocol_list = QListWidget()
|
|
1088
|
-
|
|
1089
|
-
self.delete_protocol_btn = QPushButton('')
|
|
1090
|
-
self.delete_protocol_btn.setStyleSheet(self.button_select_all)
|
|
1091
|
-
self.delete_protocol_btn.setIcon(icon(MDI6.trash_can, color="black"))
|
|
1092
|
-
self.delete_protocol_btn.setToolTip("Remove.")
|
|
1093
|
-
self.delete_protocol_btn.setIconSize(QSize(20, 20))
|
|
1094
|
-
self.delete_protocol_btn.clicked.connect(self.remove_protocol_from_list)
|
|
1095
|
-
|
|
1096
|
-
def generate_layout(self):
|
|
1097
|
-
|
|
1098
|
-
self.correction_layout = QVBoxLayout()
|
|
1099
|
-
|
|
1100
|
-
self.background_correction_layout = QVBoxLayout()
|
|
1101
|
-
self.background_correction_layout.setContentsMargins(0,0,0,0)
|
|
1102
|
-
self.title_layout = QHBoxLayout()
|
|
1103
|
-
self.title_layout.addWidget(self.title_lbl, 100, alignment=Qt.AlignCenter)
|
|
1104
|
-
self.background_correction_layout.addLayout(self.title_layout)
|
|
1105
|
-
self.background_correction_layout.addWidget(self.tabs)
|
|
1106
|
-
self.correction_layout.addLayout(self.background_correction_layout)
|
|
1107
|
-
|
|
1108
|
-
self.addLayout(self.correction_layout)
|
|
1109
|
-
|
|
1110
|
-
self.list_layout = QVBoxLayout()
|
|
1111
|
-
list_header_layout = QHBoxLayout()
|
|
1112
|
-
list_header_layout.addWidget(self.protocol_list_lbl)
|
|
1113
|
-
list_header_layout.addWidget(self.delete_protocol_btn, alignment=Qt.AlignRight)
|
|
1114
|
-
self.list_layout.addLayout(list_header_layout)
|
|
1115
|
-
self.list_layout.addWidget(self.protocol_list)
|
|
1116
|
-
|
|
1117
|
-
self.addLayout(self.list_layout)
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
def remove_protocol_from_list(self):
|
|
1121
|
-
|
|
1122
|
-
current_item = self.protocol_list.currentRow()
|
|
1123
|
-
if current_item > -1:
|
|
1124
|
-
del self.protocols[current_item]
|
|
1125
|
-
self.protocol_list.takeItem(current_item)
|
|
1126
|
-
|
|
1127
|
-
class ChannelOffsetOptionsLayout(QVBoxLayout, Styles):
|
|
1128
|
-
|
|
1129
|
-
def __init__(self, parent_window=None, *args, **kwargs):
|
|
1130
|
-
|
|
1131
|
-
super().__init__(*args, **kwargs)
|
|
1132
|
-
|
|
1133
|
-
self.parent_window = parent_window
|
|
1134
|
-
if hasattr(self.parent_window.parent_window, 'exp_config'):
|
|
1135
|
-
self.attr_parent = self.parent_window.parent_window
|
|
1136
|
-
else:
|
|
1137
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
1138
|
-
|
|
1139
|
-
self.channel_names = self.attr_parent.exp_channels
|
|
1140
|
-
|
|
1141
|
-
self.setContentsMargins(15,15,15,15)
|
|
1142
|
-
self.generate_widgets()
|
|
1143
|
-
self.add_to_layout()
|
|
1144
|
-
|
|
1145
|
-
def generate_widgets(self):
|
|
1146
|
-
|
|
1147
|
-
self.channel_lbl = QLabel('Channel: ')
|
|
1148
|
-
self.channels_cb = QComboBox()
|
|
1149
|
-
self.channels_cb.addItems(self.channel_names)
|
|
1150
|
-
|
|
1151
|
-
self.shift_lbl = QLabel('Shift: ')
|
|
1152
|
-
self.shift_h_lbl = QLabel('(h): ')
|
|
1153
|
-
self.shift_v_lbl = QLabel('(v): ')
|
|
1154
|
-
|
|
1155
|
-
self.set_shift_btn = QPushButton()
|
|
1156
|
-
self.set_shift_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
1157
|
-
self.set_shift_btn.setStyleSheet(self.button_select_all)
|
|
1158
|
-
self.set_shift_btn.setToolTip('Set the channel shift.')
|
|
1159
|
-
self.set_shift_btn.clicked.connect(self.open_offset_viewer)
|
|
1160
|
-
|
|
1161
|
-
self.add_correction_btn = QPushButton('Add correction')
|
|
1162
|
-
self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
|
|
1163
|
-
self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
|
|
1164
|
-
self.add_correction_btn.setToolTip('Add correction.')
|
|
1165
|
-
self.add_correction_btn.setIconSize(QSize(25, 25))
|
|
1166
|
-
self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
|
|
1167
|
-
|
|
1168
|
-
self.vertical_shift_le = ThresholdLineEdit(init_value=0, connected_buttons=[self.add_correction_btn],placeholder='vertical shift [pixels]', value_type='float')
|
|
1169
|
-
self.horizontal_shift_le = ThresholdLineEdit(init_value=0, connected_buttons=[self.add_correction_btn],placeholder='vertical shift [pixels]', value_type='float')
|
|
1170
|
-
|
|
1171
|
-
def add_to_layout(self):
|
|
1172
|
-
|
|
1173
|
-
channel_ch_hbox = QHBoxLayout()
|
|
1174
|
-
channel_ch_hbox.addWidget(self.channel_lbl, 25)
|
|
1175
|
-
channel_ch_hbox.addWidget(self.channels_cb, 75)
|
|
1176
|
-
self.addLayout(channel_ch_hbox)
|
|
1177
|
-
|
|
1178
|
-
shift_hbox = QHBoxLayout()
|
|
1179
|
-
shift_hbox.addWidget(self.shift_lbl, 25)
|
|
1180
|
-
|
|
1181
|
-
shift_subhbox = QHBoxLayout()
|
|
1182
|
-
shift_subhbox.addWidget(self.shift_h_lbl, 10)
|
|
1183
|
-
shift_subhbox.addWidget(self.horizontal_shift_le, 75//2)
|
|
1184
|
-
shift_subhbox.addWidget(self.shift_v_lbl, 10)
|
|
1185
|
-
shift_subhbox.addWidget(self.vertical_shift_le, 75//2)
|
|
1186
|
-
shift_subhbox.addWidget(self.set_shift_btn, 5)
|
|
1187
|
-
|
|
1188
|
-
shift_hbox.addLayout(shift_subhbox, 75)
|
|
1189
|
-
self.addLayout(shift_hbox)
|
|
1190
|
-
|
|
1191
|
-
btn_hbox = QHBoxLayout()
|
|
1192
|
-
btn_hbox.addWidget(self.add_correction_btn, 95)
|
|
1193
|
-
self.addLayout(btn_hbox)
|
|
1194
|
-
|
|
1195
|
-
def add_instructions_to_parent_list(self):
|
|
1196
|
-
|
|
1197
|
-
self.generate_instructions()
|
|
1198
|
-
self.parent_window.protocol_layout.protocols.append(self.instructions)
|
|
1199
|
-
correction_description = ""
|
|
1200
|
-
for index, (key, value) in enumerate(self.instructions.items()):
|
|
1201
|
-
if index > 0:
|
|
1202
|
-
correction_description += ", "
|
|
1203
|
-
correction_description += str(key) + " : " + str(value)
|
|
1204
|
-
self.parent_window.protocol_layout.protocol_list.addItem(correction_description)
|
|
1205
|
-
|
|
1206
|
-
def generate_instructions(self):
|
|
1207
|
-
|
|
1208
|
-
self.instructions = {
|
|
1209
|
-
"correction_type": "offset",
|
|
1210
|
-
"target_channel": self.channels_cb.currentText(),
|
|
1211
|
-
"correction_horizontal": self.horizontal_shift_le.get_threshold(),
|
|
1212
|
-
"correction_vertical": self.vertical_shift_le.get_threshold(),
|
|
1213
|
-
}
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
def set_target_channel(self):
|
|
1217
|
-
|
|
1218
|
-
channel_indices = _extract_channel_indices_from_config(self.attr_parent.exp_config, [self.channels_cb.currentText()])
|
|
1219
|
-
self.target_channel = channel_indices[0]
|
|
1220
|
-
|
|
1221
|
-
def open_offset_viewer(self):
|
|
1222
|
-
|
|
1223
|
-
self.attr_parent.locate_image()
|
|
1224
|
-
self.set_target_channel()
|
|
1225
|
-
|
|
1226
|
-
if self.attr_parent.current_stack is not None:
|
|
1227
|
-
self.viewer = ChannelOffsetViewer(
|
|
1228
|
-
parent_window = self,
|
|
1229
|
-
stack_path=self.attr_parent.current_stack,
|
|
1230
|
-
channel_names=self.attr_parent.exp_channels,
|
|
1231
|
-
n_channels=len(self.channel_names),
|
|
1232
|
-
channel_cb=True,
|
|
1233
|
-
target_channel=self.target_channel,
|
|
1234
|
-
window_title='offset viewer',
|
|
1235
|
-
)
|
|
1236
|
-
self.viewer.show()
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
|
|
1240
|
-
|
|
1241
|
-
"""docstring for ClassName"""
|
|
1242
|
-
|
|
1243
|
-
def __init__(self, parent_window=None, *args):
|
|
1244
|
-
super().__init__(*args)
|
|
1245
|
-
|
|
1246
|
-
self.parent_window = parent_window
|
|
1247
|
-
|
|
1248
|
-
if hasattr(self.parent_window.parent_window, 'exp_config'):
|
|
1249
|
-
self.attr_parent = self.parent_window.parent_window
|
|
1250
|
-
else:
|
|
1251
|
-
self.attr_parent = self.parent_window.parent_window.parent_window
|
|
1252
|
-
|
|
1253
|
-
self.channel_names = self.attr_parent.exp_channels
|
|
1254
|
-
|
|
1255
|
-
self.setContentsMargins(15,15,15,15)
|
|
1256
|
-
self.generate_widgets()
|
|
1257
|
-
self.add_to_layout()
|
|
1258
|
-
|
|
1259
|
-
def generate_widgets(self):
|
|
1260
|
-
|
|
1261
|
-
self.channel_lbl = QLabel('Channel: ')
|
|
1262
|
-
self.channels_cb = QComboBox()
|
|
1263
|
-
self.channels_cb.addItems(self.channel_names)
|
|
1264
|
-
|
|
1265
|
-
self.acquistion_lbl = QLabel('Stack mode: ')
|
|
1266
|
-
self.acq_mode_group = QButtonGroup()
|
|
1267
|
-
self.timeseries_rb = QRadioButton('timeseries')
|
|
1268
|
-
self.timeseries_rb.setChecked(True)
|
|
1269
|
-
self.tiles_rb = QRadioButton('tiles')
|
|
1270
|
-
self.acq_mode_group.addButton(self.timeseries_rb, 0)
|
|
1271
|
-
self.acq_mode_group.addButton(self.tiles_rb, 1)
|
|
1272
|
-
|
|
1273
|
-
from PyQt5.QtWidgets import QSlider
|
|
1274
|
-
from superqt import QRangeSlider
|
|
1275
|
-
self.frame_range_slider = QLabeledRangeSlider(parent=None)
|
|
1276
|
-
|
|
1277
|
-
self.timeseries_rb.toggled.connect(self.activate_time_range)
|
|
1278
|
-
self.tiles_rb.toggled.connect(self.activate_time_range)
|
|
1279
|
-
|
|
1280
|
-
self.thresh_lbl = QLabel('Threshold: ')
|
|
1281
|
-
self.thresh_lbl.setToolTip('Threshold on the STD-filtered image.\nPixel values above the threshold are\nconsidered as non-background and are\nmasked prior to background estimation.')
|
|
1282
|
-
self.threshold_viewer_btn = QPushButton()
|
|
1283
|
-
self.threshold_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
1284
|
-
self.threshold_viewer_btn.setStyleSheet(self.button_select_all)
|
|
1285
|
-
self.threshold_viewer_btn.clicked.connect(self.set_threshold_graphically)
|
|
1286
|
-
|
|
1287
|
-
self.background_viewer_btn = QPushButton()
|
|
1288
|
-
self.background_viewer_btn.setIcon(icon(MDI6.image_check, color="k"))
|
|
1289
|
-
self.background_viewer_btn.setStyleSheet(self.button_select_all)
|
|
1290
|
-
self.background_viewer_btn.setToolTip('View reconstructed background.')
|
|
1291
|
-
|
|
1292
|
-
self.corrected_stack_viewer_btn = QPushButton("")
|
|
1293
|
-
self.corrected_stack_viewer_btn.setStyleSheet(self.button_select_all)
|
|
1294
|
-
self.corrected_stack_viewer_btn.setIcon(icon(MDI6.eye_outline, color="black"))
|
|
1295
|
-
self.corrected_stack_viewer_btn.setToolTip("View corrected image")
|
|
1296
|
-
self.corrected_stack_viewer_btn.clicked.connect(self.preview_correction)
|
|
1297
|
-
self.corrected_stack_viewer_btn.setIconSize(QSize(20, 20))
|
|
1298
|
-
|
|
1299
|
-
self.add_correction_btn = QPushButton('Add correction')
|
|
1300
|
-
self.add_correction_btn.setStyleSheet(self.button_style_sheet_2)
|
|
1301
|
-
self.add_correction_btn.setIcon(icon(MDI6.plus, color="#1565c0"))
|
|
1302
|
-
self.add_correction_btn.setToolTip('Add correction.')
|
|
1303
|
-
self.add_correction_btn.setIconSize(QSize(25, 25))
|
|
1304
|
-
self.add_correction_btn.clicked.connect(self.add_instructions_to_parent_list)
|
|
1305
|
-
|
|
1306
|
-
self.threshold_le = ThresholdLineEdit(init_value=2, connected_buttons=[self.threshold_viewer_btn,
|
|
1307
|
-
self.background_viewer_btn, self.corrected_stack_viewer_btn, self.add_correction_btn])
|
|
1308
|
-
|
|
1309
|
-
self.well_slider = QLabeledSlider(parent=None)
|
|
1310
|
-
|
|
1311
|
-
self.background_viewer_btn.clicked.connect(self.estimate_bg)
|
|
1312
|
-
|
|
1313
|
-
self.regress_cb = QCheckBox('Optimize for each frame?')
|
|
1314
|
-
self.regress_cb.toggled.connect(self.activate_coef_options)
|
|
1315
|
-
self.regress_cb.setChecked(False)
|
|
1316
|
-
|
|
1317
|
-
self.coef_range_slider = QLabeledDoubleRangeSlider(parent=None)
|
|
1318
|
-
self.coef_range_layout = QuickSliderLayout(label='Coef. range: ',
|
|
1319
|
-
slider = self.coef_range_slider,
|
|
1320
|
-
slider_initial_value=(0.95,1.05),
|
|
1321
|
-
slider_range=(0.75,1.25),
|
|
1322
|
-
slider_tooltip='Coefficient range to increase or decrease the background intensity level...',
|
|
1323
|
-
)
|
|
1324
|
-
|
|
1325
|
-
self.nbr_coefs_lbl = QLabel("Nbr of coefs: ")
|
|
1326
|
-
self.nbr_coefs_lbl.setToolTip('Number of coefficients to be tested within range.\nThe more, the slower.')
|
|
1327
|
-
|
|
1328
|
-
self.nbr_coef_le = QLineEdit()
|
|
1329
|
-
self.nbr_coef_le.setText('100')
|
|
1330
|
-
self.nbr_coef_le.setValidator(QIntValidator())
|
|
1331
|
-
self.nbr_coef_le.setPlaceholderText('nbr of coefs')
|
|
1332
|
-
|
|
1333
|
-
self.coef_widgets = [self.coef_range_layout.qlabel, self.coef_range_slider, self.nbr_coefs_lbl, self.nbr_coef_le]
|
|
1334
|
-
for c in self.coef_widgets:
|
|
1335
|
-
c.setEnabled(False)
|
|
1336
|
-
|
|
1337
|
-
self.interpolate_check = QCheckBox("interpolate NaNs")
|
|
1338
|
-
|
|
1339
|
-
def add_to_layout(self):
|
|
1340
|
-
|
|
1341
|
-
channel_layout = QHBoxLayout()
|
|
1342
|
-
channel_layout.addWidget(self.channel_lbl, 25)
|
|
1343
|
-
channel_layout.addWidget(self.channels_cb, 75)
|
|
1344
|
-
self.addLayout(channel_layout, 0, 0, 1, 3)
|
|
1345
|
-
|
|
1346
|
-
acquisition_layout = QHBoxLayout()
|
|
1347
|
-
acquisition_layout.addWidget(self.acquistion_lbl, 25)
|
|
1348
|
-
acquisition_layout.addWidget(self.timeseries_rb, 75//2, alignment=Qt.AlignCenter)
|
|
1349
|
-
acquisition_layout.addWidget(self.tiles_rb, 75//2, alignment=Qt.AlignCenter)
|
|
1350
|
-
self.addLayout(acquisition_layout, 1, 0, 1, 3)
|
|
1351
|
-
|
|
1352
|
-
frame_selection_layout = QuickSliderLayout(label='Time range: ',
|
|
1353
|
-
slider = self.frame_range_slider,
|
|
1354
|
-
slider_initial_value=(0,5),
|
|
1355
|
-
slider_range=(0,self.attr_parent.len_movie),
|
|
1356
|
-
slider_tooltip='frame [#]',
|
|
1357
|
-
decimal_option = False,
|
|
1358
|
-
)
|
|
1359
|
-
frame_selection_layout.qlabel.setToolTip('Frame range for which the background\nis most likely to be observed.')
|
|
1360
|
-
self.time_range_options = [self.frame_range_slider, frame_selection_layout.qlabel]
|
|
1361
|
-
self.addLayout(frame_selection_layout, 2, 0, 1, 3)
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
threshold_layout = QHBoxLayout()
|
|
1365
|
-
threshold_layout.addWidget(self.thresh_lbl, 25)
|
|
1366
|
-
subthreshold_layout = QHBoxLayout()
|
|
1367
|
-
subthreshold_layout.addWidget(self.threshold_le, 95)
|
|
1368
|
-
subthreshold_layout.addWidget(self.threshold_viewer_btn, 5)
|
|
1369
|
-
threshold_layout.addLayout(subthreshold_layout, 75)
|
|
1370
|
-
self.addLayout(threshold_layout, 3, 0, 1, 3)
|
|
1371
|
-
|
|
1372
|
-
background_layout = QuickSliderLayout(label='QC for well: ',
|
|
1373
|
-
slider = self.well_slider,
|
|
1374
|
-
slider_initial_value=1,
|
|
1375
|
-
slider_range=(1,len(self.attr_parent.wells)),
|
|
1376
|
-
slider_tooltip='well [#]',
|
|
1377
|
-
decimal_option = False,
|
|
1378
|
-
layout_ratio=(0.25,0.70)
|
|
1379
|
-
)
|
|
1380
|
-
background_layout.addWidget(self.background_viewer_btn, 5)
|
|
1381
|
-
self.addLayout(background_layout, 4, 0, 1, 3)
|
|
1382
|
-
|
|
1383
|
-
self.addWidget(self.regress_cb, 5, 0, 1, 3)
|
|
1384
|
-
|
|
1385
|
-
self.addLayout(self.coef_range_layout, 6, 0, 1, 3)
|
|
1386
|
-
|
|
1387
|
-
coef_nbr_layout = QHBoxLayout()
|
|
1388
|
-
coef_nbr_layout.addWidget(self.nbr_coefs_lbl, 25)
|
|
1389
|
-
coef_nbr_layout.addWidget(self.nbr_coef_le, 75)
|
|
1390
|
-
self.addLayout(coef_nbr_layout, 7,0,1,3)
|
|
1391
|
-
|
|
1392
|
-
offset_layout = QHBoxLayout()
|
|
1393
|
-
offset_layout.addWidget(QLabel("Offset: "), 25)
|
|
1394
|
-
self.camera_offset_le = QLineEdit("0")
|
|
1395
|
-
self.camera_offset_le.setPlaceholderText('camera black level')
|
|
1396
|
-
self.camera_offset_le.setValidator(QDoubleValidator())
|
|
1397
|
-
offset_layout.addWidget(self.camera_offset_le, 75)
|
|
1398
|
-
self.addLayout(offset_layout, 8, 0, 1, 3)
|
|
1399
|
-
|
|
1400
|
-
self.operation_layout = OperationLayout()
|
|
1401
|
-
self.addLayout(self.operation_layout, 9, 0, 1, 3)
|
|
1402
|
-
|
|
1403
|
-
self.addWidget(self.interpolate_check, 10, 0, 1, 1)
|
|
1404
|
-
|
|
1405
|
-
correction_layout = QHBoxLayout()
|
|
1406
|
-
correction_layout.addWidget(self.add_correction_btn, 95)
|
|
1407
|
-
correction_layout.addWidget(self.corrected_stack_viewer_btn, 5)
|
|
1408
|
-
self.addLayout(correction_layout, 11, 0, 1, 3)
|
|
1409
|
-
|
|
1410
|
-
# verticalSpacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
1411
|
-
# self.addItem(verticalSpacer, 5, 0, 1, 3)
|
|
1412
|
-
|
|
1413
|
-
def add_instructions_to_parent_list(self):
|
|
1414
|
-
|
|
1415
|
-
self.generate_instructions()
|
|
1416
|
-
self.parent_window.protocols.append(self.instructions)
|
|
1417
|
-
correction_description = ""
|
|
1418
|
-
for index, (key, value) in enumerate(self.instructions.items()):
|
|
1419
|
-
if index > 0:
|
|
1420
|
-
correction_description += ", "
|
|
1421
|
-
correction_description += str(key) + " : " + str(value)
|
|
1422
|
-
self.parent_window.protocol_list.addItem(correction_description)
|
|
1423
|
-
|
|
1424
|
-
def generate_instructions(self):
|
|
1425
|
-
|
|
1426
|
-
if self.timeseries_rb.isChecked():
|
|
1427
|
-
mode = "timeseries"
|
|
1428
|
-
elif self.tiles_rb.isChecked():
|
|
1429
|
-
mode = "tiles"
|
|
1430
|
-
|
|
1431
|
-
if self.regress_cb.isChecked():
|
|
1432
|
-
optimize_option = True
|
|
1433
|
-
opt_coef_range = self.coef_range_slider.value()
|
|
1434
|
-
opt_coef_nbr = int(self.nbr_coef_le.text())
|
|
1435
|
-
else:
|
|
1436
|
-
optimize_option = False
|
|
1437
|
-
opt_coef_range = None
|
|
1438
|
-
opt_coef_nbr = None
|
|
1439
|
-
|
|
1440
|
-
if self.operation_layout.subtract_btn.isChecked():
|
|
1441
|
-
operation = "subtract"
|
|
1442
|
-
else:
|
|
1443
|
-
operation = "divide"
|
|
1444
|
-
clip = None
|
|
1445
|
-
|
|
1446
|
-
if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
|
|
1447
|
-
clip = True
|
|
1448
|
-
else:
|
|
1449
|
-
clip = False
|
|
1450
|
-
|
|
1451
|
-
if self.camera_offset_le.text()=="":
|
|
1452
|
-
offset = None
|
|
1453
|
-
else:
|
|
1454
|
-
offset = float(self.camera_offset_le.text().replace(",","."))
|
|
1455
|
-
|
|
1456
|
-
self.instructions = {
|
|
1457
|
-
"target_channel": self.channels_cb.currentText(),
|
|
1458
|
-
"correction_type": "model-free",
|
|
1459
|
-
"threshold_on_std": self.threshold_le.get_threshold(),
|
|
1460
|
-
"frame_range": self.frame_range_slider.value(),
|
|
1461
|
-
"mode": mode,
|
|
1462
|
-
"optimize_option": optimize_option,
|
|
1463
|
-
"opt_coef_range": opt_coef_range,
|
|
1464
|
-
"opt_coef_nbr": opt_coef_nbr,
|
|
1465
|
-
"operation": operation,
|
|
1466
|
-
"clip": clip,
|
|
1467
|
-
"offset": offset,
|
|
1468
|
-
"fix_nan": self.interpolate_check.isChecked(),
|
|
1469
|
-
}
|
|
1470
|
-
|
|
1471
|
-
def set_target_channel(self):
|
|
1472
|
-
|
|
1473
|
-
channel_indices = _extract_channel_indices_from_config(self.attr_parent.exp_config, [self.channels_cb.currentText()])
|
|
1474
|
-
self.target_channel = channel_indices[0]
|
|
1475
|
-
|
|
1476
|
-
def set_threshold_graphically(self):
|
|
1477
|
-
|
|
1478
|
-
self.attr_parent.locate_image()
|
|
1479
|
-
self.set_target_channel()
|
|
1480
|
-
thresh = self.threshold_le.get_threshold()
|
|
1481
|
-
|
|
1482
|
-
if self.attr_parent.current_stack is not None and thresh is not None:
|
|
1483
|
-
self.viewer = ThresholdedStackVisualizer(initial_threshold=thresh,
|
|
1484
|
-
parent_le = self.threshold_le,
|
|
1485
|
-
preprocessing=[['gauss',2],["std",4]],
|
|
1486
|
-
stack_path=self.attr_parent.current_stack,
|
|
1487
|
-
n_channels=len(self.channel_names),
|
|
1488
|
-
target_channel=self.target_channel,
|
|
1489
|
-
window_title='Set the exclusion threshold',
|
|
1490
|
-
)
|
|
1491
|
-
self.viewer.show()
|
|
1492
|
-
|
|
1493
|
-
def preview_correction(self):
|
|
1494
|
-
|
|
1495
|
-
if self.attr_parent.well_list.isMultipleSelection() or not self.attr_parent.well_list.isAnySelected() or self.attr_parent.position_list.isMultipleSelection() or not self.attr_parent.position_list.isAnySelected():
|
|
1496
|
-
msgBox = QMessageBox()
|
|
1497
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
1498
|
-
msgBox.setText("Please select a single position...")
|
|
1499
|
-
msgBox.setWindowTitle("Warning")
|
|
1500
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1501
|
-
returnValue = msgBox.exec()
|
|
1502
|
-
if returnValue == QMessageBox.Ok:
|
|
1503
|
-
return None
|
|
1504
|
-
|
|
1505
|
-
if self.timeseries_rb.isChecked():
|
|
1506
|
-
mode = "timeseries"
|
|
1507
|
-
elif self.tiles_rb.isChecked():
|
|
1508
|
-
mode = "tiles"
|
|
1509
|
-
|
|
1510
|
-
if self.regress_cb.isChecked():
|
|
1511
|
-
optimize_option = True
|
|
1512
|
-
opt_coef_range = self.coef_range_slider.value()
|
|
1513
|
-
opt_coef_nbr = int(self.nbr_coef_le.text())
|
|
1514
|
-
else:
|
|
1515
|
-
optimize_option = False
|
|
1516
|
-
opt_coef_range = None
|
|
1517
|
-
opt_coef_nbr = None
|
|
1518
|
-
|
|
1519
|
-
if self.operation_layout.subtract_btn.isChecked():
|
|
1520
|
-
operation = "subtract"
|
|
1521
|
-
else:
|
|
1522
|
-
operation = "divide"
|
|
1523
|
-
clip = None
|
|
1524
|
-
|
|
1525
|
-
if self.operation_layout.clip_btn.isChecked() and self.operation_layout.subtract_btn.isChecked():
|
|
1526
|
-
clip = True
|
|
1527
|
-
else:
|
|
1528
|
-
clip = False
|
|
1529
|
-
|
|
1530
|
-
corrected_stacks = correct_background_model_free(self.attr_parent.exp_dir,
|
|
1531
|
-
well_option=self.attr_parent.well_list.getSelectedIndices(), #+1 ??
|
|
1532
|
-
position_option=self.attr_parent.getSelectedIndices(), #+1??
|
|
1533
|
-
target_channel=self.channels_cb.currentText(),
|
|
1534
|
-
mode = mode,
|
|
1535
|
-
threshold_on_std = self.threshold_le.get_threshold(),
|
|
1536
|
-
frame_range = self.frame_range_slider.value(),
|
|
1537
|
-
optimize_option = optimize_option,
|
|
1538
|
-
opt_coef_range = opt_coef_range,
|
|
1539
|
-
opt_coef_nbr = opt_coef_nbr,
|
|
1540
|
-
operation = operation,
|
|
1541
|
-
clip = clip,
|
|
1542
|
-
export= False,
|
|
1543
|
-
return_stacks=True,
|
|
1544
|
-
fix_nan=self.interpolate_check.isChecked(),
|
|
1545
|
-
show_progress_per_well = True,
|
|
1546
|
-
show_progress_per_pos = False,
|
|
1547
|
-
)
|
|
1548
|
-
|
|
1549
|
-
self.viewer = StackVisualizer(
|
|
1550
|
-
stack=corrected_stacks[0],
|
|
1551
|
-
window_title='Corrected channel',
|
|
1552
|
-
frame_slider = True,
|
|
1553
|
-
contrast_slider = True,
|
|
1554
|
-
target_channel=self.channels_cb.currentIndex(),
|
|
1555
|
-
)
|
|
1556
|
-
self.viewer.show()
|
|
1557
|
-
|
|
1558
|
-
def activate_time_range(self):
|
|
1559
|
-
|
|
1560
|
-
if self.timeseries_rb.isChecked():
|
|
1561
|
-
for wg in self.time_range_options:
|
|
1562
|
-
wg.setEnabled(True)
|
|
1563
|
-
elif self.tiles_rb.isChecked():
|
|
1564
|
-
for wg in self.time_range_options:
|
|
1565
|
-
wg.setEnabled(False)
|
|
1566
|
-
|
|
1567
|
-
def activate_coef_options(self):
|
|
1568
|
-
|
|
1569
|
-
if self.regress_cb.isChecked():
|
|
1570
|
-
for c in self.coef_widgets:
|
|
1571
|
-
c.setEnabled(True)
|
|
1572
|
-
else:
|
|
1573
|
-
for c in self.coef_widgets:
|
|
1574
|
-
c.setEnabled(False)
|
|
1575
|
-
|
|
1576
|
-
def estimate_bg(self):
|
|
1577
|
-
|
|
1578
|
-
if self.timeseries_rb.isChecked():
|
|
1579
|
-
mode = "timeseries"
|
|
1580
|
-
elif self.tiles_rb.isChecked():
|
|
1581
|
-
mode = "tiles"
|
|
1582
|
-
|
|
1583
|
-
bg = estimate_background_per_condition(
|
|
1584
|
-
self.attr_parent.exp_dir,
|
|
1585
|
-
well_option = self.well_slider.value() - 1,
|
|
1586
|
-
frame_range = self.frame_range_slider.value(),
|
|
1587
|
-
target_channel = self.channels_cb.currentText(),
|
|
1588
|
-
show_progress_per_pos = True,
|
|
1589
|
-
threshold_on_std = self.threshold_le.get_threshold(),
|
|
1590
|
-
mode = mode,
|
|
1591
|
-
)
|
|
1592
|
-
bg = bg[0]
|
|
1593
|
-
bg = bg['bg']
|
|
1594
|
-
print(bg)
|
|
1595
|
-
if len(bg)>0:
|
|
1596
|
-
|
|
1597
|
-
self.viewer = StackVisualizer(
|
|
1598
|
-
stack=[bg],
|
|
1599
|
-
window_title='Reconstructed background',
|
|
1600
|
-
frame_slider = False,
|
|
1601
|
-
)
|
|
1602
|
-
self.viewer.show()
|