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
|
@@ -2,47 +2,90 @@ import json
|
|
|
2
2
|
import os
|
|
3
3
|
from glob import glob
|
|
4
4
|
|
|
5
|
-
import matplotlib.pyplot as plt
|
|
6
5
|
import numpy as np
|
|
7
|
-
import
|
|
8
|
-
import scipy.ndimage as ndi
|
|
9
|
-
from PyQt5.QtCore import Qt, QSize
|
|
6
|
+
from PyQt5.QtCore import Qt, QSize, QThread
|
|
10
7
|
from PyQt5.QtGui import QDoubleValidator, QIntValidator
|
|
11
|
-
from PyQt5.QtWidgets import
|
|
12
|
-
|
|
8
|
+
from PyQt5.QtWidgets import (
|
|
9
|
+
QAction,
|
|
10
|
+
QMenu,
|
|
11
|
+
QMessageBox,
|
|
12
|
+
QLabel,
|
|
13
|
+
QFileDialog,
|
|
14
|
+
QHBoxLayout,
|
|
15
|
+
QGridLayout,
|
|
16
|
+
QLineEdit,
|
|
17
|
+
QScrollArea,
|
|
18
|
+
QVBoxLayout,
|
|
19
|
+
QComboBox,
|
|
20
|
+
QPushButton,
|
|
21
|
+
QApplication,
|
|
22
|
+
QRadioButton,
|
|
23
|
+
QButtonGroup,
|
|
24
|
+
)
|
|
13
25
|
from fonticon_mdi6 import MDI6
|
|
14
|
-
|
|
26
|
+
|
|
15
27
|
from superqt import QLabeledSlider, QLabeledDoubleRangeSlider
|
|
16
28
|
from superqt.fonticon import icon
|
|
17
29
|
|
|
18
|
-
from celldetective.gui import
|
|
19
|
-
from celldetective.gui.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
from celldetective.
|
|
30
|
+
from celldetective.gui.gui_utils import PreprocessingLayout
|
|
31
|
+
from celldetective.gui.base.components import (
|
|
32
|
+
generic_message,
|
|
33
|
+
CelldetectiveMainWindow,
|
|
34
|
+
CelldetectiveWidget,
|
|
35
|
+
)
|
|
36
|
+
from celldetective.gui.gui_utils import color_from_class, help_generic
|
|
37
|
+
from celldetective.gui.base.figure_canvas import FigureCanvas
|
|
38
|
+
from celldetective.gui.viewers.threshold_viewer import ThresholdedStackVisualizer
|
|
39
|
+
from celldetective.utils.image_loaders import load_frames
|
|
40
|
+
|
|
41
|
+
from celldetective import (
|
|
42
|
+
get_software_location,
|
|
43
|
+
)
|
|
44
|
+
from celldetective.utils.data_cleaning import rename_intensity_column
|
|
45
|
+
from celldetective.utils.experiment import extract_experiment_channels
|
|
46
|
+
import logging
|
|
47
|
+
|
|
48
|
+
logger = logging.getLogger(__name__)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BackgroundLoader(QThread):
|
|
52
|
+
def run(self):
|
|
53
|
+
logger.info("Loading background packages...")
|
|
54
|
+
try:
|
|
55
|
+
from celldetective.segmentation import (
|
|
56
|
+
identify_markers_from_binary,
|
|
57
|
+
apply_watershed,
|
|
58
|
+
)
|
|
59
|
+
from scipy.ndimage._measurements import label
|
|
60
|
+
import pandas as pd
|
|
61
|
+
from celldetective.regionprops._regionprops import regionprops_table
|
|
62
|
+
except Exception:
|
|
63
|
+
logger.error("Background packages not loaded...")
|
|
64
|
+
logger.info("Background packages loaded...")
|
|
25
65
|
|
|
26
66
|
|
|
27
67
|
class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
28
68
|
"""
|
|
29
|
-
|
|
69
|
+
UI to create a threshold pipeline for segmentation.
|
|
30
70
|
|
|
31
|
-
|
|
71
|
+
"""
|
|
32
72
|
|
|
33
73
|
def __init__(self, parent_window=None):
|
|
34
74
|
|
|
35
75
|
super().__init__()
|
|
36
76
|
self.parent_window = parent_window
|
|
37
|
-
self.screen_height =
|
|
38
|
-
|
|
77
|
+
self.screen_height = (
|
|
78
|
+
self.parent_window.parent_window.parent_window.parent_window.screen_height
|
|
79
|
+
)
|
|
80
|
+
self.screen_width = (
|
|
81
|
+
self.parent_window.parent_window.parent_window.parent_window.screen_width
|
|
82
|
+
)
|
|
39
83
|
self.setMinimumWidth(int(0.8 * self.screen_width))
|
|
40
84
|
self.setMinimumHeight(int(0.8 * self.screen_height))
|
|
41
85
|
self.setWindowTitle("Threshold configuration wizard")
|
|
42
|
-
center_window(self)
|
|
43
86
|
|
|
44
87
|
self._createActions()
|
|
45
|
-
self.
|
|
88
|
+
self._create_menu_bar()
|
|
46
89
|
|
|
47
90
|
self.mode = self.parent_window.mode
|
|
48
91
|
self.pos = self.parent_window.parent_window.parent_window.pos
|
|
@@ -52,9 +95,17 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
52
95
|
self.min_dist = 30
|
|
53
96
|
self.onlyFloat = QDoubleValidator()
|
|
54
97
|
self.onlyInt = QIntValidator()
|
|
55
|
-
self.cell_properties = [
|
|
98
|
+
self.cell_properties = [
|
|
99
|
+
"centroid",
|
|
100
|
+
"area",
|
|
101
|
+
"perimeter",
|
|
102
|
+
"eccentricity",
|
|
103
|
+
"intensity_mean",
|
|
104
|
+
"solidity",
|
|
105
|
+
]
|
|
56
106
|
self.edge = None
|
|
57
107
|
self.filters = []
|
|
108
|
+
self.fill_holes = True
|
|
58
109
|
|
|
59
110
|
self.locate_stack()
|
|
60
111
|
self.generate_viewer()
|
|
@@ -70,12 +121,15 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
70
121
|
self.populate_widget()
|
|
71
122
|
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
72
123
|
|
|
73
|
-
|
|
74
|
-
|
|
124
|
+
self.bg_loader = BackgroundLoader()
|
|
125
|
+
self.bg_loader.start()
|
|
126
|
+
|
|
127
|
+
def _create_menu_bar(self):
|
|
128
|
+
menu_bar = self.menuBar()
|
|
75
129
|
# Creating menus using a QMenu object
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
130
|
+
file_menu = QMenu("&File", self)
|
|
131
|
+
file_menu.addAction(self.openAction)
|
|
132
|
+
menu_bar.addMenu(file_menu)
|
|
79
133
|
|
|
80
134
|
# Creating menus using a title
|
|
81
135
|
# editMenu = menuBar.addMenu("&Edit")
|
|
@@ -90,11 +144,10 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
90
144
|
self.openAction.triggered.connect(self.load_previous_config)
|
|
91
145
|
|
|
92
146
|
def populate_widget(self):
|
|
93
|
-
|
|
94
147
|
"""
|
|
95
|
-
|
|
148
|
+
Create the multibox design.
|
|
96
149
|
|
|
97
|
-
|
|
150
|
+
"""
|
|
98
151
|
self.button_widget = CelldetectiveWidget()
|
|
99
152
|
main_layout = QHBoxLayout()
|
|
100
153
|
self.button_widget.setLayout(main_layout)
|
|
@@ -140,11 +193,22 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
140
193
|
threshold_title_grid = QHBoxLayout()
|
|
141
194
|
section_threshold = QLabel("Threshold")
|
|
142
195
|
section_threshold.setStyleSheet("font-weight: bold;")
|
|
143
|
-
threshold_title_grid.addWidget(section_threshold,
|
|
196
|
+
threshold_title_grid.addWidget(section_threshold, 80, alignment=Qt.AlignCenter)
|
|
197
|
+
|
|
198
|
+
self.fill_holes_btn = QPushButton("")
|
|
199
|
+
self.fill_holes_btn.setIcon(icon(MDI6.format_color_fill, color="white"))
|
|
200
|
+
self.fill_holes_btn.setIconSize(QSize(20, 20))
|
|
201
|
+
self.fill_holes_btn.setStyleSheet(self.button_select_all)
|
|
202
|
+
self.fill_holes_btn.setToolTip("Fill holes in binary mask")
|
|
203
|
+
self.fill_holes_btn.setCheckable(True)
|
|
204
|
+
self.fill_holes_btn.setChecked(True)
|
|
205
|
+
self.fill_holes_btn.clicked.connect(self.toggle_fill_holes)
|
|
206
|
+
threshold_title_grid.addWidget(self.fill_holes_btn, 5)
|
|
144
207
|
|
|
145
208
|
self.ylog_check = QPushButton("")
|
|
146
209
|
self.ylog_check.setIcon(icon(MDI6.math_log, color="black"))
|
|
147
210
|
self.ylog_check.setStyleSheet(self.button_select_all)
|
|
211
|
+
self.ylog_check.setCheckable(True)
|
|
148
212
|
self.ylog_check.clicked.connect(self.switch_to_log)
|
|
149
213
|
threshold_title_grid.addWidget(self.ylog_check, 5)
|
|
150
214
|
|
|
@@ -166,8 +230,13 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
166
230
|
self.threshold_slider.setTickInterval(0.00001)
|
|
167
231
|
self.threshold_slider.setOrientation(Qt.Horizontal)
|
|
168
232
|
self.threshold_slider.setDecimals(5)
|
|
169
|
-
self.threshold_slider.setRange(
|
|
170
|
-
|
|
233
|
+
self.threshold_slider.setRange(
|
|
234
|
+
np.amin(self.img[self.img == self.img]),
|
|
235
|
+
np.amax(self.img[self.img == self.img]),
|
|
236
|
+
)
|
|
237
|
+
self.threshold_slider.setValue(
|
|
238
|
+
[np.percentile(self.img.ravel(), 90), np.amax(self.img)]
|
|
239
|
+
)
|
|
171
240
|
self.threshold_slider.valueChanged.connect(self.threshold_changed)
|
|
172
241
|
|
|
173
242
|
# self.initialize_histogram()
|
|
@@ -186,24 +255,38 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
186
255
|
# FINAL SAVE BTN#
|
|
187
256
|
#################
|
|
188
257
|
|
|
189
|
-
self.save_btn = QPushButton(
|
|
258
|
+
self.save_btn = QPushButton("Save")
|
|
190
259
|
self.save_btn.setStyleSheet(self.button_style_sheet)
|
|
191
260
|
self.save_btn.clicked.connect(self.write_instructions)
|
|
192
261
|
self.left_panel.addWidget(self.save_btn)
|
|
193
262
|
|
|
194
|
-
self.properties_box_widgets = [
|
|
195
|
-
|
|
263
|
+
self.properties_box_widgets = [
|
|
264
|
+
self.propscanvas,
|
|
265
|
+
*self.features_cb,
|
|
266
|
+
self.property_query_le,
|
|
267
|
+
self.submit_query_btn,
|
|
268
|
+
self.save_btn,
|
|
269
|
+
]
|
|
196
270
|
for p in self.properties_box_widgets:
|
|
197
271
|
p.setEnabled(False)
|
|
198
272
|
|
|
199
|
-
|
|
273
|
+
# Force initial update after all UI elements are created
|
|
274
|
+
self.threshold_changed(self.threshold_slider.value())
|
|
200
275
|
|
|
276
|
+
def help_prefilter(self):
|
|
277
|
+
"""
|
|
278
|
+
Helper for prefiltering strategy
|
|
201
279
|
"""
|
|
202
|
-
Helper for prefiltering strategy
|
|
203
|
-
"""
|
|
204
280
|
|
|
205
281
|
dict_path = os.sep.join(
|
|
206
|
-
[
|
|
282
|
+
[
|
|
283
|
+
get_software_location(),
|
|
284
|
+
"celldetective",
|
|
285
|
+
"gui",
|
|
286
|
+
"help",
|
|
287
|
+
"prefilter-for-segmentation.json",
|
|
288
|
+
]
|
|
289
|
+
)
|
|
207
290
|
|
|
208
291
|
with open(dict_path) as f:
|
|
209
292
|
d = json.load(f)
|
|
@@ -214,8 +297,10 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
214
297
|
message_box = QMessageBox()
|
|
215
298
|
message_box.setIcon(QMessageBox.Information)
|
|
216
299
|
message_box.setTextFormat(Qt.RichText)
|
|
217
|
-
message_box.setText(
|
|
218
|
-
|
|
300
|
+
message_box.setText(
|
|
301
|
+
f"The suggested technique is to {suggestion}.\nSee a tutorial <a "
|
|
302
|
+
f"href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>."
|
|
303
|
+
)
|
|
219
304
|
message_box.setWindowTitle("Info")
|
|
220
305
|
message_box.setStandardButtons(QMessageBox.Ok)
|
|
221
306
|
return_value = message_box.exec()
|
|
@@ -227,22 +312,24 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
227
312
|
marker_box = QVBoxLayout()
|
|
228
313
|
marker_box.setContentsMargins(30, 30, 30, 30)
|
|
229
314
|
|
|
230
|
-
marker_lbl = QLabel(
|
|
315
|
+
marker_lbl = QLabel("Objects")
|
|
231
316
|
marker_lbl.setStyleSheet("font-weight: bold;")
|
|
232
317
|
marker_box.addWidget(marker_lbl, alignment=Qt.AlignCenter)
|
|
233
318
|
|
|
234
319
|
object_option_hbox = QHBoxLayout()
|
|
235
|
-
self.marker_option = QRadioButton(
|
|
236
|
-
self.all_objects_option = QRadioButton(
|
|
320
|
+
self.marker_option = QRadioButton("markers")
|
|
321
|
+
self.all_objects_option = QRadioButton("all non-contiguous objects")
|
|
237
322
|
self.marker_option_group = QButtonGroup()
|
|
238
323
|
self.marker_option_group.addButton(self.marker_option)
|
|
239
324
|
self.marker_option_group.addButton(self.all_objects_option)
|
|
240
325
|
object_option_hbox.addWidget(self.marker_option, 50, alignment=Qt.AlignCenter)
|
|
241
|
-
object_option_hbox.addWidget(
|
|
326
|
+
object_option_hbox.addWidget(
|
|
327
|
+
self.all_objects_option, 50, alignment=Qt.AlignCenter
|
|
328
|
+
)
|
|
242
329
|
marker_box.addLayout(object_option_hbox)
|
|
243
330
|
|
|
244
331
|
hbox_footprint = QHBoxLayout()
|
|
245
|
-
hbox_footprint.addWidget(QLabel(
|
|
332
|
+
hbox_footprint.addWidget(QLabel("Footprint: "), 20)
|
|
246
333
|
self.footprint_slider = QLabeledSlider()
|
|
247
334
|
self.footprint_slider.setSingleStep(1)
|
|
248
335
|
self.footprint_slider.setOrientation(Qt.Horizontal)
|
|
@@ -250,11 +337,11 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
250
337
|
self.footprint_slider.setValue(self.footprint)
|
|
251
338
|
self.footprint_slider.valueChanged.connect(self.set_footprint)
|
|
252
339
|
hbox_footprint.addWidget(self.footprint_slider, 30)
|
|
253
|
-
hbox_footprint.addWidget(QLabel(
|
|
340
|
+
hbox_footprint.addWidget(QLabel(""), 50)
|
|
254
341
|
marker_box.addLayout(hbox_footprint)
|
|
255
342
|
|
|
256
343
|
hbox_distance = QHBoxLayout()
|
|
257
|
-
hbox_distance.addWidget(QLabel(
|
|
344
|
+
hbox_distance.addWidget(QLabel("Min distance: "), 20)
|
|
258
345
|
self.min_dist_slider = QLabeledSlider()
|
|
259
346
|
self.min_dist_slider.setSingleStep(1)
|
|
260
347
|
self.min_dist_slider.setOrientation(Qt.Horizontal)
|
|
@@ -262,7 +349,7 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
262
349
|
self.min_dist_slider.setValue(self.min_dist)
|
|
263
350
|
self.min_dist_slider.valueChanged.connect(self.set_min_dist)
|
|
264
351
|
hbox_distance.addWidget(self.min_dist_slider, 30)
|
|
265
|
-
hbox_distance.addWidget(QLabel(
|
|
352
|
+
hbox_distance.addWidget(QLabel(""), 50)
|
|
266
353
|
marker_box.addLayout(hbox_distance)
|
|
267
354
|
|
|
268
355
|
hbox_marker_btns = QHBoxLayout()
|
|
@@ -303,26 +390,27 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
303
390
|
properties_box = QVBoxLayout()
|
|
304
391
|
properties_box.setContentsMargins(30, 30, 30, 30)
|
|
305
392
|
|
|
306
|
-
properties_lbl = QLabel(
|
|
307
|
-
properties_lbl.setStyleSheet(
|
|
393
|
+
properties_lbl = QLabel("Filter on properties")
|
|
394
|
+
properties_lbl.setStyleSheet("font-weight: bold;")
|
|
308
395
|
properties_box.addWidget(properties_lbl, alignment=Qt.AlignCenter)
|
|
309
396
|
|
|
310
397
|
properties_box.addWidget(self.propscanvas)
|
|
311
398
|
|
|
312
|
-
self.features_cb = [QComboBox() for
|
|
399
|
+
self.features_cb = [QComboBox() for _ in range(2)]
|
|
313
400
|
for i in range(2):
|
|
314
401
|
hbox_feat = QHBoxLayout()
|
|
315
|
-
hbox_feat.addWidget(QLabel(f
|
|
402
|
+
hbox_feat.addWidget(QLabel(f"feature {i}: "), 20)
|
|
316
403
|
hbox_feat.addWidget(self.features_cb[i], 80)
|
|
317
404
|
properties_box.addLayout(hbox_feat)
|
|
318
405
|
|
|
319
406
|
hbox_classify = QHBoxLayout()
|
|
320
|
-
hbox_classify.addWidget(QLabel(
|
|
407
|
+
hbox_classify.addWidget(QLabel("remove: "), 10)
|
|
321
408
|
self.property_query_le = QLineEdit()
|
|
322
409
|
self.property_query_le.setPlaceholderText(
|
|
323
|
-
|
|
410
|
+
"eliminate points using a query such as: area > 100 or eccentricity > 0.95"
|
|
411
|
+
)
|
|
324
412
|
hbox_classify.addWidget(self.property_query_le, 70)
|
|
325
|
-
self.submit_query_btn = QPushButton(
|
|
413
|
+
self.submit_query_btn = QPushButton("Submit...")
|
|
326
414
|
self.submit_query_btn.setStyleSheet(self.button_style_sheet)
|
|
327
415
|
self.submit_query_btn.clicked.connect(self.apply_property_query)
|
|
328
416
|
hbox_classify.addWidget(self.submit_query_btn, 20)
|
|
@@ -331,33 +419,49 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
331
419
|
self.left_panel.addLayout(properties_box)
|
|
332
420
|
|
|
333
421
|
def generate_viewer(self):
|
|
334
|
-
self.viewer = ThresholdedStackVisualizer(
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
422
|
+
self.viewer = ThresholdedStackVisualizer(
|
|
423
|
+
preprocessing=self.filters,
|
|
424
|
+
show_opacity_slider=False,
|
|
425
|
+
show_threshold_slider=False,
|
|
426
|
+
stack_path=self.stack_path,
|
|
427
|
+
frame_slider=True,
|
|
428
|
+
contrast_slider=True,
|
|
429
|
+
channel_cb=True,
|
|
430
|
+
channel_names=self.channel_names,
|
|
431
|
+
n_channels=self.nbr_channels,
|
|
432
|
+
target_channel=0,
|
|
433
|
+
PxToUm=None,
|
|
434
|
+
initial_threshold=None,
|
|
435
|
+
fill_holes=self.fill_holes,
|
|
436
|
+
)
|
|
339
437
|
|
|
340
438
|
def populate_right_panel(self):
|
|
341
439
|
self.right_panel.addWidget(self.viewer.canvas)
|
|
342
440
|
|
|
343
441
|
def locate_stack(self):
|
|
344
|
-
|
|
345
442
|
"""
|
|
346
|
-
|
|
443
|
+
Locate the target movie.
|
|
347
444
|
|
|
348
|
-
|
|
445
|
+
"""
|
|
349
446
|
|
|
350
447
|
if isinstance(self.pos, str):
|
|
351
|
-
movies = glob(
|
|
448
|
+
movies = glob(
|
|
449
|
+
self.pos
|
|
450
|
+
+ f"movie/{self.parent_window.parent_window.parent_window.movie_prefix}*.tif"
|
|
451
|
+
)
|
|
352
452
|
|
|
353
453
|
else:
|
|
354
|
-
generic_message(
|
|
454
|
+
generic_message(
|
|
455
|
+
"Please select a unique position before launching the wizard..."
|
|
456
|
+
)
|
|
355
457
|
self.img = None
|
|
356
458
|
self.close()
|
|
357
459
|
return None
|
|
358
460
|
|
|
359
461
|
if len(movies) == 0:
|
|
360
|
-
generic_message(
|
|
462
|
+
generic_message(
|
|
463
|
+
"No movies are detected in the experiment folder. Cannot load an image to test Haralick."
|
|
464
|
+
)
|
|
361
465
|
self.img = None
|
|
362
466
|
self.close()
|
|
363
467
|
else:
|
|
@@ -367,16 +471,18 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
367
471
|
self.nbr_channels = len(self.channel_names)
|
|
368
472
|
|
|
369
473
|
def initalize_props_scatter(self):
|
|
370
|
-
|
|
371
474
|
"""
|
|
372
|
-
|
|
373
|
-
|
|
475
|
+
Define properties scatter.
|
|
476
|
+
"""
|
|
374
477
|
|
|
375
|
-
|
|
478
|
+
from matplotlib.figure import Figure
|
|
479
|
+
|
|
480
|
+
self.fig_props = Figure(tight_layout=True)
|
|
481
|
+
self.ax_props = self.fig_props.add_subplot(111)
|
|
376
482
|
self.propscanvas = FigureCanvas(self.fig_props, interactive=True)
|
|
377
|
-
self.fig_props.set_facecolor(
|
|
483
|
+
self.fig_props.set_facecolor("none")
|
|
378
484
|
self.fig_props.canvas.setStyleSheet("background-color: transparent;")
|
|
379
|
-
self.scat_props = self.ax_props.scatter([], [], color=
|
|
485
|
+
self.scat_props = self.ax_props.scatter([], [], color="k", alpha=0.75)
|
|
380
486
|
self.propscanvas.canvas.draw_idle()
|
|
381
487
|
self.propscanvas.canvas.setMinimumHeight(self.screen_height // 5)
|
|
382
488
|
|
|
@@ -384,66 +490,93 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
384
490
|
|
|
385
491
|
self.img = self.viewer.init_frame
|
|
386
492
|
|
|
387
|
-
|
|
493
|
+
from matplotlib.figure import Figure
|
|
494
|
+
|
|
495
|
+
self.fig_hist = Figure(tight_layout=True)
|
|
496
|
+
self.ax_hist = self.fig_hist.add_subplot(111)
|
|
388
497
|
self.canvas_hist = FigureCanvas(self.fig_hist, interactive=False)
|
|
389
|
-
self.fig_hist.set_facecolor(
|
|
498
|
+
self.fig_hist.set_facecolor("none")
|
|
390
499
|
self.fig_hist.canvas.setStyleSheet("background-color: transparent;")
|
|
391
500
|
|
|
392
501
|
# self.ax_hist.clear()
|
|
393
502
|
# self.ax_hist.cla()
|
|
394
|
-
self.ax_hist.patch.set_facecolor(
|
|
395
|
-
self.hist_y, x, _ = self.ax_hist.hist(
|
|
503
|
+
self.ax_hist.patch.set_facecolor("none")
|
|
504
|
+
self.hist_y, x, _ = self.ax_hist.hist(
|
|
505
|
+
self.img.ravel(), density=True, bins=300, color="k"
|
|
506
|
+
)
|
|
396
507
|
# self.ax_hist.set_xlim(np.amin(self.img),np.amax(self.img))
|
|
397
|
-
self.ax_hist.set_xlabel(
|
|
398
|
-
self.ax_hist.spines[
|
|
399
|
-
self.ax_hist.spines[
|
|
508
|
+
self.ax_hist.set_xlabel("intensity [a.u.]")
|
|
509
|
+
self.ax_hist.spines["top"].set_visible(False)
|
|
510
|
+
self.ax_hist.spines["right"].set_visible(False)
|
|
400
511
|
# self.ax_hist.set_yticks([])
|
|
401
|
-
self.ax_hist.set_xlim(
|
|
512
|
+
self.ax_hist.set_xlim(
|
|
513
|
+
np.amin(self.img[self.img == self.img]),
|
|
514
|
+
np.amax(self.img[self.img == self.img]),
|
|
515
|
+
)
|
|
402
516
|
self.ax_hist.set_ylim(0, self.hist_y.max())
|
|
403
517
|
|
|
404
|
-
self.threshold_slider.setRange(
|
|
405
|
-
|
|
518
|
+
self.threshold_slider.setRange(
|
|
519
|
+
np.amin(self.img[self.img == self.img]),
|
|
520
|
+
np.amax(self.img[self.img == self.img]),
|
|
521
|
+
)
|
|
522
|
+
self.threshold_slider.setValue(
|
|
523
|
+
[np.nanpercentile(self.img.ravel(), 90), np.amax(self.img)]
|
|
524
|
+
)
|
|
406
525
|
self.add_hist_threshold()
|
|
407
526
|
|
|
408
527
|
self.canvas_hist.canvas.draw_idle()
|
|
409
528
|
self.canvas_hist.canvas.setMinimumHeight(self.screen_height // 8)
|
|
410
529
|
|
|
411
530
|
def update_histogram(self):
|
|
412
|
-
|
|
413
531
|
"""
|
|
414
|
-
|
|
415
|
-
|
|
532
|
+
Redraw the histogram after an update on the image.
|
|
533
|
+
Move the threshold slider accordingly.
|
|
416
534
|
|
|
417
|
-
|
|
535
|
+
"""
|
|
418
536
|
|
|
419
537
|
self.ax_hist.clear()
|
|
420
|
-
self.ax_hist.patch.set_facecolor(
|
|
421
|
-
self.hist_y, x, _ = self.ax_hist.hist(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
self.ax_hist.
|
|
538
|
+
self.ax_hist.patch.set_facecolor("none")
|
|
539
|
+
self.hist_y, x, _ = self.ax_hist.hist(
|
|
540
|
+
self.img.ravel(), density=True, bins=300, color="k"
|
|
541
|
+
)
|
|
542
|
+
self.ax_hist.set_xlabel("intensity [a.u.]")
|
|
543
|
+
self.ax_hist.spines["top"].set_visible(False)
|
|
544
|
+
self.ax_hist.spines["right"].set_visible(False)
|
|
425
545
|
# self.ax_hist.set_yticks([])
|
|
426
|
-
self.ax_hist.set_xlim(
|
|
546
|
+
self.ax_hist.set_xlim(
|
|
547
|
+
np.amin(self.img[self.img == self.img]),
|
|
548
|
+
np.amax(self.img[self.img == self.img]),
|
|
549
|
+
)
|
|
427
550
|
self.ax_hist.set_ylim(0, self.hist_y.max())
|
|
428
551
|
self.add_hist_threshold()
|
|
429
552
|
self.canvas_hist.canvas.draw()
|
|
430
553
|
|
|
431
|
-
self.threshold_slider.setRange(
|
|
432
|
-
|
|
554
|
+
self.threshold_slider.setRange(
|
|
555
|
+
np.amin(self.img[self.img == self.img]),
|
|
556
|
+
np.amax(self.img[self.img == self.img]),
|
|
557
|
+
)
|
|
558
|
+
self.threshold_slider.setValue(
|
|
559
|
+
[np.nanpercentile(self.img.ravel(), 90), np.amax(self.img)]
|
|
560
|
+
)
|
|
433
561
|
self.threshold_changed(self.threshold_slider.value())
|
|
434
562
|
|
|
435
563
|
def add_hist_threshold(self):
|
|
436
564
|
|
|
437
565
|
ymin, ymax = self.ax_hist.get_ylim()
|
|
438
|
-
self.min_intensity_line, = self.ax_hist.plot(
|
|
439
|
-
[self.threshold_slider.value()[0], self.threshold_slider.value()[0]],
|
|
440
|
-
|
|
441
|
-
|
|
566
|
+
(self.min_intensity_line,) = self.ax_hist.plot(
|
|
567
|
+
[self.threshold_slider.value()[0], self.threshold_slider.value()[0]],
|
|
568
|
+
[0, ymax],
|
|
569
|
+
c="tab:purple",
|
|
570
|
+
)
|
|
571
|
+
(self.max_intensity_line,) = self.ax_hist.plot(
|
|
572
|
+
[self.threshold_slider.value()[1], self.threshold_slider.value()[1]],
|
|
573
|
+
[0, ymax],
|
|
574
|
+
c="tab:purple",
|
|
575
|
+
)
|
|
442
576
|
|
|
443
577
|
# self.canvas_hist.canvas.draw_idle()
|
|
444
578
|
|
|
445
579
|
def reload_frame(self):
|
|
446
|
-
|
|
447
580
|
"""
|
|
448
581
|
Load the frame from the current channel and time choice. Show imshow, update histogram.
|
|
449
582
|
"""
|
|
@@ -454,45 +587,51 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
454
587
|
self.update_histogram()
|
|
455
588
|
|
|
456
589
|
def preprocess_image(self):
|
|
457
|
-
|
|
458
590
|
"""
|
|
459
|
-
|
|
591
|
+
Reload the frame, apply the filters, update imshow and histogram.
|
|
460
592
|
|
|
461
|
-
|
|
593
|
+
"""
|
|
462
594
|
|
|
463
595
|
self.filters = self.preprocessing.list.items
|
|
464
596
|
self.reload_frame()
|
|
465
597
|
self.update_histogram()
|
|
466
598
|
|
|
467
599
|
def threshold_changed(self, value):
|
|
468
|
-
|
|
469
600
|
"""
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
601
|
+
Move the threshold values on histogram, when slider is moved.
|
|
602
|
+
"""
|
|
473
603
|
self.clear_post_threshold_options()
|
|
474
604
|
self.viewer.change_threshold(value)
|
|
475
605
|
|
|
476
606
|
ymin, ymax = self.ax_hist.get_ylim()
|
|
477
|
-
self.min_intensity_line.set_data([value[0],value[0]], [0, ymax])
|
|
478
|
-
self.max_intensity_line.set_data([value[1],value[1]], [0, ymax])
|
|
607
|
+
self.min_intensity_line.set_data([value[0], value[0]], [0, ymax])
|
|
608
|
+
self.max_intensity_line.set_data([value[1], value[1]], [0, ymax])
|
|
479
609
|
self.canvas_hist.canvas.draw_idle()
|
|
480
610
|
|
|
481
611
|
def switch_to_log(self):
|
|
482
|
-
|
|
483
612
|
"""
|
|
484
|
-
|
|
485
|
-
|
|
613
|
+
Switch threshold histogram to log scale. Auto adjust.
|
|
614
|
+
"""
|
|
486
615
|
|
|
487
|
-
if self.ax_hist.get_yscale() ==
|
|
488
|
-
self.ax_hist.set_yscale(
|
|
616
|
+
if self.ax_hist.get_yscale() == "linear":
|
|
617
|
+
self.ax_hist.set_yscale("log")
|
|
618
|
+
self.ylog_check.setIcon(icon(MDI6.math_log, color="white"))
|
|
489
619
|
else:
|
|
490
|
-
self.ax_hist.set_yscale(
|
|
620
|
+
self.ax_hist.set_yscale("linear")
|
|
621
|
+
self.ylog_check.setIcon(icon(MDI6.math_log, color="black"))
|
|
491
622
|
|
|
492
623
|
# self.ax_hist.autoscale()
|
|
493
|
-
|
|
624
|
+
ymin = 0 if self.ax_hist.get_yscale() == "linear" else 1e-1
|
|
625
|
+
self.ax_hist.set_ylim(ymin, self.hist_y.max())
|
|
494
626
|
self.canvas_hist.canvas.draw_idle()
|
|
495
627
|
|
|
628
|
+
def toggle_fill_holes(self):
|
|
629
|
+
self.fill_holes = self.fill_holes_btn.isChecked()
|
|
630
|
+
self.viewer.fill_holes = self.fill_holes
|
|
631
|
+
self.viewer.change_threshold(self.threshold_slider.value())
|
|
632
|
+
color = "white" if self.fill_holes else "black"
|
|
633
|
+
self.fill_holes_btn.setIcon(icon(MDI6.format_color_fill, color=color))
|
|
634
|
+
|
|
496
635
|
def set_footprint(self):
|
|
497
636
|
self.footprint = self.footprint_slider.value()
|
|
498
637
|
|
|
@@ -510,9 +649,15 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
510
649
|
if self.viewer.mask.ndim == 3:
|
|
511
650
|
self.viewer.mask = np.squeeze(self.viewer.mask)
|
|
512
651
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
652
|
+
from celldetective.segmentation import identify_markers_from_binary
|
|
653
|
+
|
|
654
|
+
self.coords, self.edt_map = identify_markers_from_binary(
|
|
655
|
+
self.viewer.mask,
|
|
656
|
+
self.min_dist,
|
|
657
|
+
footprint_size=self.footprint,
|
|
658
|
+
footprint=None,
|
|
659
|
+
return_edt=True,
|
|
660
|
+
)
|
|
516
661
|
if len(self.coords) > 0:
|
|
517
662
|
self.viewer.scat_markers.set_offsets(self.coords[:, [1, 0]])
|
|
518
663
|
self.viewer.scat_markers.set_visible(True)
|
|
@@ -524,14 +669,24 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
524
669
|
|
|
525
670
|
def apply_watershed_to_selection(self):
|
|
526
671
|
|
|
672
|
+
import scipy.ndimage as ndi
|
|
673
|
+
from celldetective.segmentation import apply_watershed
|
|
674
|
+
|
|
527
675
|
if self.marker_option.isChecked():
|
|
528
|
-
self.labels = apply_watershed(
|
|
676
|
+
self.labels = apply_watershed(
|
|
677
|
+
self.viewer.mask, self.coords, self.edt_map, fill_holes=self.fill_holes
|
|
678
|
+
)
|
|
529
679
|
else:
|
|
530
|
-
|
|
680
|
+
from scipy.ndimage._measurements import label
|
|
531
681
|
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
self.viewer.
|
|
682
|
+
self.labels, _ = label(self.viewer.mask.astype(int))
|
|
683
|
+
|
|
684
|
+
self.viewer.channel_trigger = True
|
|
685
|
+
self.viewer.change_frame_from_channel_switch(self.viewer.frame_slider.value())
|
|
686
|
+
self.viewer.im_mask.set_cmap("tab20c")
|
|
687
|
+
self.viewer.im_mask.set_data(
|
|
688
|
+
np.ma.masked_where(self.labels == 0.0, self.labels)
|
|
689
|
+
)
|
|
535
690
|
self.viewer.im_mask.autoscale()
|
|
536
691
|
self.viewer.canvas.canvas.draw_idle()
|
|
537
692
|
|
|
@@ -544,67 +699,104 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
544
699
|
|
|
545
700
|
def compute_features(self):
|
|
546
701
|
|
|
702
|
+
import pandas as pd
|
|
703
|
+
from skimage.measure import regionprops_table
|
|
704
|
+
|
|
547
705
|
# Run regionprops to have properties for filtering
|
|
548
706
|
intensity_image_idx = [self.nbr_channels * self.viewer.frame_slider.value()]
|
|
549
707
|
for i in range(self.nbr_channels - 1):
|
|
550
708
|
intensity_image_idx += [intensity_image_idx[-1] + 1]
|
|
551
709
|
|
|
552
710
|
# Load channels at time t
|
|
553
|
-
multichannel = load_frames(
|
|
711
|
+
multichannel = load_frames(
|
|
712
|
+
intensity_image_idx, self.stack_path, normalize_input=False
|
|
713
|
+
)
|
|
554
714
|
self.props = pd.DataFrame(
|
|
555
|
-
regionprops_table(
|
|
715
|
+
regionprops_table(
|
|
716
|
+
self.labels,
|
|
717
|
+
intensity_image=multichannel,
|
|
718
|
+
properties=self.cell_properties,
|
|
719
|
+
)
|
|
720
|
+
)
|
|
556
721
|
self.props = rename_intensity_column(self.props, self.channel_names)
|
|
557
|
-
self.props[
|
|
558
|
-
|
|
722
|
+
self.props["radial_distance"] = np.sqrt(
|
|
723
|
+
(self.props["centroid-1"] - self.img.shape[0] / 2) ** 2
|
|
724
|
+
+ (self.props["centroid-0"] - self.img.shape[1] / 2) ** 2
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
self.props["class"] = 1
|
|
559
728
|
|
|
560
729
|
for i in range(2):
|
|
561
730
|
self.features_cb[i].clear()
|
|
562
731
|
self.features_cb[i].addItems(list(self.props.columns))
|
|
563
732
|
self.features_cb[i].setCurrentIndex(i)
|
|
564
|
-
self.props["class"] = 1
|
|
565
733
|
|
|
566
734
|
self.update_props_scatter()
|
|
567
735
|
|
|
568
736
|
def update_props_scatter(self):
|
|
569
737
|
|
|
570
|
-
self.
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
self.
|
|
577
|
-
self.
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
self.ax_props.
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
738
|
+
feat1 = self.features_cb[1].currentText()
|
|
739
|
+
feat0 = self.features_cb[0].currentText()
|
|
740
|
+
|
|
741
|
+
if feat1 == "" or feat0 == "":
|
|
742
|
+
return
|
|
743
|
+
|
|
744
|
+
self.scat_props.set_offsets(self.props[[feat1, feat0]].to_numpy())
|
|
745
|
+
self.scat_props.set_facecolor(
|
|
746
|
+
[color_from_class(c) for c in self.props["class"].to_numpy()]
|
|
747
|
+
)
|
|
748
|
+
self.ax_props.set_xlabel(feat1)
|
|
749
|
+
self.ax_props.set_ylabel(feat0)
|
|
750
|
+
|
|
751
|
+
self.viewer.scat_markers.set_offsets(
|
|
752
|
+
self.props[["centroid-1", "centroid-0"]].to_numpy()
|
|
753
|
+
)
|
|
754
|
+
self.viewer.scat_markers.set_color(["k"] * len(self.props))
|
|
755
|
+
self.viewer.scat_markers.set_facecolor(
|
|
756
|
+
[color_from_class(c) for c in self.props["class"].to_numpy()]
|
|
757
|
+
)
|
|
758
|
+
self.viewer.scat_markers.set_visible(True)
|
|
759
|
+
|
|
760
|
+
if not self.props.empty:
|
|
761
|
+
min_f1, max_f1 = self.props[feat1].min(), self.props[feat1].max()
|
|
762
|
+
min_f0, max_f0 = self.props[feat0].min(), self.props[feat0].max()
|
|
763
|
+
|
|
764
|
+
if np.isfinite([min_f1, max_f1, min_f0, max_f0]).all():
|
|
765
|
+
self.ax_props.set_xlim(
|
|
766
|
+
0.75 * min_f1,
|
|
767
|
+
1.05 * max_f1,
|
|
768
|
+
)
|
|
769
|
+
self.ax_props.set_ylim(
|
|
770
|
+
0.75 * min_f0,
|
|
771
|
+
1.05 * max_f0,
|
|
772
|
+
)
|
|
584
773
|
self.propscanvas.canvas.draw_idle()
|
|
585
|
-
self.viewer.canvas.canvas.
|
|
774
|
+
self.viewer.canvas.canvas.draw()
|
|
775
|
+
logger.info(f"Update markers for {len(self.props)} objects.")
|
|
586
776
|
|
|
587
777
|
def prep_cell_properties(self):
|
|
588
778
|
|
|
589
779
|
self.cell_properties_options = list(np.copy(self.cell_properties))
|
|
590
780
|
self.cell_properties_options.remove("centroid")
|
|
591
781
|
for k in range(self.nbr_channels):
|
|
592
|
-
self.cell_properties_options.append(f
|
|
593
|
-
self.cell_properties_options.remove(
|
|
782
|
+
self.cell_properties_options.append(f"intensity_mean-{k}")
|
|
783
|
+
self.cell_properties_options.remove("intensity_mean")
|
|
594
784
|
|
|
595
785
|
def apply_property_query(self):
|
|
596
786
|
query = self.property_query_le.text()
|
|
597
|
-
self.props[
|
|
787
|
+
self.props["class"] = 1
|
|
598
788
|
|
|
599
|
-
if query ==
|
|
600
|
-
|
|
789
|
+
if query == "":
|
|
790
|
+
logger.warning("empty query")
|
|
601
791
|
else:
|
|
602
792
|
try:
|
|
603
793
|
self.selection = self.props.query(query).index
|
|
604
|
-
|
|
605
|
-
self.props.loc[self.selection,
|
|
794
|
+
logger.info(f"{self.selection}")
|
|
795
|
+
self.props.loc[self.selection, "class"] = 0
|
|
606
796
|
except Exception as e:
|
|
607
|
-
generic_message(
|
|
797
|
+
generic_message(
|
|
798
|
+
f"The query could not be understood. No filtering was applied. {e}"
|
|
799
|
+
)
|
|
608
800
|
return None
|
|
609
801
|
|
|
610
802
|
self.update_props_scatter()
|
|
@@ -618,47 +810,56 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
618
810
|
for i in range(2):
|
|
619
811
|
try:
|
|
620
812
|
self.features_cb[i].disconnect()
|
|
621
|
-
except Exception as
|
|
813
|
+
except Exception as _:
|
|
622
814
|
pass
|
|
623
815
|
self.features_cb[i].clear()
|
|
624
816
|
|
|
625
|
-
self.property_query_le.setText(
|
|
817
|
+
self.property_query_le.setText("")
|
|
626
818
|
|
|
627
819
|
self.viewer.change_threshold(self.threshold_slider.value())
|
|
628
|
-
self.viewer.scat_markers.set_color(
|
|
820
|
+
self.viewer.scat_markers.set_color("tab:red")
|
|
629
821
|
self.viewer.scat_markers.set_visible(False)
|
|
630
822
|
|
|
631
823
|
def write_instructions(self):
|
|
632
824
|
|
|
633
825
|
instructions = {
|
|
634
|
-
"target_channel": self.viewer.
|
|
826
|
+
"target_channel": self.viewer.channel_cb.currentText(),
|
|
635
827
|
# for now index but would be more universal to use name
|
|
636
828
|
"thresholds": self.threshold_slider.value(),
|
|
637
829
|
"filters": self.preprocessing.list.items,
|
|
638
830
|
"marker_min_distance": self.min_dist,
|
|
639
831
|
"marker_footprint_size": self.footprint,
|
|
640
832
|
"feature_queries": [self.property_query_le.text()],
|
|
641
|
-
"equalize_reference": [
|
|
833
|
+
"equalize_reference": [
|
|
834
|
+
self.equalize_option,
|
|
835
|
+
self.viewer.frame_slider.value(),
|
|
836
|
+
],
|
|
642
837
|
"do_watershed": self.marker_option.isChecked(),
|
|
838
|
+
"fill_holes": self.fill_holes,
|
|
643
839
|
}
|
|
644
840
|
|
|
645
|
-
|
|
646
|
-
self.instruction_file =
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
841
|
+
logger.info(f"The following instructions will be written: {instructions}")
|
|
842
|
+
self.instruction_file = QFileDialog.getSaveFileName(
|
|
843
|
+
self,
|
|
844
|
+
"Save File",
|
|
845
|
+
self.exp_dir + f"configs/threshold_config_{self.mode}.json",
|
|
846
|
+
".json",
|
|
847
|
+
)[0]
|
|
848
|
+
if self.instruction_file != "":
|
|
650
849
|
json_object = json.dumps(instructions, indent=4)
|
|
651
850
|
with open(self.instruction_file, "w") as outfile:
|
|
652
851
|
outfile.write(json_object)
|
|
653
|
-
|
|
852
|
+
logger.info(
|
|
853
|
+
f"Configuration successfully written in {self.instruction_file}"
|
|
854
|
+
)
|
|
654
855
|
|
|
655
856
|
self.parent_window.filename = self.instruction_file
|
|
656
|
-
self.parent_window.file_label.setText(self.instruction_file[:16] +
|
|
857
|
+
self.parent_window.file_label.setText(self.instruction_file[:16] + "...")
|
|
657
858
|
self.parent_window.file_label.setToolTip(self.instruction_file)
|
|
658
859
|
|
|
659
860
|
self.close()
|
|
660
861
|
else:
|
|
661
|
-
|
|
862
|
+
logger.error("The instruction file could not be written...")
|
|
662
863
|
|
|
663
864
|
def activate_histogram_equalizer(self):
|
|
664
865
|
|
|
@@ -672,42 +873,44 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
672
873
|
self.equalize_option_btn.setIconSize(QSize(20, 20))
|
|
673
874
|
|
|
674
875
|
def load_previous_config(self):
|
|
675
|
-
self.previous_instruction_file =
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
876
|
+
self.previous_instruction_file = QFileDialog.getOpenFileName(
|
|
877
|
+
self,
|
|
878
|
+
"Load config",
|
|
879
|
+
self.exp_dir + f"configs/threshold_config_{self.mode}.json",
|
|
880
|
+
"JSON (*.json)",
|
|
881
|
+
)[0]
|
|
882
|
+
with open(self.previous_instruction_file, "r") as f:
|
|
680
883
|
threshold_instructions = json.load(f)
|
|
681
884
|
|
|
682
|
-
target_channel = threshold_instructions[
|
|
885
|
+
target_channel = threshold_instructions["target_channel"]
|
|
683
886
|
index = self.viewer.channels_cb.findText(target_channel)
|
|
684
887
|
self.viewer.channels_cb.setCurrentIndex(index)
|
|
685
888
|
|
|
686
|
-
filters = threshold_instructions[
|
|
687
|
-
items_to_add = [f[0] +
|
|
889
|
+
filters = threshold_instructions["filters"]
|
|
890
|
+
items_to_add = [f[0] + "_filter" for f in filters]
|
|
688
891
|
self.preprocessing.list.list_widget.clear()
|
|
689
892
|
self.preprocessing.list.list_widget.addItems(items_to_add)
|
|
690
893
|
self.preprocessing.list.items = filters
|
|
691
894
|
self.preprocessing.apply_btn.click()
|
|
692
895
|
|
|
693
|
-
thresholds = threshold_instructions[
|
|
896
|
+
thresholds = threshold_instructions["thresholds"]
|
|
694
897
|
self.threshold_slider.setValue(thresholds)
|
|
695
898
|
|
|
696
|
-
marker_footprint_size = threshold_instructions[
|
|
899
|
+
marker_footprint_size = threshold_instructions["marker_footprint_size"]
|
|
697
900
|
self.footprint_slider.setValue(marker_footprint_size)
|
|
698
901
|
|
|
699
|
-
marker_min_dist = threshold_instructions[
|
|
902
|
+
marker_min_dist = threshold_instructions["marker_min_distance"]
|
|
700
903
|
self.min_dist_slider.setValue(marker_min_dist)
|
|
701
904
|
|
|
702
905
|
self.markers_btn.click()
|
|
703
906
|
self.watershed_btn.click()
|
|
704
907
|
|
|
705
|
-
feature_queries = threshold_instructions[
|
|
908
|
+
feature_queries = threshold_instructions["feature_queries"]
|
|
706
909
|
self.property_query_le.setText(feature_queries[0])
|
|
707
910
|
self.submit_query_btn.click()
|
|
708
911
|
|
|
709
|
-
if
|
|
710
|
-
do_watershed = threshold_instructions[
|
|
912
|
+
if "do_watershed" in threshold_instructions:
|
|
913
|
+
do_watershed = threshold_instructions["do_watershed"]
|
|
711
914
|
if do_watershed:
|
|
712
915
|
self.marker_option.click()
|
|
713
916
|
else:
|