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
|
@@ -1,1848 +1,1695 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
from PyQt5.QtWidgets import (
|
|
2
|
+
QDialog,
|
|
3
|
+
QFrame,
|
|
4
|
+
QGridLayout,
|
|
5
|
+
QComboBox,
|
|
6
|
+
QLabel,
|
|
7
|
+
QPushButton,
|
|
8
|
+
QVBoxLayout,
|
|
9
|
+
QHBoxLayout,
|
|
10
|
+
QCheckBox,
|
|
11
|
+
QMessageBox,
|
|
12
|
+
)
|
|
13
|
+
from PyQt5.QtCore import Qt, QSize, QTimer, QThread, pyqtSignal
|
|
4
14
|
from superqt.fonticon import icon
|
|
5
15
|
from fonticon_mdi6 import MDI6
|
|
6
16
|
import gc
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from celldetective.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
from celldetective.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
|
|
18
|
+
from celldetective.utils.model_getters import (
|
|
19
|
+
get_signal_models_list,
|
|
20
|
+
get_segmentation_models_list,
|
|
21
|
+
)
|
|
22
|
+
from celldetective.utils.data_loaders import load_experiment_tables
|
|
23
|
+
from celldetective.utils.model_loaders import (
|
|
24
|
+
locate_signal_model,
|
|
25
|
+
locate_segmentation_model,
|
|
26
|
+
)
|
|
27
|
+
from celldetective.utils.image_loaders import fix_missing_labels
|
|
28
|
+
|
|
29
|
+
from celldetective.gui.base.components import (
|
|
30
|
+
CelldetectiveWidget,
|
|
31
|
+
CelldetectiveProgressDialog,
|
|
32
|
+
QHSeperationLine,
|
|
33
|
+
)
|
|
34
|
+
|
|
24
35
|
import numpy as np
|
|
25
36
|
from glob import glob
|
|
37
|
+
from celldetective import get_logger
|
|
38
|
+
|
|
39
|
+
logger = get_logger()
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class NapariLoaderThread(QThread):
|
|
43
|
+
progress = pyqtSignal(int)
|
|
44
|
+
status = pyqtSignal(str)
|
|
45
|
+
finished_with_result = pyqtSignal(object)
|
|
46
|
+
|
|
47
|
+
def __init__(self, pos, prefix, population, threads):
|
|
48
|
+
super().__init__()
|
|
49
|
+
self.pos = pos
|
|
50
|
+
self.prefix = prefix
|
|
51
|
+
self.population = population
|
|
52
|
+
self.threads = threads
|
|
53
|
+
self._is_cancelled = False
|
|
54
|
+
|
|
55
|
+
def stop(self):
|
|
56
|
+
self._is_cancelled = True
|
|
57
|
+
|
|
58
|
+
def run(self):
|
|
59
|
+
from celldetective.napari.utils import control_tracks
|
|
60
|
+
|
|
61
|
+
def callback(p):
|
|
62
|
+
if self._is_cancelled:
|
|
63
|
+
return False
|
|
64
|
+
self.progress.emit(p)
|
|
65
|
+
return True
|
|
66
|
+
|
|
67
|
+
try:
|
|
68
|
+
res = control_tracks(
|
|
69
|
+
self.pos,
|
|
70
|
+
prefix=self.prefix,
|
|
71
|
+
population=self.population,
|
|
72
|
+
threads=self.threads,
|
|
73
|
+
progress_callback=callback,
|
|
74
|
+
prepare_only=True,
|
|
75
|
+
)
|
|
76
|
+
self.finished_with_result.emit(res)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
self.finished_with_result.emit(e)
|
|
79
|
+
|
|
80
|
+
|
|
26
81
|
from natsort import natsorted
|
|
27
82
|
import os
|
|
28
|
-
|
|
29
|
-
from celldetective.gui.
|
|
83
|
+
|
|
84
|
+
from celldetective.gui.base.utils import center_window
|
|
85
|
+
from celldetective.utils.io import remove_file_if_exists
|
|
30
86
|
from tifffile import imwrite
|
|
31
87
|
import json
|
|
32
|
-
from celldetective.preprocessing import correct_background_model_free, correct_background_model, correct_channel_offset
|
|
33
88
|
from celldetective.gui.gui_utils import help_generic
|
|
34
|
-
from celldetective.gui.
|
|
35
|
-
from celldetective
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
from celldetective.gui.workers import ProgressWindow
|
|
39
|
-
from celldetective.gui.processes.segment_cells import SegmentCellThresholdProcess, SegmentCellDLProcess
|
|
40
|
-
from celldetective.gui.processes.track_cells import TrackingProcess
|
|
41
|
-
from celldetective.gui.processes.measure_cells import MeasurementProcess
|
|
42
|
-
|
|
43
|
-
class ProcessPanel(QFrame, Styles):
|
|
44
|
-
|
|
45
|
-
def __init__(self, parent_window, mode):
|
|
46
|
-
|
|
47
|
-
super().__init__()
|
|
48
|
-
self.parent_window = parent_window
|
|
49
|
-
self.mode = mode
|
|
50
|
-
self.exp_channels = self.parent_window.exp_channels
|
|
51
|
-
self.exp_dir = self.parent_window.exp_dir
|
|
52
|
-
self.exp_config = self.parent_window.exp_config
|
|
53
|
-
self.movie_prefix = self.parent_window.movie_prefix
|
|
54
|
-
self.threshold_configs = [None for _ in range(len(self.parent_window.populations))]
|
|
55
|
-
self.wells = np.array(self.parent_window.wells, dtype=str)
|
|
56
|
-
self.cellpose_calibrated = False
|
|
57
|
-
self.stardist_calibrated = False
|
|
58
|
-
self.segChannelsSet = False
|
|
59
|
-
self.signalChannelsSet = False
|
|
60
|
-
self.flipSeg = False
|
|
61
|
-
|
|
62
|
-
self.use_gpu = self.parent_window.parent_window.use_gpu
|
|
63
|
-
self.n_threads = self.parent_window.parent_window.n_threads
|
|
64
|
-
|
|
65
|
-
self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
|
|
66
|
-
self.grid = QGridLayout(self)
|
|
67
|
-
self.grid.setContentsMargins(5, 5, 5, 5)
|
|
68
|
-
self.generate_header()
|
|
69
|
-
|
|
70
|
-
def generate_header(self):
|
|
71
|
-
|
|
72
|
-
"""
|
|
73
|
-
Read the mode and prepare a collapsable block to process a specific cell population.
|
|
74
|
-
|
|
75
|
-
"""
|
|
76
|
-
|
|
77
|
-
panel_title = QLabel(f"PROCESS {self.mode.upper()} ")
|
|
78
|
-
panel_title.setStyleSheet("""
|
|
79
|
-
font-weight: bold;
|
|
80
|
-
padding: 0px;
|
|
81
|
-
""")
|
|
82
|
-
|
|
83
|
-
title_hbox = QHBoxLayout()
|
|
84
|
-
self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
|
|
85
|
-
|
|
86
|
-
# self.help_pop_btn = QPushButton()
|
|
87
|
-
# self.help_pop_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
|
|
88
|
-
# self.help_pop_btn.setIconSize(QSize(20, 20))
|
|
89
|
-
# self.help_pop_btn.clicked.connect(self.help_population)
|
|
90
|
-
# self.help_pop_btn.setStyleSheet(self.button_select_all)
|
|
91
|
-
# self.help_pop_btn.setToolTip("Help.")
|
|
92
|
-
# self.grid.addWidget(self.help_pop_btn, 0, 0, 1, 3, alignment=Qt.AlignRight)
|
|
93
|
-
|
|
94
|
-
# self.select_all_btn = QPushButton()
|
|
95
|
-
# self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
96
|
-
# self.select_all_btn.setIconSize(QSize(20, 20))
|
|
97
|
-
# self.all_ticked = False
|
|
98
|
-
# self.select_all_btn.clicked.connect(self.tick_all_actions)
|
|
99
|
-
# self.select_all_btn.setStyleSheet(self.button_select_all)
|
|
100
|
-
#self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
|
|
101
|
-
#self.to_disable.append(self.all_tc_actions)
|
|
102
|
-
|
|
103
|
-
self.collapse_btn = QPushButton()
|
|
104
|
-
self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
|
|
105
|
-
self.collapse_btn.setIconSize(QSize(25, 25))
|
|
106
|
-
self.collapse_btn.setStyleSheet(self.button_select_all)
|
|
107
|
-
#self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
|
|
108
|
-
|
|
109
|
-
title_hbox.addWidget(QLabel(), 5) #self.select_all_btn
|
|
110
|
-
title_hbox.addWidget(QLabel(), 85, alignment=Qt.AlignCenter)
|
|
111
|
-
# title_hbox.addWidget(self.help_pop_btn, 5)
|
|
112
|
-
title_hbox.addWidget(self.collapse_btn, 5)
|
|
113
|
-
|
|
114
|
-
self.grid.addLayout(title_hbox, 0, 0, 1, 4)
|
|
115
|
-
self.populate_contents()
|
|
116
|
-
|
|
117
|
-
self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
|
|
118
|
-
self.collapse_btn.clicked.connect(lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden()))
|
|
119
|
-
self.collapse_btn.clicked.connect(self.collapse_advanced)
|
|
120
|
-
self.ContentsFrame.hide()
|
|
121
|
-
|
|
122
|
-
def collapse_advanced(self):
|
|
123
|
-
|
|
124
|
-
panels_open = [not p.ContentsFrame.isHidden() for p in self.parent_window.ProcessPopulations]
|
|
125
|
-
interactions_open = not self.parent_window.NeighPanel.ContentsFrame.isHidden()
|
|
126
|
-
preprocessing_open = not self.parent_window.PreprocessingPanel.ContentsFrame.isHidden()
|
|
127
|
-
is_open = np.array(panels_open+[interactions_open, preprocessing_open])
|
|
128
|
-
|
|
129
|
-
if self.ContentsFrame.isHidden():
|
|
130
|
-
self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
|
|
131
|
-
self.collapse_btn.setIconSize(QSize(20, 20))
|
|
132
|
-
if len(is_open[is_open])==0:
|
|
133
|
-
self.parent_window.scroll.setMinimumHeight(int(550))
|
|
134
|
-
self.parent_window.adjustSize()
|
|
135
|
-
else:
|
|
136
|
-
self.collapse_btn.setIcon(icon(MDI6.chevron_up, color="black"))
|
|
137
|
-
self.collapse_btn.setIconSize(QSize(20, 20))
|
|
138
|
-
self.parent_window.scroll.setMinimumHeight(min(int(930), int(0.9*self.parent_window.screen_height)))
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
# def help_population(self):
|
|
142
|
-
|
|
143
|
-
# """
|
|
144
|
-
# Helper to choose a proper cell population structure.
|
|
145
|
-
# """
|
|
146
|
-
|
|
147
|
-
# dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','cell-populations.json'])
|
|
148
|
-
|
|
149
|
-
# with open(dict_path) as f:
|
|
150
|
-
# d = json.load(f)
|
|
151
|
-
|
|
152
|
-
# suggestion = help_generic(d)
|
|
153
|
-
# if isinstance(suggestion, str):
|
|
154
|
-
# print(f"{suggestion=}")
|
|
155
|
-
# msgBox = QMessageBox()
|
|
156
|
-
# msgBox.setIcon(QMessageBox.Information)
|
|
157
|
-
# msgBox.setTextFormat(Qt.RichText)
|
|
158
|
-
# msgBox.setText(suggestion)
|
|
159
|
-
# msgBox.setWindowTitle("Info")
|
|
160
|
-
# msgBox.setStandardButtons(QMessageBox.Ok)
|
|
161
|
-
# returnValue = msgBox.exec()
|
|
162
|
-
# if returnValue == QMessageBox.Ok:
|
|
163
|
-
# return None
|
|
164
|
-
|
|
165
|
-
def populate_contents(self):
|
|
166
|
-
self.ContentsFrame = QFrame()
|
|
167
|
-
self.ContentsFrame.setContentsMargins(5,5,5,5)
|
|
168
|
-
self.grid_contents = QGridLayout(self.ContentsFrame)
|
|
169
|
-
self.grid_contents.setContentsMargins(0,0,0,0)
|
|
170
|
-
self.generate_segmentation_options()
|
|
171
|
-
self.generate_tracking_options()
|
|
172
|
-
self.generate_measure_options()
|
|
173
|
-
self.generate_signal_analysis_options()
|
|
174
|
-
|
|
175
|
-
self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
|
|
176
|
-
self.view_tab_btn = QPushButton("Explore table")
|
|
177
|
-
self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
|
|
178
|
-
self.view_tab_btn.clicked.connect(self.view_table_ui)
|
|
179
|
-
self.view_tab_btn.setToolTip('Explore table')
|
|
180
|
-
self.view_tab_btn.setIcon(icon(MDI6.table,color="#1565c0"))
|
|
181
|
-
self.view_tab_btn.setIconSize(QSize(20, 20))
|
|
182
|
-
#self.view_tab_btn.setEnabled(False)
|
|
183
|
-
self.grid_contents.addWidget(self.view_tab_btn, 10, 0, 1, 4)
|
|
184
|
-
|
|
185
|
-
self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
|
|
186
|
-
self.submit_btn = QPushButton("Submit")
|
|
187
|
-
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
188
|
-
self.submit_btn.clicked.connect(self.process_population)
|
|
189
|
-
self.grid_contents.addWidget(self.submit_btn, 11, 0, 1, 4)
|
|
190
|
-
|
|
191
|
-
def generate_measure_options(self):
|
|
192
|
-
|
|
193
|
-
measure_layout = QHBoxLayout()
|
|
194
|
-
|
|
195
|
-
self.measure_action = QCheckBox("MEASURE")
|
|
196
|
-
self.measure_action.setStyleSheet(self.menu_check_style)
|
|
197
|
-
|
|
198
|
-
self.measure_action.setIcon(icon(MDI6.eyedropper,color="black"))
|
|
199
|
-
self.measure_action.setIconSize(QSize(20, 20))
|
|
200
|
-
self.measure_action.setToolTip("Measure.")
|
|
201
|
-
measure_layout.addWidget(self.measure_action, 90)
|
|
202
|
-
#self.to_disable.append(self.measure_action_tc)
|
|
203
|
-
|
|
204
|
-
self.classify_btn = QPushButton()
|
|
205
|
-
self.classify_btn.setIcon(icon(MDI6.scatter_plot, color="black"))
|
|
206
|
-
self.classify_btn.setIconSize(QSize(20, 20))
|
|
207
|
-
self.classify_btn.setToolTip("Classify data.")
|
|
208
|
-
self.classify_btn.setStyleSheet(self.button_select_all)
|
|
209
|
-
self.classify_btn.clicked.connect(self.open_classifier_ui)
|
|
210
|
-
measure_layout.addWidget(self.classify_btn, 5) #4,2,1,1, alignment=Qt.AlignRight
|
|
211
|
-
|
|
212
|
-
self.check_measurements_btn=QPushButton()
|
|
213
|
-
self.check_measurements_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
|
|
214
|
-
self.check_measurements_btn.setIconSize(QSize(20, 20))
|
|
215
|
-
self.check_measurements_btn.setToolTip("Explore measurements in-situ.")
|
|
216
|
-
self.check_measurements_btn.setStyleSheet(self.button_select_all)
|
|
217
|
-
self.check_measurements_btn.clicked.connect(self.check_measurements)
|
|
218
|
-
measure_layout.addWidget(self.check_measurements_btn, 5)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
self.measurements_config_btn = QPushButton()
|
|
222
|
-
self.measurements_config_btn.setIcon(icon(MDI6.cog_outline,color="black"))
|
|
223
|
-
self.measurements_config_btn.setIconSize(QSize(20, 20))
|
|
224
|
-
self.measurements_config_btn.setToolTip("Configure measurements.")
|
|
225
|
-
self.measurements_config_btn.setStyleSheet(self.button_select_all)
|
|
226
|
-
self.measurements_config_btn.clicked.connect(self.open_measurement_configuration_ui)
|
|
227
|
-
measure_layout.addWidget(self.measurements_config_btn, 5) #4,2,1,1, alignment=Qt.AlignRight
|
|
228
|
-
|
|
229
|
-
self.grid_contents.addLayout(measure_layout,5,0,1,4)
|
|
230
|
-
|
|
231
|
-
def generate_signal_analysis_options(self):
|
|
232
|
-
|
|
233
|
-
signal_layout = QVBoxLayout()
|
|
234
|
-
signal_hlayout = QHBoxLayout()
|
|
235
|
-
self.signal_analysis_action = QCheckBox("DETECT EVENTS")
|
|
236
|
-
self.signal_analysis_action.setStyleSheet(self.menu_check_style)
|
|
237
|
-
self.signal_analysis_action.setIcon(icon(MDI6.chart_bell_curve_cumulative,color="black"))
|
|
238
|
-
self.signal_analysis_action.setIconSize(QSize(20, 20))
|
|
239
|
-
self.signal_analysis_action.setToolTip("Detect events in single-cell signals.")
|
|
240
|
-
self.signal_analysis_action.toggled.connect(self.enable_signal_model_list)
|
|
241
|
-
signal_hlayout.addWidget(self.signal_analysis_action, 90)
|
|
242
|
-
|
|
243
|
-
self.check_signals_btn = QPushButton()
|
|
244
|
-
self.check_signals_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
|
|
245
|
-
self.check_signals_btn.setIconSize(QSize(20, 20))
|
|
246
|
-
self.check_signals_btn.clicked.connect(self.check_signals)
|
|
247
|
-
self.check_signals_btn.setToolTip("Explore signals in-situ.")
|
|
248
|
-
self.check_signals_btn.setStyleSheet(self.button_select_all)
|
|
249
|
-
signal_hlayout.addWidget(self.check_signals_btn, 6)
|
|
250
|
-
|
|
251
|
-
self.config_signal_annotator_btn = QPushButton()
|
|
252
|
-
self.config_signal_annotator_btn.setIcon(icon(MDI6.cog_outline,color="black"))
|
|
253
|
-
self.config_signal_annotator_btn.setIconSize(QSize(20, 20))
|
|
254
|
-
self.config_signal_annotator_btn.setToolTip("Configure the dynamic visualizer.")
|
|
255
|
-
self.config_signal_annotator_btn.setStyleSheet(self.button_select_all)
|
|
256
|
-
self.config_signal_annotator_btn.clicked.connect(self.open_signal_annotator_configuration_ui)
|
|
257
|
-
signal_hlayout.addWidget(self.config_signal_annotator_btn, 6)
|
|
258
|
-
|
|
259
|
-
#self.to_disable.append(self.measure_action_tc)
|
|
260
|
-
signal_layout.addLayout(signal_hlayout)
|
|
261
|
-
|
|
262
|
-
signal_model_vbox = QVBoxLayout()
|
|
263
|
-
signal_model_vbox.setContentsMargins(25,0,25,0)
|
|
264
|
-
|
|
265
|
-
model_zoo_layout = QHBoxLayout()
|
|
266
|
-
model_zoo_layout.addWidget(QLabel("Model zoo:"),90)
|
|
267
|
-
|
|
268
|
-
self.signal_models_list = QComboBox()
|
|
269
|
-
self.signal_models_list.setEnabled(False)
|
|
270
|
-
self.refresh_signal_models()
|
|
271
|
-
#self.to_disable.append(self.cell_models_list)
|
|
272
|
-
|
|
273
|
-
self.train_signal_model_btn = QPushButton("TRAIN")
|
|
274
|
-
self.train_signal_model_btn.setToolTip("Train or retrain an event detection model\non newly annotated data.")
|
|
275
|
-
self.train_signal_model_btn.setIcon(icon(MDI6.redo_variant,color='black'))
|
|
276
|
-
self.train_signal_model_btn.setIconSize(QSize(20, 20))
|
|
277
|
-
self.train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
278
|
-
model_zoo_layout.addWidget(self.train_signal_model_btn, 5)
|
|
279
|
-
self.train_signal_model_btn.clicked.connect(self.open_signal_model_config_ui)
|
|
280
|
-
|
|
281
|
-
signal_model_vbox.addLayout(model_zoo_layout)
|
|
282
|
-
signal_model_vbox.addWidget(self.signal_models_list)
|
|
283
|
-
|
|
284
|
-
signal_layout.addLayout(signal_model_vbox)
|
|
285
|
-
|
|
286
|
-
self.grid_contents.addLayout(signal_layout,6,0,1,4)
|
|
287
|
-
|
|
288
|
-
def refresh_signal_models(self):
|
|
289
|
-
self.signal_models = get_signal_models_list()
|
|
290
|
-
self.signal_models_list.clear()
|
|
291
|
-
|
|
292
|
-
thresh = 35
|
|
293
|
-
models_truncated = [m[:thresh - 3]+'...' if len(m)>thresh else m for m in self.signal_models]
|
|
294
|
-
|
|
295
|
-
self.signal_models_list.addItems(models_truncated)
|
|
296
|
-
for i in range(len(self.signal_models)):
|
|
297
|
-
self.signal_models_list.setItemData(i, self.signal_models[i], Qt.ToolTipRole)
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
def generate_tracking_options(self):
|
|
301
|
-
|
|
302
|
-
grid_track = QHBoxLayout()
|
|
303
|
-
|
|
304
|
-
self.track_action = QCheckBox("TRACK")
|
|
305
|
-
self.track_action.setStyleSheet(self.menu_check_style)
|
|
306
|
-
self.track_action.setIcon(icon(MDI6.chart_timeline_variant,color="black"))
|
|
307
|
-
self.track_action.setIconSize(QSize(20, 20))
|
|
308
|
-
self.track_action.setToolTip(f"Track the {self.mode[:-1]} cells.")
|
|
309
|
-
grid_track.addWidget(self.track_action, 75)
|
|
310
|
-
|
|
311
|
-
self.delete_tracks_btn = QPushButton()
|
|
312
|
-
self.delete_tracks_btn.setIcon(icon(MDI6.trash_can,color="black"))
|
|
313
|
-
self.delete_tracks_btn.setIconSize(QSize(20, 20))
|
|
314
|
-
self.delete_tracks_btn.setToolTip("Delete existing tracks.")
|
|
315
|
-
self.delete_tracks_btn.setStyleSheet(self.button_select_all)
|
|
316
|
-
self.delete_tracks_btn.clicked.connect(self.delete_tracks)
|
|
317
|
-
self.delete_tracks_btn.setEnabled(True)
|
|
318
|
-
self.delete_tracks_btn.hide()
|
|
319
|
-
grid_track.addWidget(self.delete_tracks_btn, 6) #4,3,1,1, alignment=Qt.AlignLeft
|
|
320
|
-
|
|
321
|
-
self.check_tracking_result_btn = QPushButton()
|
|
322
|
-
self.check_tracking_result_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
|
|
323
|
-
self.check_tracking_result_btn.setIconSize(QSize(20, 20))
|
|
324
|
-
self.check_tracking_result_btn.setToolTip("View tracking output in napari.")
|
|
325
|
-
self.check_tracking_result_btn.setStyleSheet(self.button_select_all)
|
|
326
|
-
self.check_tracking_result_btn.clicked.connect(self.open_napari_tracking)
|
|
327
|
-
self.check_tracking_result_btn.setEnabled(False)
|
|
328
|
-
grid_track.addWidget(self.check_tracking_result_btn, 6) #4,3,1,1, alignment=Qt.AlignLeft
|
|
329
|
-
|
|
330
|
-
self.track_config_btn = QPushButton()
|
|
331
|
-
self.track_config_btn.setIcon(icon(MDI6.cog_outline,color="black"))
|
|
332
|
-
self.track_config_btn.setIconSize(QSize(20, 20))
|
|
333
|
-
self.track_config_btn.setToolTip("Configure tracking.")
|
|
334
|
-
self.track_config_btn.setStyleSheet(self.button_select_all)
|
|
335
|
-
self.track_config_btn.clicked.connect(self.open_tracking_configuration_ui)
|
|
336
|
-
grid_track.addWidget(self.track_config_btn, 6) #4,2,1,1, alignment=Qt.AlignRight
|
|
337
|
-
|
|
338
|
-
self.help_track_btn = QPushButton()
|
|
339
|
-
self.help_track_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
|
|
340
|
-
self.help_track_btn.setIconSize(QSize(20, 20))
|
|
341
|
-
self.help_track_btn.clicked.connect(self.help_tracking)
|
|
342
|
-
self.help_track_btn.setStyleSheet(self.button_select_all)
|
|
343
|
-
self.help_track_btn.setToolTip("Help.")
|
|
344
|
-
grid_track.addWidget(self.help_track_btn, 6) #4,2,1,1, alignment=Qt.AlignRight
|
|
345
|
-
|
|
346
|
-
self.grid_contents.addLayout(grid_track, 4, 0, 1,4)
|
|
347
|
-
|
|
348
|
-
def delete_tracks(self):
|
|
349
|
-
|
|
350
|
-
msgBox = QMessageBox()
|
|
351
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
352
|
-
msgBox.setText("Do you want to erase the tracks? All subsequent annotations will be erased...")
|
|
353
|
-
msgBox.setWindowTitle("Info")
|
|
354
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
355
|
-
returnValue = msgBox.exec()
|
|
356
|
-
if returnValue == QMessageBox.No:
|
|
357
|
-
return None
|
|
358
|
-
elif returnValue == QMessageBox.Yes:
|
|
359
|
-
remove_file_if_exists(os.sep.join([self.parent_window.pos, 'output', 'tables', f'trajectories_{self.mode}.csv']))
|
|
360
|
-
remove_file_if_exists(os.sep.join([self.parent_window.pos, 'output', 'tables', f'trajectories_{self.mode}.pkl']))
|
|
361
|
-
remove_file_if_exists(os.sep.join([self.parent_window.pos, 'output', 'tables', f'napari_{self.mode[:-1]}_trajectories.npy']))
|
|
362
|
-
remove_file_if_exists(os.sep.join([self.parent_window.pos, 'output', 'tables', f'trajectories_pairs.csv']))
|
|
363
|
-
self.parent_window.update_position_options()
|
|
364
|
-
else:
|
|
365
|
-
return None
|
|
366
|
-
|
|
367
|
-
def generate_segmentation_options(self):
|
|
368
|
-
|
|
369
|
-
grid_segment = QHBoxLayout()
|
|
370
|
-
grid_segment.setContentsMargins(0,0,0,0)
|
|
371
|
-
grid_segment.setSpacing(0)
|
|
372
|
-
|
|
373
|
-
self.segment_action = QCheckBox("SEGMENT")
|
|
374
|
-
self.segment_action.setStyleSheet(self.menu_check_style)
|
|
375
|
-
self.segment_action.setIcon(icon(MDI6.bacteria, color='black'))
|
|
376
|
-
self.segment_action.setToolTip(f"Segment the {self.mode[:-1]} cells on the images.")
|
|
377
|
-
self.segment_action.toggled.connect(self.enable_segmentation_model_list)
|
|
378
|
-
#self.to_disable.append(self.segment_action)
|
|
379
|
-
grid_segment.addWidget(self.segment_action, 90)
|
|
380
|
-
|
|
381
|
-
# self.flip_segment_btn = QPushButton()
|
|
382
|
-
# self.flip_segment_btn.setIcon(icon(MDI6.camera_flip_outline,color="black"))
|
|
383
|
-
# self.flip_segment_btn.setIconSize(QSize(20, 20))
|
|
384
|
-
# self.flip_segment_btn.clicked.connect(self.flip_segmentation)
|
|
385
|
-
# self.flip_segment_btn.setStyleSheet(self.button_select_all)
|
|
386
|
-
# self.flip_segment_btn.setToolTip("Flip the order of the frames for segmentation.")
|
|
387
|
-
# grid_segment.addWidget(self.flip_segment_btn, 5)
|
|
388
|
-
|
|
389
|
-
self.segmentation_config_btn = QPushButton()
|
|
390
|
-
self.segmentation_config_btn.setIcon(icon(MDI6.cog_outline,color="black"))
|
|
391
|
-
self.segmentation_config_btn.setIconSize(QSize(20, 20))
|
|
392
|
-
self.segmentation_config_btn.setToolTip("Configure segmentation.")
|
|
393
|
-
self.segmentation_config_btn.setStyleSheet(self.button_select_all)
|
|
394
|
-
self.segmentation_config_btn.clicked.connect(self.open_segmentation_configuration_ui)
|
|
395
|
-
grid_segment.addWidget(self.segmentation_config_btn, 5)
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
self.check_seg_btn = QPushButton()
|
|
399
|
-
self.check_seg_btn.setIcon(icon(MDI6.eye_check_outline,color="black"))
|
|
400
|
-
self.check_seg_btn.setIconSize(QSize(20, 20))
|
|
401
|
-
self.check_seg_btn.clicked.connect(self.check_segmentation)
|
|
402
|
-
self.check_seg_btn.setStyleSheet(self.button_select_all)
|
|
403
|
-
self.check_seg_btn.setToolTip("View segmentation output in napari.")
|
|
404
|
-
grid_segment.addWidget(self.check_seg_btn, 5)
|
|
405
|
-
|
|
406
|
-
self.help_seg_btn = QPushButton()
|
|
407
|
-
self.help_seg_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
|
|
408
|
-
self.help_seg_btn.setIconSize(QSize(20, 20))
|
|
409
|
-
self.help_seg_btn.clicked.connect(self.help_segmentation)
|
|
410
|
-
self.help_seg_btn.setStyleSheet(self.button_select_all)
|
|
411
|
-
self.help_seg_btn.setToolTip("Help.")
|
|
412
|
-
grid_segment.addWidget(self.help_seg_btn, 5)
|
|
413
|
-
self.grid_contents.addLayout(grid_segment, 0,0,1,4)
|
|
414
|
-
|
|
415
|
-
seg_option_vbox = QVBoxLayout()
|
|
416
|
-
seg_option_vbox.setContentsMargins(25,0,25,0)
|
|
417
|
-
model_zoo_layout = QHBoxLayout()
|
|
418
|
-
model_zoo_layout.addWidget(QLabel("Model zoo:"),90)
|
|
419
|
-
self.seg_model_list = QComboBox()
|
|
420
|
-
self.seg_model_list.currentIndexChanged.connect(self.reset_generalist_setup)
|
|
421
|
-
#self.to_disable.append(self.tc_seg_model_list)
|
|
422
|
-
self.seg_model_list.setGeometry(50, 50, 200, 30)
|
|
423
|
-
self.init_seg_model_list()
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
self.upload_model_btn = QPushButton("UPLOAD")
|
|
427
|
-
self.upload_model_btn.setIcon(icon(MDI6.upload,color="black"))
|
|
428
|
-
self.upload_model_btn.setIconSize(QSize(20, 20))
|
|
429
|
-
self.upload_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
430
|
-
self.upload_model_btn.setToolTip("Upload a new segmentation model\n(Deep learning or threshold-based).")
|
|
431
|
-
model_zoo_layout.addWidget(self.upload_model_btn, 5)
|
|
432
|
-
self.upload_model_btn.clicked.connect(self.upload_segmentation_model)
|
|
433
|
-
# self.to_disable.append(self.upload_tc_model)
|
|
434
|
-
|
|
435
|
-
self.train_btn = QPushButton("TRAIN")
|
|
436
|
-
self.train_btn.setToolTip("Train or retrain a segmentation model\non newly annotated data.")
|
|
437
|
-
self.train_btn.setIcon(icon(MDI6.redo_variant,color='black'))
|
|
438
|
-
self.train_btn.setIconSize(QSize(20, 20))
|
|
439
|
-
self.train_btn.setStyleSheet(self.button_style_sheet_3)
|
|
440
|
-
self.train_btn.clicked.connect(self.open_segmentation_model_config_ui)
|
|
441
|
-
model_zoo_layout.addWidget(self.train_btn, 5)
|
|
442
|
-
# self.train_button_tc.clicked.connect(self.train_stardist_model_tc)
|
|
443
|
-
# self.to_disable.append(self.train_button_tc)
|
|
444
|
-
|
|
445
|
-
seg_option_vbox.addLayout(model_zoo_layout)
|
|
446
|
-
seg_option_vbox.addWidget(self.seg_model_list)
|
|
447
|
-
self.seg_model_list.setEnabled(False)
|
|
448
|
-
self.grid_contents.addLayout(seg_option_vbox, 2, 0, 1, 4)
|
|
449
|
-
|
|
450
|
-
def flip_segmentation(self):
|
|
451
|
-
if not self.flipSeg:
|
|
452
|
-
self.flipSeg = True
|
|
453
|
-
self.flip_segment_btn.setIcon(icon(MDI6.camera_flip,color=self.celldetective_blue))
|
|
454
|
-
self.flip_segment_btn.setIconSize(QSize(20, 20))
|
|
455
|
-
self.flip_segment_btn.setToolTip("Unflip the order of the frames for segmentation.")
|
|
456
|
-
else:
|
|
457
|
-
self.flipSeg = False
|
|
458
|
-
self.flip_segment_btn.setIcon(icon(MDI6.camera_flip_outline,color='black'))
|
|
459
|
-
self.flip_segment_btn.setIconSize(QSize(20, 20))
|
|
460
|
-
self.flip_segment_btn.setToolTip("Flip the order of the frames for segmentation.")
|
|
461
|
-
|
|
462
|
-
def help_segmentation(self):
|
|
463
|
-
|
|
464
|
-
"""
|
|
465
|
-
Widget with different decision helper decision trees.
|
|
466
|
-
"""
|
|
467
|
-
|
|
468
|
-
self.help_w = CelldetectiveWidget()
|
|
469
|
-
self.help_w.setWindowTitle('Helper')
|
|
470
|
-
layout = QVBoxLayout()
|
|
471
|
-
seg_strategy_btn = QPushButton('A guide to choose a segmentation strategy.')
|
|
472
|
-
seg_strategy_btn.setIcon(icon(MDI6.help_circle,color=self.celldetective_blue))
|
|
473
|
-
seg_strategy_btn.setIconSize(QSize(40, 40))
|
|
474
|
-
seg_strategy_btn.setStyleSheet(self.button_style_sheet_5)
|
|
475
|
-
seg_strategy_btn.clicked.connect(self.help_seg_strategy)
|
|
476
|
-
|
|
477
|
-
dl_strategy_btn = QPushButton('A guide to choose your Deep learning segmentation strategy.')
|
|
478
|
-
dl_strategy_btn.setIcon(icon(MDI6.help_circle,color=self.celldetective_blue))
|
|
479
|
-
dl_strategy_btn.setIconSize(QSize(40, 40))
|
|
480
|
-
dl_strategy_btn.setStyleSheet(self.button_style_sheet_5)
|
|
481
|
-
dl_strategy_btn.clicked.connect(self.help_seg_dl_strategy)
|
|
482
|
-
|
|
483
|
-
layout.addWidget(seg_strategy_btn)
|
|
484
|
-
layout.addWidget(dl_strategy_btn)
|
|
485
|
-
|
|
486
|
-
self.help_w.setLayout(layout)
|
|
487
|
-
center_window(self.help_w)
|
|
488
|
-
self.help_w.show()
|
|
489
|
-
|
|
490
|
-
return None
|
|
491
|
-
|
|
492
|
-
def help_seg_strategy(self):
|
|
493
|
-
|
|
494
|
-
"""
|
|
495
|
-
Helper for segmentation strategy between threshold-based and Deep learning.
|
|
496
|
-
"""
|
|
497
|
-
|
|
498
|
-
dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','Threshold-vs-DL.json'])
|
|
499
|
-
|
|
500
|
-
with open(dict_path) as f:
|
|
501
|
-
d = json.load(f)
|
|
502
|
-
|
|
503
|
-
suggestion = help_generic(d)
|
|
504
|
-
if isinstance(suggestion, str):
|
|
505
|
-
print(f"{suggestion=}")
|
|
506
|
-
msgBox = QMessageBox()
|
|
507
|
-
msgBox.setIcon(QMessageBox.Information)
|
|
508
|
-
msgBox.setTextFormat(Qt.RichText)
|
|
509
|
-
msgBox.setText(f"The suggested technique is {suggestion}.\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>.")
|
|
510
|
-
msgBox.setWindowTitle("Info")
|
|
511
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
512
|
-
returnValue = msgBox.exec()
|
|
513
|
-
if returnValue == QMessageBox.Ok:
|
|
514
|
-
return None
|
|
515
|
-
|
|
516
|
-
def help_seg_dl_strategy(self):
|
|
517
|
-
|
|
518
|
-
"""
|
|
519
|
-
Helper for DL segmentation strategy, between pretrained models and custom models.
|
|
520
|
-
"""
|
|
521
|
-
|
|
522
|
-
dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','DL-segmentation-strategy.json'])
|
|
523
|
-
|
|
524
|
-
with open(dict_path) as f:
|
|
525
|
-
d = json.load(f)
|
|
526
|
-
|
|
527
|
-
suggestion = help_generic(d)
|
|
528
|
-
if isinstance(suggestion, str):
|
|
529
|
-
print(f"{suggestion=}")
|
|
530
|
-
msgBox = QMessageBox()
|
|
531
|
-
msgBox.setIcon(QMessageBox.Information)
|
|
532
|
-
msgBox.setText(f"The suggested technique is {suggestion}.")
|
|
533
|
-
msgBox.setWindowTitle("Info")
|
|
534
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
535
|
-
returnValue = msgBox.exec()
|
|
536
|
-
if returnValue == QMessageBox.Ok:
|
|
537
|
-
return None
|
|
538
|
-
|
|
539
|
-
def help_tracking(self):
|
|
540
|
-
|
|
541
|
-
"""
|
|
542
|
-
Helper for segmentation strategy between threshold-based and Deep learning.
|
|
543
|
-
"""
|
|
544
|
-
|
|
545
|
-
dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','tracking.json'])
|
|
546
|
-
|
|
547
|
-
with open(dict_path) as f:
|
|
548
|
-
d = json.load(f)
|
|
549
|
-
|
|
550
|
-
suggestion = help_generic(d)
|
|
551
|
-
if isinstance(suggestion, str):
|
|
552
|
-
print(f"{suggestion=}")
|
|
553
|
-
msgBox = QMessageBox()
|
|
554
|
-
msgBox.setIcon(QMessageBox.Information)
|
|
555
|
-
msgBox.setTextFormat(Qt.RichText)
|
|
556
|
-
msgBox.setText(f"{suggestion}")
|
|
557
|
-
msgBox.setWindowTitle("Info")
|
|
558
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
559
|
-
returnValue = msgBox.exec()
|
|
560
|
-
if returnValue == QMessageBox.Ok:
|
|
561
|
-
return None
|
|
562
|
-
|
|
563
|
-
def check_segmentation(self):
|
|
564
|
-
|
|
565
|
-
if not os.path.exists(os.sep.join([self.parent_window.pos,f'labels_{self.mode}', os.sep])):
|
|
566
|
-
msgBox = QMessageBox()
|
|
567
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
568
|
-
msgBox.setText("No labels can be found for this position. Do you want to annotate from scratch?")
|
|
569
|
-
msgBox.setWindowTitle("Info")
|
|
570
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
571
|
-
returnValue = msgBox.exec()
|
|
572
|
-
if returnValue == QMessageBox.No:
|
|
573
|
-
return None
|
|
574
|
-
else:
|
|
575
|
-
os.mkdir(os.sep.join([self.parent_window.pos,f'labels_{self.mode}']))
|
|
576
|
-
lbl = np.zeros((self.parent_window.shape_x, self.parent_window.shape_y), dtype=int)
|
|
577
|
-
for i in range(self.parent_window.len_movie):
|
|
578
|
-
imwrite(os.sep.join([self.parent_window.pos,f'labels_{self.mode}', str(i).zfill(4)+'.tif']), lbl)
|
|
579
|
-
|
|
580
|
-
#self.freeze()
|
|
581
|
-
#QApplication.setOverrideCursor(Qt.WaitCursor)
|
|
582
|
-
test = self.parent_window.locate_selected_position()
|
|
583
|
-
if test:
|
|
584
|
-
#print('Memory use: ', dict(psutil.virtual_memory()._asdict()))
|
|
585
|
-
print(f"Loading images and labels into napari...")
|
|
586
|
-
try:
|
|
587
|
-
control_segmentation_napari(self.parent_window.pos, prefix=self.parent_window.movie_prefix, population=self.mode,flush_memory=True)
|
|
588
|
-
except FileNotFoundError as e:
|
|
589
|
-
msgBox = QMessageBox()
|
|
590
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
591
|
-
msgBox.setText(str(e))
|
|
592
|
-
msgBox.setWindowTitle("Warning")
|
|
593
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
594
|
-
_ = msgBox.exec()
|
|
595
|
-
return
|
|
596
|
-
except Exception as e:
|
|
597
|
-
print(f'Task unsuccessful... Exception {e}...')
|
|
598
|
-
msgBox = QMessageBox()
|
|
599
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
600
|
-
msgBox.setText(str(e))
|
|
601
|
-
msgBox.setWindowTitle("Warning")
|
|
602
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
603
|
-
_ = msgBox.exec()
|
|
604
|
-
|
|
605
|
-
msgBox = QMessageBox()
|
|
606
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
607
|
-
msgBox.setText("Would you like to pass empty frames to fix the asymmetry?")
|
|
608
|
-
msgBox.setWindowTitle("Question")
|
|
609
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
|
|
610
|
-
returnValue = msgBox.exec()
|
|
611
|
-
if returnValue == QMessageBox.Yes:
|
|
612
|
-
print('Fixing the missing labels...')
|
|
613
|
-
fix_missing_labels(self.parent_window.pos, prefix=self.parent_window.movie_prefix,population=self.mode)
|
|
614
|
-
try:
|
|
615
|
-
control_segmentation_napari(self.parent_window.pos, prefix=self.parent_window.movie_prefix, population=self.mode,flush_memory=True)
|
|
616
|
-
except Exception as e:
|
|
617
|
-
print(f'Error {e}')
|
|
618
|
-
return None
|
|
619
|
-
else:
|
|
620
|
-
return None
|
|
621
|
-
|
|
622
|
-
gc.collect()
|
|
623
|
-
|
|
624
|
-
def check_signals(self):
|
|
625
|
-
|
|
626
|
-
test = self.parent_window.locate_selected_position()
|
|
627
|
-
if test:
|
|
628
|
-
self.event_annotator = EventAnnotator(self)
|
|
629
|
-
self.event_annotator.show()
|
|
630
|
-
|
|
631
|
-
def check_measurements(self):
|
|
632
|
-
|
|
633
|
-
test = self.parent_window.locate_selected_position()
|
|
634
|
-
if test:
|
|
635
|
-
self.measure_annotator = MeasureAnnotator(self)
|
|
636
|
-
self.measure_annotator.show()
|
|
637
|
-
|
|
638
|
-
def enable_segmentation_model_list(self):
|
|
639
|
-
if self.segment_action.isChecked():
|
|
640
|
-
self.seg_model_list.setEnabled(True)
|
|
641
|
-
else:
|
|
642
|
-
self.seg_model_list.setEnabled(False)
|
|
643
|
-
|
|
644
|
-
def enable_signal_model_list(self):
|
|
645
|
-
if self.signal_analysis_action.isChecked():
|
|
646
|
-
self.signal_models_list.setEnabled(True)
|
|
647
|
-
else:
|
|
648
|
-
self.signal_models_list.setEnabled(False)
|
|
649
|
-
|
|
650
|
-
def init_seg_model_list(self):
|
|
651
|
-
|
|
652
|
-
self.seg_model_list.clear()
|
|
653
|
-
self.seg_models_specific = get_segmentation_models_list(mode=self.mode, return_path=False)
|
|
654
|
-
self.seg_models = self.seg_models_specific.copy()
|
|
655
|
-
self.n_specific_seg_models = len(self.seg_models)
|
|
656
|
-
|
|
657
|
-
self.seg_models_generic = get_segmentation_models_list(mode="generic", return_path=False)
|
|
658
|
-
self.seg_models.append('Threshold')
|
|
659
|
-
self.seg_models.extend(self.seg_models_generic)
|
|
660
|
-
|
|
661
|
-
thresh = 35
|
|
662
|
-
self.models_truncated = [m[:thresh - 3]+'...' if len(m)>thresh else m for m in self.seg_models]
|
|
663
|
-
|
|
664
|
-
self.seg_model_list.addItems(self.models_truncated)
|
|
665
|
-
|
|
666
|
-
for i in range(len(self.seg_models)):
|
|
667
|
-
self.seg_model_list.setItemData(i, self.seg_models[i], Qt.ToolTipRole)
|
|
668
|
-
|
|
669
|
-
self.seg_model_list.insertSeparator(self.n_specific_seg_models)
|
|
670
|
-
|
|
671
|
-
# def tick_all_actions(self):
|
|
672
|
-
# self.switch_all_ticks_option()
|
|
673
|
-
# if self.all_ticked:
|
|
674
|
-
# self.select_all_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
|
|
675
|
-
# self.select_all_btn.setIconSize(QSize(20, 20))
|
|
676
|
-
# self.segment_action.setChecked(True)
|
|
677
|
-
# else:
|
|
678
|
-
# self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
679
|
-
# self.select_all_btn.setIconSize(QSize(20, 20))
|
|
680
|
-
# self.segment_action.setChecked(False)
|
|
681
|
-
|
|
682
|
-
# def switch_all_ticks_option(self):
|
|
683
|
-
# if self.all_ticked == True:
|
|
684
|
-
# self.all_ticked = False
|
|
685
|
-
# else:
|
|
686
|
-
# self.all_ticked = True
|
|
687
|
-
|
|
688
|
-
def upload_segmentation_model(self):
|
|
689
|
-
print('Load a segmentation model or pipeline...')
|
|
690
|
-
self.SegModelLoader = SegmentationModelLoader(self)
|
|
691
|
-
self.SegModelLoader.show()
|
|
692
|
-
|
|
693
|
-
def open_tracking_configuration_ui(self):
|
|
694
|
-
print('Set the tracking parameters...')
|
|
695
|
-
self.settings_tracking = SettingsTracking(self)
|
|
696
|
-
self.settings_tracking.show()
|
|
697
|
-
|
|
698
|
-
def open_signal_model_config_ui(self):
|
|
699
|
-
print('Set the training parameters for new signal models...')
|
|
700
|
-
self.settings_event_detection_training = SettingsEventDetectionModelTraining(self)
|
|
701
|
-
self.settings_event_detection_training.show()
|
|
702
|
-
|
|
703
|
-
def open_segmentation_model_config_ui(self):
|
|
704
|
-
print('Set the training parameters for a new segmentation model...')
|
|
705
|
-
self.settings_segmentation_training = SettingsSegmentationModelTraining(self)
|
|
706
|
-
self.settings_segmentation_training.show()
|
|
707
|
-
|
|
708
|
-
def open_measurement_configuration_ui(self):
|
|
709
|
-
print('Set the measurements to be performed...')
|
|
710
|
-
self.settings_measurements = SettingsMeasurements(self)
|
|
711
|
-
self.settings_measurements.show()
|
|
712
|
-
|
|
713
|
-
def open_segmentation_configuration_ui(self):
|
|
714
|
-
print('Set the segmentation settings to be performed...')
|
|
715
|
-
self.settings_segmentation = SettingsSegmentation(self)
|
|
716
|
-
self.settings_segmentation.show()
|
|
717
|
-
|
|
718
|
-
def open_classifier_ui(self):
|
|
719
|
-
|
|
720
|
-
self.load_available_tables()
|
|
721
|
-
if self.df is None:
|
|
722
|
-
|
|
723
|
-
msgBox = QMessageBox()
|
|
724
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
725
|
-
msgBox.setText("No table was found...")
|
|
726
|
-
msgBox.setWindowTitle("Warning")
|
|
727
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
728
|
-
returnValue = msgBox.exec()
|
|
729
|
-
if returnValue == QMessageBox.Ok:
|
|
730
|
-
return None
|
|
731
|
-
else:
|
|
732
|
-
return None
|
|
733
|
-
else:
|
|
734
|
-
self.ClassifierWidget = ClassifierWidget(self)
|
|
735
|
-
self.ClassifierWidget.show()
|
|
736
|
-
|
|
737
|
-
def open_signal_annotator_configuration_ui(self):
|
|
738
|
-
self.settings_signal_annotator = SettingsSignalAnnotator(self)
|
|
739
|
-
self.settings_signal_annotator.show()
|
|
740
|
-
|
|
741
|
-
def reset_generalist_setup(self, index):
|
|
742
|
-
self.cellpose_calibrated = False
|
|
743
|
-
self.stardist_calibrated = False
|
|
744
|
-
self.segChannelsSet = False
|
|
745
|
-
|
|
746
|
-
def reset_signals(self):
|
|
747
|
-
self.signalChannelsSet = False
|
|
748
|
-
|
|
749
|
-
def process_population(self):
|
|
750
|
-
|
|
751
|
-
# if self.parent_window.well_list.currentText().startswith('Multiple'):
|
|
752
|
-
# self.well_index = np.linspace(0,len(self.wells)-1,len(self.wells),dtype=int)
|
|
753
|
-
# else:
|
|
754
|
-
|
|
755
|
-
self.well_index = self.parent_window.well_list.getSelectedIndices()
|
|
756
|
-
if len(self.well_index)==0:
|
|
757
|
-
msgBox = QMessageBox()
|
|
758
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
759
|
-
msgBox.setText("Please select at least one well first...")
|
|
760
|
-
msgBox.setWindowTitle("Warning")
|
|
761
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
762
|
-
returnValue = msgBox.exec()
|
|
763
|
-
if returnValue == QMessageBox.Ok:
|
|
764
|
-
return None
|
|
765
|
-
else:
|
|
766
|
-
return None
|
|
767
|
-
|
|
768
|
-
print(f"Processing {self.parent_window.well_list.currentText()}...")
|
|
769
|
-
|
|
770
|
-
# self.freeze()
|
|
771
|
-
# QApplication.setOverrideCursor(Qt.WaitCursor)
|
|
772
|
-
|
|
773
|
-
idx = self.parent_window.populations.index(self.mode)
|
|
774
|
-
self.threshold_config = self.threshold_configs[idx]
|
|
775
|
-
|
|
776
|
-
self.load_available_tables()
|
|
777
|
-
|
|
778
|
-
if self.df is not None and self.segment_action.isChecked():
|
|
779
|
-
msgBox = QMessageBox()
|
|
780
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
781
|
-
msgBox.setText("Measurement tables have been found... Re-segmenting may create mismatches between the cell labels and the associated measurements. Do you want to erase the tables post-segmentation?")
|
|
782
|
-
msgBox.setWindowTitle("Info")
|
|
783
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
|
|
784
|
-
returnValue = msgBox.exec()
|
|
785
|
-
if returnValue == QMessageBox.No:
|
|
786
|
-
pass
|
|
787
|
-
elif returnValue == QMessageBox.Cancel:
|
|
788
|
-
return None
|
|
789
|
-
else:
|
|
790
|
-
print('erase tabs!')
|
|
791
|
-
tabs = [pos+os.sep.join(['output', 'tables', f'trajectories_{self.mode}.csv']) for pos in self.df_pos_info['pos_path'].unique()]
|
|
792
|
-
#tabs += [pos+os.sep.join(['output', 'tables', f'trajectories_pairs.csv']) for pos in self.df_pos_info['pos_path'].unique()]
|
|
793
|
-
tabs += [pos+os.sep.join(['output', 'tables', f'napari_{self.mode}_trajectories.npy']) for pos in self.df_pos_info['pos_path'].unique()]
|
|
794
|
-
for t in tabs:
|
|
795
|
-
remove_file_if_exists(t.replace('.csv','.pkl'))
|
|
796
|
-
try:
|
|
797
|
-
os.remove(t)
|
|
798
|
-
except:
|
|
799
|
-
pass
|
|
800
|
-
loop_iter=0
|
|
801
|
-
|
|
802
|
-
if self.parent_window.position_list.isMultipleSelection():
|
|
803
|
-
msgBox = QMessageBox()
|
|
804
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
805
|
-
msgBox.setText("If you continue, several positions will be processed.\nDo you want to proceed?")
|
|
806
|
-
msgBox.setWindowTitle("Info")
|
|
807
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
808
|
-
returnValue = msgBox.exec()
|
|
809
|
-
if returnValue == QMessageBox.No:
|
|
810
|
-
return None
|
|
811
|
-
|
|
812
|
-
if self.seg_model_list.currentIndex() > self.n_specific_seg_models:
|
|
813
|
-
self.model_name = self.seg_models[self.seg_model_list.currentIndex()-1]
|
|
814
|
-
else:
|
|
815
|
-
self.model_name = self.seg_models[self.seg_model_list.currentIndex()]
|
|
816
|
-
|
|
817
|
-
if self.segment_action.isChecked() and self.model_name.startswith('CP') and self.model_name in self.seg_models_generic and not self.cellpose_calibrated:
|
|
818
|
-
|
|
819
|
-
self.diamWidget = CellposeParamsWidget(self, model_name=self.model_name)
|
|
820
|
-
self.diamWidget.show()
|
|
821
|
-
return None
|
|
822
|
-
|
|
823
|
-
elif self.segment_action.isChecked() and self.model_name.startswith('SD') and self.model_name in self.seg_models_generic and not self.stardist_calibrated:
|
|
824
|
-
|
|
825
|
-
self.diamWidget = StarDistParamsWidget(self, model_name = self.model_name)
|
|
826
|
-
self.diamWidget.show()
|
|
827
|
-
return None
|
|
828
|
-
|
|
829
|
-
elif self.segment_action.isChecked() and self.model_name in self.seg_models_specific and not self.segChannelsSet:
|
|
830
|
-
|
|
831
|
-
self.segChannelWidget = SegModelParamsWidget(self, model_name = self.model_name)
|
|
832
|
-
self.segChannelWidget.show()
|
|
833
|
-
return None
|
|
834
|
-
|
|
835
|
-
if self.signal_analysis_action.isChecked() and not self.signalChannelsSet:
|
|
836
|
-
self.signal_model_name = self.signal_models[self.signal_models_list.currentIndex()]
|
|
837
|
-
self.signalChannelWidget = SignalModelParamsWidget(self, model_name = self.signal_model_name)
|
|
838
|
-
self.signalChannelWidget.show()
|
|
839
|
-
return None
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
self.movie_prefix = self.parent_window.movie_prefix
|
|
843
|
-
|
|
844
|
-
for w_idx in self.well_index:
|
|
845
|
-
|
|
846
|
-
pos = self.parent_window.positions[w_idx]
|
|
847
|
-
pos_indices = self.parent_window.position_list.getSelectedIndices()
|
|
848
|
-
#print(f"Processing position {self.parent_window.position_list.currentText()}...")
|
|
849
|
-
|
|
850
|
-
well = self.parent_window.wells[w_idx]
|
|
851
|
-
|
|
852
|
-
for pos_idx in pos_indices:
|
|
853
|
-
|
|
854
|
-
self.pos = natsorted(glob(well+f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*/"))[pos_idx]
|
|
855
|
-
print(f"Position {self.pos}...\nLoading stack movie...")
|
|
856
|
-
self.pos_name = extract_position_name(self.pos)
|
|
857
|
-
|
|
858
|
-
if not os.path.exists(self.pos + 'output/'):
|
|
859
|
-
os.mkdir(self.pos + 'output/')
|
|
860
|
-
if not os.path.exists(self.pos + 'output/tables/'):
|
|
861
|
-
os.mkdir(self.pos + 'output/tables/')
|
|
862
|
-
|
|
863
|
-
if self.segment_action.isChecked():
|
|
864
|
-
|
|
865
|
-
if len(glob(os.sep.join([self.pos, f'labels_{self.mode}','*.tif'])))>0 and not self.parent_window.position_list.isMultipleSelection():
|
|
866
|
-
msgBox = QMessageBox()
|
|
867
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
868
|
-
msgBox.setText("Labels have already been produced for this position. Do you want to segment again?")
|
|
869
|
-
msgBox.setWindowTitle("Info")
|
|
870
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
871
|
-
returnValue = msgBox.exec()
|
|
872
|
-
if returnValue == QMessageBox.No:
|
|
873
|
-
return None
|
|
874
|
-
|
|
875
|
-
if (self.seg_model_list.currentText()=="Threshold"):
|
|
876
|
-
if self.threshold_config is None:
|
|
877
|
-
msgBox = QMessageBox()
|
|
878
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
879
|
-
msgBox.setText("Please set a threshold configuration from the upload menu first. Abort.")
|
|
880
|
-
msgBox.setWindowTitle("Warning")
|
|
881
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
882
|
-
returnValue = msgBox.exec()
|
|
883
|
-
if returnValue == QMessageBox.Ok:
|
|
884
|
-
return None
|
|
885
|
-
else:
|
|
886
|
-
print(f"Segmentation from threshold config: {self.threshold_config}")
|
|
887
|
-
process_args = {"pos": self.pos, "mode": self.mode, "n_threads": self.n_threads, "threshold_instructions": self.threshold_config, "use_gpu": self.use_gpu}
|
|
888
|
-
self.job = ProgressWindow(SegmentCellThresholdProcess, parent_window=self, title="Segment", process_args = process_args)
|
|
889
|
-
result = self.job.exec_()
|
|
890
|
-
if result == QDialog.Accepted:
|
|
891
|
-
pass
|
|
892
|
-
elif result == QDialog.Rejected:
|
|
893
|
-
self.reset_generalist_setup(0)
|
|
894
|
-
return None
|
|
895
|
-
#segment_from_threshold_at_position(self.pos, self.mode, self.threshold_config, threads=self.parent_window.parent_window.n_threads)
|
|
896
|
-
else:
|
|
897
|
-
# model = locate_segmentation_model(self.model_name)
|
|
898
|
-
# if model is None:
|
|
899
|
-
# process = {"output_dir": self.output_dir, "file": self.model_name}
|
|
900
|
-
# self.download_model_job = ProgressWindow(DownloadProcess, parent_window=self, title="Download", process_args = args)
|
|
901
|
-
|
|
902
|
-
process_args = {"pos": self.pos, "mode": self.mode, "n_threads": self.n_threads, "model_name": self.model_name, "use_gpu": self.use_gpu}
|
|
903
|
-
self.job = ProgressWindow(SegmentCellDLProcess, parent_window=self, title="Segment", process_args = process_args)
|
|
904
|
-
result = self.job.exec_()
|
|
905
|
-
if result == QDialog.Accepted:
|
|
906
|
-
pass
|
|
907
|
-
elif result == QDialog.Rejected:
|
|
908
|
-
self.reset_generalist_setup(0)
|
|
909
|
-
return None
|
|
910
|
-
|
|
911
|
-
if self.track_action.isChecked():
|
|
912
|
-
if os.path.exists(os.sep.join([self.pos, 'output', 'tables', f'trajectories_{self.mode}.csv'])) and not self.parent_window.position_list.isMultipleSelection():
|
|
913
|
-
msgBox = QMessageBox()
|
|
914
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
915
|
-
msgBox.setText("A measurement table already exists. Previously annotated data for\nthis position will be lost. Do you want to proceed?")
|
|
916
|
-
msgBox.setWindowTitle("Info")
|
|
917
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
918
|
-
returnValue = msgBox.exec()
|
|
919
|
-
if returnValue == QMessageBox.No:
|
|
920
|
-
return None
|
|
921
|
-
|
|
922
|
-
process_args = {"pos": self.pos, "mode": self.mode, "n_threads": self.n_threads}
|
|
923
|
-
self.job = ProgressWindow(TrackingProcess, parent_window=self, title="Tracking", process_args=process_args)
|
|
924
|
-
result = self.job.exec_()
|
|
925
|
-
if result == QDialog.Accepted:
|
|
926
|
-
pass
|
|
927
|
-
elif result == QDialog.Rejected:
|
|
928
|
-
return None
|
|
929
|
-
#track_at_position(self.pos, self.mode, threads=self.parent_window.parent_window.n_threads)
|
|
930
|
-
|
|
931
|
-
if self.measure_action.isChecked():
|
|
932
|
-
process_args = {"pos": self.pos, "mode": self.mode, "n_threads": self.n_threads}
|
|
933
|
-
self.job = ProgressWindow(MeasurementProcess, parent_window=self, title="Measurement", process_args=process_args)
|
|
934
|
-
result = self.job.exec_()
|
|
935
|
-
if result == QDialog.Accepted:
|
|
936
|
-
pass
|
|
937
|
-
elif result == QDialog.Rejected:
|
|
938
|
-
return None
|
|
939
|
-
#measure_at_position(self.pos, self.mode, threads=self.parent_window.parent_window.n_threads)
|
|
940
|
-
|
|
941
|
-
table = os.sep.join([self.pos, 'output', 'tables', f'trajectories_{self.mode}.csv'])
|
|
942
|
-
if self.signal_analysis_action.isChecked() and os.path.exists(table):
|
|
943
|
-
table = pd.read_csv(table)
|
|
944
|
-
cols = list(table.columns)
|
|
945
|
-
if 'class_color' in cols:
|
|
946
|
-
colors = list(table['class_color'].to_numpy())
|
|
947
|
-
if 'tab:orange' in colors or 'tab:cyan' in colors:
|
|
948
|
-
if not self.parent_window.position_list.isMultipleSelection():
|
|
949
|
-
msgBox = QMessageBox()
|
|
950
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
951
|
-
msgBox.setText("The signals of the cells in the position appear to have been annotated... Do you want to proceed?")
|
|
952
|
-
msgBox.setWindowTitle("Info")
|
|
953
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
954
|
-
returnValue = msgBox.exec()
|
|
955
|
-
if returnValue == QMessageBox.No:
|
|
956
|
-
return None
|
|
957
|
-
self.signal_model_name = self.signal_models[self.signal_models_list.currentIndex()]
|
|
958
|
-
analyze_signals_at_position(self.pos, self.signal_model_name, self.mode)
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
# self.stack = None
|
|
962
|
-
self.parent_window.update_position_options()
|
|
963
|
-
for action in [self.segment_action, self.track_action, self.measure_action, self.signal_analysis_action]:
|
|
964
|
-
if action.isChecked():
|
|
965
|
-
action.setChecked(False)
|
|
966
|
-
|
|
967
|
-
self.reset_generalist_setup(0)
|
|
968
|
-
self.reset_signals()
|
|
969
|
-
|
|
970
|
-
def open_napari_tracking(self):
|
|
971
|
-
print(f'View the tracks before post-processing for position {self.parent_window.pos} in napari...')
|
|
972
|
-
try:
|
|
973
|
-
control_tracks(self.parent_window.pos, prefix=self.parent_window.movie_prefix, population=self.mode, threads=self.parent_window.parent_window.n_threads)
|
|
974
|
-
except FileNotFoundError as e:
|
|
975
|
-
msgBox = QMessageBox()
|
|
976
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
977
|
-
msgBox.setText(str(e))
|
|
978
|
-
msgBox.setWindowTitle("Warning")
|
|
979
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
980
|
-
_ = msgBox.exec()
|
|
981
|
-
return
|
|
982
|
-
|
|
983
|
-
def view_table_ui(self):
|
|
984
|
-
|
|
985
|
-
print('Load table...')
|
|
986
|
-
self.load_available_tables()
|
|
987
|
-
|
|
988
|
-
if self.df is not None:
|
|
989
|
-
plot_mode = 'plot_track_signals'
|
|
990
|
-
if 'TRACK_ID' not in list(self.df.columns):
|
|
991
|
-
plot_mode = 'static'
|
|
992
|
-
self.tab_ui = TableUI(self.df, f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}", population=self.mode, plot_mode=plot_mode, save_inplace_option=True)
|
|
993
|
-
self.tab_ui.show()
|
|
994
|
-
else:
|
|
995
|
-
print('Table could not be loaded...')
|
|
996
|
-
msgBox = QMessageBox()
|
|
997
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
998
|
-
msgBox.setText("No table could be loaded...")
|
|
999
|
-
msgBox.setWindowTitle("Info")
|
|
1000
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1001
|
-
returnValue = msgBox.exec()
|
|
1002
|
-
if returnValue == QMessageBox.Ok:
|
|
1003
|
-
return None
|
|
1004
|
-
|
|
1005
|
-
def load_available_tables(self):
|
|
1006
|
-
|
|
1007
|
-
"""
|
|
1008
|
-
Load the tables of the selected wells/positions from the control Panel for the population of interest
|
|
1009
|
-
|
|
1010
|
-
"""
|
|
1011
|
-
|
|
1012
|
-
self.well_option = self.parent_window.well_list.getSelectedIndices()
|
|
1013
|
-
self.position_option = self.parent_window.position_list.getSelectedIndices()
|
|
1014
|
-
|
|
1015
|
-
self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population=self.mode, return_pos_info=True)
|
|
1016
|
-
self.signals = []
|
|
1017
|
-
if self.df is not None:
|
|
1018
|
-
self.signals = list(self.df.columns)
|
|
1019
|
-
if self.df is None:
|
|
1020
|
-
print('No table could be found for the selected position(s)...')
|
|
1021
|
-
|
|
1022
|
-
def set_cellpose_scale(self):
|
|
1023
|
-
|
|
1024
|
-
scale = self.parent_window.PxToUm * float(self.diamWidget.diameter_le.get_threshold()) / 30.0
|
|
1025
|
-
if self.model_name=="CP_nuclei":
|
|
1026
|
-
scale = self.parent_window.PxToUm * float(self.diamWidget.diameter_le.get_threshold()) / 17.0
|
|
1027
|
-
flow_thresh = self.diamWidget.flow_slider.value()
|
|
1028
|
-
cellprob_thresh = self.diamWidget.cellprob_slider.value()
|
|
1029
|
-
model_complete_path = locate_segmentation_model(self.model_name)
|
|
1030
|
-
input_config_path = model_complete_path+"config_input.json"
|
|
1031
|
-
new_channels = [self.diamWidget.cellpose_channel_cb[i].currentText() for i in range(2)]
|
|
1032
|
-
with open(input_config_path) as config_file:
|
|
1033
|
-
input_config = json.load(config_file)
|
|
1034
|
-
|
|
1035
|
-
input_config['spatial_calibration'] = scale
|
|
1036
|
-
input_config['channels'] = new_channels
|
|
1037
|
-
input_config['flow_threshold'] = flow_thresh
|
|
1038
|
-
input_config['cellprob_threshold'] = cellprob_thresh
|
|
1039
|
-
with open(input_config_path, 'w') as f:
|
|
1040
|
-
json.dump(input_config, f, indent=4)
|
|
1041
|
-
|
|
1042
|
-
self.cellpose_calibrated = True
|
|
1043
|
-
print('model scale automatically computed: ', scale)
|
|
1044
|
-
self.diamWidget.close()
|
|
1045
|
-
self.process_population()
|
|
1046
|
-
|
|
1047
|
-
def set_stardist_scale(self):
|
|
1048
|
-
|
|
1049
|
-
model_complete_path = locate_segmentation_model(self.model_name)
|
|
1050
|
-
input_config_path = model_complete_path+"config_input.json"
|
|
1051
|
-
new_channels = [self.diamWidget.stardist_channel_cb[i].currentText() for i in range(len(self.diamWidget.stardist_channel_cb))]
|
|
1052
|
-
with open(input_config_path) as config_file:
|
|
1053
|
-
input_config = json.load(config_file)
|
|
1054
|
-
|
|
1055
|
-
input_config['channels'] = new_channels
|
|
1056
|
-
with open(input_config_path, 'w') as f:
|
|
1057
|
-
json.dump(input_config, f, indent=4)
|
|
1058
|
-
|
|
1059
|
-
self.stardist_calibrated = True
|
|
1060
|
-
self.diamWidget.close()
|
|
1061
|
-
self.process_population()
|
|
1062
|
-
|
|
1063
|
-
def set_selected_channels_for_segmentation(self):
|
|
1064
|
-
|
|
1065
|
-
model_complete_path = locate_segmentation_model(self.model_name)
|
|
1066
|
-
input_config_path = model_complete_path+"config_input.json"
|
|
1067
|
-
new_channels = [self.segChannelWidget.channel_cbs[i].currentText() for i in range(len(self.segChannelWidget.channel_cbs))]
|
|
1068
|
-
target_cell_size = None
|
|
1069
|
-
if hasattr(self.segChannelWidget, "diameter_le"):
|
|
1070
|
-
target_cell_size = float(self.segChannelWidget.diameter_le.get_threshold())
|
|
1071
|
-
|
|
1072
|
-
with open(input_config_path) as config_file:
|
|
1073
|
-
input_config = json.load(config_file)
|
|
1074
|
-
|
|
1075
|
-
input_config.update({'selected_channels': new_channels, 'target_cell_size_um': target_cell_size})
|
|
1076
|
-
|
|
1077
|
-
#input_config['channels'] = new_channels
|
|
1078
|
-
with open(input_config_path, 'w') as f:
|
|
1079
|
-
json.dump(input_config, f, indent=4)
|
|
1080
|
-
|
|
1081
|
-
self.segChannelsSet = True
|
|
1082
|
-
self.segChannelWidget.close()
|
|
1083
|
-
self.process_population()
|
|
1084
|
-
|
|
1085
|
-
def set_selected_signals_for_event_detection(self):
|
|
1086
|
-
self.signal_model_name = self.signal_models[self.signal_models_list.currentIndex()]
|
|
1087
|
-
model_complete_path = locate_signal_model(self.signal_model_name)
|
|
1088
|
-
input_config_path = model_complete_path+"config_input.json"
|
|
1089
|
-
new_channels = [self.signalChannelWidget.channel_cbs[i].currentText() for i in range(len(self.signalChannelWidget.channel_cbs))]
|
|
1090
|
-
with open(input_config_path) as config_file:
|
|
1091
|
-
input_config = json.load(config_file)
|
|
1092
|
-
|
|
1093
|
-
input_config.update({'selected_channels': new_channels})
|
|
1094
|
-
|
|
1095
|
-
#input_config['channels'] = new_channels
|
|
1096
|
-
with open(input_config_path, 'w') as f:
|
|
1097
|
-
json.dump(input_config, f, indent=4)
|
|
1098
|
-
|
|
1099
|
-
self.signalChannelsSet = True
|
|
1100
|
-
self.signalChannelWidget.close()
|
|
1101
|
-
self.process_population()
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
class NeighPanel(QFrame, Styles):
|
|
1106
|
-
def __init__(self, parent_window):
|
|
1107
|
-
|
|
1108
|
-
super().__init__()
|
|
1109
|
-
self.parent_window = parent_window
|
|
1110
|
-
self.exp_channels = self.parent_window.exp_channels
|
|
1111
|
-
self.exp_dir = self.parent_window.exp_dir
|
|
1112
|
-
self.wells = np.array(self.parent_window.wells,dtype=str)
|
|
1113
|
-
self.protocols = []
|
|
1114
|
-
self.mode='neighborhood'
|
|
1115
|
-
|
|
1116
|
-
self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
|
|
1117
|
-
self.grid = QGridLayout(self)
|
|
1118
|
-
self.generate_header()
|
|
1119
|
-
|
|
1120
|
-
def generate_header(self):
|
|
1121
|
-
|
|
1122
|
-
"""
|
|
1123
|
-
Read the mode and prepare a collapsable block to process a specific cell population.
|
|
1124
|
-
|
|
1125
|
-
"""
|
|
1126
|
-
|
|
1127
|
-
panel_title = QLabel(f"INTERACTIONS")
|
|
1128
|
-
panel_title.setStyleSheet(self.block_title)
|
|
1129
|
-
|
|
1130
|
-
self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
|
|
1131
|
-
|
|
1132
|
-
# self.select_all_btn = QPushButton()
|
|
1133
|
-
# self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
1134
|
-
# self.select_all_btn.setIconSize(QSize(20, 20))
|
|
1135
|
-
# self.all_ticked = False
|
|
1136
|
-
# self.select_all_btn.setStyleSheet(self.button_select_all)
|
|
1137
|
-
# self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
|
|
1138
|
-
|
|
1139
|
-
self.collapse_btn = QPushButton()
|
|
1140
|
-
self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
|
|
1141
|
-
self.collapse_btn.setIconSize(QSize(25, 25))
|
|
1142
|
-
self.collapse_btn.setStyleSheet(self.button_select_all)
|
|
1143
|
-
self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
|
|
1144
|
-
|
|
1145
|
-
self.populate_contents()
|
|
1146
|
-
|
|
1147
|
-
self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
|
|
1148
|
-
self.collapse_btn.clicked.connect(lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden()))
|
|
1149
|
-
self.collapse_btn.clicked.connect(self.collapse_advanced)
|
|
1150
|
-
self.ContentsFrame.hide()
|
|
1151
|
-
|
|
1152
|
-
def collapse_advanced(self):
|
|
1153
|
-
|
|
1154
|
-
panels_open = [not p.ContentsFrame.isHidden() for p in self.parent_window.ProcessPopulations]
|
|
1155
|
-
interactions_open = not self.parent_window.NeighPanel.ContentsFrame.isHidden()
|
|
1156
|
-
preprocessing_open = not self.parent_window.PreprocessingPanel.ContentsFrame.isHidden()
|
|
1157
|
-
is_open = np.array(panels_open+[interactions_open, preprocessing_open])
|
|
1158
|
-
|
|
1159
|
-
if self.ContentsFrame.isHidden():
|
|
1160
|
-
self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
|
|
1161
|
-
self.collapse_btn.setIconSize(QSize(20, 20))
|
|
1162
|
-
if len(is_open[is_open])==0:
|
|
1163
|
-
self.parent_window.scroll.setMinimumHeight(int(550))
|
|
1164
|
-
self.parent_window.adjustSize()
|
|
1165
|
-
else:
|
|
1166
|
-
self.collapse_btn.setIcon(icon(MDI6.chevron_up,color="black"))
|
|
1167
|
-
self.collapse_btn.setIconSize(QSize(20, 20))
|
|
1168
|
-
self.parent_window.scroll.setMinimumHeight(min(int(1000), int(0.9*self.parent_window.screen_height)))
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
def populate_contents(self):
|
|
1172
|
-
|
|
1173
|
-
self.ContentsFrame = QFrame()
|
|
1174
|
-
self.grid_contents = QGridLayout(self.ContentsFrame)
|
|
1175
|
-
self.grid_contents.setContentsMargins(0,0,0,0)
|
|
1176
|
-
self.grid_contents.setSpacing(3)
|
|
1177
|
-
|
|
1178
|
-
# Button to compute the neighborhoods
|
|
1179
|
-
neigh_option_hbox = QHBoxLayout()
|
|
1180
|
-
self.neigh_action = QCheckBox('NEIGHBORHOODS')
|
|
1181
|
-
self.neigh_action.setStyleSheet(self.menu_check_style)
|
|
1182
|
-
|
|
1183
|
-
#self.neigh_action.setIcon(icon(MDI6.eyedropper, color="black"))
|
|
1184
|
-
#self.neigh_action.setIconSize(QSize(20, 20))
|
|
1185
|
-
self.neigh_action.setToolTip(
|
|
1186
|
-
"Compute neighborhoods in list below.")
|
|
1187
|
-
|
|
1188
|
-
neigh_option_hbox.addWidget(self.neigh_action,90)
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
self.help_neigh_btn = QPushButton()
|
|
1192
|
-
self.help_neigh_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
|
|
1193
|
-
self.help_neigh_btn.setIconSize(QSize(20, 20))
|
|
1194
|
-
self.help_neigh_btn.clicked.connect(self.help_neighborhood)
|
|
1195
|
-
self.help_neigh_btn.setStyleSheet(self.button_select_all)
|
|
1196
|
-
self.help_neigh_btn.setToolTip("Help.")
|
|
1197
|
-
neigh_option_hbox.addWidget(self.help_neigh_btn,5,alignment=Qt.AlignRight)
|
|
1198
|
-
|
|
1199
|
-
self.grid_contents.addLayout(neigh_option_hbox, 1,0,1,4)
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
neigh_options_layout = QVBoxLayout()
|
|
1203
|
-
|
|
1204
|
-
neigh_options_vbox = QVBoxLayout()
|
|
1205
|
-
|
|
1206
|
-
# DISTANCE NEIGHBORHOOD
|
|
1207
|
-
dist_neigh_hbox = QHBoxLayout()
|
|
1208
|
-
dist_neigh_hbox.setContentsMargins(0,0,0,0)
|
|
1209
|
-
dist_neigh_hbox.setSpacing(0)
|
|
1210
|
-
|
|
1211
|
-
self.dist_neigh_action = QLabel("ISOTROPIC DISTANCE THRESHOLD")
|
|
1212
|
-
self.dist_neigh_action.setStyleSheet(self.action_lbl_style_sheet)
|
|
1213
|
-
#self.dist_neigh_action.setIcon(icon(MDI6.circle_expand, color='black'))
|
|
1214
|
-
self.dist_neigh_action.setToolTip("")
|
|
1215
|
-
self.dist_neigh_action.setToolTip("Define an isotropic neighborhood between the center of mass\nof the cells, within a threshold distance.")
|
|
1216
|
-
#self.segment_action.toggled.connect(self.enable_segmentation_model_list)
|
|
1217
|
-
#self.to_disable.append(self.segment_action)
|
|
1218
|
-
|
|
1219
|
-
self.config_distance_neigh_btn = QPushButton()
|
|
1220
|
-
self.config_distance_neigh_btn.setIcon(icon(MDI6.plus,color="black"))
|
|
1221
|
-
self.config_distance_neigh_btn.setIconSize(QSize(20, 20))
|
|
1222
|
-
self.config_distance_neigh_btn.setToolTip("Configure.")
|
|
1223
|
-
self.config_distance_neigh_btn.setStyleSheet(self.button_select_all)
|
|
1224
|
-
self.config_distance_neigh_btn.clicked.connect(self.open_config_distance_threshold_neighborhood)
|
|
1225
|
-
dist_neigh_hbox.addWidget(self.config_distance_neigh_btn,5)
|
|
1226
|
-
dist_neigh_hbox.addWidget(self.dist_neigh_action, 95)
|
|
1227
|
-
neigh_options_vbox.addLayout(dist_neigh_hbox)
|
|
1228
|
-
|
|
1229
|
-
# CONTACT NEIGHBORHOOD
|
|
1230
|
-
contact_neighborhood_layout = QHBoxLayout()
|
|
1231
|
-
contact_neighborhood_layout.setContentsMargins(0,0,0,0)
|
|
1232
|
-
contact_neighborhood_layout.setSpacing(0)
|
|
1233
|
-
|
|
1234
|
-
self.contact_neigh_action = QLabel("MASK CONTACT")
|
|
1235
|
-
self.contact_neigh_action.setToolTip("Identify touching cell masks, within a threshold edge distance.")
|
|
1236
|
-
self.contact_neigh_action.setStyleSheet(self.action_lbl_style_sheet)
|
|
1237
|
-
#self.contact_neigh_action.setIcon(icon(MDI6.transition_masked, color='black'))
|
|
1238
|
-
self.contact_neigh_action.setToolTip("")
|
|
1239
|
-
|
|
1240
|
-
self.config_contact_neigh_btn = QPushButton()
|
|
1241
|
-
self.config_contact_neigh_btn.setIcon(icon(MDI6.plus,color="black"))
|
|
1242
|
-
self.config_contact_neigh_btn.setIconSize(QSize(20, 20))
|
|
1243
|
-
self.config_contact_neigh_btn.setToolTip("Configure.")
|
|
1244
|
-
self.config_contact_neigh_btn.setStyleSheet(self.button_select_all)
|
|
1245
|
-
self.config_contact_neigh_btn.clicked.connect(self.open_config_contact_neighborhood)
|
|
1246
|
-
contact_neighborhood_layout.addWidget(self.config_contact_neigh_btn,5)
|
|
1247
|
-
contact_neighborhood_layout.addWidget(self.contact_neigh_action, 95)
|
|
1248
|
-
neigh_options_vbox.addLayout(contact_neighborhood_layout)
|
|
1249
|
-
#self.grid_contents.addLayout(neigh_options_vbox, 2,0,1,4)
|
|
1250
|
-
|
|
1251
|
-
#self.grid_contents.addWidget(QHSeperationLine(), 3, 0, 1, 4)
|
|
1252
|
-
|
|
1253
|
-
self.delete_protocol_btn = QPushButton('')
|
|
1254
|
-
self.delete_protocol_btn.setStyleSheet(self.button_select_all)
|
|
1255
|
-
self.delete_protocol_btn.setIcon(icon(MDI6.trash_can, color="black"))
|
|
1256
|
-
self.delete_protocol_btn.setToolTip("Remove a neighborhood computation.")
|
|
1257
|
-
self.delete_protocol_btn.setIconSize(QSize(20, 20))
|
|
1258
|
-
self.delete_protocol_btn.clicked.connect(self.remove_protocol_from_list)
|
|
1259
|
-
|
|
1260
|
-
self.protocol_list_lbl = QLabel('Neighborhoods to compute: ')
|
|
1261
|
-
self.protocol_list = QListWidget()
|
|
1262
|
-
self.protocol_list.setToolTip("Neighborhoods to compute sequentially.")
|
|
1263
|
-
|
|
1264
|
-
list_header_layout = QHBoxLayout()
|
|
1265
|
-
list_header_layout.addWidget(self.protocol_list_lbl)
|
|
1266
|
-
list_header_layout.addWidget(self.delete_protocol_btn, alignment=Qt.AlignRight)
|
|
1267
|
-
#self.grid_contents.addLayout(list_header_layout, 4, 0, 1, 4)
|
|
1268
|
-
#self.grid_contents.addWidget(self.protocol_list, 5, 0, 1, 4)
|
|
1269
|
-
|
|
1270
|
-
neigh_options_layout.addLayout(neigh_options_vbox)
|
|
1271
|
-
neigh_options_layout.addWidget(QHSeperationLine())
|
|
1272
|
-
neigh_options_layout.addLayout(list_header_layout)
|
|
1273
|
-
neigh_options_layout.addWidget(self.protocol_list)
|
|
1274
|
-
|
|
1275
|
-
neigh_options_layout.setContentsMargins(30,5,30,5)
|
|
1276
|
-
neigh_options_layout.setSpacing(1)
|
|
1277
|
-
self.grid_contents.addLayout(neigh_options_layout, 5, 0, 1, 4)
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
rel_layout = QHBoxLayout()
|
|
1281
|
-
self.measure_pairs_action = QCheckBox("MEASURE PAIRS")
|
|
1282
|
-
self.measure_pairs_action.setStyleSheet(self.menu_check_style)
|
|
1283
|
-
|
|
1284
|
-
self.measure_pairs_action.setIcon(icon(MDI6.eyedropper, color="black"))
|
|
1285
|
-
self.measure_pairs_action.setIconSize(QSize(20, 20))
|
|
1286
|
-
self.measure_pairs_action.setToolTip("Measure the relative quantities defined for the cell pairs, for all neighborhoods.")
|
|
1287
|
-
rel_layout.addWidget(self.measure_pairs_action, 90)
|
|
1288
|
-
|
|
1289
|
-
self.classify_pairs_btn = QPushButton()
|
|
1290
|
-
self.classify_pairs_btn.setIcon(icon(MDI6.scatter_plot, color="black"))
|
|
1291
|
-
self.classify_pairs_btn.setIconSize(QSize(20, 20))
|
|
1292
|
-
self.classify_pairs_btn.setToolTip("Classify data.")
|
|
1293
|
-
self.classify_pairs_btn.setStyleSheet(self.button_select_all)
|
|
1294
|
-
self.classify_pairs_btn.clicked.connect(self.open_classifier_ui_pairs)
|
|
1295
|
-
rel_layout.addWidget(self.classify_pairs_btn, 5) #4,2,1,1, alignment=Qt.AlignRight
|
|
1296
|
-
|
|
1297
|
-
self.grid_contents.addLayout(rel_layout, 6, 0, 1, 4)
|
|
1298
|
-
|
|
1299
|
-
signal_layout = QVBoxLayout()
|
|
1300
|
-
signal_hlayout = QHBoxLayout()
|
|
1301
|
-
self.signal_analysis_action = QCheckBox("DETECT PAIR EVENTS")
|
|
1302
|
-
self.signal_analysis_action.setStyleSheet(self.menu_check_style)
|
|
1303
|
-
|
|
1304
|
-
self.signal_analysis_action.setIcon(icon(MDI6.chart_bell_curve_cumulative, color="black"))
|
|
1305
|
-
self.signal_analysis_action.setIconSize(QSize(20, 20))
|
|
1306
|
-
self.signal_analysis_action.setToolTip("Detect cell pair events using a DL model.")
|
|
1307
|
-
self.signal_analysis_action.toggled.connect(self.enable_signal_model_list)
|
|
1308
|
-
signal_hlayout.addWidget(self.signal_analysis_action, 90)
|
|
1309
|
-
|
|
1310
|
-
self.check_signals_btn = QPushButton()
|
|
1311
|
-
self.check_signals_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
|
|
1312
|
-
self.check_signals_btn.setIconSize(QSize(20, 20))
|
|
1313
|
-
self.check_signals_btn.clicked.connect(self.check_signals2)
|
|
1314
|
-
self.check_signals_btn.setToolTip("Annotate dynamic cell pairs.")
|
|
1315
|
-
self.check_signals_btn.setStyleSheet(self.button_select_all)
|
|
1316
|
-
signal_hlayout.addWidget(self.check_signals_btn, 6)
|
|
1317
|
-
|
|
1318
|
-
self.config_signal_annotator_btn = QPushButton()
|
|
1319
|
-
self.config_signal_annotator_btn.setIcon(icon(MDI6.cog_outline, color="black"))
|
|
1320
|
-
self.config_signal_annotator_btn.setIconSize(QSize(20, 20))
|
|
1321
|
-
self.config_signal_annotator_btn.setToolTip("Configure the animation of the annotation tool.")
|
|
1322
|
-
self.config_signal_annotator_btn.setStyleSheet(self.button_select_all)
|
|
1323
|
-
self.config_signal_annotator_btn.clicked.connect(self.open_signal_annotator_configuration_ui)
|
|
1324
|
-
signal_hlayout.addWidget(self.config_signal_annotator_btn, 6)
|
|
1325
|
-
signal_layout.addLayout(signal_hlayout)
|
|
1326
|
-
# self.to_disable.append(self.measure_action_tc)
|
|
1327
|
-
pair_signal_model_vbox = QVBoxLayout()
|
|
1328
|
-
pair_signal_model_vbox.setContentsMargins(25, 0, 25, 0)
|
|
1329
|
-
|
|
1330
|
-
pair_model_zoo_layout = QHBoxLayout()
|
|
1331
|
-
pair_model_zoo_layout.addWidget(QLabel("Model zoo:"), 90)
|
|
1332
|
-
|
|
1333
|
-
self.pair_signal_models_list = QComboBox()
|
|
1334
|
-
self.pair_signal_models_list.setEnabled(False)
|
|
1335
|
-
self.refresh_signal_models()
|
|
1336
|
-
# self.to_disable.append(self.cell_models_list)
|
|
1337
|
-
|
|
1338
|
-
self.pair_train_signal_model_btn = QPushButton("TRAIN")
|
|
1339
|
-
self.pair_train_signal_model_btn.setToolTip("Train a cell pair event detection model.")
|
|
1340
|
-
self.pair_train_signal_model_btn.setIcon(icon(MDI6.redo_variant, color='black'))
|
|
1341
|
-
self.pair_train_signal_model_btn.setIconSize(QSize(20, 20))
|
|
1342
|
-
self.pair_train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
1343
|
-
pair_model_zoo_layout.addWidget(self.pair_train_signal_model_btn, 5)
|
|
1344
|
-
self.pair_train_signal_model_btn.clicked.connect(self.open_signal_model_config_ui)
|
|
1345
|
-
|
|
1346
|
-
pair_signal_model_vbox.addLayout(pair_model_zoo_layout)
|
|
1347
|
-
pair_signal_model_vbox.addWidget(self.pair_signal_models_list)
|
|
1348
|
-
|
|
1349
|
-
signal_layout.addLayout(pair_signal_model_vbox)
|
|
1350
|
-
self.grid_contents.addLayout(signal_layout, 7, 0, 1, 4)
|
|
1351
|
-
self.grid_contents.addWidget(QHSeperationLine(), 11, 0, 1, 4)
|
|
1352
|
-
|
|
1353
|
-
self.view_tab_btn = QPushButton("Explore table")
|
|
1354
|
-
self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
|
|
1355
|
-
self.view_tab_btn.clicked.connect(self.view_table_ui)
|
|
1356
|
-
self.view_tab_btn.setToolTip('Explore table')
|
|
1357
|
-
self.view_tab_btn.setIcon(icon(MDI6.table,color="#1565c0"))
|
|
1358
|
-
self.view_tab_btn.setIconSize(QSize(20, 20))
|
|
1359
|
-
#self.view_tab_btn.setEnabled(False)
|
|
1360
|
-
self.grid_contents.addWidget(self.view_tab_btn, 12, 0, 1, 4)
|
|
1361
|
-
|
|
1362
|
-
#self.grid_contents.addWidget(QLabel(''), 12, 0, 1, 4)
|
|
1363
|
-
|
|
1364
|
-
self.submit_btn = QPushButton("Submit")
|
|
1365
|
-
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
1366
|
-
self.submit_btn.setToolTip("Compute the neighborhoods of the selected positions.")
|
|
1367
|
-
self.submit_btn.clicked.connect(self.process_neighborhood)
|
|
1368
|
-
self.grid_contents.addWidget(self.submit_btn, 14, 0, 1, 4)
|
|
1369
|
-
|
|
1370
|
-
self.neigh_action.toggled.connect(self.activate_neigh_options)
|
|
1371
|
-
self.neigh_action.setChecked(True)
|
|
1372
|
-
self.neigh_action.setChecked(False)
|
|
1373
|
-
|
|
1374
|
-
def open_classifier_ui_pairs(self):
|
|
1375
|
-
|
|
1376
|
-
self.mode = "pairs"
|
|
1377
|
-
self.load_available_tables()
|
|
1378
|
-
if self.df is None:
|
|
1379
|
-
|
|
1380
|
-
msgBox = QMessageBox()
|
|
1381
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
1382
|
-
msgBox.setText("No table was found...")
|
|
1383
|
-
msgBox.setWindowTitle("Warning")
|
|
1384
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1385
|
-
returnValue = msgBox.exec()
|
|
1386
|
-
if returnValue == QMessageBox.Ok:
|
|
1387
|
-
return None
|
|
1388
|
-
else:
|
|
1389
|
-
return None
|
|
1390
|
-
else:
|
|
1391
|
-
self.ClassifierWidget = ClassifierWidget(self)
|
|
1392
|
-
self.ClassifierWidget.show()
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
def help_neighborhood(self):
|
|
1396
|
-
|
|
1397
|
-
"""
|
|
1398
|
-
Helper for neighborhood strategy.
|
|
1399
|
-
"""
|
|
1400
|
-
|
|
1401
|
-
dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','neighborhood.json'])
|
|
1402
|
-
|
|
1403
|
-
with open(dict_path) as f:
|
|
1404
|
-
d = json.load(f)
|
|
1405
|
-
|
|
1406
|
-
suggestion = help_generic(d)
|
|
1407
|
-
if isinstance(suggestion, str):
|
|
1408
|
-
print(f"{suggestion=}")
|
|
1409
|
-
msgBox = QMessageBox()
|
|
1410
|
-
msgBox.setIcon(QMessageBox.Information)
|
|
1411
|
-
msgBox.setTextFormat(Qt.RichText)
|
|
1412
|
-
msgBox.setText(f"{suggestion}\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/interactions.html#neighborhood'>here</a>.")
|
|
1413
|
-
msgBox.setWindowTitle("Info")
|
|
1414
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1415
|
-
returnValue = msgBox.exec()
|
|
1416
|
-
if returnValue == QMessageBox.Ok:
|
|
1417
|
-
return None
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
def load_available_tables(self):
|
|
1421
|
-
|
|
1422
|
-
"""
|
|
1423
|
-
Load the tables of the selected wells/positions from the control Panel for the population of interest
|
|
1424
|
-
|
|
1425
|
-
"""
|
|
1426
|
-
|
|
1427
|
-
self.well_option = self.parent_window.well_list.getSelectedIndices()
|
|
1428
|
-
self.position_option = self.parent_window.position_list.getSelectedIndices()
|
|
1429
|
-
|
|
1430
|
-
self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population="pairs", return_pos_info=True)
|
|
1431
|
-
if self.df is None:
|
|
1432
|
-
print('No table could be found...')
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
def view_table_ui(self):
|
|
1436
|
-
|
|
1437
|
-
print('Load table...')
|
|
1438
|
-
self.load_available_tables()
|
|
1439
|
-
|
|
1440
|
-
if self.df is not None:
|
|
1441
|
-
plot_mode = 'static'
|
|
1442
|
-
self.tab_ui = TableUI(self.df, f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}", population='pairs', plot_mode=plot_mode, save_inplace_option=True)
|
|
1443
|
-
self.tab_ui.show()
|
|
1444
|
-
else:
|
|
1445
|
-
print('Table could not be loaded...')
|
|
1446
|
-
msgBox = QMessageBox()
|
|
1447
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
1448
|
-
msgBox.setText("No table could be loaded...")
|
|
1449
|
-
msgBox.setWindowTitle("Info")
|
|
1450
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1451
|
-
returnValue = msgBox.exec()
|
|
1452
|
-
if returnValue == QMessageBox.Ok:
|
|
1453
|
-
return None
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
def activate_neigh_options(self):
|
|
1457
|
-
|
|
1458
|
-
if self.neigh_action.isChecked():
|
|
1459
|
-
self.dist_neigh_action.setEnabled(True)
|
|
1460
|
-
self.contact_neigh_action.setEnabled(True)
|
|
1461
|
-
self.config_distance_neigh_btn.setEnabled(True)
|
|
1462
|
-
self.config_contact_neigh_btn.setEnabled(True)
|
|
1463
|
-
self.protocol_list_lbl.setEnabled(True)
|
|
1464
|
-
self.protocol_list.setEnabled(True)
|
|
1465
|
-
self.delete_protocol_btn.setEnabled(True)
|
|
1466
|
-
else:
|
|
1467
|
-
self.dist_neigh_action.setEnabled(False)
|
|
1468
|
-
self.contact_neigh_action.setEnabled(False)
|
|
1469
|
-
self.config_distance_neigh_btn.setEnabled(False)
|
|
1470
|
-
self.config_contact_neigh_btn.setEnabled(False)
|
|
1471
|
-
self.protocol_list_lbl.setEnabled(False)
|
|
1472
|
-
self.protocol_list.setEnabled(False)
|
|
1473
|
-
self.delete_protocol_btn.setEnabled(False)
|
|
1474
|
-
|
|
1475
|
-
def refresh_signal_models(self):
|
|
1476
|
-
signal_models = get_pair_signal_models_list()
|
|
1477
|
-
self.pair_signal_models_list.clear()
|
|
1478
|
-
self.pair_signal_models_list.addItems(signal_models)
|
|
1479
|
-
|
|
1480
|
-
def open_signal_annotator_configuration_ui(self):
|
|
1481
|
-
self.mode = 'pairs'
|
|
1482
|
-
self.config_signal_annotator = SettingsSignalAnnotator(self)
|
|
1483
|
-
self.config_signal_annotator.show()
|
|
1484
|
-
|
|
1485
|
-
def open_signal_model_config_ui(self):
|
|
1486
|
-
self.settings_pair_event_detection_training = SettingsEventDetectionModelTraining(self, signal_mode='pairs')
|
|
1487
|
-
self.settings_pair_event_detection_training.show()
|
|
1488
|
-
|
|
1489
|
-
def remove_protocol_from_list(self):
|
|
1490
|
-
|
|
1491
|
-
current_item = self.protocol_list.currentRow()
|
|
1492
|
-
if current_item > -1:
|
|
1493
|
-
del self.protocols[current_item]
|
|
1494
|
-
self.protocol_list.takeItem(current_item)
|
|
1495
|
-
|
|
1496
|
-
def open_config_distance_threshold_neighborhood(self):
|
|
1497
|
-
|
|
1498
|
-
self.ConfigNeigh = SettingsNeighborhood(parent_window=self,
|
|
1499
|
-
neighborhood_type='distance_threshold',
|
|
1500
|
-
neighborhood_parameter_name='threshold distance',
|
|
1501
|
-
)
|
|
1502
|
-
self.ConfigNeigh.show()
|
|
1503
|
-
|
|
1504
|
-
def open_config_contact_neighborhood(self):
|
|
1505
|
-
|
|
1506
|
-
self.ConfigNeigh = SettingsNeighborhood(parent_window=self,
|
|
1507
|
-
neighborhood_type='mask_contact',
|
|
1508
|
-
neighborhood_parameter_name='tolerance contact distance',
|
|
1509
|
-
)
|
|
1510
|
-
self.ConfigNeigh.show()
|
|
1511
|
-
|
|
1512
|
-
def enable_signal_model_list(self):
|
|
1513
|
-
if self.signal_analysis_action.isChecked():
|
|
1514
|
-
self.pair_signal_models_list.setEnabled(True)
|
|
1515
|
-
else:
|
|
1516
|
-
self.pair_signal_models_list.setEnabled(False)
|
|
1517
|
-
|
|
1518
|
-
def process_neighborhood(self):
|
|
1519
|
-
|
|
1520
|
-
# if self.parent_window.well_list.currentText().startswith('Multiple'):
|
|
1521
|
-
# self.well_index = np.linspace(0,len(self.wells)-1,len(self.wells),dtype=int)
|
|
1522
|
-
# else:
|
|
1523
|
-
self.well_index = self.parent_window.well_list.getSelectedIndices()
|
|
1524
|
-
print(f"Processing well {self.parent_window.well_list.currentText()}...")
|
|
1525
|
-
|
|
1526
|
-
# self.freeze()
|
|
1527
|
-
# QApplication.setOverrideCursor(Qt.WaitCursor)
|
|
1528
|
-
|
|
1529
|
-
loop_iter=0
|
|
1530
|
-
|
|
1531
|
-
if self.parent_window.position_list.isMultipleSelection():
|
|
1532
|
-
msgBox = QMessageBox()
|
|
1533
|
-
msgBox.setIcon(QMessageBox.Question)
|
|
1534
|
-
msgBox.setText("If you continue, all positions will be processed.\nDo you want to proceed?")
|
|
1535
|
-
msgBox.setWindowTitle("Info")
|
|
1536
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
1537
|
-
returnValue = msgBox.exec()
|
|
1538
|
-
if returnValue == QMessageBox.No:
|
|
1539
|
-
return None
|
|
1540
|
-
|
|
1541
|
-
for w_idx in self.well_index:
|
|
1542
|
-
|
|
1543
|
-
pos = self.parent_window.positions[w_idx]
|
|
1544
|
-
pos_indices = self.parent_window.position_list.getSelectedIndices()
|
|
1545
|
-
|
|
1546
|
-
well = self.parent_window.wells[w_idx]
|
|
1547
|
-
|
|
1548
|
-
for pos_idx in pos_indices:
|
|
1549
|
-
|
|
1550
|
-
self.pos = natsorted(glob(well+f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*{os.sep}"))[pos_idx]
|
|
1551
|
-
self.pos_name = extract_position_name(self.pos)
|
|
1552
|
-
print(f"Position {self.pos}...\nLoading stack movie...")
|
|
1553
|
-
|
|
1554
|
-
if not os.path.exists(self.pos + 'output' + os.sep):
|
|
1555
|
-
os.mkdir(self.pos + 'output' + os.sep)
|
|
1556
|
-
if not os.path.exists(self.pos + os.sep.join(['output','tables'])+os.sep):
|
|
1557
|
-
os.mkdir(self.pos + os.sep.join(['output','tables'])+os.sep)
|
|
1558
|
-
|
|
1559
|
-
if self.neigh_action.isChecked():
|
|
1560
|
-
for protocol in self.protocols:
|
|
1561
|
-
|
|
1562
|
-
process_args = {"pos": self.pos, "pos_name": self.pos_name,"protocol": protocol,"img_shape": (self.parent_window.shape_x,self.parent_window.shape_y)} #"n_threads": self.n_threads
|
|
1563
|
-
self.job = ProgressWindow(NeighborhoodProcess, parent_window=self, title="Neighborhood",
|
|
1564
|
-
process_args=process_args)
|
|
1565
|
-
result = self.job.exec_()
|
|
1566
|
-
if result == QDialog.Accepted:
|
|
1567
|
-
pass
|
|
1568
|
-
elif result == QDialog.Rejected:
|
|
1569
|
-
return None
|
|
1570
|
-
|
|
1571
|
-
if self.measure_pairs_action.isChecked():
|
|
1572
|
-
rel_measure_at_position(self.pos)
|
|
1573
|
-
|
|
1574
|
-
if self.signal_analysis_action.isChecked():
|
|
1575
|
-
|
|
1576
|
-
analyze_pair_signals_at_position(self.pos, self.pair_signal_models_list.currentText(), use_gpu=self.parent_window.parent_window.use_gpu, populations=self.parent_window.populations)
|
|
1577
|
-
|
|
1578
|
-
self.parent_window.update_position_options()
|
|
1579
|
-
for action in [self.neigh_action, self.measure_pairs_action, self.signal_analysis_action]:
|
|
1580
|
-
if action.isChecked():
|
|
1581
|
-
action.setChecked(False)
|
|
1582
|
-
|
|
1583
|
-
print('Done.')
|
|
1584
|
-
|
|
1585
|
-
def check_signals2(self):
|
|
1586
|
-
|
|
1587
|
-
test = self.parent_window.locate_selected_position()
|
|
1588
|
-
if test:
|
|
1589
|
-
self.pair_event_annotator = PairEventAnnotator(self)
|
|
1590
|
-
self.pair_event_annotator.show()
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
class PreprocessingPanel(QFrame, Styles):
|
|
1594
|
-
|
|
1595
|
-
def __init__(self, parent_window):
|
|
1596
|
-
|
|
1597
|
-
super().__init__()
|
|
1598
|
-
self.parent_window = parent_window
|
|
1599
|
-
self.exp_channels = self.parent_window.exp_channels
|
|
1600
|
-
self.exp_dir = self.parent_window.exp_dir
|
|
1601
|
-
self.wells = np.array(self.parent_window.wells,dtype=str)
|
|
1602
|
-
exp_config = self.exp_dir + "config.ini"
|
|
1603
|
-
self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
|
|
1604
|
-
self.channel_names = np.array(self.channel_names)
|
|
1605
|
-
self.background_correction = []
|
|
1606
|
-
self.onlyFloat = QDoubleValidator()
|
|
1607
|
-
self.onlyInt = QIntValidator()
|
|
1608
|
-
|
|
1609
|
-
self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
|
|
1610
|
-
self.grid = QGridLayout(self)
|
|
89
|
+
from celldetective.gui.base.styles import Styles
|
|
90
|
+
from celldetective import get_software_location
|
|
91
|
+
import pandas as pd
|
|
1611
92
|
|
|
1612
|
-
|
|
93
|
+
import logging
|
|
1613
94
|
|
|
1614
|
-
|
|
95
|
+
logger = logging.getLogger("celldetective")
|
|
1615
96
|
|
|
1616
|
-
"""
|
|
1617
|
-
Read the mode and prepare a collapsable block to process a specific cell population.
|
|
1618
97
|
|
|
1619
|
-
|
|
98
|
+
class ProcessPanel(QFrame, Styles):
|
|
1620
99
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
if self.ContentsFrame.isHidden():
|
|
1659
|
-
self.collapse_btn.setIcon(icon(MDI6.chevron_down,color="black"))
|
|
1660
|
-
self.collapse_btn.setIconSize(QSize(20, 20))
|
|
1661
|
-
if len(is_open[is_open])==0:
|
|
1662
|
-
self.parent_window.scroll.setMinimumHeight(int(550))
|
|
1663
|
-
self.parent_window.adjustSize()
|
|
1664
|
-
else:
|
|
1665
|
-
self.collapse_btn.setIcon(icon(MDI6.chevron_up,color="black"))
|
|
1666
|
-
self.collapse_btn.setIconSize(QSize(20, 20))
|
|
1667
|
-
self.parent_window.scroll.setMinimumHeight(min(int(930), int(0.9*self.parent_window.screen_height)))
|
|
1668
|
-
|
|
1669
|
-
def populate_contents(self):
|
|
1670
|
-
|
|
1671
|
-
self.ContentsFrame = QFrame()
|
|
1672
|
-
self.grid_contents = QGridLayout(self.ContentsFrame)
|
|
1673
|
-
|
|
1674
|
-
self.model_free_correction_layout = BackgroundModelFreeCorrectionLayout(self)
|
|
1675
|
-
self.fit_correction_layout = BackgroundFitCorrectionLayout(self)
|
|
1676
|
-
|
|
1677
|
-
self.protocol_layout = ProtocolDesignerLayout(parent_window=self,
|
|
1678
|
-
tab_layouts=[self.fit_correction_layout, self.model_free_correction_layout],
|
|
1679
|
-
tab_names=['Fit', 'Model-free'],
|
|
1680
|
-
title='BACKGROUND CORRECTION',
|
|
1681
|
-
list_title='Corrections to apply:')
|
|
1682
|
-
|
|
1683
|
-
self.help_background_btn = QPushButton()
|
|
1684
|
-
self.help_background_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
|
|
1685
|
-
self.help_background_btn.setIconSize(QSize(20, 20))
|
|
1686
|
-
self.help_background_btn.clicked.connect(self.help_background)
|
|
1687
|
-
self.help_background_btn.setStyleSheet(self.button_select_all)
|
|
1688
|
-
self.help_background_btn.setToolTip("Help.")
|
|
1689
|
-
|
|
1690
|
-
self.protocol_layout.title_layout.addWidget(self.help_background_btn, 5, alignment=Qt.AlignRight)
|
|
1691
|
-
|
|
1692
|
-
self.channel_offset_correction_layout = QVBoxLayout()
|
|
1693
|
-
|
|
1694
|
-
self.channel_shift_lbl = QLabel("CHANNEL OFFSET CORRECTION")
|
|
1695
|
-
self.channel_shift_lbl.setStyleSheet("""
|
|
100
|
+
def __init__(self, parent_window, mode):
|
|
101
|
+
|
|
102
|
+
super().__init__()
|
|
103
|
+
self.parent_window = parent_window
|
|
104
|
+
self.mode = mode
|
|
105
|
+
self.exp_channels = self.parent_window.exp_channels
|
|
106
|
+
self.exp_dir = self.parent_window.exp_dir
|
|
107
|
+
self.exp_config = self.parent_window.exp_config
|
|
108
|
+
self.movie_prefix = self.parent_window.movie_prefix
|
|
109
|
+
self.threshold_configs = [
|
|
110
|
+
None for _ in range(len(self.parent_window.populations))
|
|
111
|
+
]
|
|
112
|
+
self.wells = np.array(self.parent_window.wells, dtype=str)
|
|
113
|
+
self.cellpose_calibrated = False
|
|
114
|
+
self.stardist_calibrated = False
|
|
115
|
+
self.segChannelsSet = False
|
|
116
|
+
self.signalChannelsSet = False
|
|
117
|
+
self.flipSeg = False
|
|
118
|
+
|
|
119
|
+
self.use_gpu = self.parent_window.parent_window.use_gpu
|
|
120
|
+
self.n_threads = self.parent_window.parent_window.n_threads
|
|
121
|
+
|
|
122
|
+
self.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
|
|
123
|
+
self.grid = QGridLayout(self)
|
|
124
|
+
self.grid.setContentsMargins(5, 5, 5, 5)
|
|
125
|
+
self.generate_header()
|
|
126
|
+
|
|
127
|
+
def generate_header(self):
|
|
128
|
+
"""
|
|
129
|
+
Read the mode and prepare a collapsable block to process a specific cell population.
|
|
130
|
+
|
|
131
|
+
"""
|
|
132
|
+
|
|
133
|
+
panel_title = QLabel(f"PROCESS {self.mode.upper()} ")
|
|
134
|
+
panel_title.setStyleSheet(
|
|
135
|
+
"""
|
|
1696
136
|
font-weight: bold;
|
|
1697
137
|
padding: 0px;
|
|
1698
|
-
"""
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
138
|
+
"""
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
title_hbox = QHBoxLayout()
|
|
142
|
+
self.grid.addWidget(panel_title, 0, 0, 1, 4, alignment=Qt.AlignCenter)
|
|
143
|
+
|
|
144
|
+
# self.help_pop_btn = QPushButton()
|
|
145
|
+
# self.help_pop_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
|
|
146
|
+
# self.help_pop_btn.setIconSize(QSize(20, 20))
|
|
147
|
+
# self.help_pop_btn.clicked.connect(self.help_population)
|
|
148
|
+
# self.help_pop_btn.setStyleSheet(self.button_select_all)
|
|
149
|
+
# self.help_pop_btn.setToolTip("Help.")
|
|
150
|
+
# self.grid.addWidget(self.help_pop_btn, 0, 0, 1, 3, alignment=Qt.AlignRight)
|
|
151
|
+
|
|
152
|
+
# self.select_all_btn = QPushButton()
|
|
153
|
+
# self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
154
|
+
# self.select_all_btn.setIconSize(QSize(20, 20))
|
|
155
|
+
# self.all_ticked = False
|
|
156
|
+
# self.select_all_btn.clicked.connect(self.tick_all_actions)
|
|
157
|
+
# self.select_all_btn.setStyleSheet(self.button_select_all)
|
|
158
|
+
# self.grid.addWidget(self.select_all_btn, 0, 0, 1, 4, alignment=Qt.AlignLeft)
|
|
159
|
+
# self.to_disable.append(self.all_tc_actions)
|
|
160
|
+
|
|
161
|
+
self.collapse_btn = QPushButton()
|
|
162
|
+
self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
|
|
163
|
+
self.collapse_btn.setIconSize(QSize(25, 25))
|
|
164
|
+
self.collapse_btn.setStyleSheet(self.button_select_all)
|
|
165
|
+
# self.grid.addWidget(self.collapse_btn, 0, 0, 1, 4, alignment=Qt.AlignRight)
|
|
166
|
+
|
|
167
|
+
title_hbox.addWidget(QLabel(), 5) # self.select_all_btn
|
|
168
|
+
title_hbox.addWidget(QLabel(), 85, alignment=Qt.AlignCenter)
|
|
169
|
+
# title_hbox.addWidget(self.help_pop_btn, 5)
|
|
170
|
+
title_hbox.addWidget(self.collapse_btn, 5)
|
|
171
|
+
|
|
172
|
+
self.grid.addLayout(title_hbox, 0, 0, 1, 4)
|
|
173
|
+
self.populate_contents()
|
|
174
|
+
|
|
175
|
+
self.grid.addWidget(self.ContentsFrame, 1, 0, 1, 4, alignment=Qt.AlignTop)
|
|
176
|
+
self.collapse_btn.clicked.connect(
|
|
177
|
+
lambda: self.ContentsFrame.setHidden(not self.ContentsFrame.isHidden())
|
|
178
|
+
)
|
|
179
|
+
self.collapse_btn.clicked.connect(self.collapse_advanced)
|
|
180
|
+
self.ContentsFrame.hide()
|
|
181
|
+
|
|
182
|
+
def collapse_advanced(self):
|
|
183
|
+
|
|
184
|
+
panels_open = [
|
|
185
|
+
not p.ContentsFrame.isHidden()
|
|
186
|
+
for p in self.parent_window.ProcessPopulations
|
|
187
|
+
]
|
|
188
|
+
interactions_open = not self.parent_window.NeighPanel.ContentsFrame.isHidden()
|
|
189
|
+
preprocessing_open = (
|
|
190
|
+
not self.parent_window.PreprocessingPanel.ContentsFrame.isHidden()
|
|
191
|
+
)
|
|
192
|
+
is_open = np.array(panels_open + [interactions_open, preprocessing_open])
|
|
193
|
+
|
|
194
|
+
if self.ContentsFrame.isHidden():
|
|
195
|
+
self.collapse_btn.setIcon(icon(MDI6.chevron_down, color="black"))
|
|
196
|
+
self.collapse_btn.setIconSize(QSize(20, 20))
|
|
197
|
+
if len(is_open[is_open]) == 0:
|
|
198
|
+
self.parent_window.scroll.setMinimumHeight(int(550))
|
|
199
|
+
self.parent_window.adjustSize()
|
|
200
|
+
else:
|
|
201
|
+
self.collapse_btn.setIcon(icon(MDI6.chevron_up, color="black"))
|
|
202
|
+
self.collapse_btn.setIconSize(QSize(20, 20))
|
|
203
|
+
self.parent_window.scroll.setMinimumHeight(
|
|
204
|
+
min(int(930), int(0.9 * self.parent_window.screen_height))
|
|
205
|
+
)
|
|
206
|
+
try:
|
|
207
|
+
QTimer.singleShot(10, lambda: center_window(self.window()))
|
|
208
|
+
except:
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
def populate_contents(self):
|
|
212
|
+
self.ContentsFrame = QFrame()
|
|
213
|
+
self.ContentsFrame.setContentsMargins(5, 5, 5, 5)
|
|
214
|
+
self.grid_contents = QGridLayout(self.ContentsFrame)
|
|
215
|
+
self.grid_contents.setContentsMargins(0, 0, 0, 0)
|
|
216
|
+
self.generate_segmentation_options()
|
|
217
|
+
self.generate_tracking_options()
|
|
218
|
+
self.generate_measure_options()
|
|
219
|
+
self.generate_signal_analysis_options()
|
|
220
|
+
|
|
221
|
+
self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
|
|
222
|
+
self.view_tab_btn = QPushButton("Explore table")
|
|
223
|
+
self.view_tab_btn.setStyleSheet(self.button_style_sheet_2)
|
|
224
|
+
self.view_tab_btn.clicked.connect(self.view_table_ui)
|
|
225
|
+
self.view_tab_btn.setToolTip("Explore table")
|
|
226
|
+
self.view_tab_btn.setIcon(icon(MDI6.table, color="#1565c0"))
|
|
227
|
+
self.view_tab_btn.setIconSize(QSize(20, 20))
|
|
228
|
+
# self.view_tab_btn.setEnabled(False)
|
|
229
|
+
self.grid_contents.addWidget(self.view_tab_btn, 10, 0, 1, 4)
|
|
230
|
+
|
|
231
|
+
self.grid_contents.addWidget(QHSeperationLine(), 9, 0, 1, 4)
|
|
232
|
+
self.submit_btn = QPushButton("Submit")
|
|
233
|
+
self.submit_btn.setStyleSheet(self.button_style_sheet)
|
|
234
|
+
self.submit_btn.clicked.connect(self.process_population)
|
|
235
|
+
self.grid_contents.addWidget(self.submit_btn, 11, 0, 1, 4)
|
|
236
|
+
|
|
237
|
+
for action in [
|
|
238
|
+
self.segment_action,
|
|
239
|
+
self.track_action,
|
|
240
|
+
self.measure_action,
|
|
241
|
+
self.signal_analysis_action,
|
|
242
|
+
]:
|
|
243
|
+
action.toggled.connect(self.check_readiness)
|
|
244
|
+
self.check_readiness()
|
|
245
|
+
|
|
246
|
+
def check_readiness(self):
|
|
247
|
+
if (
|
|
248
|
+
self.segment_action.isChecked()
|
|
249
|
+
or self.track_action.isChecked()
|
|
250
|
+
or self.measure_action.isChecked()
|
|
251
|
+
or self.signal_analysis_action.isChecked()
|
|
252
|
+
):
|
|
253
|
+
self.submit_btn.setEnabled(True)
|
|
254
|
+
else:
|
|
255
|
+
self.submit_btn.setEnabled(False)
|
|
256
|
+
|
|
257
|
+
def generate_measure_options(self):
|
|
258
|
+
|
|
259
|
+
measure_layout = QHBoxLayout()
|
|
260
|
+
|
|
261
|
+
self.measure_action = QCheckBox("MEASURE")
|
|
262
|
+
self.measure_action.setStyleSheet(self.menu_check_style)
|
|
263
|
+
|
|
264
|
+
self.measure_action.setIcon(icon(MDI6.eyedropper, color="black"))
|
|
265
|
+
self.measure_action.setIconSize(QSize(20, 20))
|
|
266
|
+
self.measure_action.setToolTip("Measure.")
|
|
267
|
+
measure_layout.addWidget(self.measure_action, 90)
|
|
268
|
+
# self.to_disable.append(self.measure_action_tc)
|
|
269
|
+
|
|
270
|
+
self.classify_btn = QPushButton()
|
|
271
|
+
self.classify_btn.setIcon(icon(MDI6.scatter_plot, color="black"))
|
|
272
|
+
self.classify_btn.setIconSize(QSize(20, 20))
|
|
273
|
+
self.classify_btn.setToolTip("Classify data.")
|
|
274
|
+
self.classify_btn.setStyleSheet(self.button_select_all)
|
|
275
|
+
self.classify_btn.clicked.connect(self.open_classifier_ui)
|
|
276
|
+
measure_layout.addWidget(
|
|
277
|
+
self.classify_btn, 5
|
|
278
|
+
) # 4,2,1,1, alignment=Qt.AlignRight
|
|
279
|
+
|
|
280
|
+
self.check_measurements_btn = QPushButton()
|
|
281
|
+
self.check_measurements_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
|
|
282
|
+
self.check_measurements_btn.setIconSize(QSize(20, 20))
|
|
283
|
+
self.check_measurements_btn.setToolTip("Explore measurements in-situ.")
|
|
284
|
+
self.check_measurements_btn.setStyleSheet(self.button_select_all)
|
|
285
|
+
self.check_measurements_btn.clicked.connect(self.check_measurements)
|
|
286
|
+
measure_layout.addWidget(self.check_measurements_btn, 5)
|
|
287
|
+
|
|
288
|
+
self.measurements_config_btn = QPushButton()
|
|
289
|
+
self.measurements_config_btn.setIcon(icon(MDI6.cog_outline, color="black"))
|
|
290
|
+
self.measurements_config_btn.setIconSize(QSize(20, 20))
|
|
291
|
+
self.measurements_config_btn.setToolTip("Configure measurements.")
|
|
292
|
+
self.measurements_config_btn.setStyleSheet(self.button_select_all)
|
|
293
|
+
self.measurements_config_btn.clicked.connect(
|
|
294
|
+
self.open_measurement_configuration_ui
|
|
295
|
+
)
|
|
296
|
+
measure_layout.addWidget(
|
|
297
|
+
self.measurements_config_btn, 5
|
|
298
|
+
) # 4,2,1,1, alignment=Qt.AlignRight
|
|
299
|
+
|
|
300
|
+
self.grid_contents.addLayout(measure_layout, 5, 0, 1, 4)
|
|
301
|
+
|
|
302
|
+
def generate_signal_analysis_options(self):
|
|
303
|
+
|
|
304
|
+
signal_layout = QVBoxLayout()
|
|
305
|
+
signal_hlayout = QHBoxLayout()
|
|
306
|
+
self.signal_analysis_action = QCheckBox("DETECT EVENTS")
|
|
307
|
+
self.signal_analysis_action.setStyleSheet(self.menu_check_style)
|
|
308
|
+
self.signal_analysis_action.setIcon(
|
|
309
|
+
icon(MDI6.chart_bell_curve_cumulative, color="black")
|
|
310
|
+
)
|
|
311
|
+
self.signal_analysis_action.setIconSize(QSize(20, 20))
|
|
312
|
+
self.signal_analysis_action.setToolTip("Detect events in single-cell signals.")
|
|
313
|
+
self.signal_analysis_action.toggled.connect(self.enable_signal_model_list)
|
|
314
|
+
signal_hlayout.addWidget(self.signal_analysis_action, 90)
|
|
315
|
+
|
|
316
|
+
self.check_signals_btn = QPushButton()
|
|
317
|
+
self.check_signals_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
|
|
318
|
+
self.check_signals_btn.setIconSize(QSize(20, 20))
|
|
319
|
+
self.check_signals_btn.clicked.connect(self.check_signals)
|
|
320
|
+
self.check_signals_btn.setToolTip("Explore signals in-situ.")
|
|
321
|
+
self.check_signals_btn.setStyleSheet(self.button_select_all)
|
|
322
|
+
signal_hlayout.addWidget(self.check_signals_btn, 6)
|
|
323
|
+
|
|
324
|
+
self.config_signal_annotator_btn = QPushButton()
|
|
325
|
+
self.config_signal_annotator_btn.setIcon(icon(MDI6.cog_outline, color="black"))
|
|
326
|
+
self.config_signal_annotator_btn.setIconSize(QSize(20, 20))
|
|
327
|
+
self.config_signal_annotator_btn.setToolTip("Configure the dynamic visualizer.")
|
|
328
|
+
self.config_signal_annotator_btn.setStyleSheet(self.button_select_all)
|
|
329
|
+
self.config_signal_annotator_btn.clicked.connect(
|
|
330
|
+
self.open_signal_annotator_configuration_ui
|
|
331
|
+
)
|
|
332
|
+
signal_hlayout.addWidget(self.config_signal_annotator_btn, 6)
|
|
333
|
+
|
|
334
|
+
# self.to_disable.append(self.measure_action_tc)
|
|
335
|
+
signal_layout.addLayout(signal_hlayout)
|
|
336
|
+
|
|
337
|
+
signal_model_vbox = QVBoxLayout()
|
|
338
|
+
signal_model_vbox.setContentsMargins(25, 0, 25, 0)
|
|
339
|
+
|
|
340
|
+
model_zoo_layout = QHBoxLayout()
|
|
341
|
+
model_zoo_layout.addWidget(QLabel("Model zoo:"), 90)
|
|
342
|
+
|
|
343
|
+
self.signal_models_list = QComboBox()
|
|
344
|
+
self.signal_models_list.setEnabled(False)
|
|
345
|
+
self.refresh_signal_models()
|
|
346
|
+
# self.to_disable.append(self.cell_models_list)
|
|
347
|
+
|
|
348
|
+
self.train_signal_model_btn = QPushButton("TRAIN")
|
|
349
|
+
self.train_signal_model_btn.setToolTip(
|
|
350
|
+
"Train or retrain an event detection model\non newly annotated data."
|
|
351
|
+
)
|
|
352
|
+
self.train_signal_model_btn.setIcon(icon(MDI6.redo_variant, color="black"))
|
|
353
|
+
self.train_signal_model_btn.setIconSize(QSize(20, 20))
|
|
354
|
+
self.train_signal_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
355
|
+
model_zoo_layout.addWidget(self.train_signal_model_btn, 5)
|
|
356
|
+
self.train_signal_model_btn.clicked.connect(self.open_signal_model_config_ui)
|
|
357
|
+
|
|
358
|
+
signal_model_vbox.addLayout(model_zoo_layout)
|
|
359
|
+
signal_model_vbox.addWidget(self.signal_models_list)
|
|
360
|
+
|
|
361
|
+
signal_layout.addLayout(signal_model_vbox)
|
|
362
|
+
|
|
363
|
+
self.grid_contents.addLayout(signal_layout, 6, 0, 1, 4)
|
|
364
|
+
|
|
365
|
+
def refresh_signal_models(self):
|
|
366
|
+
self.signal_models = get_signal_models_list()
|
|
367
|
+
self.signal_models_list.clear()
|
|
368
|
+
|
|
369
|
+
thresh = 35
|
|
370
|
+
models_truncated = [
|
|
371
|
+
m[: thresh - 3] + "..." if len(m) > thresh else m
|
|
372
|
+
for m in self.signal_models
|
|
373
|
+
]
|
|
374
|
+
|
|
375
|
+
self.signal_models_list.addItems(models_truncated)
|
|
376
|
+
for i in range(len(self.signal_models)):
|
|
377
|
+
self.signal_models_list.setItemData(
|
|
378
|
+
i, self.signal_models[i], Qt.ToolTipRole
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
def generate_tracking_options(self):
|
|
382
|
+
|
|
383
|
+
grid_track = QHBoxLayout()
|
|
384
|
+
|
|
385
|
+
self.track_action = QCheckBox("TRACK")
|
|
386
|
+
self.track_action.setStyleSheet(self.menu_check_style)
|
|
387
|
+
self.track_action.setIcon(icon(MDI6.chart_timeline_variant, color="black"))
|
|
388
|
+
self.track_action.setIconSize(QSize(20, 20))
|
|
389
|
+
self.track_action.setToolTip(f"Track the {self.mode[:-1]} cells.")
|
|
390
|
+
grid_track.addWidget(self.track_action, 75)
|
|
391
|
+
|
|
392
|
+
self.delete_tracks_btn = QPushButton()
|
|
393
|
+
self.delete_tracks_btn.setIcon(icon(MDI6.trash_can, color="black"))
|
|
394
|
+
self.delete_tracks_btn.setIconSize(QSize(20, 20))
|
|
395
|
+
self.delete_tracks_btn.setToolTip("Delete existing tracks.")
|
|
396
|
+
self.delete_tracks_btn.setStyleSheet(self.button_select_all)
|
|
397
|
+
self.delete_tracks_btn.clicked.connect(self.delete_tracks)
|
|
398
|
+
self.delete_tracks_btn.setEnabled(True)
|
|
399
|
+
self.delete_tracks_btn.hide()
|
|
400
|
+
grid_track.addWidget(
|
|
401
|
+
self.delete_tracks_btn, 6
|
|
402
|
+
) # 4,3,1,1, alignment=Qt.AlignLeft
|
|
403
|
+
|
|
404
|
+
self.check_tracking_result_btn = QPushButton()
|
|
405
|
+
self.check_tracking_result_btn.setIcon(
|
|
406
|
+
icon(MDI6.eye_check_outline, color="black")
|
|
407
|
+
)
|
|
408
|
+
self.check_tracking_result_btn.setIconSize(QSize(20, 20))
|
|
409
|
+
self.check_tracking_result_btn.setToolTip("View tracking output in napari.")
|
|
410
|
+
self.check_tracking_result_btn.setStyleSheet(self.button_select_all)
|
|
411
|
+
self.check_tracking_result_btn.clicked.connect(self.open_napari_tracking)
|
|
412
|
+
self.check_tracking_result_btn.setEnabled(False)
|
|
413
|
+
grid_track.addWidget(
|
|
414
|
+
self.check_tracking_result_btn, 6
|
|
415
|
+
) # 4,3,1,1, alignment=Qt.AlignLeft
|
|
416
|
+
|
|
417
|
+
self.track_config_btn = QPushButton()
|
|
418
|
+
self.track_config_btn.setIcon(icon(MDI6.cog_outline, color="black"))
|
|
419
|
+
self.track_config_btn.setIconSize(QSize(20, 20))
|
|
420
|
+
self.track_config_btn.setToolTip("Configure tracking.")
|
|
421
|
+
self.track_config_btn.setStyleSheet(self.button_select_all)
|
|
422
|
+
self.track_config_btn.clicked.connect(self.open_tracking_configuration_ui)
|
|
423
|
+
grid_track.addWidget(
|
|
424
|
+
self.track_config_btn, 6
|
|
425
|
+
) # 4,2,1,1, alignment=Qt.AlignRight
|
|
426
|
+
|
|
427
|
+
self.help_track_btn = QPushButton()
|
|
428
|
+
self.help_track_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
|
|
429
|
+
self.help_track_btn.setIconSize(QSize(20, 20))
|
|
430
|
+
self.help_track_btn.clicked.connect(self.help_tracking)
|
|
431
|
+
self.help_track_btn.setStyleSheet(self.button_select_all)
|
|
432
|
+
self.help_track_btn.setToolTip("Help.")
|
|
433
|
+
grid_track.addWidget(self.help_track_btn, 6) # 4,2,1,1, alignment=Qt.AlignRight
|
|
434
|
+
|
|
435
|
+
self.grid_contents.addLayout(grid_track, 4, 0, 1, 4)
|
|
436
|
+
|
|
437
|
+
def delete_tracks(self):
|
|
438
|
+
|
|
439
|
+
msgBox = QMessageBox()
|
|
440
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
441
|
+
msgBox.setText(
|
|
442
|
+
"Do you want to erase the tracks? All subsequent annotations will be erased..."
|
|
443
|
+
)
|
|
444
|
+
msgBox.setWindowTitle("Info")
|
|
445
|
+
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
446
|
+
returnValue = msgBox.exec()
|
|
447
|
+
if returnValue == QMessageBox.No:
|
|
448
|
+
return None
|
|
449
|
+
elif returnValue == QMessageBox.Yes:
|
|
450
|
+
remove_file_if_exists(
|
|
451
|
+
os.sep.join(
|
|
452
|
+
[
|
|
453
|
+
self.parent_window.pos,
|
|
454
|
+
"output",
|
|
455
|
+
"tables",
|
|
456
|
+
f"trajectories_{self.mode}.csv",
|
|
457
|
+
]
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
remove_file_if_exists(
|
|
461
|
+
os.sep.join(
|
|
462
|
+
[
|
|
463
|
+
self.parent_window.pos,
|
|
464
|
+
"output",
|
|
465
|
+
"tables",
|
|
466
|
+
f"trajectories_{self.mode}.pkl",
|
|
467
|
+
]
|
|
468
|
+
)
|
|
469
|
+
)
|
|
470
|
+
remove_file_if_exists(
|
|
471
|
+
os.sep.join(
|
|
472
|
+
[
|
|
473
|
+
self.parent_window.pos,
|
|
474
|
+
"output",
|
|
475
|
+
"tables",
|
|
476
|
+
f"napari_{self.mode[:-1]}_trajectories.npy",
|
|
477
|
+
]
|
|
478
|
+
)
|
|
479
|
+
)
|
|
480
|
+
remove_file_if_exists(
|
|
481
|
+
os.sep.join(
|
|
482
|
+
[
|
|
483
|
+
self.parent_window.pos,
|
|
484
|
+
"output",
|
|
485
|
+
"tables",
|
|
486
|
+
f"trajectories_pairs.csv",
|
|
487
|
+
]
|
|
488
|
+
)
|
|
489
|
+
)
|
|
490
|
+
try:
|
|
491
|
+
QTimer.singleShot(
|
|
492
|
+
100, lambda: self.parent_window.update_position_options()
|
|
493
|
+
)
|
|
494
|
+
except Exception as _:
|
|
495
|
+
pass
|
|
496
|
+
else:
|
|
497
|
+
return None
|
|
498
|
+
|
|
499
|
+
def generate_segmentation_options(self):
|
|
500
|
+
|
|
501
|
+
grid_segment = QHBoxLayout()
|
|
502
|
+
grid_segment.setContentsMargins(0, 0, 0, 0)
|
|
503
|
+
grid_segment.setSpacing(0)
|
|
504
|
+
|
|
505
|
+
self.segment_action = QCheckBox("SEGMENT")
|
|
506
|
+
self.segment_action.setStyleSheet(self.menu_check_style)
|
|
507
|
+
self.segment_action.setIcon(icon(MDI6.bacteria, color="black"))
|
|
508
|
+
self.segment_action.setToolTip(
|
|
509
|
+
f"Segment the {self.mode[:-1]} cells on the images."
|
|
510
|
+
)
|
|
511
|
+
self.segment_action.toggled.connect(self.enable_segmentation_model_list)
|
|
512
|
+
# self.to_disable.append(self.segment_action)
|
|
513
|
+
grid_segment.addWidget(self.segment_action, 90)
|
|
514
|
+
|
|
515
|
+
# self.flip_segment_btn = QPushButton()
|
|
516
|
+
# self.flip_segment_btn.setIcon(icon(MDI6.camera_flip_outline,color="black"))
|
|
517
|
+
# self.flip_segment_btn.setIconSize(QSize(20, 20))
|
|
518
|
+
# self.flip_segment_btn.clicked.connect(self.flip_segmentation)
|
|
519
|
+
# self.flip_segment_btn.setStyleSheet(self.button_select_all)
|
|
520
|
+
# self.flip_segment_btn.setToolTip("Flip the order of the frames for segmentation.")
|
|
521
|
+
# grid_segment.addWidget(self.flip_segment_btn, 5)
|
|
522
|
+
|
|
523
|
+
self.segmentation_config_btn = QPushButton()
|
|
524
|
+
self.segmentation_config_btn.setIcon(icon(MDI6.cog_outline, color="black"))
|
|
525
|
+
self.segmentation_config_btn.setIconSize(QSize(20, 20))
|
|
526
|
+
self.segmentation_config_btn.setToolTip("Configure segmentation.")
|
|
527
|
+
self.segmentation_config_btn.setStyleSheet(self.button_select_all)
|
|
528
|
+
self.segmentation_config_btn.clicked.connect(
|
|
529
|
+
self.open_segmentation_configuration_ui
|
|
530
|
+
)
|
|
531
|
+
grid_segment.addWidget(self.segmentation_config_btn, 5)
|
|
532
|
+
|
|
533
|
+
self.check_seg_btn = QPushButton()
|
|
534
|
+
self.check_seg_btn.setIcon(icon(MDI6.eye_check_outline, color="black"))
|
|
535
|
+
self.check_seg_btn.setIconSize(QSize(20, 20))
|
|
536
|
+
self.check_seg_btn.clicked.connect(self.check_segmentation)
|
|
537
|
+
self.check_seg_btn.setStyleSheet(self.button_select_all)
|
|
538
|
+
self.check_seg_btn.setToolTip("View segmentation output in napari.")
|
|
539
|
+
grid_segment.addWidget(self.check_seg_btn, 5)
|
|
540
|
+
|
|
541
|
+
self.help_seg_btn = QPushButton()
|
|
542
|
+
self.help_seg_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
|
|
543
|
+
self.help_seg_btn.setIconSize(QSize(20, 20))
|
|
544
|
+
self.help_seg_btn.clicked.connect(self.help_segmentation)
|
|
545
|
+
self.help_seg_btn.setStyleSheet(self.button_select_all)
|
|
546
|
+
self.help_seg_btn.setToolTip("Help.")
|
|
547
|
+
grid_segment.addWidget(self.help_seg_btn, 5)
|
|
548
|
+
self.grid_contents.addLayout(grid_segment, 0, 0, 1, 4)
|
|
549
|
+
|
|
550
|
+
seg_option_vbox = QVBoxLayout()
|
|
551
|
+
seg_option_vbox.setContentsMargins(25, 0, 25, 0)
|
|
552
|
+
model_zoo_layout = QHBoxLayout()
|
|
553
|
+
model_zoo_layout.addWidget(QLabel("Model zoo:"), 90)
|
|
554
|
+
self.seg_model_list = QComboBox()
|
|
555
|
+
self.seg_model_list.currentIndexChanged.connect(self.reset_generalist_setup)
|
|
556
|
+
# self.to_disable.append(self.tc_seg_model_list)
|
|
557
|
+
self.seg_model_list.setGeometry(50, 50, 200, 30)
|
|
558
|
+
self.init_seg_model_list()
|
|
559
|
+
|
|
560
|
+
self.upload_model_btn = QPushButton("UPLOAD")
|
|
561
|
+
self.upload_model_btn.setIcon(icon(MDI6.upload, color="black"))
|
|
562
|
+
self.upload_model_btn.setIconSize(QSize(20, 20))
|
|
563
|
+
self.upload_model_btn.setStyleSheet(self.button_style_sheet_3)
|
|
564
|
+
self.upload_model_btn.setToolTip(
|
|
565
|
+
"Upload a new segmentation model\n(Deep learning or threshold-based)."
|
|
566
|
+
)
|
|
567
|
+
model_zoo_layout.addWidget(self.upload_model_btn, 5)
|
|
568
|
+
self.upload_model_btn.clicked.connect(self.upload_segmentation_model)
|
|
569
|
+
# self.to_disable.append(self.upload_tc_model)
|
|
570
|
+
|
|
571
|
+
self.train_btn = QPushButton("TRAIN")
|
|
572
|
+
self.train_btn.setToolTip(
|
|
573
|
+
"Train or retrain a segmentation model\non newly annotated data."
|
|
574
|
+
)
|
|
575
|
+
self.train_btn.setIcon(icon(MDI6.redo_variant, color="black"))
|
|
576
|
+
self.train_btn.setIconSize(QSize(20, 20))
|
|
577
|
+
self.train_btn.setStyleSheet(self.button_style_sheet_3)
|
|
578
|
+
self.train_btn.clicked.connect(self.open_segmentation_model_config_ui)
|
|
579
|
+
model_zoo_layout.addWidget(self.train_btn, 5)
|
|
580
|
+
# self.train_button_tc.clicked.connect(self.train_stardist_model_tc)
|
|
581
|
+
# self.to_disable.append(self.train_button_tc)
|
|
582
|
+
|
|
583
|
+
seg_option_vbox.addLayout(model_zoo_layout)
|
|
584
|
+
seg_option_vbox.addWidget(self.seg_model_list)
|
|
585
|
+
self.seg_model_list.setEnabled(False)
|
|
586
|
+
self.grid_contents.addLayout(seg_option_vbox, 2, 0, 1, 4)
|
|
587
|
+
|
|
588
|
+
def flip_segmentation(self):
|
|
589
|
+
if not self.flipSeg:
|
|
590
|
+
self.flipSeg = True
|
|
591
|
+
self.flip_segment_btn.setIcon(
|
|
592
|
+
icon(MDI6.camera_flip, color=self.celldetective_blue)
|
|
593
|
+
)
|
|
594
|
+
self.flip_segment_btn.setIconSize(QSize(20, 20))
|
|
595
|
+
self.flip_segment_btn.setToolTip(
|
|
596
|
+
"Unflip the order of the frames for segmentation."
|
|
597
|
+
)
|
|
598
|
+
else:
|
|
599
|
+
self.flipSeg = False
|
|
600
|
+
self.flip_segment_btn.setIcon(icon(MDI6.camera_flip_outline, color="black"))
|
|
601
|
+
self.flip_segment_btn.setIconSize(QSize(20, 20))
|
|
602
|
+
self.flip_segment_btn.setToolTip(
|
|
603
|
+
"Flip the order of the frames for segmentation."
|
|
604
|
+
)
|
|
605
|
+
|
|
606
|
+
def help_segmentation(self):
|
|
607
|
+
"""
|
|
608
|
+
Widget with different decision helper decision trees.
|
|
609
|
+
"""
|
|
610
|
+
|
|
611
|
+
self.help_w = CelldetectiveWidget()
|
|
612
|
+
self.help_w.setWindowTitle("Helper")
|
|
613
|
+
layout = QVBoxLayout()
|
|
614
|
+
seg_strategy_btn = QPushButton("A guide to choose a segmentation strategy.")
|
|
615
|
+
seg_strategy_btn.setIcon(icon(MDI6.help_circle, color=self.celldetective_blue))
|
|
616
|
+
seg_strategy_btn.setIconSize(QSize(40, 40))
|
|
617
|
+
seg_strategy_btn.setStyleSheet(self.button_style_sheet_5)
|
|
618
|
+
seg_strategy_btn.clicked.connect(self.help_seg_strategy)
|
|
619
|
+
|
|
620
|
+
dl_strategy_btn = QPushButton(
|
|
621
|
+
"A guide to choose your Deep learning segmentation strategy."
|
|
622
|
+
)
|
|
623
|
+
dl_strategy_btn.setIcon(icon(MDI6.help_circle, color=self.celldetective_blue))
|
|
624
|
+
dl_strategy_btn.setIconSize(QSize(40, 40))
|
|
625
|
+
dl_strategy_btn.setStyleSheet(self.button_style_sheet_5)
|
|
626
|
+
dl_strategy_btn.clicked.connect(self.help_seg_dl_strategy)
|
|
627
|
+
|
|
628
|
+
layout.addWidget(seg_strategy_btn)
|
|
629
|
+
layout.addWidget(dl_strategy_btn)
|
|
630
|
+
|
|
631
|
+
self.help_w.setLayout(layout)
|
|
632
|
+
center_window(self.help_w)
|
|
633
|
+
self.help_w.show()
|
|
634
|
+
|
|
635
|
+
return None
|
|
636
|
+
|
|
637
|
+
def help_seg_strategy(self):
|
|
638
|
+
"""
|
|
639
|
+
Helper for segmentation strategy between threshold-based and Deep learning.
|
|
640
|
+
"""
|
|
641
|
+
|
|
642
|
+
dict_path = os.sep.join(
|
|
643
|
+
[
|
|
644
|
+
get_software_location(),
|
|
645
|
+
"celldetective",
|
|
646
|
+
"gui",
|
|
647
|
+
"help",
|
|
648
|
+
"Threshold-vs-DL.json",
|
|
649
|
+
]
|
|
650
|
+
)
|
|
651
|
+
|
|
652
|
+
with open(dict_path) as f:
|
|
653
|
+
d = json.load(f)
|
|
654
|
+
|
|
655
|
+
suggestion = help_generic(d)
|
|
656
|
+
if isinstance(suggestion, str):
|
|
657
|
+
logger.info(f"{suggestion=}")
|
|
658
|
+
msgBox = QMessageBox()
|
|
659
|
+
msgBox.setIcon(QMessageBox.Information)
|
|
660
|
+
msgBox.setTextFormat(Qt.RichText)
|
|
661
|
+
msgBox.setText(
|
|
662
|
+
f"The suggested technique is {suggestion}.\nSee a tutorial <a href='https://celldetective.readthedocs.io/en/latest/segment.html'>here</a>."
|
|
663
|
+
)
|
|
664
|
+
msgBox.setWindowTitle("Info")
|
|
665
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
666
|
+
returnValue = msgBox.exec()
|
|
667
|
+
if returnValue == QMessageBox.Ok:
|
|
668
|
+
return None
|
|
669
|
+
|
|
670
|
+
def help_seg_dl_strategy(self):
|
|
671
|
+
"""
|
|
672
|
+
Helper for DL segmentation strategy, between pretrained models and custom models.
|
|
673
|
+
"""
|
|
674
|
+
|
|
675
|
+
dict_path = os.sep.join(
|
|
676
|
+
[
|
|
677
|
+
get_software_location(),
|
|
678
|
+
"celldetective",
|
|
679
|
+
"gui",
|
|
680
|
+
"help",
|
|
681
|
+
"DL-segmentation-strategy.json",
|
|
682
|
+
]
|
|
683
|
+
)
|
|
684
|
+
|
|
685
|
+
with open(dict_path) as f:
|
|
686
|
+
d = json.load(f)
|
|
687
|
+
|
|
688
|
+
suggestion = help_generic(d)
|
|
689
|
+
if isinstance(suggestion, str):
|
|
690
|
+
logger.info(f"{suggestion=}")
|
|
691
|
+
msgBox = QMessageBox()
|
|
692
|
+
msgBox.setIcon(QMessageBox.Information)
|
|
693
|
+
msgBox.setText(f"The suggested technique is {suggestion}.")
|
|
694
|
+
msgBox.setWindowTitle("Info")
|
|
695
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
696
|
+
returnValue = msgBox.exec()
|
|
697
|
+
if returnValue == QMessageBox.Ok:
|
|
698
|
+
return None
|
|
699
|
+
|
|
700
|
+
def help_tracking(self):
|
|
701
|
+
"""
|
|
702
|
+
Helper for segmentation strategy between threshold-based and Deep learning.
|
|
703
|
+
"""
|
|
704
|
+
|
|
705
|
+
dict_path = os.sep.join(
|
|
706
|
+
[get_software_location(), "celldetective", "gui", "help", "tracking.json"]
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
with open(dict_path) as f:
|
|
710
|
+
d = json.load(f)
|
|
711
|
+
|
|
712
|
+
suggestion = help_generic(d)
|
|
713
|
+
if isinstance(suggestion, str):
|
|
714
|
+
logger.info(f"{suggestion=}")
|
|
715
|
+
msgBox = QMessageBox()
|
|
716
|
+
msgBox.setIcon(QMessageBox.Information)
|
|
717
|
+
msgBox.setTextFormat(Qt.RichText)
|
|
718
|
+
msgBox.setText(f"{suggestion}")
|
|
719
|
+
msgBox.setWindowTitle("Info")
|
|
720
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
721
|
+
returnValue = msgBox.exec()
|
|
722
|
+
if returnValue == QMessageBox.Ok:
|
|
723
|
+
return None
|
|
724
|
+
|
|
725
|
+
def check_segmentation(self):
|
|
726
|
+
from celldetective.napari.utils import control_segmentation_napari
|
|
727
|
+
|
|
728
|
+
if not os.path.exists(
|
|
729
|
+
os.sep.join([self.parent_window.pos, f"labels_{self.mode}", os.sep])
|
|
730
|
+
):
|
|
731
|
+
msgBox = QMessageBox()
|
|
732
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
733
|
+
msgBox.setText(
|
|
734
|
+
"No labels can be found for this position. Do you want to annotate from scratch?"
|
|
735
|
+
)
|
|
736
|
+
msgBox.setWindowTitle("Info")
|
|
737
|
+
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
738
|
+
returnValue = msgBox.exec()
|
|
739
|
+
if returnValue == QMessageBox.No:
|
|
740
|
+
return None
|
|
741
|
+
else:
|
|
742
|
+
os.mkdir(os.sep.join([self.parent_window.pos, f"labels_{self.mode}"]))
|
|
743
|
+
lbl = np.zeros(
|
|
744
|
+
(self.parent_window.shape_x, self.parent_window.shape_y), dtype=int
|
|
745
|
+
)
|
|
746
|
+
for i in range(self.parent_window.len_movie):
|
|
747
|
+
imwrite(
|
|
748
|
+
os.sep.join(
|
|
749
|
+
[
|
|
750
|
+
self.parent_window.pos,
|
|
751
|
+
f"labels_{self.mode}",
|
|
752
|
+
str(i).zfill(4) + ".tif",
|
|
753
|
+
]
|
|
754
|
+
),
|
|
755
|
+
lbl,
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
# self.freeze()
|
|
759
|
+
# QApplication.setOverrideCursor(Qt.WaitCursor)
|
|
760
|
+
test = self.parent_window.locate_selected_position()
|
|
761
|
+
if test:
|
|
762
|
+
# print('Memory use: ', dict(psutil.virtual_memory()._asdict()))
|
|
763
|
+
logger.info(f"Loading images and labels into napari...")
|
|
764
|
+
try:
|
|
765
|
+
control_segmentation_napari(
|
|
766
|
+
self.parent_window.pos,
|
|
767
|
+
prefix=self.parent_window.movie_prefix,
|
|
768
|
+
population=self.mode,
|
|
769
|
+
flush_memory=True,
|
|
770
|
+
)
|
|
771
|
+
except FileNotFoundError as e:
|
|
772
|
+
msgBox = QMessageBox()
|
|
773
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
774
|
+
msgBox.setText(str(e))
|
|
775
|
+
msgBox.setWindowTitle("Warning")
|
|
776
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
777
|
+
_ = msgBox.exec()
|
|
778
|
+
return
|
|
779
|
+
except Exception as e:
|
|
780
|
+
logger.error(f"Task unsuccessful... Exception {e}...")
|
|
781
|
+
msgBox = QMessageBox()
|
|
782
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
783
|
+
msgBox.setText(str(e))
|
|
784
|
+
msgBox.setWindowTitle("Warning")
|
|
785
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
786
|
+
_ = msgBox.exec()
|
|
787
|
+
|
|
788
|
+
msgBox = QMessageBox()
|
|
789
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
790
|
+
msgBox.setText(
|
|
791
|
+
"Would you like to pass empty frames to fix the asymmetry?"
|
|
792
|
+
)
|
|
793
|
+
msgBox.setWindowTitle("Question")
|
|
794
|
+
msgBox.setStandardButtons(
|
|
795
|
+
QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
|
|
796
|
+
)
|
|
797
|
+
returnValue = msgBox.exec()
|
|
798
|
+
if returnValue == QMessageBox.Yes:
|
|
799
|
+
logger.info("Fixing the missing labels...")
|
|
800
|
+
fix_missing_labels(
|
|
801
|
+
self.parent_window.pos,
|
|
802
|
+
prefix=self.parent_window.movie_prefix,
|
|
803
|
+
population=self.mode,
|
|
804
|
+
)
|
|
805
|
+
try:
|
|
806
|
+
control_segmentation_napari(
|
|
807
|
+
self.parent_window.pos,
|
|
808
|
+
prefix=self.parent_window.movie_prefix,
|
|
809
|
+
population=self.mode,
|
|
810
|
+
flush_memory=True,
|
|
811
|
+
)
|
|
812
|
+
except Exception as e:
|
|
813
|
+
logger.error(f"Error {e}")
|
|
814
|
+
return None
|
|
815
|
+
else:
|
|
816
|
+
return None
|
|
817
|
+
|
|
818
|
+
gc.collect()
|
|
819
|
+
|
|
820
|
+
def check_signals(self):
|
|
821
|
+
from celldetective.gui.event_annotator import EventAnnotator, StackLoaderThread
|
|
822
|
+
|
|
823
|
+
test = self.parent_window.locate_selected_position()
|
|
824
|
+
if test:
|
|
825
|
+
self.event_annotator = EventAnnotator(self, lazy_load=True)
|
|
826
|
+
|
|
827
|
+
if not getattr(self.event_annotator, "proceed", True):
|
|
828
|
+
return
|
|
829
|
+
|
|
830
|
+
self.signal_loader = StackLoaderThread(self.event_annotator)
|
|
831
|
+
|
|
832
|
+
self.signal_progress = CelldetectiveProgressDialog(
|
|
833
|
+
"Loading data...", "Cancel", 0, 100, self, window_title="Please wait"
|
|
834
|
+
)
|
|
835
|
+
|
|
836
|
+
self.signal_progress.setValue(0)
|
|
837
|
+
|
|
838
|
+
self.signal_loader.progress.connect(self.signal_progress.setValue)
|
|
839
|
+
self.signal_loader.status_update.connect(self.signal_progress.setLabelText)
|
|
840
|
+
self.signal_progress.canceled.connect(self.signal_loader.stop)
|
|
841
|
+
|
|
842
|
+
def on_finished():
|
|
843
|
+
self.signal_progress.blockSignals(True)
|
|
844
|
+
self.signal_progress.close()
|
|
845
|
+
if not self.signal_loader._is_cancelled:
|
|
846
|
+
try:
|
|
847
|
+
self.event_annotator.finalize_init()
|
|
848
|
+
self.event_annotator.show()
|
|
849
|
+
try:
|
|
850
|
+
QTimer.singleShot(
|
|
851
|
+
100,
|
|
852
|
+
lambda: self.event_annotator.resize(
|
|
853
|
+
self.event_annotator.width() + 1,
|
|
854
|
+
self.event_annotator.height() + 1,
|
|
855
|
+
),
|
|
856
|
+
)
|
|
857
|
+
except:
|
|
858
|
+
pass
|
|
859
|
+
except Exception as e:
|
|
860
|
+
print(f"Error finalizing annotator: {e}")
|
|
861
|
+
else:
|
|
862
|
+
self.event_annotator.close()
|
|
863
|
+
|
|
864
|
+
self.signal_loader.finished.connect(on_finished)
|
|
865
|
+
self.signal_loader.start()
|
|
866
|
+
|
|
867
|
+
def check_measurements(self):
|
|
868
|
+
from celldetective.gui.event_annotator import MeasureAnnotator
|
|
869
|
+
|
|
870
|
+
test = self.parent_window.locate_selected_position()
|
|
871
|
+
if test:
|
|
872
|
+
self.measure_annotator = MeasureAnnotator(self)
|
|
873
|
+
self.measure_annotator.show()
|
|
874
|
+
|
|
875
|
+
def enable_segmentation_model_list(self):
|
|
876
|
+
if self.segment_action.isChecked():
|
|
877
|
+
self.seg_model_list.setEnabled(True)
|
|
878
|
+
else:
|
|
879
|
+
self.seg_model_list.setEnabled(False)
|
|
880
|
+
|
|
881
|
+
def enable_signal_model_list(self):
|
|
882
|
+
if self.signal_analysis_action.isChecked():
|
|
883
|
+
self.signal_models_list.setEnabled(True)
|
|
884
|
+
else:
|
|
885
|
+
self.signal_models_list.setEnabled(False)
|
|
886
|
+
|
|
887
|
+
def init_seg_model_list(self):
|
|
888
|
+
|
|
889
|
+
self.seg_model_list.clear()
|
|
890
|
+
self.seg_models_specific = get_segmentation_models_list(
|
|
891
|
+
mode=self.mode, return_path=False
|
|
892
|
+
)
|
|
893
|
+
self.seg_models = self.seg_models_specific.copy()
|
|
894
|
+
self.n_specific_seg_models = len(self.seg_models)
|
|
895
|
+
|
|
896
|
+
self.seg_models_generic = get_segmentation_models_list(
|
|
897
|
+
mode="generic", return_path=False
|
|
898
|
+
)
|
|
899
|
+
self.seg_models.append("Threshold")
|
|
900
|
+
self.seg_models.extend(self.seg_models_generic)
|
|
901
|
+
|
|
902
|
+
thresh = 35
|
|
903
|
+
self.models_truncated = [
|
|
904
|
+
m[: thresh - 3] + "..." if len(m) > thresh else m for m in self.seg_models
|
|
905
|
+
]
|
|
906
|
+
|
|
907
|
+
self.seg_model_list.addItems(self.models_truncated)
|
|
908
|
+
|
|
909
|
+
for i in range(len(self.seg_models)):
|
|
910
|
+
self.seg_model_list.setItemData(i, self.seg_models[i], Qt.ToolTipRole)
|
|
911
|
+
|
|
912
|
+
self.seg_model_list.insertSeparator(self.n_specific_seg_models)
|
|
913
|
+
|
|
914
|
+
# def tick_all_actions(self):
|
|
915
|
+
# self.switch_all_ticks_option()
|
|
916
|
+
# if self.all_ticked:
|
|
917
|
+
# self.select_all_btn.setIcon(icon(MDI6.checkbox_outline,color="black"))
|
|
918
|
+
# self.select_all_btn.setIconSize(QSize(20, 20))
|
|
919
|
+
# self.segment_action.setChecked(True)
|
|
920
|
+
# else:
|
|
921
|
+
# self.select_all_btn.setIcon(icon(MDI6.checkbox_blank_outline,color="black"))
|
|
922
|
+
# self.select_all_btn.setIconSize(QSize(20, 20))
|
|
923
|
+
# self.segment_action.setChecked(False)
|
|
924
|
+
|
|
925
|
+
# def switch_all_ticks_option(self):
|
|
926
|
+
# if self.all_ticked == True:
|
|
927
|
+
# self.all_ticked = False
|
|
928
|
+
# else:
|
|
929
|
+
# self.all_ticked = True
|
|
930
|
+
|
|
931
|
+
def upload_segmentation_model(self):
|
|
932
|
+
from celldetective.gui.seg_model_loader import SegmentationModelLoader
|
|
933
|
+
|
|
934
|
+
logger.info("Load a segmentation model or pipeline...")
|
|
935
|
+
self.seg_model_loader = SegmentationModelLoader(self)
|
|
936
|
+
self.seg_model_loader.show()
|
|
937
|
+
center_window(self.seg_model_loader)
|
|
938
|
+
|
|
939
|
+
def open_tracking_configuration_ui(self):
|
|
940
|
+
from celldetective.gui.settings._settings_tracking import SettingsTracking
|
|
941
|
+
|
|
942
|
+
logger.info("Set the tracking parameters...")
|
|
943
|
+
self.settings_tracking = SettingsTracking(self)
|
|
944
|
+
self.settings_tracking.show()
|
|
945
|
+
center_window(self.settings_tracking)
|
|
946
|
+
|
|
947
|
+
def open_signal_model_config_ui(self):
|
|
948
|
+
from celldetective.gui.settings._settings_event_model_training import (
|
|
949
|
+
SettingsEventDetectionModelTraining,
|
|
950
|
+
)
|
|
951
|
+
|
|
952
|
+
logger.info("Set the training parameters for new signal models...")
|
|
953
|
+
self.settings_event_detection_training = SettingsEventDetectionModelTraining(
|
|
954
|
+
self
|
|
955
|
+
)
|
|
956
|
+
self.settings_event_detection_training.show()
|
|
957
|
+
center_window(self.settings_event_detection_training)
|
|
958
|
+
|
|
959
|
+
def open_segmentation_model_config_ui(self):
|
|
960
|
+
from celldetective.gui.settings._settings_segmentation_model_training import (
|
|
961
|
+
SettingsSegmentationModelTraining,
|
|
962
|
+
)
|
|
963
|
+
|
|
964
|
+
logger.info("Set the training parameters for a new segmentation model...")
|
|
965
|
+
self.settings_segmentation_training = SettingsSegmentationModelTraining(self)
|
|
966
|
+
self.settings_segmentation_training.show()
|
|
967
|
+
center_window(self.settings_segmentation_training)
|
|
968
|
+
|
|
969
|
+
def open_measurement_configuration_ui(self):
|
|
970
|
+
from celldetective.gui.settings._settings_measurements import (
|
|
971
|
+
SettingsMeasurements,
|
|
972
|
+
)
|
|
973
|
+
|
|
974
|
+
logger.info("Set the measurements to be performed...")
|
|
975
|
+
self.settings_measurements = SettingsMeasurements(self)
|
|
976
|
+
self.settings_measurements.show()
|
|
977
|
+
center_window(self.settings_measurements)
|
|
978
|
+
|
|
979
|
+
def open_segmentation_configuration_ui(self):
|
|
980
|
+
from celldetective.gui.settings._settings_segmentation import (
|
|
981
|
+
SettingsSegmentation,
|
|
982
|
+
)
|
|
983
|
+
|
|
984
|
+
logger.info("Set the segmentation settings to be performed...")
|
|
985
|
+
self.settings_segmentation = SettingsSegmentation(self)
|
|
986
|
+
self.settings_segmentation.show()
|
|
987
|
+
|
|
988
|
+
def open_classifier_ui(self):
|
|
989
|
+
from celldetective.gui.classifier_widget import ClassifierWidget
|
|
990
|
+
|
|
991
|
+
self.load_available_tables()
|
|
992
|
+
if self.df is None:
|
|
993
|
+
|
|
994
|
+
msgBox = QMessageBox()
|
|
995
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
996
|
+
msgBox.setText("No table was found...")
|
|
997
|
+
msgBox.setWindowTitle("Warning")
|
|
998
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
999
|
+
returnValue = msgBox.exec()
|
|
1000
|
+
if returnValue == QMessageBox.Ok:
|
|
1001
|
+
return None
|
|
1002
|
+
else:
|
|
1003
|
+
return None
|
|
1004
|
+
else:
|
|
1005
|
+
self.classifier_widget = ClassifierWidget(self)
|
|
1006
|
+
self.classifier_widget.show()
|
|
1007
|
+
try:
|
|
1008
|
+
|
|
1009
|
+
def post_widget(wdg):
|
|
1010
|
+
try:
|
|
1011
|
+
wdg.resize(wdg.width() + 1, wdg.height() + 1)
|
|
1012
|
+
center_window(wdg)
|
|
1013
|
+
except Exception as _:
|
|
1014
|
+
pass
|
|
1015
|
+
|
|
1016
|
+
QTimer.singleShot(100, lambda: post_widget(self.classifier_widget))
|
|
1017
|
+
except Exception as _:
|
|
1018
|
+
pass
|
|
1019
|
+
|
|
1020
|
+
def open_signal_annotator_configuration_ui(self):
|
|
1021
|
+
from celldetective.gui.settings._settings_signal_annotator import (
|
|
1022
|
+
SettingsSignalAnnotator,
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
self.settings_signal_annotator = SettingsSignalAnnotator(self)
|
|
1026
|
+
self.settings_signal_annotator.show()
|
|
1027
|
+
try:
|
|
1028
|
+
QTimer.singleShot(
|
|
1029
|
+
100, lambda: center_window(self.settings_signal_annotator)
|
|
1030
|
+
)
|
|
1031
|
+
except Exception as _:
|
|
1032
|
+
pass
|
|
1033
|
+
|
|
1034
|
+
def reset_generalist_setup(self, index):
|
|
1035
|
+
self.cellpose_calibrated = False
|
|
1036
|
+
self.stardist_calibrated = False
|
|
1037
|
+
self.segChannelsSet = False
|
|
1038
|
+
|
|
1039
|
+
def reset_signals(self):
|
|
1040
|
+
self.signalChannelsSet = False
|
|
1041
|
+
|
|
1042
|
+
def process_population(self):
|
|
1043
|
+
from celldetective.processes.unified_process import UnifiedBatchProcess
|
|
1044
|
+
from celldetective.gui.workers import ProgressWindow
|
|
1045
|
+
|
|
1046
|
+
# Check positions/wells
|
|
1047
|
+
self.well_index = self.parent_window.well_list.getSelectedIndices()
|
|
1048
|
+
if len(self.well_index) == 0:
|
|
1049
|
+
msgBox = QMessageBox()
|
|
1050
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
1051
|
+
msgBox.setText("Please select at least one well first...")
|
|
1052
|
+
msgBox.setWindowTitle("Warning")
|
|
1053
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1054
|
+
returnValue = msgBox.exec()
|
|
1055
|
+
if returnValue == QMessageBox.Ok:
|
|
1056
|
+
return None
|
|
1057
|
+
else:
|
|
1058
|
+
return None
|
|
1059
|
+
|
|
1060
|
+
logger.info(f"Processing {self.parent_window.well_list.currentText()}...")
|
|
1061
|
+
|
|
1062
|
+
idx = self.parent_window.populations.index(self.mode)
|
|
1063
|
+
self.threshold_config = self.threshold_configs[idx]
|
|
1064
|
+
|
|
1065
|
+
self.load_available_tables()
|
|
1066
|
+
|
|
1067
|
+
# Checks for segmentation action
|
|
1068
|
+
if self.df is not None and self.segment_action.isChecked():
|
|
1069
|
+
msgBox = QMessageBox()
|
|
1070
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
1071
|
+
msgBox.setText(
|
|
1072
|
+
"Measurement tables have been found... Re-segmenting may create mismatches between the cell labels and the associated measurements. Do you want to erase the tables post-segmentation?"
|
|
1073
|
+
)
|
|
1074
|
+
msgBox.setWindowTitle("Info")
|
|
1075
|
+
msgBox.setStandardButtons(
|
|
1076
|
+
QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel
|
|
1077
|
+
)
|
|
1078
|
+
returnValue = msgBox.exec()
|
|
1079
|
+
if returnValue == QMessageBox.No:
|
|
1080
|
+
pass
|
|
1081
|
+
elif returnValue == QMessageBox.Cancel:
|
|
1082
|
+
return None
|
|
1083
|
+
else:
|
|
1084
|
+
logger.info("erase tabs!")
|
|
1085
|
+
tabs = [
|
|
1086
|
+
pos
|
|
1087
|
+
+ os.sep.join(["output", "tables", f"trajectories_{self.mode}.csv"])
|
|
1088
|
+
for pos in self.df_pos_info["pos_path"].unique()
|
|
1089
|
+
]
|
|
1090
|
+
# tabs += [pos+os.sep.join(['output', 'tables', f'trajectories_pairs.csv']) for pos in self.df_pos_info['pos_path'].unique()]
|
|
1091
|
+
tabs += [
|
|
1092
|
+
pos
|
|
1093
|
+
+ os.sep.join(
|
|
1094
|
+
["output", "tables", f"napari_{self.mode}_trajectories.npy"]
|
|
1095
|
+
)
|
|
1096
|
+
for pos in self.df_pos_info["pos_path"].unique()
|
|
1097
|
+
]
|
|
1098
|
+
for t in tabs:
|
|
1099
|
+
remove_file_if_exists(t.replace(".csv", ".pkl"))
|
|
1100
|
+
try:
|
|
1101
|
+
os.remove(t)
|
|
1102
|
+
except:
|
|
1103
|
+
pass
|
|
1104
|
+
|
|
1105
|
+
if self.seg_model_list.currentIndex() > self.n_specific_seg_models:
|
|
1106
|
+
self.model_name = self.seg_models[self.seg_model_list.currentIndex() - 1]
|
|
1107
|
+
else:
|
|
1108
|
+
self.model_name = self.seg_models[self.seg_model_list.currentIndex()]
|
|
1109
|
+
|
|
1110
|
+
if (
|
|
1111
|
+
self.segment_action.isChecked()
|
|
1112
|
+
and self.model_name.startswith("CP")
|
|
1113
|
+
and self.model_name in self.seg_models_generic
|
|
1114
|
+
and not self.cellpose_calibrated
|
|
1115
|
+
):
|
|
1116
|
+
from celldetective.gui.settings._cellpose_model_params import (
|
|
1117
|
+
CellposeParamsWidget,
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
self.diamWidget = CellposeParamsWidget(self, model_name=self.model_name)
|
|
1121
|
+
self.diamWidget.show()
|
|
1122
|
+
|
|
1123
|
+
return None
|
|
1124
|
+
elif (
|
|
1125
|
+
self.segment_action.isChecked()
|
|
1126
|
+
and self.model_name.startswith("SD")
|
|
1127
|
+
and self.model_name in self.seg_models_generic
|
|
1128
|
+
and not self.stardist_calibrated
|
|
1129
|
+
):
|
|
1130
|
+
from celldetective.gui.settings._stardist_model_params import (
|
|
1131
|
+
StarDistParamsWidget,
|
|
1132
|
+
)
|
|
1133
|
+
|
|
1134
|
+
self.diamWidget = StarDistParamsWidget(self, model_name=self.model_name)
|
|
1135
|
+
self.diamWidget.show()
|
|
1136
|
+
|
|
1137
|
+
return None
|
|
1138
|
+
|
|
1139
|
+
elif (
|
|
1140
|
+
self.segment_action.isChecked()
|
|
1141
|
+
and self.model_name in self.seg_models_specific
|
|
1142
|
+
and not self.segChannelsSet
|
|
1143
|
+
):
|
|
1144
|
+
from celldetective.gui.settings._segmentation_model_params import (
|
|
1145
|
+
SegModelParamsWidget,
|
|
1146
|
+
)
|
|
1147
|
+
|
|
1148
|
+
self.segChannelWidget = SegModelParamsWidget(
|
|
1149
|
+
self, model_name=self.model_name
|
|
1150
|
+
)
|
|
1151
|
+
self.segChannelWidget.show()
|
|
1152
|
+
|
|
1153
|
+
return None
|
|
1154
|
+
|
|
1155
|
+
if self.signal_analysis_action.isChecked() and not self.signalChannelsSet:
|
|
1156
|
+
from celldetective.gui.settings._event_detection_model_params import (
|
|
1157
|
+
SignalModelParamsWidget,
|
|
1158
|
+
)
|
|
1159
|
+
|
|
1160
|
+
self.signal_model_name = self.signal_models[
|
|
1161
|
+
self.signal_models_list.currentIndex()
|
|
1162
|
+
]
|
|
1163
|
+
self.signalChannelWidget = SignalModelParamsWidget(
|
|
1164
|
+
self, model_name=self.signal_model_name
|
|
1165
|
+
)
|
|
1166
|
+
self.signalChannelWidget.show()
|
|
1167
|
+
|
|
1168
|
+
return None
|
|
1169
|
+
|
|
1170
|
+
self.movie_prefix = self.parent_window.movie_prefix
|
|
1171
|
+
|
|
1172
|
+
if self.parent_window.position_list.isMultipleSelection():
|
|
1173
|
+
msgBox = QMessageBox()
|
|
1174
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
1175
|
+
msgBox.setText(
|
|
1176
|
+
"If you continue, several positions will be processed.\nDo you want to proceed?"
|
|
1177
|
+
)
|
|
1178
|
+
msgBox.setWindowTitle("Info")
|
|
1179
|
+
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
1180
|
+
returnValue = msgBox.exec()
|
|
1181
|
+
if returnValue == QMessageBox.No:
|
|
1182
|
+
return None
|
|
1183
|
+
|
|
1184
|
+
self.movie_prefix = self.parent_window.movie_prefix
|
|
1185
|
+
|
|
1186
|
+
# COLLECT POSITIONS
|
|
1187
|
+
batch_structure = {}
|
|
1188
|
+
all_positions_flat = (
|
|
1189
|
+
[]
|
|
1190
|
+
) # Keep flat list for legacy check logic or easy counting
|
|
1191
|
+
|
|
1192
|
+
for w_idx in self.well_index:
|
|
1193
|
+
well = self.parent_window.wells[w_idx]
|
|
1194
|
+
|
|
1195
|
+
batch_structure[w_idx] = {"well_name": well, "positions": []}
|
|
1196
|
+
|
|
1197
|
+
pos_indices = self.parent_window.position_list.getSelectedIndices()
|
|
1198
|
+
# Optimization: Glob once per well
|
|
1199
|
+
all_well_positions = natsorted(
|
|
1200
|
+
glob(
|
|
1201
|
+
well
|
|
1202
|
+
+ f"{os.path.split(well)[-1].replace('W','').replace(os.sep,'')}*/"
|
|
1203
|
+
)
|
|
1204
|
+
)
|
|
1205
|
+
for pos_idx in pos_indices:
|
|
1206
|
+
if pos_idx < len(all_well_positions):
|
|
1207
|
+
pos = all_well_positions[pos_idx]
|
|
1208
|
+
else:
|
|
1209
|
+
logger.warning(
|
|
1210
|
+
f"Position index {pos_idx} out of range for well {well}"
|
|
1211
|
+
)
|
|
1212
|
+
continue
|
|
1213
|
+
|
|
1214
|
+
batch_structure[w_idx]["positions"].append(pos)
|
|
1215
|
+
all_positions_flat.append(pos)
|
|
1216
|
+
|
|
1217
|
+
# Check output folders creation
|
|
1218
|
+
if not os.path.exists(pos + "output/"):
|
|
1219
|
+
os.mkdir(pos + "output/")
|
|
1220
|
+
if not os.path.exists(pos + "output/tables/"):
|
|
1221
|
+
os.mkdir(pos + "output/tables/")
|
|
1222
|
+
|
|
1223
|
+
# BATCH SEGMENTATION
|
|
1224
|
+
# --- UNIFIED BATCH PROCESS SETUP ---
|
|
1225
|
+
|
|
1226
|
+
run_segmentation = self.segment_action.isChecked()
|
|
1227
|
+
run_tracking = self.track_action.isChecked()
|
|
1228
|
+
run_measurement = self.measure_action.isChecked()
|
|
1229
|
+
run_signals = self.signal_analysis_action.isChecked()
|
|
1230
|
+
|
|
1231
|
+
seg_args = {}
|
|
1232
|
+
track_args = {}
|
|
1233
|
+
measure_args = {}
|
|
1234
|
+
signal_args = {}
|
|
1235
|
+
|
|
1236
|
+
# 1. SEGMENTATION CHECKS & ARGS
|
|
1237
|
+
if run_segmentation:
|
|
1238
|
+
# Single-position overwrite check
|
|
1239
|
+
if (
|
|
1240
|
+
len(all_positions_flat) == 1
|
|
1241
|
+
and not self.parent_window.position_list.isMultipleSelection()
|
|
1242
|
+
):
|
|
1243
|
+
p = all_positions_flat[0]
|
|
1244
|
+
if len(glob(os.sep.join([p, f"labels_{self.mode}", "*.tif"]))) > 0:
|
|
1245
|
+
msgBox = QMessageBox()
|
|
1246
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
1247
|
+
msgBox.setText(
|
|
1248
|
+
"Labels have already been produced for this position. Do you want to segment again?"
|
|
1249
|
+
)
|
|
1250
|
+
msgBox.setWindowTitle("Info")
|
|
1251
|
+
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
1252
|
+
if msgBox.exec() == QMessageBox.No:
|
|
1253
|
+
run_segmentation = False
|
|
1254
|
+
|
|
1255
|
+
# Threshold config check
|
|
1256
|
+
if run_segmentation and self.seg_model_list.currentText() == "Threshold":
|
|
1257
|
+
if self.threshold_config is None:
|
|
1258
|
+
msgBox = QMessageBox()
|
|
1259
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
1260
|
+
msgBox.setText(
|
|
1261
|
+
"Please set a threshold configuration from the upload menu first. Abort."
|
|
1262
|
+
)
|
|
1263
|
+
msgBox.setWindowTitle("Warning")
|
|
1264
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1265
|
+
msgBox.exec()
|
|
1266
|
+
return None
|
|
1267
|
+
|
|
1268
|
+
seg_args = {
|
|
1269
|
+
"mode": self.mode,
|
|
1270
|
+
"n_threads": self.n_threads,
|
|
1271
|
+
"threshold_instructions": self.threshold_config,
|
|
1272
|
+
"use_gpu": self.use_gpu,
|
|
1273
|
+
}
|
|
1274
|
+
elif run_segmentation: # Deep Learning
|
|
1275
|
+
# Prepare representative position for process initialization
|
|
1276
|
+
first_pos = None
|
|
1277
|
+
if all_positions_flat and isinstance(all_positions_flat[0], str):
|
|
1278
|
+
first_pos = all_positions_flat[0]
|
|
1279
|
+
|
|
1280
|
+
seg_args = {
|
|
1281
|
+
"mode": self.mode,
|
|
1282
|
+
"pos": first_pos,
|
|
1283
|
+
"n_threads": self.n_threads,
|
|
1284
|
+
"model_name": self.model_name,
|
|
1285
|
+
"use_gpu": self.use_gpu,
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
# 2. TRACKING CHECKS & ARGS
|
|
1289
|
+
if run_tracking:
|
|
1290
|
+
|
|
1291
|
+
# Single-position overwrite check
|
|
1292
|
+
if (
|
|
1293
|
+
len(all_positions_flat) == 1
|
|
1294
|
+
and not self.parent_window.position_list.isMultipleSelection()
|
|
1295
|
+
):
|
|
1296
|
+
p = all_positions_flat[0]
|
|
1297
|
+
table_path = os.sep.join(
|
|
1298
|
+
[p, "output", "tables", f"trajectories_{self.mode}.csv"]
|
|
1299
|
+
)
|
|
1300
|
+
if os.path.exists(table_path):
|
|
1301
|
+
msgBox = QMessageBox()
|
|
1302
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
1303
|
+
msgBox.setText(
|
|
1304
|
+
"A measurement table already exists. Previously annotated data for\nthis position will be lost. Do you want to proceed?"
|
|
1305
|
+
)
|
|
1306
|
+
msgBox.setWindowTitle("Info")
|
|
1307
|
+
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
1308
|
+
if msgBox.exec() == QMessageBox.No:
|
|
1309
|
+
run_tracking = False
|
|
1310
|
+
|
|
1311
|
+
if run_tracking:
|
|
1312
|
+
track_args = {"mode": self.mode, "n_threads": self.n_threads}
|
|
1313
|
+
|
|
1314
|
+
# 3. MEASUREMENT ARGS
|
|
1315
|
+
if run_measurement:
|
|
1316
|
+
measure_args = {"mode": self.mode, "n_threads": self.n_threads}
|
|
1317
|
+
|
|
1318
|
+
# 4. SIGNAL ANALYSIS CHECKS & ARGS
|
|
1319
|
+
if run_signals:
|
|
1320
|
+
if (
|
|
1321
|
+
len(all_positions_flat) == 1
|
|
1322
|
+
and not self.parent_window.position_list.isMultipleSelection()
|
|
1323
|
+
):
|
|
1324
|
+
p = all_positions_flat[0]
|
|
1325
|
+
table_path = os.sep.join(
|
|
1326
|
+
[p, "output", "tables", f"trajectories_{self.mode}.csv"]
|
|
1327
|
+
)
|
|
1328
|
+
if os.path.exists(table_path):
|
|
1329
|
+
# Check for annotations (logic from original code)
|
|
1330
|
+
try:
|
|
1331
|
+
# Optimized reading: Check header first, then read interesting column
|
|
1332
|
+
header = pd.read_csv(table_path, nrows=0).columns
|
|
1333
|
+
if "class_color" in list(header):
|
|
1334
|
+
colors = pd.read_csv(table_path, usecols=["class_color"])[
|
|
1335
|
+
"class_color"
|
|
1336
|
+
].unique()
|
|
1337
|
+
if "tab:orange" in colors or "tab:cyan" in colors:
|
|
1338
|
+
msgBox = QMessageBox()
|
|
1339
|
+
msgBox.setIcon(QMessageBox.Question)
|
|
1340
|
+
msgBox.setText(
|
|
1341
|
+
"The signals of the cells in the position appear to have been annotated... Do you want to proceed?"
|
|
1342
|
+
)
|
|
1343
|
+
msgBox.setWindowTitle("Info")
|
|
1344
|
+
msgBox.setStandardButtons(
|
|
1345
|
+
QMessageBox.Yes | QMessageBox.No
|
|
1346
|
+
)
|
|
1347
|
+
if msgBox.exec() == QMessageBox.No:
|
|
1348
|
+
run_signals = False
|
|
1349
|
+
except Exception as e:
|
|
1350
|
+
logger.warning(f"Could not check table for annotations: {e}")
|
|
1351
|
+
|
|
1352
|
+
if run_signals:
|
|
1353
|
+
self.signal_model_name = self.signal_models[
|
|
1354
|
+
self.signal_models_list.currentIndex()
|
|
1355
|
+
]
|
|
1356
|
+
|
|
1357
|
+
model_complete_path = locate_signal_model(self.signal_model_name)
|
|
1358
|
+
input_config_path = os.path.join(
|
|
1359
|
+
model_complete_path, "config_input.json"
|
|
1360
|
+
)
|
|
1361
|
+
with open(input_config_path) as config_file:
|
|
1362
|
+
input_config = json.load(config_file)
|
|
1363
|
+
|
|
1364
|
+
channels = input_config.get(
|
|
1365
|
+
"selected_channels", input_config.get("channels", [])
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
signal_args = {
|
|
1369
|
+
"model_name": self.signal_model_name,
|
|
1370
|
+
"mode": self.mode,
|
|
1371
|
+
"channels": channels,
|
|
1372
|
+
}
|
|
1373
|
+
|
|
1374
|
+
# --- EXECUTE UNIFIED PROCESS ---
|
|
1375
|
+
if any([run_segmentation, run_tracking, run_measurement, run_signals]):
|
|
1376
|
+
|
|
1377
|
+
process_args = {
|
|
1378
|
+
"batch_structure": batch_structure,
|
|
1379
|
+
"run_segmentation": run_segmentation,
|
|
1380
|
+
"run_tracking": run_tracking,
|
|
1381
|
+
"run_measurement": run_measurement,
|
|
1382
|
+
"run_signals": run_signals,
|
|
1383
|
+
"seg_args": seg_args,
|
|
1384
|
+
"track_args": track_args,
|
|
1385
|
+
"measure_args": measure_args,
|
|
1386
|
+
"signal_args": signal_args,
|
|
1387
|
+
"log_file": getattr(self.parent_window.parent_window, "log_file", None),
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
self.job = ProgressWindow(
|
|
1391
|
+
UnifiedBatchProcess,
|
|
1392
|
+
parent_window=self,
|
|
1393
|
+
title=f"Processing {self.mode}",
|
|
1394
|
+
process_args=process_args,
|
|
1395
|
+
)
|
|
1396
|
+
result = self.job.exec_()
|
|
1397
|
+
|
|
1398
|
+
if result == QDialog.Rejected:
|
|
1399
|
+
self.reset_generalist_setup(0)
|
|
1400
|
+
return None
|
|
1401
|
+
|
|
1402
|
+
# Post-Process actions (like updating list)
|
|
1403
|
+
if run_tracking:
|
|
1404
|
+
self.parent_window.update_position_options()
|
|
1405
|
+
|
|
1406
|
+
if run_signals:
|
|
1407
|
+
from celldetective.gui.interactive_timeseries_viewer import (
|
|
1408
|
+
InteractiveEventViewer,
|
|
1409
|
+
)
|
|
1410
|
+
|
|
1411
|
+
self.parent_window.update_position_options()
|
|
1412
|
+
|
|
1413
|
+
if len(all_positions_flat) == 1:
|
|
1414
|
+
p = all_positions_flat[0]
|
|
1415
|
+
mode_fixed = self.mode
|
|
1416
|
+
if self.mode.lower() in ["target", "targets"]:
|
|
1417
|
+
mode_fixed = "targets"
|
|
1418
|
+
elif self.mode.lower() in ["effector", "effectors"]:
|
|
1419
|
+
mode_fixed = "effectors"
|
|
1420
|
+
|
|
1421
|
+
table_path = os.sep.join(
|
|
1422
|
+
[p, "output", "tables", f"trajectories_{mode_fixed}.csv"]
|
|
1423
|
+
)
|
|
1424
|
+
|
|
1425
|
+
if os.path.exists(table_path):
|
|
1426
|
+
# Determine event label
|
|
1427
|
+
event_label = None
|
|
1428
|
+
signal_name = None
|
|
1429
|
+
try:
|
|
1430
|
+
if hasattr(self, "signal_model_name"):
|
|
1431
|
+
model_complete_path = locate_signal_model(
|
|
1432
|
+
self.signal_model_name
|
|
1433
|
+
)
|
|
1434
|
+
input_config_path = os.path.join(
|
|
1435
|
+
model_complete_path, "config_input.json"
|
|
1436
|
+
)
|
|
1437
|
+
if os.path.exists(input_config_path):
|
|
1438
|
+
with open(input_config_path) as f:
|
|
1439
|
+
conf = json.load(f)
|
|
1440
|
+
event_label = conf.get("label", None)
|
|
1441
|
+
channels = conf.get("channels", [])
|
|
1442
|
+
if channels:
|
|
1443
|
+
signal_name = channels[0]
|
|
1444
|
+
except Exception as e:
|
|
1445
|
+
logger.warning(f"Could not determine event label: {e}")
|
|
1446
|
+
|
|
1447
|
+
logger.info(
|
|
1448
|
+
f"Launching Interactive Event Viewer for {table_path} with label {event_label}"
|
|
1449
|
+
)
|
|
1450
|
+
self.viewer = InteractiveEventViewer(
|
|
1451
|
+
table_path,
|
|
1452
|
+
signal_name=signal_name,
|
|
1453
|
+
event_label=event_label,
|
|
1454
|
+
parent=self,
|
|
1455
|
+
)
|
|
1456
|
+
self.viewer.show()
|
|
1457
|
+
center_window(self.viewer)
|
|
1458
|
+
else:
|
|
1459
|
+
logger.warning(
|
|
1460
|
+
f"Could not find table for interactive viewer: {table_path}"
|
|
1461
|
+
)
|
|
1462
|
+
for action in [
|
|
1463
|
+
self.segment_action,
|
|
1464
|
+
self.track_action,
|
|
1465
|
+
self.measure_action,
|
|
1466
|
+
self.signal_analysis_action,
|
|
1467
|
+
]:
|
|
1468
|
+
if action.isChecked():
|
|
1469
|
+
action.setChecked(False)
|
|
1470
|
+
|
|
1471
|
+
self.reset_generalist_setup(0)
|
|
1472
|
+
self.reset_signals()
|
|
1473
|
+
|
|
1474
|
+
def open_napari_tracking(self):
|
|
1475
|
+
|
|
1476
|
+
logger.info(
|
|
1477
|
+
f"View the tracks before post-processing for position {self.parent_window.pos} in napari..."
|
|
1478
|
+
)
|
|
1479
|
+
|
|
1480
|
+
self.napari_loader = NapariLoaderThread(
|
|
1481
|
+
self.parent_window.pos,
|
|
1482
|
+
self.parent_window.movie_prefix,
|
|
1483
|
+
self.mode,
|
|
1484
|
+
self.parent_window.parent_window.n_threads,
|
|
1485
|
+
)
|
|
1486
|
+
|
|
1487
|
+
self.napari_progress = CelldetectiveProgressDialog(
|
|
1488
|
+
"Loading images, tracks and relabeling masks...",
|
|
1489
|
+
"Cancel",
|
|
1490
|
+
0,
|
|
1491
|
+
100,
|
|
1492
|
+
self,
|
|
1493
|
+
window_title="Preparing the napari viewer...",
|
|
1494
|
+
)
|
|
1495
|
+
|
|
1496
|
+
self.napari_progress.setValue(0)
|
|
1497
|
+
self.napari_loader.progress.connect(self.napari_progress.setValue)
|
|
1498
|
+
self.napari_loader.status.connect(self.napari_progress.setLabelText)
|
|
1499
|
+
self.napari_progress.canceled.connect(self.napari_loader.stop)
|
|
1500
|
+
|
|
1501
|
+
def on_finished(result):
|
|
1502
|
+
from celldetective.napari.utils import launch_napari_viewer
|
|
1503
|
+
|
|
1504
|
+
self.napari_progress.blockSignals(True)
|
|
1505
|
+
self.napari_progress.close()
|
|
1506
|
+
if self.napari_loader._is_cancelled:
|
|
1507
|
+
logger.info("Task was cancelled...")
|
|
1508
|
+
return
|
|
1509
|
+
|
|
1510
|
+
if isinstance(result, Exception):
|
|
1511
|
+
logger.error(f"napari loading error: {result}")
|
|
1512
|
+
msgBox = QMessageBox()
|
|
1513
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
1514
|
+
msgBox.setText(str(result))
|
|
1515
|
+
msgBox.setWindowTitle("Warning")
|
|
1516
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1517
|
+
_ = msgBox.exec()
|
|
1518
|
+
return
|
|
1519
|
+
|
|
1520
|
+
if result:
|
|
1521
|
+
logger.info("Launching the napari viewer with tracks...")
|
|
1522
|
+
try:
|
|
1523
|
+
launch_napari_viewer(**result)
|
|
1524
|
+
logger.info("napari viewer was closed...")
|
|
1525
|
+
except Exception as e:
|
|
1526
|
+
logger.error(f"Failed to launch Napari: {e}")
|
|
1527
|
+
QMessageBox.warning(self, "Error", f"Failed to launch Napari: {e}")
|
|
1528
|
+
else:
|
|
1529
|
+
logger.warning(
|
|
1530
|
+
"napari loading returned None (likely no trajectories found)."
|
|
1531
|
+
)
|
|
1532
|
+
QMessageBox.warning(
|
|
1533
|
+
self,
|
|
1534
|
+
"Warning",
|
|
1535
|
+
"Could not load tracks. Please ensure trajectories are computed.",
|
|
1536
|
+
)
|
|
1537
|
+
|
|
1538
|
+
self.napari_loader.finished_with_result.connect(on_finished)
|
|
1539
|
+
self.napari_loader.start()
|
|
1540
|
+
|
|
1541
|
+
def view_table_ui(self):
|
|
1542
|
+
from celldetective.gui.tableUI import TableUI
|
|
1543
|
+
|
|
1544
|
+
logger.info("Load table...")
|
|
1545
|
+
self.load_available_tables()
|
|
1546
|
+
|
|
1547
|
+
if self.df is not None:
|
|
1548
|
+
plot_mode = "plot_track_signals"
|
|
1549
|
+
if "TRACK_ID" not in list(self.df.columns):
|
|
1550
|
+
plot_mode = "static"
|
|
1551
|
+
self.tab_ui = TableUI(
|
|
1552
|
+
self.df,
|
|
1553
|
+
f"{self.parent_window.well_list.currentText()}; Position {self.parent_window.position_list.currentText()}",
|
|
1554
|
+
population=self.mode,
|
|
1555
|
+
plot_mode=plot_mode,
|
|
1556
|
+
save_inplace_option=True,
|
|
1557
|
+
)
|
|
1558
|
+
self.tab_ui.show()
|
|
1559
|
+
center_window(self.tab_ui)
|
|
1560
|
+
else:
|
|
1561
|
+
logger.info("Table could not be loaded...")
|
|
1562
|
+
msgBox = QMessageBox()
|
|
1563
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
1564
|
+
msgBox.setText("No table could be loaded...")
|
|
1565
|
+
msgBox.setWindowTitle("Info")
|
|
1566
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
1567
|
+
returnValue = msgBox.exec()
|
|
1568
|
+
if returnValue == QMessageBox.Ok:
|
|
1569
|
+
return None
|
|
1570
|
+
|
|
1571
|
+
def load_available_tables(self):
|
|
1572
|
+
"""
|
|
1573
|
+
Load the tables of the selected wells/positions from the control Panel for the population of interest
|
|
1574
|
+
|
|
1575
|
+
"""
|
|
1576
|
+
|
|
1577
|
+
self.well_option = self.parent_window.well_list.getSelectedIndices()
|
|
1578
|
+
self.position_option = self.parent_window.position_list.getSelectedIndices()
|
|
1579
|
+
|
|
1580
|
+
self.df, self.df_pos_info = load_experiment_tables(
|
|
1581
|
+
self.exp_dir,
|
|
1582
|
+
well_option=self.well_option,
|
|
1583
|
+
position_option=self.position_option,
|
|
1584
|
+
population=self.mode,
|
|
1585
|
+
return_pos_info=True,
|
|
1586
|
+
)
|
|
1587
|
+
self.signals = []
|
|
1588
|
+
if self.df is not None:
|
|
1589
|
+
self.signals = list(self.df.columns)
|
|
1590
|
+
if self.df is None:
|
|
1591
|
+
logger.info("No table could be found for the selected position(s)...")
|
|
1592
|
+
|
|
1593
|
+
def set_cellpose_scale(self):
|
|
1594
|
+
|
|
1595
|
+
scale = (
|
|
1596
|
+
self.parent_window.PxToUm
|
|
1597
|
+
* float(self.diamWidget.diameter_le.get_threshold())
|
|
1598
|
+
/ 30.0
|
|
1599
|
+
)
|
|
1600
|
+
if self.model_name == "CP_nuclei":
|
|
1601
|
+
scale = (
|
|
1602
|
+
self.parent_window.PxToUm
|
|
1603
|
+
* float(self.diamWidget.diameter_le.get_threshold())
|
|
1604
|
+
/ 17.0
|
|
1605
|
+
)
|
|
1606
|
+
flow_thresh = self.diamWidget.flow_slider.value()
|
|
1607
|
+
cellprob_thresh = self.diamWidget.cellprob_slider.value()
|
|
1608
|
+
model_complete_path = locate_segmentation_model(self.model_name)
|
|
1609
|
+
input_config_path = model_complete_path + "config_input.json"
|
|
1610
|
+
new_channels = [
|
|
1611
|
+
self.diamWidget.cellpose_channel_cb[i].currentText() for i in range(2)
|
|
1612
|
+
]
|
|
1613
|
+
with open(input_config_path) as config_file:
|
|
1614
|
+
input_config = json.load(config_file)
|
|
1615
|
+
|
|
1616
|
+
input_config["spatial_calibration"] = scale
|
|
1617
|
+
input_config["channels"] = new_channels
|
|
1618
|
+
input_config["flow_threshold"] = flow_thresh
|
|
1619
|
+
input_config["cellprob_threshold"] = cellprob_thresh
|
|
1620
|
+
with open(input_config_path, "w") as f:
|
|
1621
|
+
json.dump(input_config, f, indent=4)
|
|
1622
|
+
|
|
1623
|
+
self.cellpose_calibrated = True
|
|
1624
|
+
logger.info(f"model scale automatically computed: {scale}")
|
|
1625
|
+
self.diamWidget.close()
|
|
1626
|
+
self.process_population()
|
|
1627
|
+
|
|
1628
|
+
def set_stardist_scale(self):
|
|
1629
|
+
|
|
1630
|
+
model_complete_path = locate_segmentation_model(self.model_name)
|
|
1631
|
+
input_config_path = model_complete_path + "config_input.json"
|
|
1632
|
+
new_channels = [
|
|
1633
|
+
self.diamWidget.stardist_channel_cb[i].currentText()
|
|
1634
|
+
for i in range(len(self.diamWidget.stardist_channel_cb))
|
|
1635
|
+
]
|
|
1636
|
+
with open(input_config_path) as config_file:
|
|
1637
|
+
input_config = json.load(config_file)
|
|
1638
|
+
|
|
1639
|
+
input_config["channels"] = new_channels
|
|
1640
|
+
with open(input_config_path, "w") as f:
|
|
1641
|
+
json.dump(input_config, f, indent=4)
|
|
1642
|
+
|
|
1643
|
+
self.stardist_calibrated = True
|
|
1644
|
+
self.diamWidget.close()
|
|
1645
|
+
self.process_population()
|
|
1646
|
+
|
|
1647
|
+
def set_selected_channels_for_segmentation(self):
|
|
1648
|
+
|
|
1649
|
+
model_complete_path = locate_segmentation_model(self.model_name)
|
|
1650
|
+
input_config_path = model_complete_path + "config_input.json"
|
|
1651
|
+
new_channels = [
|
|
1652
|
+
self.segChannelWidget.channel_cbs[i].currentText()
|
|
1653
|
+
for i in range(len(self.segChannelWidget.channel_cbs))
|
|
1654
|
+
]
|
|
1655
|
+
target_cell_size = None
|
|
1656
|
+
if hasattr(self.segChannelWidget, "diameter_le"):
|
|
1657
|
+
target_cell_size = float(self.segChannelWidget.diameter_le.get_threshold())
|
|
1658
|
+
|
|
1659
|
+
with open(input_config_path) as config_file:
|
|
1660
|
+
input_config = json.load(config_file)
|
|
1661
|
+
|
|
1662
|
+
input_config.update(
|
|
1663
|
+
{"selected_channels": new_channels, "target_cell_size_um": target_cell_size}
|
|
1664
|
+
)
|
|
1665
|
+
|
|
1666
|
+
# input_config['channels'] = new_channels
|
|
1667
|
+
with open(input_config_path, "w") as f:
|
|
1668
|
+
json.dump(input_config, f, indent=4)
|
|
1669
|
+
|
|
1670
|
+
self.segChannelsSet = True
|
|
1671
|
+
self.segChannelWidget.close()
|
|
1672
|
+
self.process_population()
|
|
1673
|
+
|
|
1674
|
+
def set_selected_signals_for_event_detection(self):
|
|
1675
|
+
self.signal_model_name = self.signal_models[
|
|
1676
|
+
self.signal_models_list.currentIndex()
|
|
1677
|
+
]
|
|
1678
|
+
model_complete_path = locate_signal_model(self.signal_model_name)
|
|
1679
|
+
input_config_path = model_complete_path + "config_input.json"
|
|
1680
|
+
new_channels = [
|
|
1681
|
+
self.signalChannelWidget.channel_cbs[i].currentText()
|
|
1682
|
+
for i in range(len(self.signalChannelWidget.channel_cbs))
|
|
1683
|
+
]
|
|
1684
|
+
with open(input_config_path) as config_file:
|
|
1685
|
+
input_config = json.load(config_file)
|
|
1686
|
+
|
|
1687
|
+
input_config.update({"selected_channels": new_channels})
|
|
1688
|
+
|
|
1689
|
+
# input_config['channels'] = new_channels
|
|
1690
|
+
with open(input_config_path, "w") as f:
|
|
1691
|
+
json.dump(input_config, f, indent=4)
|
|
1692
|
+
|
|
1693
|
+
self.signalChannelsSet = True
|
|
1694
|
+
self.signalChannelWidget.close()
|
|
1695
|
+
self.process_population()
|