celldetective 1.3.9.post5__py3-none-any.whl → 1.4.1__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 +0 -3
- celldetective/_version.py +1 -1
- celldetective/events.py +2 -4
- celldetective/exceptions.py +11 -0
- celldetective/extra_properties.py +132 -0
- celldetective/filters.py +7 -1
- celldetective/gui/InitWindow.py +37 -46
- celldetective/gui/__init__.py +3 -9
- celldetective/gui/about.py +19 -15
- celldetective/gui/analyze_block.py +34 -19
- celldetective/gui/base_annotator.py +786 -0
- celldetective/gui/base_components.py +23 -0
- celldetective/gui/classifier_widget.py +86 -94
- celldetective/gui/configure_new_exp.py +163 -46
- celldetective/gui/control_panel.py +76 -146
- celldetective/gui/{signal_annotator.py → event_annotator.py} +533 -1438
- celldetective/gui/generic_signal_plot.py +11 -13
- celldetective/gui/gui_utils.py +54 -23
- celldetective/gui/help/neighborhood.json +2 -2
- celldetective/gui/json_readers.py +5 -4
- celldetective/gui/layouts.py +265 -31
- celldetective/gui/{signal_annotator2.py → pair_event_annotator.py} +433 -635
- celldetective/gui/plot_measurements.py +21 -17
- celldetective/gui/plot_signals_ui.py +125 -72
- celldetective/gui/process_block.py +283 -188
- celldetective/gui/processes/compute_neighborhood.py +594 -0
- celldetective/gui/processes/downloader.py +37 -34
- celldetective/gui/processes/measure_cells.py +19 -8
- celldetective/gui/processes/segment_cells.py +47 -11
- celldetective/gui/processes/track_cells.py +18 -13
- celldetective/gui/seg_model_loader.py +21 -62
- celldetective/gui/settings/__init__.py +7 -0
- celldetective/gui/settings/_settings_base.py +70 -0
- celldetective/gui/{retrain_signal_model_options.py → settings/_settings_event_model_training.py} +54 -109
- celldetective/gui/{measurement_options.py → settings/_settings_measurements.py} +54 -92
- celldetective/gui/{neighborhood_options.py → settings/_settings_neighborhood.py} +10 -13
- celldetective/gui/settings/_settings_segmentation.py +49 -0
- celldetective/gui/{retrain_segmentation_model_options.py → settings/_settings_segmentation_model_training.py} +38 -92
- celldetective/gui/{signal_annotator_options.py → settings/_settings_signal_annotator.py} +78 -103
- celldetective/gui/{btrack_options.py → settings/_settings_tracking.py} +85 -116
- celldetective/gui/styles.py +2 -1
- celldetective/gui/survival_ui.py +49 -95
- celldetective/gui/tableUI.py +53 -25
- celldetective/gui/table_ops/__init__.py +0 -0
- celldetective/gui/table_ops/merge_groups.py +118 -0
- celldetective/gui/thresholds_gui.py +617 -1221
- celldetective/gui/viewers.py +107 -42
- celldetective/gui/workers.py +8 -4
- celldetective/io.py +137 -57
- celldetective/links/zenodo.json +145 -144
- celldetective/measure.py +94 -53
- celldetective/neighborhood.py +342 -268
- celldetective/preprocessing.py +56 -35
- celldetective/regionprops/_regionprops.py +16 -5
- celldetective/relative_measurements.py +50 -29
- celldetective/scripts/analyze_signals.py +4 -1
- celldetective/scripts/measure_cells.py +5 -5
- celldetective/scripts/measure_relative.py +20 -12
- celldetective/scripts/segment_cells.py +4 -10
- celldetective/scripts/segment_cells_thresholds.py +3 -3
- celldetective/scripts/track_cells.py +10 -8
- celldetective/scripts/train_segmentation_model.py +18 -6
- celldetective/signals.py +29 -14
- celldetective/tracking.py +14 -3
- celldetective/utils.py +91 -62
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/METADATA +24 -16
- celldetective-1.4.1.dist-info/RECORD +123 -0
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/WHEEL +1 -1
- tests/gui/__init__.py +0 -0
- tests/gui/test_new_project.py +228 -0
- tests/gui/test_project.py +99 -0
- tests/test_preprocessing.py +2 -2
- celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +0 -79
- celldetective/models/segmentation_effectors/ricm_bf_all_last/ricm_bf_all_last +0 -0
- celldetective/models/segmentation_effectors/ricm_bf_all_last/training_instructions.json +0 -37
- celldetective/models/segmentation_effectors/test-transfer/config_input.json +0 -39
- celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
- celldetective/models/signal_detection/NucCond/classification_loss.png +0 -0
- celldetective/models/signal_detection/NucCond/classifier.h5 +0 -0
- celldetective/models/signal_detection/NucCond/config_input.json +0 -1
- celldetective/models/signal_detection/NucCond/log_classifier.csv +0 -126
- celldetective/models/signal_detection/NucCond/log_regressor.csv +0 -282
- celldetective/models/signal_detection/NucCond/regression_loss.png +0 -0
- celldetective/models/signal_detection/NucCond/regressor.h5 +0 -0
- celldetective/models/signal_detection/NucCond/scores.npy +0 -0
- celldetective/models/signal_detection/NucCond/test_confusion_matrix.png +0 -0
- celldetective/models/signal_detection/NucCond/test_regression.png +0 -0
- celldetective/models/signal_detection/NucCond/validation_confusion_matrix.png +0 -0
- celldetective/models/signal_detection/NucCond/validation_regression.png +0 -0
- celldetective-1.3.9.post5.dist-info/RECORD +0 -129
- tests/test_qt.py +0 -103
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info/licenses}/LICENSE +0 -0
- {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -1,217 +1,126 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import
|
|
2
|
-
QPushButton,
|
|
1
|
+
from PyQt5.QtWidgets import QComboBox, QLabel, QRadioButton, QFileDialog, QApplication, \
|
|
2
|
+
QPushButton, QVBoxLayout, QHBoxLayout, QMessageBox, QShortcut, QLineEdit, QSlider
|
|
3
3
|
from PyQt5.QtCore import Qt, QSize
|
|
4
4
|
from PyQt5.QtGui import QKeySequence, QIntValidator
|
|
5
5
|
|
|
6
6
|
from celldetective.gui.gui_utils import center_window, color_from_state
|
|
7
7
|
from superqt import QLabeledDoubleSlider, QLabeledDoubleRangeSlider, QSearchableComboBox
|
|
8
|
-
from celldetective.utils import
|
|
9
|
-
from celldetective.io import
|
|
10
|
-
|
|
11
|
-
from celldetective.gui.gui_utils import FigureCanvas, color_from_status, color_from_class, ExportPlotBtn
|
|
12
|
-
import json
|
|
8
|
+
from celldetective.utils import _get_img_num_per_channel
|
|
9
|
+
from celldetective.io import load_frames, get_experiment_metadata, get_experiment_labels, locate_labels
|
|
10
|
+
from celldetective.gui.gui_utils import FigureCanvas, color_from_status, color_from_class
|
|
13
11
|
import numpy as np
|
|
14
12
|
from superqt.fonticon import icon
|
|
15
13
|
from fonticon_mdi6 import MDI6
|
|
16
14
|
import os
|
|
17
|
-
from glob import glob
|
|
18
15
|
import matplotlib.pyplot as plt
|
|
19
|
-
from matplotlib.ticker import MultipleLocator
|
|
20
16
|
from tqdm import tqdm
|
|
21
17
|
import gc
|
|
22
18
|
from matplotlib.animation import FuncAnimation
|
|
23
19
|
from matplotlib.cm import tab10
|
|
24
20
|
import pandas as pd
|
|
25
21
|
from sklearn.preprocessing import MinMaxScaler
|
|
26
|
-
from celldetective.gui import
|
|
22
|
+
from celldetective.gui import CelldetectiveWidget
|
|
27
23
|
from celldetective.measure import contour_of_instance_segmentation
|
|
24
|
+
from celldetective.utils import pretty_table
|
|
25
|
+
from celldetective.gui.base_annotator import BaseAnnotator
|
|
28
26
|
|
|
29
|
-
class
|
|
27
|
+
class EventAnnotator(BaseAnnotator):
|
|
30
28
|
"""
|
|
31
29
|
UI to set tracking parameters for bTrack.
|
|
32
30
|
|
|
33
31
|
"""
|
|
34
32
|
|
|
35
|
-
def __init__(self,
|
|
33
|
+
def __init__(self, *args, **kwargs):
|
|
36
34
|
|
|
37
|
-
super().__init__()
|
|
38
|
-
|
|
39
|
-
center_window(self)
|
|
40
|
-
self.proceed = True
|
|
41
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
42
|
-
|
|
43
|
-
self.parent_window = parent_window
|
|
35
|
+
super().__init__(*args, **kwargs)
|
|
44
36
|
self.setWindowTitle("Signal annotator")
|
|
45
|
-
self.mode = self.parent_window.mode
|
|
46
|
-
self.pos = self.parent_window.parent_window.pos
|
|
47
|
-
self.exp_dir = self.parent_window.exp_dir
|
|
48
|
-
self.PxToUm = self.parent_window.parent_window.PxToUm
|
|
49
|
-
self.n_signals = 3
|
|
50
|
-
self.soft_path = get_software_location()
|
|
51
|
-
self.recently_modified = False
|
|
52
|
-
self.selection = []
|
|
53
|
-
|
|
54
|
-
if self.mode == "targets":
|
|
55
|
-
self.instructions_path = self.exp_dir + os.sep.join(['configs', 'signal_annotator_config_targets.json'])
|
|
56
|
-
self.trajectories_path = self.pos + os.sep.join(['output','tables','trajectories_targets.csv'])
|
|
57
|
-
elif self.mode == "effectors":
|
|
58
|
-
self.instructions_path = self.exp_dir + os.sep.join(['configs', 'signal_annotator_config_effectors.json'])
|
|
59
|
-
self.trajectories_path = self.pos + os.sep.join(['output','tables','trajectories_effectors.csv'])
|
|
60
|
-
|
|
61
|
-
self.screen_height = self.parent_window.parent_window.parent_window.screen_height
|
|
62
|
-
self.screen_width = self.parent_window.parent_window.parent_window.screen_width
|
|
63
|
-
#self.setMinimumHeight(int(0.8*self.screen_height))
|
|
64
|
-
self.value_magnitude = 1
|
|
65
37
|
|
|
66
38
|
# default params
|
|
67
39
|
self.class_name = 'class'
|
|
68
40
|
self.time_name = 't0'
|
|
69
41
|
self.status_name = 'status'
|
|
70
42
|
|
|
71
|
-
self.locate_stack()
|
|
43
|
+
# self.locate_stack()
|
|
72
44
|
if not self.proceed:
|
|
73
45
|
self.close()
|
|
74
46
|
else:
|
|
75
|
-
self.load_annotator_config()
|
|
76
|
-
self.locate_tracks()
|
|
47
|
+
#self.load_annotator_config()
|
|
48
|
+
#self.locate_tracks()
|
|
77
49
|
self.prepare_stack()
|
|
78
50
|
|
|
79
|
-
self.generate_signal_choices()
|
|
80
51
|
self.frame_lbl = QLabel('frame: ')
|
|
81
52
|
self.looped_animation()
|
|
82
|
-
self.
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
super().resizeEvent(event)
|
|
89
|
-
|
|
90
|
-
try:
|
|
91
|
-
self.cell_fig.tight_layout()
|
|
92
|
-
except:
|
|
93
|
-
pass
|
|
94
|
-
|
|
95
|
-
def populate_widget(self):
|
|
96
|
-
|
|
97
|
-
"""
|
|
98
|
-
Create the multibox design.
|
|
99
|
-
|
|
100
|
-
"""
|
|
101
|
-
|
|
102
|
-
self.button_widget = QWidget()
|
|
103
|
-
main_layout = QHBoxLayout()
|
|
104
|
-
self.button_widget.setLayout(main_layout)
|
|
105
|
-
|
|
106
|
-
main_layout.setContentsMargins(30, 30, 30, 30)
|
|
107
|
-
self.left_panel = QVBoxLayout()
|
|
108
|
-
self.left_panel.setContentsMargins(30, 5, 30, 5)
|
|
109
|
-
self.left_panel.setSpacing(3)
|
|
110
|
-
|
|
111
|
-
self.right_panel = QVBoxLayout()
|
|
112
|
-
|
|
113
|
-
class_hbox = QHBoxLayout()
|
|
114
|
-
class_hbox.setContentsMargins(0,0,0,0)
|
|
115
|
-
class_hbox.addWidget(QLabel('event: '), 25)
|
|
116
|
-
self.class_choice_cb = QComboBox()
|
|
117
|
-
|
|
118
|
-
cols = np.array(self.df_tracks.columns)
|
|
119
|
-
self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
|
|
120
|
-
self.class_cols = list(cols[self.class_cols])
|
|
121
|
-
try:
|
|
122
|
-
self.class_cols.remove('class_id')
|
|
123
|
-
except Exception:
|
|
124
|
-
pass
|
|
125
|
-
try:
|
|
126
|
-
self.class_cols.remove('class_color')
|
|
127
|
-
except Exception:
|
|
128
|
-
pass
|
|
53
|
+
self.init_event_buttons()
|
|
54
|
+
self.populate_window()
|
|
55
|
+
|
|
56
|
+
self.outliers_check.hide()
|
|
57
|
+
if hasattr(self, "contrast_slider"):
|
|
58
|
+
self.im.set_clim(self.contrast_slider.value()[0], self.contrast_slider.value()[1])
|
|
129
59
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
class_hbox.addWidget(self.class_choice_cb, 70)
|
|
134
|
-
|
|
135
|
-
self.add_class_btn = QPushButton('')
|
|
136
|
-
self.add_class_btn.setStyleSheet(self.button_select_all)
|
|
137
|
-
self.add_class_btn.setIcon(icon(MDI6.plus, color="black"))
|
|
138
|
-
self.add_class_btn.setToolTip("Add a new event class")
|
|
139
|
-
self.add_class_btn.setIconSize(QSize(20, 20))
|
|
140
|
-
self.add_class_btn.clicked.connect(self.create_new_event_class)
|
|
141
|
-
class_hbox.addWidget(self.add_class_btn, 5)
|
|
142
|
-
|
|
143
|
-
self.del_class_btn = QPushButton('')
|
|
144
|
-
self.del_class_btn.setStyleSheet(self.button_select_all)
|
|
145
|
-
self.del_class_btn.setIcon(icon(MDI6.delete, color="black"))
|
|
146
|
-
self.del_class_btn.setToolTip("Delete an event class")
|
|
147
|
-
self.del_class_btn.setIconSize(QSize(20, 20))
|
|
148
|
-
self.del_class_btn.clicked.connect(self.del_event_class)
|
|
149
|
-
class_hbox.addWidget(self.del_class_btn, 5)
|
|
150
|
-
|
|
151
|
-
self.left_panel.addLayout(class_hbox,5)
|
|
152
|
-
|
|
153
|
-
self.cell_info = QLabel('')
|
|
154
|
-
self.left_panel.addWidget(self.cell_info,10)
|
|
155
|
-
|
|
156
|
-
# Annotation buttons
|
|
157
|
-
options_hbox = QHBoxLayout()
|
|
158
|
-
options_hbox.setContentsMargins(0, 0, 0, 0)
|
|
60
|
+
def init_event_buttons(self):
|
|
61
|
+
|
|
159
62
|
self.event_btn = QRadioButton('event')
|
|
160
63
|
self.event_btn.setStyleSheet(self.button_style_sheet_2)
|
|
161
64
|
self.event_btn.toggled.connect(self.enable_time_of_interest)
|
|
162
|
-
|
|
65
|
+
|
|
163
66
|
self.no_event_btn = QRadioButton('no event')
|
|
164
67
|
self.no_event_btn.setStyleSheet(self.button_style_sheet_2)
|
|
165
68
|
self.no_event_btn.toggled.connect(self.enable_time_of_interest)
|
|
166
|
-
|
|
69
|
+
|
|
167
70
|
self.else_btn = QRadioButton('else')
|
|
168
71
|
self.else_btn.setStyleSheet(self.button_style_sheet_2)
|
|
169
72
|
self.else_btn.toggled.connect(self.enable_time_of_interest)
|
|
170
|
-
|
|
73
|
+
|
|
171
74
|
self.suppr_btn = QRadioButton('remove')
|
|
172
75
|
self.suppr_btn.setToolTip('Mark for deletion. Upon saving, the cell\nwill be removed from the tables.')
|
|
173
76
|
self.suppr_btn.setStyleSheet(self.button_style_sheet_2)
|
|
174
77
|
self.suppr_btn.toggled.connect(self.enable_time_of_interest)
|
|
78
|
+
|
|
79
|
+
self.time_of_interest_label = QLabel('time of interest: ')
|
|
80
|
+
self.time_of_interest_le = QLineEdit()
|
|
175
81
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
self.
|
|
82
|
+
|
|
83
|
+
def populate_options_layout(self):
|
|
84
|
+
|
|
85
|
+
# clear options hbox
|
|
86
|
+
for i in reversed(range(self.options_hbox.count())):
|
|
87
|
+
self.options_hbox.itemAt(i).widget().setParent(None)
|
|
88
|
+
|
|
89
|
+
options_layout = QVBoxLayout()
|
|
90
|
+
# add new widgets
|
|
181
91
|
|
|
92
|
+
btn_hbox = QHBoxLayout()
|
|
93
|
+
btn_hbox.addWidget(self.event_btn, 25, alignment=Qt.AlignCenter)
|
|
94
|
+
btn_hbox.addWidget(self.no_event_btn, 25, alignment=Qt.AlignCenter)
|
|
95
|
+
btn_hbox.addWidget(self.else_btn, 25, alignment=Qt.AlignCenter)
|
|
96
|
+
btn_hbox.addWidget(self.suppr_btn, 25, alignment=Qt.AlignCenter)
|
|
97
|
+
|
|
182
98
|
time_option_hbox = QHBoxLayout()
|
|
183
99
|
time_option_hbox.setContentsMargins(0, 5, 100, 10)
|
|
184
|
-
self.time_of_interest_label = QLabel('time of interest: ')
|
|
185
100
|
time_option_hbox.addWidget(self.time_of_interest_label, 10)
|
|
186
|
-
self.time_of_interest_le = QLineEdit()
|
|
187
101
|
time_option_hbox.addWidget(self.time_of_interest_le, 15)
|
|
188
102
|
time_option_hbox.addWidget(QLabel(''), 75)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
self.
|
|
194
|
-
|
|
195
|
-
self.correct_btn.setIconSize(QSize(20, 20))
|
|
196
|
-
self.correct_btn.setStyleSheet(self.button_style_sheet)
|
|
197
|
-
self.correct_btn.clicked.connect(self.show_annotation_buttons)
|
|
198
|
-
self.correct_btn.setEnabled(False)
|
|
199
|
-
main_action_hbox.addWidget(self.correct_btn)
|
|
200
|
-
|
|
201
|
-
self.cancel_btn = QPushButton('cancel')
|
|
202
|
-
self.cancel_btn.setStyleSheet(self.button_style_sheet_2)
|
|
203
|
-
self.cancel_btn.setShortcut(QKeySequence("Esc"))
|
|
204
|
-
self.cancel_btn.setEnabled(False)
|
|
205
|
-
self.cancel_btn.clicked.connect(self.cancel_selection)
|
|
206
|
-
main_action_hbox.addWidget(self.cancel_btn)
|
|
207
|
-
self.left_panel.addLayout(main_action_hbox,5)
|
|
208
|
-
|
|
103
|
+
|
|
104
|
+
options_layout.addLayout(btn_hbox)
|
|
105
|
+
options_layout.addLayout(time_option_hbox)
|
|
106
|
+
|
|
107
|
+
self.options_hbox.addLayout(options_layout)
|
|
108
|
+
|
|
209
109
|
self.annotation_btns_to_hide = [self.event_btn, self.no_event_btn,
|
|
210
110
|
self.else_btn, self.time_of_interest_label,
|
|
211
111
|
self.time_of_interest_le, self.suppr_btn]
|
|
212
112
|
self.hide_annotation_buttons()
|
|
213
|
-
|
|
113
|
+
|
|
114
|
+
def populate_window(self):
|
|
115
|
+
|
|
116
|
+
"""
|
|
117
|
+
Create the multibox design.
|
|
214
118
|
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
super().populate_window()
|
|
122
|
+
self.populate_options_layout()
|
|
123
|
+
|
|
215
124
|
self.del_shortcut = QShortcut(Qt.Key_Delete, self) # QKeySequence("s")
|
|
216
125
|
self.del_shortcut.activated.connect(self.shortcut_suppr)
|
|
217
126
|
self.del_shortcut.setEnabled(False)
|
|
@@ -220,68 +129,8 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
220
129
|
self.no_event_shortcut.activated.connect(self.shortcut_no_event)
|
|
221
130
|
self.no_event_shortcut.setEnabled(False)
|
|
222
131
|
|
|
223
|
-
#
|
|
224
|
-
|
|
225
|
-
self.left_panel.addWidget(self.cell_fcanvas, 45)
|
|
226
|
-
|
|
227
|
-
plot_buttons_hbox = QHBoxLayout()
|
|
228
|
-
plot_buttons_hbox.setContentsMargins(0, 0, 0, 0)
|
|
229
|
-
self.normalize_features_btn = QPushButton('')
|
|
230
|
-
self.normalize_features_btn.setStyleSheet(self.button_select_all)
|
|
231
|
-
self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
|
|
232
|
-
self.normalize_features_btn.setIconSize(QSize(25, 25))
|
|
233
|
-
self.normalize_features_btn.setFixedSize(QSize(30, 30))
|
|
234
|
-
# self.normalize_features_btn.setShortcut(QKeySequence('n'))
|
|
235
|
-
self.normalize_features_btn.clicked.connect(self.normalize_features)
|
|
236
|
-
|
|
237
|
-
plot_buttons_hbox.addWidget(QLabel(''), 90)
|
|
238
|
-
plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
|
|
239
|
-
self.normalized_signals = False
|
|
240
|
-
|
|
241
|
-
self.log_btn = QPushButton()
|
|
242
|
-
self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
|
|
243
|
-
self.log_btn.setStyleSheet(self.button_select_all)
|
|
244
|
-
self.log_btn.clicked.connect(self.switch_to_log)
|
|
245
|
-
plot_buttons_hbox.addWidget(self.log_btn, 5)
|
|
246
|
-
|
|
247
|
-
self.export_plot_btn = ExportPlotBtn(self.cell_fig, export_dir = self.exp_dir)
|
|
248
|
-
plot_buttons_hbox.addWidget(self.export_plot_btn, 5)
|
|
249
|
-
|
|
250
|
-
self.left_panel.addLayout(plot_buttons_hbox,5)
|
|
251
|
-
|
|
252
|
-
signal_choice_vbox = QVBoxLayout()
|
|
253
|
-
signal_choice_vbox.setContentsMargins(30, 0, 30, 0)
|
|
254
|
-
for i in range(len(self.signal_choice_cb)):
|
|
255
|
-
hlayout = QHBoxLayout()
|
|
256
|
-
hlayout.addWidget(self.signal_choice_label[i], 20)
|
|
257
|
-
hlayout.addWidget(self.signal_choice_cb[i], 75)
|
|
258
|
-
# hlayout.addWidget(self.log_btns[i], 5)
|
|
259
|
-
signal_choice_vbox.addLayout(hlayout)
|
|
260
|
-
|
|
261
|
-
# self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
262
|
-
# self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
263
|
-
# self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
|
|
264
|
-
|
|
265
|
-
self.left_panel.addLayout(signal_choice_vbox,15)
|
|
266
|
-
|
|
267
|
-
btn_hbox = QHBoxLayout()
|
|
268
|
-
btn_hbox.setContentsMargins(0,10,0,0)
|
|
269
|
-
self.save_btn = QPushButton('Save')
|
|
270
|
-
self.save_btn.setStyleSheet(self.button_style_sheet)
|
|
271
|
-
self.save_btn.clicked.connect(self.save_trajectories)
|
|
272
|
-
btn_hbox.addWidget(self.save_btn, 90)
|
|
273
|
-
|
|
274
|
-
self.export_btn = QPushButton('')
|
|
275
|
-
self.export_btn.setStyleSheet(self.button_select_all)
|
|
276
|
-
self.export_btn.clicked.connect(self.export_signals)
|
|
277
|
-
self.export_btn.setIcon(icon(MDI6.export, color="black"))
|
|
278
|
-
self.export_btn.setIconSize(QSize(25, 25))
|
|
279
|
-
btn_hbox.addWidget(self.export_btn, 10)
|
|
280
|
-
self.left_panel.addLayout(btn_hbox,5)
|
|
281
|
-
|
|
282
|
-
# Animation
|
|
132
|
+
# Right side
|
|
283
133
|
animation_buttons_box = QHBoxLayout()
|
|
284
|
-
|
|
285
134
|
animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
|
|
286
135
|
|
|
287
136
|
self.first_frame_btn = QPushButton()
|
|
@@ -321,7 +170,6 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
321
170
|
animation_buttons_box.addWidget(self.last_frame_btn, 5, alignment=Qt.AlignRight)
|
|
322
171
|
|
|
323
172
|
self.right_panel.addLayout(animation_buttons_box, 5)
|
|
324
|
-
|
|
325
173
|
self.right_panel.addWidget(self.fcanvas, 90)
|
|
326
174
|
|
|
327
175
|
if not self.rgb_mode:
|
|
@@ -330,7 +178,7 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
330
178
|
self.contrast_slider = QLabeledDoubleRangeSlider()
|
|
331
179
|
self.contrast_slider.setSingleStep(0.001)
|
|
332
180
|
self.contrast_slider.setTickInterval(0.001)
|
|
333
|
-
self.contrast_slider.setOrientation(
|
|
181
|
+
self.contrast_slider.setOrientation(Qt.Horizontal)
|
|
334
182
|
self.contrast_slider.setRange(
|
|
335
183
|
*[np.nanpercentile(self.stack, 0.001), np.nanpercentile(self.stack, 99.999)])
|
|
336
184
|
self.contrast_slider.setValue(
|
|
@@ -339,99 +187,16 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
339
187
|
contrast_hbox.addWidget(QLabel('contrast: '))
|
|
340
188
|
contrast_hbox.addWidget(self.contrast_slider, 90)
|
|
341
189
|
self.right_panel.addLayout(contrast_hbox, 5)
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
# self.interval_slider.setRange(1, 10000)
|
|
350
|
-
# self.interval_slider.setValue(self.speed)
|
|
351
|
-
# self.interval_slider.valueChanged.connect(self.interval_slider_action)
|
|
352
|
-
# speed_hbox.addWidget(QLabel('interval (ms): '))
|
|
353
|
-
# speed_hbox.addWidget(self.interval_slider,90)
|
|
354
|
-
# self.right_panel.addLayout(speed_hbox, 10)
|
|
355
|
-
|
|
356
|
-
# self.populate_left_panel()
|
|
357
|
-
# grid.addLayout(self.left_side, 0, 0, 1, 1)
|
|
358
|
-
|
|
359
|
-
main_layout.addLayout(self.left_panel, 35)
|
|
360
|
-
main_layout.addLayout(self.right_panel, 65)
|
|
361
|
-
self.button_widget.adjustSize()
|
|
362
|
-
|
|
363
|
-
self.compute_status_and_colors(0)
|
|
364
|
-
|
|
365
|
-
self.setCentralWidget(self.button_widget)
|
|
366
|
-
self.show()
|
|
190
|
+
|
|
191
|
+
if self.class_choice_cb.currentText()!="":
|
|
192
|
+
self.compute_status_and_colors(0)
|
|
193
|
+
|
|
194
|
+
self.no_event_shortcut = QShortcut(QKeySequence("n"), self) # QKeySequence("s")
|
|
195
|
+
self.no_event_shortcut.activated.connect(self.shortcut_no_event)
|
|
196
|
+
self.no_event_shortcut.setEnabled(False)
|
|
367
197
|
|
|
368
198
|
QApplication.processEvents()
|
|
369
199
|
|
|
370
|
-
def del_event_class(self):
|
|
371
|
-
|
|
372
|
-
msgBox = QMessageBox()
|
|
373
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
374
|
-
msgBox.setText(
|
|
375
|
-
f"You are about to delete event class {self.class_choice_cb.currentText()}. The associated time and\nstatus will also be deleted. Do you still want to proceed?")
|
|
376
|
-
msgBox.setWindowTitle("Warning")
|
|
377
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
378
|
-
returnValue = msgBox.exec()
|
|
379
|
-
if returnValue == QMessageBox.No:
|
|
380
|
-
return None
|
|
381
|
-
else:
|
|
382
|
-
class_to_delete = self.class_choice_cb.currentText()
|
|
383
|
-
time_to_delete = class_to_delete.replace('class', 't')
|
|
384
|
-
status_to_delete = class_to_delete.replace('class', 'status')
|
|
385
|
-
cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
|
|
386
|
-
for c in cols_to_delete:
|
|
387
|
-
try:
|
|
388
|
-
self.df_tracks = self.df_tracks.drop([c], axis=1)
|
|
389
|
-
except Exception as e:
|
|
390
|
-
print(e)
|
|
391
|
-
item_idx = self.class_choice_cb.findText(class_to_delete)
|
|
392
|
-
self.class_choice_cb.removeItem(item_idx)
|
|
393
|
-
|
|
394
|
-
def create_new_event_class(self):
|
|
395
|
-
|
|
396
|
-
# display qwidget to name the event
|
|
397
|
-
self.newClassWidget = QWidget()
|
|
398
|
-
self.newClassWidget.setWindowTitle('Create new event class')
|
|
399
|
-
|
|
400
|
-
layout = QVBoxLayout()
|
|
401
|
-
self.newClassWidget.setLayout(layout)
|
|
402
|
-
name_hbox = QHBoxLayout()
|
|
403
|
-
name_hbox.addWidget(QLabel('event name: '), 25)
|
|
404
|
-
self.class_name_le = QLineEdit('event')
|
|
405
|
-
name_hbox.addWidget(self.class_name_le, 75)
|
|
406
|
-
layout.addLayout(name_hbox)
|
|
407
|
-
|
|
408
|
-
class_labels = ['event', 'no event', 'else']
|
|
409
|
-
layout.addWidget(QLabel('prefill: '))
|
|
410
|
-
radio_box = QHBoxLayout()
|
|
411
|
-
self.class_option_rb = [QRadioButton() for i in range(3)]
|
|
412
|
-
for i, c in enumerate(self.class_option_rb):
|
|
413
|
-
if i == 0:
|
|
414
|
-
c.setChecked(True)
|
|
415
|
-
c.setText(class_labels[i])
|
|
416
|
-
radio_box.addWidget(c, 33, alignment=Qt.AlignCenter)
|
|
417
|
-
layout.addLayout(radio_box)
|
|
418
|
-
|
|
419
|
-
btn_hbox = QHBoxLayout()
|
|
420
|
-
submit_btn = QPushButton('submit')
|
|
421
|
-
cancel_btn = QPushButton('cancel')
|
|
422
|
-
btn_hbox.addWidget(cancel_btn, 50)
|
|
423
|
-
btn_hbox.addWidget(submit_btn, 50)
|
|
424
|
-
layout.addLayout(btn_hbox)
|
|
425
|
-
|
|
426
|
-
submit_btn.clicked.connect(self.write_new_event_class)
|
|
427
|
-
cancel_btn.clicked.connect(self.close_without_new_class)
|
|
428
|
-
|
|
429
|
-
self.newClassWidget.show()
|
|
430
|
-
center_window(self.newClassWidget)
|
|
431
|
-
|
|
432
|
-
# Prefill with class value
|
|
433
|
-
# write in table
|
|
434
|
-
|
|
435
200
|
def write_new_event_class(self):
|
|
436
201
|
|
|
437
202
|
if self.class_name_le.text() == '':
|
|
@@ -476,8 +241,8 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
476
241
|
|
|
477
242
|
self.newClassWidget.close()
|
|
478
243
|
|
|
479
|
-
def close_without_new_class(self):
|
|
480
|
-
|
|
244
|
+
# def close_without_new_class(self):
|
|
245
|
+
# self.newClassWidget.close()
|
|
481
246
|
|
|
482
247
|
def compute_status_and_colors(self, i):
|
|
483
248
|
|
|
@@ -522,36 +287,14 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
522
287
|
|
|
523
288
|
self.fcanvas.canvas.draw()
|
|
524
289
|
|
|
525
|
-
|
|
526
|
-
def contrast_slider_action(self):
|
|
527
|
-
|
|
528
|
-
"""
|
|
529
|
-
Recontrast the imshow as the contrast slider is moved.
|
|
530
|
-
"""
|
|
531
|
-
|
|
532
|
-
self.vmin = self.contrast_slider.value()[0]
|
|
533
|
-
self.vmax = self.contrast_slider.value()[1]
|
|
534
|
-
self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
|
|
535
|
-
self.fcanvas.canvas.draw_idle()
|
|
536
|
-
|
|
537
290
|
def cancel_selection(self):
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
self.correct_btn.setEnabled(False)
|
|
541
|
-
self.correct_btn.setText('correct')
|
|
542
|
-
self.cancel_btn.setEnabled(False)
|
|
543
|
-
|
|
544
|
-
try:
|
|
545
|
-
self.selection.pop(0)
|
|
546
|
-
except Exception as e:
|
|
547
|
-
print(f"L 536 {e=}")
|
|
548
|
-
|
|
291
|
+
|
|
292
|
+
super().cancel_selection()
|
|
549
293
|
try:
|
|
550
294
|
for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
|
|
551
|
-
self.colors[t][idx, 0] = self.previous_color[k][0]
|
|
552
295
|
self.colors[t][idx, 1] = self.previous_color[k][1]
|
|
553
296
|
except Exception as e:
|
|
554
|
-
|
|
297
|
+
pass
|
|
555
298
|
|
|
556
299
|
def hide_annotation_buttons(self):
|
|
557
300
|
|
|
@@ -657,167 +400,6 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
657
400
|
|
|
658
401
|
self.selection.pop(0)
|
|
659
402
|
|
|
660
|
-
# self.fcanvas.canvas.draw()
|
|
661
|
-
|
|
662
|
-
def locate_stack(self):
|
|
663
|
-
|
|
664
|
-
"""
|
|
665
|
-
Locate the target movie.
|
|
666
|
-
|
|
667
|
-
"""
|
|
668
|
-
|
|
669
|
-
movies = glob(self.pos + os.sep.join(["movie", f"{self.parent_window.parent_window.movie_prefix}*.tif"]))
|
|
670
|
-
|
|
671
|
-
if len(movies) == 0:
|
|
672
|
-
msgBox = QMessageBox()
|
|
673
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
674
|
-
msgBox.setText("No movie is detected in the experiment folder.\nPlease check the stack prefix...")
|
|
675
|
-
msgBox.setWindowTitle("Warning")
|
|
676
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
677
|
-
returnValue = msgBox.exec()
|
|
678
|
-
if returnValue == QMessageBox.Ok:
|
|
679
|
-
self.proceed = False
|
|
680
|
-
self.close()
|
|
681
|
-
else:
|
|
682
|
-
self.close()
|
|
683
|
-
else:
|
|
684
|
-
self.stack_path = movies[0]
|
|
685
|
-
self.len_movie = self.parent_window.parent_window.len_movie
|
|
686
|
-
len_movie_auto = auto_load_number_of_frames(self.stack_path)
|
|
687
|
-
if len_movie_auto is not None:
|
|
688
|
-
self.len_movie = len_movie_auto
|
|
689
|
-
exp_config = self.exp_dir + "config.ini"
|
|
690
|
-
self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
|
|
691
|
-
self.channel_names = np.array(self.channel_names)
|
|
692
|
-
self.channels = np.array(self.channels)
|
|
693
|
-
self.nbr_channels = len(self.channels)
|
|
694
|
-
|
|
695
|
-
def locate_tracks(self):
|
|
696
|
-
|
|
697
|
-
"""
|
|
698
|
-
Locate the tracks.
|
|
699
|
-
"""
|
|
700
|
-
|
|
701
|
-
if not os.path.exists(self.trajectories_path):
|
|
702
|
-
|
|
703
|
-
msgBox = QMessageBox()
|
|
704
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
705
|
-
msgBox.setText("The trajectories cannot be detected.")
|
|
706
|
-
msgBox.setWindowTitle("Warning")
|
|
707
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
708
|
-
returnValue = msgBox.exec()
|
|
709
|
-
if returnValue == QMessageBox.Yes:
|
|
710
|
-
self.close()
|
|
711
|
-
else:
|
|
712
|
-
|
|
713
|
-
# Load and prep tracks
|
|
714
|
-
self.df_tracks = pd.read_csv(self.trajectories_path)
|
|
715
|
-
self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
|
|
716
|
-
|
|
717
|
-
cols = np.array(self.df_tracks.columns)
|
|
718
|
-
self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
|
|
719
|
-
self.class_cols = list(cols[self.class_cols])
|
|
720
|
-
try:
|
|
721
|
-
self.class_cols.remove('class_id')
|
|
722
|
-
except:
|
|
723
|
-
pass
|
|
724
|
-
try:
|
|
725
|
-
self.class_cols.remove('class_color')
|
|
726
|
-
except:
|
|
727
|
-
pass
|
|
728
|
-
if len(self.class_cols) > 0:
|
|
729
|
-
self.class_name = self.class_cols[0]
|
|
730
|
-
self.expected_status = 'status'
|
|
731
|
-
suffix = self.class_name.replace('class', '').replace('_', '')
|
|
732
|
-
if suffix != '':
|
|
733
|
-
self.expected_status += '_' + suffix
|
|
734
|
-
self.expected_time = 't_' + suffix
|
|
735
|
-
else:
|
|
736
|
-
self.expected_time = 't0'
|
|
737
|
-
self.time_name = self.expected_time
|
|
738
|
-
self.status_name = self.expected_status
|
|
739
|
-
else:
|
|
740
|
-
self.class_name = 'class'
|
|
741
|
-
self.time_name = 't0'
|
|
742
|
-
self.status_name = 'status'
|
|
743
|
-
|
|
744
|
-
if self.time_name in self.df_tracks.columns and self.class_name in self.df_tracks.columns and not self.status_name in self.df_tracks.columns:
|
|
745
|
-
# only create the status column if it does not exist to not erase static classification results
|
|
746
|
-
self.make_status_column()
|
|
747
|
-
elif self.time_name in self.df_tracks.columns and self.class_name in self.df_tracks.columns:
|
|
748
|
-
# all good, do nothing
|
|
749
|
-
pass
|
|
750
|
-
else:
|
|
751
|
-
if not self.status_name in self.df_tracks.columns:
|
|
752
|
-
self.df_tracks[self.status_name] = 0
|
|
753
|
-
self.df_tracks['status_color'] = color_from_status(0)
|
|
754
|
-
self.df_tracks['class_color'] = color_from_class(1)
|
|
755
|
-
|
|
756
|
-
if not self.class_name in self.df_tracks.columns:
|
|
757
|
-
self.df_tracks[self.class_name] = 1
|
|
758
|
-
if not self.time_name in self.df_tracks.columns:
|
|
759
|
-
self.df_tracks[self.time_name] = -1
|
|
760
|
-
|
|
761
|
-
self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
|
|
762
|
-
self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
|
|
763
|
-
|
|
764
|
-
self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
|
|
765
|
-
self.df_tracks['x_anim'] = self.df_tracks['POSITION_X'] * self.fraction
|
|
766
|
-
self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y'] * self.fraction
|
|
767
|
-
self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
|
|
768
|
-
self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
|
|
769
|
-
|
|
770
|
-
self.extract_scatter_from_trajectories()
|
|
771
|
-
self.track_of_interest = self.df_tracks['TRACK_ID'].min()
|
|
772
|
-
|
|
773
|
-
self.loc_t = []
|
|
774
|
-
self.loc_idx = []
|
|
775
|
-
for t in range(len(self.tracks)):
|
|
776
|
-
indices = np.where(self.tracks[t] == self.track_of_interest)[0]
|
|
777
|
-
if len(indices) > 0:
|
|
778
|
-
self.loc_t.append(t)
|
|
779
|
-
self.loc_idx.append(indices[0])
|
|
780
|
-
|
|
781
|
-
self.MinMaxScaler = MinMaxScaler()
|
|
782
|
-
self.columns_to_rescale = list(self.df_tracks.columns)
|
|
783
|
-
#self.columns_to_rescale = self.df_tracks.select_dtypes(exclude=['object']).columns
|
|
784
|
-
|
|
785
|
-
# is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
|
|
786
|
-
# is_number_test = is_number(self.df_tracks.dtypes)
|
|
787
|
-
# self.columns_to_rescale = [col for t,col in zip(is_number_test,self.df_tracks.columns) if t]
|
|
788
|
-
# print(self.columns_to_rescale)
|
|
789
|
-
|
|
790
|
-
cols_to_remove = ['status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME', 'x_anim', 'y_anim', 't',
|
|
791
|
-
'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
|
|
792
|
-
'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',] + self.class_cols
|
|
793
|
-
|
|
794
|
-
meta = get_experiment_metadata(self.exp_dir)
|
|
795
|
-
if meta is not None:
|
|
796
|
-
keys = list(meta.keys())
|
|
797
|
-
cols_to_remove.extend(keys)
|
|
798
|
-
|
|
799
|
-
labels = get_experiment_labels(self.exp_dir)
|
|
800
|
-
if labels is not None:
|
|
801
|
-
keys = list(labels.keys())
|
|
802
|
-
cols_to_remove.extend(labels)
|
|
803
|
-
|
|
804
|
-
cols = np.array(list(self.df_tracks.columns))
|
|
805
|
-
time_cols = np.array([c.startswith('t_') for c in cols])
|
|
806
|
-
time_cols = list(cols[time_cols])
|
|
807
|
-
cols_to_remove += time_cols
|
|
808
|
-
#cols_to_remove.extend(self.df_tracks.select_dtypes(include=['object']).columns)
|
|
809
|
-
|
|
810
|
-
for tr in cols_to_remove:
|
|
811
|
-
try:
|
|
812
|
-
self.columns_to_rescale.remove(tr)
|
|
813
|
-
except:
|
|
814
|
-
pass
|
|
815
|
-
# print(f'column {tr} could not be found...')
|
|
816
|
-
|
|
817
|
-
x = self.df_tracks[self.columns_to_rescale].values
|
|
818
|
-
self.MinMaxScaler.fit(x)
|
|
819
|
-
|
|
820
|
-
# self.loc_t, self.loc_idx = np.where(self.tracks==self.track_of_interest)
|
|
821
403
|
|
|
822
404
|
def make_status_column(self):
|
|
823
405
|
|
|
@@ -829,12 +411,14 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
829
411
|
cclass = group[self.class_name].to_numpy()[0]
|
|
830
412
|
timeline = group['FRAME'].to_numpy()
|
|
831
413
|
status = np.zeros_like(timeline)
|
|
414
|
+
|
|
832
415
|
if t0 > 0:
|
|
833
|
-
status[timeline >= t0] = 1.
|
|
834
|
-
if cclass == 2:
|
|
835
|
-
|
|
416
|
+
status[timeline >= t0] = 1.
|
|
417
|
+
# if cclass == 2:
|
|
418
|
+
# status[:] = 1.
|
|
836
419
|
if cclass > 2:
|
|
837
420
|
status[:] = 42
|
|
421
|
+
|
|
838
422
|
status_color = [color_from_status(s) for s in status]
|
|
839
423
|
class_color = [color_from_class(cclass) for i in range(len(status))]
|
|
840
424
|
|
|
@@ -852,7 +436,7 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
852
436
|
|
|
853
437
|
to_remove = ['TRACK_ID', 'FRAME', 'x_anim', 'y_anim', 't', 'state', 'generation', 'root', 'parent', 'class_id',
|
|
854
438
|
'class', 't0', 'POSITION_X', 'POSITION_Y', 'position', 'well', 'well_index', 'well_name',
|
|
855
|
-
'pos_name', 'index','class_color','status_color']
|
|
439
|
+
'pos_name', 'index','class_color','status_color','dummy','group_color']
|
|
856
440
|
|
|
857
441
|
meta = get_experiment_metadata(self.exp_dir)
|
|
858
442
|
if meta is not None:
|
|
@@ -882,7 +466,11 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
882
466
|
for i in range(len(self.signal_choice_cb)):
|
|
883
467
|
|
|
884
468
|
signal_choice = self.signal_choice_cb[i].currentText()
|
|
885
|
-
|
|
469
|
+
lbl = signal_choice
|
|
470
|
+
n_cut = 35
|
|
471
|
+
if len(lbl)>n_cut:
|
|
472
|
+
lbl = lbl[:(n_cut-3)]+'...'
|
|
473
|
+
self.lines[i].set_label(lbl)
|
|
886
474
|
|
|
887
475
|
if signal_choice == "--":
|
|
888
476
|
self.lines[i].set_xdata([])
|
|
@@ -911,9 +499,10 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
911
499
|
self.line_dt.set_xdata([t0, t0])
|
|
912
500
|
self.line_dt.set_ydata([min_val, max_val])
|
|
913
501
|
|
|
914
|
-
self.cell_ax.legend()
|
|
502
|
+
self.cell_ax.legend(fontsize=8)
|
|
915
503
|
self.cell_fcanvas.canvas.draw()
|
|
916
504
|
except Exception as e:
|
|
505
|
+
print(e)
|
|
917
506
|
pass
|
|
918
507
|
|
|
919
508
|
if len(range_values)>0:
|
|
@@ -942,53 +531,53 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
942
531
|
self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
|
|
943
532
|
self.tracks.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, 'TRACK_ID'].to_numpy())
|
|
944
533
|
|
|
945
|
-
def load_annotator_config(self):
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
534
|
+
# def load_annotator_config(self):
|
|
535
|
+
#
|
|
536
|
+
# """
|
|
537
|
+
# Load settings from config or set default values.
|
|
538
|
+
# """
|
|
539
|
+
#
|
|
540
|
+
# if os.path.exists(self.instructions_path):
|
|
541
|
+
# with open(self.instructions_path, 'r') as f:
|
|
542
|
+
#
|
|
543
|
+
# instructions = json.load(f)
|
|
544
|
+
#
|
|
545
|
+
# if 'rgb_mode' in instructions:
|
|
546
|
+
# self.rgb_mode = instructions['rgb_mode']
|
|
547
|
+
# else:
|
|
548
|
+
# self.rgb_mode = False
|
|
549
|
+
#
|
|
550
|
+
# if 'percentile_mode' in instructions:
|
|
551
|
+
# self.percentile_mode = instructions['percentile_mode']
|
|
552
|
+
# else:
|
|
553
|
+
# self.percentile_mode = True
|
|
554
|
+
#
|
|
555
|
+
# if 'channels' in instructions:
|
|
556
|
+
# self.target_channels = instructions['channels']
|
|
557
|
+
# else:
|
|
558
|
+
# self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
|
|
559
|
+
#
|
|
560
|
+
# if 'fraction' in instructions:
|
|
561
|
+
# self.fraction = float(instructions['fraction'])
|
|
562
|
+
# else:
|
|
563
|
+
# self.fraction = 0.25
|
|
564
|
+
#
|
|
565
|
+
# if 'interval' in instructions:
|
|
566
|
+
# self.anim_interval = int(instructions['interval'])
|
|
567
|
+
# else:
|
|
568
|
+
# self.anim_interval = 1
|
|
569
|
+
#
|
|
570
|
+
# if 'log' in instructions:
|
|
571
|
+
# self.log_option = instructions['log']
|
|
572
|
+
# else:
|
|
573
|
+
# self.log_option = False
|
|
574
|
+
# else:
|
|
575
|
+
# self.rgb_mode = False
|
|
576
|
+
# self.log_option = False
|
|
577
|
+
# self.percentile_mode = True
|
|
578
|
+
# self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
|
|
579
|
+
# self.fraction = 0.25
|
|
580
|
+
# self.anim_interval = 1
|
|
992
581
|
|
|
993
582
|
def prepare_stack(self):
|
|
994
583
|
|
|
@@ -1074,76 +663,17 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
1074
663
|
self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
|
|
1075
664
|
self.fcanvas.canvas.draw()
|
|
1076
665
|
|
|
1077
|
-
def
|
|
1078
|
-
|
|
1079
|
-
self.cell_fig, self.cell_ax = plt.subplots(tight_layout=True)
|
|
1080
|
-
self.cell_fcanvas = FigureCanvas(self.cell_fig, interactive=True)
|
|
1081
|
-
self.cell_ax.clear()
|
|
1082
|
-
|
|
1083
|
-
spacing = 0.5
|
|
1084
|
-
minorLocator = MultipleLocator(1)
|
|
1085
|
-
self.cell_ax.xaxis.set_minor_locator(minorLocator)
|
|
1086
|
-
self.cell_ax.xaxis.set_major_locator(MultipleLocator(5))
|
|
1087
|
-
self.cell_ax.grid(which='major')
|
|
1088
|
-
self.cell_ax.set_xlabel("time [frame]")
|
|
1089
|
-
self.cell_ax.set_ylabel("signal")
|
|
666
|
+
def select_single_cell(self, index, timepoint):
|
|
1090
667
|
|
|
1091
|
-
self.
|
|
1092
|
-
self.
|
|
668
|
+
self.correct_btn.setEnabled(True)
|
|
669
|
+
self.cancel_btn.setEnabled(True)
|
|
670
|
+
self.del_shortcut.setEnabled(True)
|
|
671
|
+
self.no_event_shortcut.setEnabled(True)
|
|
1093
672
|
|
|
1094
|
-
self.
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
self.lines[i].set_label(f'signal {i}')
|
|
1099
|
-
|
|
1100
|
-
min_val, max_val = self.cell_ax.get_ylim()
|
|
1101
|
-
self.line_dt, = self.cell_ax.plot([-1, -1], [min_val, max_val], c="k", linestyle="--")
|
|
1102
|
-
|
|
1103
|
-
self.cell_ax.set_xlim(0, self.len_movie)
|
|
1104
|
-
self.cell_ax.legend()
|
|
1105
|
-
self.cell_fcanvas.canvas.draw()
|
|
1106
|
-
|
|
1107
|
-
self.plot_signals()
|
|
1108
|
-
|
|
1109
|
-
def on_scatter_pick(self, event):
|
|
1110
|
-
|
|
1111
|
-
self.event = event
|
|
1112
|
-
|
|
1113
|
-
self.correct_btn.disconnect()
|
|
1114
|
-
self.correct_btn.clicked.connect(self.show_annotation_buttons)
|
|
1115
|
-
|
|
1116
|
-
ind = event.ind
|
|
1117
|
-
|
|
1118
|
-
if len(ind) > 1:
|
|
1119
|
-
# More than one point in vicinity
|
|
1120
|
-
datax, datay = [self.positions[self.framedata][i, 0] for i in ind], [self.positions[self.framedata][i, 1]
|
|
1121
|
-
for i in ind]
|
|
1122
|
-
msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
|
|
1123
|
-
dist = np.sqrt((np.array(datax) - msx) ** 2 + (np.array(datay) - msy) ** 2)
|
|
1124
|
-
ind = [ind[np.argmin(dist)]]
|
|
1125
|
-
|
|
1126
|
-
if len(ind) > 0 and (len(self.selection) == 0):
|
|
1127
|
-
|
|
1128
|
-
self.selection.append([ind[0],self.framedata])
|
|
1129
|
-
self.select_single_cell(ind[0], self.framedata)
|
|
1130
|
-
|
|
1131
|
-
elif len(ind) > 0 and len(self.selection) == 1:
|
|
1132
|
-
self.cancel_btn.click()
|
|
1133
|
-
else:
|
|
1134
|
-
pass
|
|
1135
|
-
|
|
1136
|
-
def select_single_cell(self, index, timepoint):
|
|
1137
|
-
|
|
1138
|
-
self.correct_btn.setEnabled(True)
|
|
1139
|
-
self.cancel_btn.setEnabled(True)
|
|
1140
|
-
self.del_shortcut.setEnabled(True)
|
|
1141
|
-
self.no_event_shortcut.setEnabled(True)
|
|
1142
|
-
|
|
1143
|
-
self.track_of_interest = self.tracks[timepoint][index]
|
|
1144
|
-
print(f'You selected cell #{self.track_of_interest}...')
|
|
1145
|
-
self.give_cell_information()
|
|
1146
|
-
self.plot_signals()
|
|
673
|
+
self.track_of_interest = self.tracks[timepoint][index]
|
|
674
|
+
print(f'You selected cell #{self.track_of_interest}...')
|
|
675
|
+
self.give_cell_information()
|
|
676
|
+
self.plot_signals()
|
|
1147
677
|
|
|
1148
678
|
self.loc_t = []
|
|
1149
679
|
self.loc_idx = []
|
|
@@ -1159,11 +689,6 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
1159
689
|
self.colors[t][idx] = 'lime'
|
|
1160
690
|
|
|
1161
691
|
|
|
1162
|
-
def shortcut_suppr(self):
|
|
1163
|
-
self.correct_btn.click()
|
|
1164
|
-
self.suppr_btn.click()
|
|
1165
|
-
self.correct_btn.click()
|
|
1166
|
-
|
|
1167
692
|
def shortcut_no_event(self):
|
|
1168
693
|
self.correct_btn.click()
|
|
1169
694
|
self.no_event_btn.click()
|
|
@@ -1232,25 +757,6 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
1232
757
|
self.anim.pause()
|
|
1233
758
|
self.stop_btn.clicked.connect(self.start)
|
|
1234
759
|
|
|
1235
|
-
def start(self):
|
|
1236
|
-
'''
|
|
1237
|
-
Starts interactive animation. Adds the draw frame command to the GUI
|
|
1238
|
-
handler, calls show to start the event loop.
|
|
1239
|
-
'''
|
|
1240
|
-
self.start_btn.setShortcut(QKeySequence(""))
|
|
1241
|
-
|
|
1242
|
-
self.last_frame_btn.setEnabled(True)
|
|
1243
|
-
self.last_frame_btn.clicked.connect(self.set_last_frame)
|
|
1244
|
-
|
|
1245
|
-
self.first_frame_btn.setEnabled(True)
|
|
1246
|
-
self.first_frame_btn.clicked.connect(self.set_first_frame)
|
|
1247
|
-
|
|
1248
|
-
self.start_btn.hide()
|
|
1249
|
-
self.stop_btn.show()
|
|
1250
|
-
|
|
1251
|
-
self.anim.event_source.start()
|
|
1252
|
-
self.stop_btn.clicked.connect(self.stop)
|
|
1253
|
-
|
|
1254
760
|
def give_cell_information(self):
|
|
1255
761
|
|
|
1256
762
|
cell_selected = f"cell: {self.track_of_interest}\n"
|
|
@@ -1268,17 +774,10 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
1268
774
|
self.df_tracks = self.df_tracks.drop(self.df_tracks[self.df_tracks[self.class_name] > 2].index)
|
|
1269
775
|
self.df_tracks.to_csv(self.trajectories_path, index=False)
|
|
1270
776
|
print('Table successfully exported...')
|
|
1271
|
-
self.
|
|
777
|
+
if self.class_choice_cb.currentText()!="":
|
|
778
|
+
self.compute_status_and_colors(0)
|
|
1272
779
|
self.extract_scatter_from_trajectories()
|
|
1273
780
|
|
|
1274
|
-
# self.give_cell_information()
|
|
1275
|
-
|
|
1276
|
-
# def interval_slider_action(self):
|
|
1277
|
-
|
|
1278
|
-
# print(dir(self.anim.event_source))
|
|
1279
|
-
|
|
1280
|
-
# self.anim.event_source.interval = self.interval_slider.value()
|
|
1281
|
-
# self.anim.event_source._timer_set_interval()
|
|
1282
781
|
|
|
1283
782
|
def set_last_frame(self):
|
|
1284
783
|
|
|
@@ -1310,6 +809,11 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
1310
809
|
|
|
1311
810
|
self.first_key = 0
|
|
1312
811
|
self.anim._drawn_artists = self.draw_frame(0)
|
|
812
|
+
self.vmin = self.contrast_slider.value()[0]
|
|
813
|
+
self.vmax = self.contrast_slider.value()[1]
|
|
814
|
+
self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
|
|
815
|
+
|
|
816
|
+
|
|
1313
817
|
self.anim._drawn_artists = sorted(self.anim._drawn_artists, key=lambda x: x.get_zorder())
|
|
1314
818
|
for a in self.anim._drawn_artists:
|
|
1315
819
|
a.set_visible(True)
|
|
@@ -1323,324 +827,109 @@ class SignalAnnotator(QMainWindow, Styles):
|
|
|
1323
827
|
self.stop_btn.clicked.connect(self.start)
|
|
1324
828
|
self.start_btn.setShortcut(QKeySequence("f"))
|
|
1325
829
|
|
|
1326
|
-
def export_signals(self):
|
|
1327
|
-
|
|
1328
|
-
auto_dataset_name = self.pos.split(os.sep)[-4] + '_' + self.pos.split(os.sep)[-2] + '.npy'
|
|
1329
|
-
|
|
1330
|
-
if self.normalized_signals:
|
|
1331
|
-
self.normalize_features_btn.click()
|
|
1332
|
-
|
|
1333
|
-
training_set = []
|
|
1334
|
-
cols = self.df_tracks.columns
|
|
1335
|
-
tracks = np.unique(self.df_tracks["TRACK_ID"].to_numpy())
|
|
1336
|
-
|
|
1337
|
-
for track in tracks:
|
|
1338
|
-
# Add all signals at given track
|
|
1339
|
-
signals = {}
|
|
1340
|
-
for c in cols:
|
|
1341
|
-
signals.update({c: self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, c].to_numpy()})
|
|
1342
|
-
time_of_interest = self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, self.time_name].to_numpy()[0]
|
|
1343
|
-
cclass = self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, self.class_name].to_numpy()[0]
|
|
1344
|
-
signals.update({"time_of_interest": time_of_interest, "class": cclass})
|
|
1345
|
-
# Here auto add all available channels
|
|
1346
|
-
training_set.append(signals)
|
|
1347
|
-
|
|
1348
|
-
pathsave = QFileDialog.getSaveFileName(self, "Select file name", self.exp_dir + auto_dataset_name, ".npy")[0]
|
|
1349
|
-
if pathsave != '':
|
|
1350
|
-
if not pathsave.endswith(".npy"):
|
|
1351
|
-
pathsave += ".npy"
|
|
1352
|
-
try:
|
|
1353
|
-
np.save(pathsave, training_set)
|
|
1354
|
-
print(f'File successfully written in {pathsave}.')
|
|
1355
|
-
except Exception as e:
|
|
1356
|
-
print(f"Error {e}...")
|
|
1357
|
-
|
|
1358
|
-
def normalize_features(self):
|
|
1359
|
-
|
|
1360
|
-
x = self.df_tracks[self.columns_to_rescale].values
|
|
1361
|
-
|
|
1362
|
-
if not self.normalized_signals:
|
|
1363
|
-
x = self.MinMaxScaler.transform(x)
|
|
1364
|
-
self.df_tracks[self.columns_to_rescale] = x
|
|
1365
|
-
self.plot_signals()
|
|
1366
|
-
self.normalized_signals = True
|
|
1367
|
-
self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="#1565c0"))
|
|
1368
|
-
self.normalize_features_btn.setIconSize(QSize(25, 25))
|
|
1369
|
-
else:
|
|
1370
|
-
x = self.MinMaxScaler.inverse_transform(x)
|
|
1371
|
-
self.df_tracks[self.columns_to_rescale] = x
|
|
1372
|
-
self.plot_signals()
|
|
1373
|
-
self.normalized_signals = False
|
|
1374
|
-
self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
|
|
1375
|
-
self.normalize_features_btn.setIconSize(QSize(25, 25))
|
|
1376
|
-
|
|
1377
|
-
def switch_to_log(self):
|
|
1378
|
-
|
|
1379
|
-
"""
|
|
1380
|
-
Better would be to create a log(quantity) and plot it...
|
|
1381
|
-
"""
|
|
1382
|
-
|
|
1383
|
-
try:
|
|
1384
|
-
if self.cell_ax.get_yscale()=='linear':
|
|
1385
|
-
ymin,ymax = self.cell_ax.get_ylim()
|
|
1386
|
-
self.cell_ax.set_yscale('log')
|
|
1387
|
-
self.log_btn.setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
1388
|
-
self.cell_ax.set_ylim(self.value_magnitude, ymax)
|
|
1389
|
-
else:
|
|
1390
|
-
self.cell_ax.set_yscale('linear')
|
|
1391
|
-
self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
|
|
1392
|
-
except Exception as e:
|
|
1393
|
-
print(e)
|
|
1394
830
|
|
|
1395
|
-
|
|
1396
|
-
self.cell_fcanvas.canvas.draw_idle()
|
|
831
|
+
class MeasureAnnotator(BaseAnnotator):
|
|
1397
832
|
|
|
1398
|
-
|
|
833
|
+
def __init__(self, *args, **kwargs):
|
|
834
|
+
|
|
835
|
+
super().__init__(read_config=False, *args, **kwargs)
|
|
1399
836
|
|
|
1400
|
-
|
|
837
|
+
self.setWindowTitle("Static annotator")
|
|
1401
838
|
|
|
1402
|
-
QMainWindow.__init__(self)
|
|
1403
|
-
self.parent_window = parent_window
|
|
1404
|
-
self.setWindowTitle("Signal annotator")
|
|
1405
|
-
self.mode = self.parent_window.mode
|
|
1406
|
-
self.pos = self.parent_window.parent_window.pos
|
|
1407
|
-
self.exp_dir = self.parent_window.exp_dir
|
|
1408
|
-
self.n_signals = 3
|
|
1409
|
-
self.soft_path = get_software_location()
|
|
1410
|
-
self.recently_modified = False
|
|
1411
|
-
self.selection = []
|
|
1412
839
|
self.int_validator = QIntValidator()
|
|
1413
840
|
self.current_alpha=0.5
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
self.screen_height = self.parent_window.parent_window.parent_window.screen_height
|
|
1422
|
-
self.screen_width = self.parent_window.parent_window.parent_window.screen_width
|
|
841
|
+
self.value_magnitude = 1
|
|
842
|
+
|
|
843
|
+
epsilon = 0.01
|
|
844
|
+
self.observed_min_intensity = 0
|
|
845
|
+
self.observed_max_intensity = 0 + epsilon
|
|
846
|
+
|
|
1423
847
|
self.current_frame = 0
|
|
1424
848
|
self.show_fliers = False
|
|
1425
849
|
self.status_name = 'group'
|
|
1426
850
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
self.generate_signal_choices()
|
|
1442
|
-
self.frame_lbl = QLabel('position: ')
|
|
1443
|
-
self.static_image()
|
|
1444
|
-
self.create_cell_signal_canvas()
|
|
1445
|
-
|
|
1446
|
-
self.populate_widget()
|
|
1447
|
-
self.changed_class()
|
|
1448
|
-
|
|
1449
|
-
self.setMinimumWidth(int(0.8 * self.screen_width))
|
|
1450
|
-
# self.setMaximumHeight(int(0.8*self.screen_height))
|
|
1451
|
-
self.setMinimumHeight(int(0.8 * self.screen_height))
|
|
1452
|
-
# self.setMaximumHeight(int(0.8*self.screen_height))
|
|
1453
|
-
|
|
1454
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
1455
|
-
self.previous_index = None
|
|
851
|
+
if self.proceed:
|
|
852
|
+
|
|
853
|
+
self.labels = locate_labels(self.pos, population=self.mode)
|
|
854
|
+
|
|
855
|
+
self.current_channel = 0
|
|
856
|
+
self.frame_lbl = QLabel('position: ')
|
|
857
|
+
self.static_image()
|
|
858
|
+
|
|
859
|
+
self.populate_window()
|
|
860
|
+
self.changed_class()
|
|
861
|
+
|
|
862
|
+
self.previous_index = None
|
|
863
|
+
if hasattr(self, "contrast_slider"):
|
|
864
|
+
self.im.set_clim(self.contrast_slider.value()[0], self.contrast_slider.value()[1])
|
|
1456
865
|
|
|
1457
|
-
|
|
866
|
+
else:
|
|
867
|
+
self.close()
|
|
868
|
+
|
|
869
|
+
def locate_tracks(self):
|
|
1458
870
|
|
|
1459
871
|
"""
|
|
1460
|
-
|
|
1461
|
-
|
|
872
|
+
Locate the tracks.
|
|
1462
873
|
"""
|
|
1463
874
|
|
|
1464
|
-
self.
|
|
1465
|
-
self.current_label=self.labels[self.current_frame]
|
|
1466
|
-
self.fig, self.ax = plt.subplots(tight_layout=True)
|
|
1467
|
-
self.fcanvas = FigureCanvas(self.fig, interactive=True)
|
|
1468
|
-
self.ax.clear()
|
|
1469
|
-
# print(self.current_stack.shape)
|
|
1470
|
-
self.im = self.ax.imshow(self.img, cmap='gray')
|
|
1471
|
-
self.status_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker="o",
|
|
1472
|
-
facecolors='none', edgecolors=self.colors[0][:, 0], s=200, picker=True)
|
|
1473
|
-
self.im_mask = self.ax.imshow(np.ma.masked_where(self.current_label == 0, self.current_label),
|
|
1474
|
-
cmap='viridis', interpolation='none',alpha=self.current_alpha,vmin=0,vmax=np.nanmax(self.labels.flatten()))
|
|
1475
|
-
self.ax.set_xticks([])
|
|
1476
|
-
self.ax.set_yticks([])
|
|
1477
|
-
self.ax.set_aspect('equal')
|
|
1478
|
-
|
|
1479
|
-
self.fig.set_facecolor('none') # or 'None'
|
|
1480
|
-
self.fig.canvas.setStyleSheet("background-color: black;")
|
|
1481
|
-
|
|
1482
|
-
self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
|
|
1483
|
-
self.fcanvas.canvas.draw()
|
|
1484
|
-
|
|
1485
|
-
def create_cell_signal_canvas(self):
|
|
1486
|
-
|
|
1487
|
-
self.cell_fig, self.cell_ax = plt.subplots()
|
|
1488
|
-
self.cell_fcanvas = FigureCanvas(self.cell_fig, interactive=False)
|
|
1489
|
-
self.cell_ax.clear()
|
|
1490
|
-
|
|
1491
|
-
# spacing = 0.5
|
|
1492
|
-
# minorLocator = MultipleLocator(1)
|
|
1493
|
-
# self.cell_ax.xaxis.set_minor_locator(minorLocator)
|
|
1494
|
-
# self.cell_ax.xaxis.set_major_locator(MultipleLocator(5))
|
|
1495
|
-
self.cell_ax.grid(which='major')
|
|
1496
|
-
self.cell_ax.set_xlabel("time [frame]")
|
|
1497
|
-
self.cell_ax.set_ylabel("signal")
|
|
1498
|
-
|
|
1499
|
-
self.cell_fig.set_facecolor('none') # or 'None'
|
|
1500
|
-
self.cell_fig.canvas.setStyleSheet("background-color: transparent;")
|
|
1501
|
-
|
|
1502
|
-
self.lines = [
|
|
1503
|
-
self.cell_ax.plot([np.linspace(0, self.len_movie - 1, self.len_movie)], [np.zeros(self.len_movie)])[0] for
|
|
1504
|
-
i in range(len(self.signal_choice_cb))]
|
|
1505
|
-
for i in range(len(self.lines)):
|
|
1506
|
-
self.lines[i].set_label(f'signal {i}')
|
|
1507
|
-
|
|
1508
|
-
min_val, max_val = self.cell_ax.get_ylim()
|
|
1509
|
-
self.line_dt, = self.cell_ax.plot([-1, -1], [min_val, max_val], c="k", linestyle="--")
|
|
1510
|
-
|
|
1511
|
-
self.cell_ax.set_xlim(0, self.len_movie)
|
|
1512
|
-
self.cell_fcanvas.canvas.draw()
|
|
1513
|
-
|
|
1514
|
-
self.plot_signals()
|
|
1515
|
-
|
|
1516
|
-
def plot_signals(self):
|
|
1517
|
-
|
|
1518
|
-
#try:
|
|
1519
|
-
current_frame = self.current_frame # Assuming you have a variable for the current frame
|
|
1520
|
-
|
|
1521
|
-
yvalues = []
|
|
1522
|
-
all_yvalues = []
|
|
1523
|
-
current_yvalues = []
|
|
1524
|
-
all_median_values = []
|
|
1525
|
-
labels = []
|
|
1526
|
-
|
|
1527
|
-
for i in range(len(self.signal_choice_cb)):
|
|
1528
|
-
|
|
1529
|
-
signal_choice = self.signal_choice_cb[i].currentText()
|
|
1530
|
-
|
|
1531
|
-
if signal_choice != "--":
|
|
1532
|
-
if 'TRACK_ID' in self.df_tracks.columns:
|
|
1533
|
-
ydata = self.df_tracks.loc[
|
|
1534
|
-
(self.df_tracks['TRACK_ID'] == self.track_of_interest) &
|
|
1535
|
-
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1536
|
-
else:
|
|
1537
|
-
ydata = self.df_tracks.loc[
|
|
1538
|
-
(self.df_tracks['ID'] == self.track_of_interest), signal_choice].to_numpy()
|
|
1539
|
-
all_ydata = self.df_tracks.loc[:, signal_choice].to_numpy()
|
|
1540
|
-
ydataNaN = ydata
|
|
1541
|
-
ydata = ydata[ydata == ydata] # remove nan
|
|
1542
|
-
current_ydata = self.df_tracks.loc[
|
|
1543
|
-
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1544
|
-
current_ydata = current_ydata[current_ydata == current_ydata]
|
|
1545
|
-
all_ydata = all_ydata[all_ydata == all_ydata]
|
|
1546
|
-
yvalues.extend(ydataNaN)
|
|
1547
|
-
current_yvalues.append(current_ydata)
|
|
1548
|
-
all_yvalues.append(all_ydata)
|
|
1549
|
-
labels.append(signal_choice)
|
|
1550
|
-
|
|
1551
|
-
self.cell_ax.clear()
|
|
1552
|
-
|
|
1553
|
-
if len(yvalues) > 0:
|
|
1554
|
-
self.cell_ax.boxplot(all_yvalues, showfliers=self.show_fliers)
|
|
1555
|
-
ylim = self.cell_ax.get_ylim()
|
|
1556
|
-
self.cell_ax.set_ylim(ylim)
|
|
1557
|
-
x_pos = np.arange(len(all_yvalues)) + 1
|
|
1558
|
-
|
|
1559
|
-
for index, feature in enumerate(current_yvalues):
|
|
1560
|
-
x_values_strip = (index + 1) + np.random.normal(0, 0.04, size=len(
|
|
1561
|
-
feature))
|
|
1562
|
-
self.cell_ax.plot(x_values_strip, feature, marker='o', linestyle='None', color=tab10.colors[0],
|
|
1563
|
-
alpha=0.1)
|
|
1564
|
-
|
|
1565
|
-
self.cell_ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3], alpha=1)
|
|
1566
|
-
|
|
875
|
+
if not os.path.exists(self.trajectories_path):
|
|
1567
876
|
|
|
877
|
+
msgBox = QMessageBox()
|
|
878
|
+
msgBox.setIcon(QMessageBox.Warning)
|
|
879
|
+
msgBox.setText("The trajectories cannot be detected.")
|
|
880
|
+
msgBox.setWindowTitle("Warning")
|
|
881
|
+
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
882
|
+
returnValue = msgBox.exec()
|
|
883
|
+
if returnValue == QMessageBox.Yes:
|
|
884
|
+
self.close()
|
|
1568
885
|
else:
|
|
1569
|
-
self.cell_ax.text(0.5, 0.5, "No data available", horizontalalignment='center',
|
|
1570
|
-
verticalalignment='center', transform=self.cell_ax.transAxes)
|
|
1571
886
|
|
|
1572
|
-
|
|
887
|
+
# Load and prep tracks
|
|
888
|
+
self.df_tracks = pd.read_csv(self.trajectories_path)
|
|
889
|
+
if 'TRACK_ID' in self.df_tracks.columns:
|
|
890
|
+
self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
|
|
891
|
+
else:
|
|
892
|
+
self.df_tracks = self.df_tracks.sort_values(by=['ID', 'FRAME'])
|
|
1573
893
|
|
|
1574
|
-
|
|
1575
|
-
|
|
894
|
+
cols = np.array(self.df_tracks.columns)
|
|
895
|
+
self.class_cols = np.array([c.startswith('group') or c.startswith('class') for c in list(self.df_tracks.columns)])
|
|
896
|
+
self.class_cols = list(cols[self.class_cols])
|
|
1576
897
|
|
|
1577
|
-
|
|
898
|
+
to_remove = ['class_id','group_color','class_color']
|
|
899
|
+
for col in to_remove:
|
|
900
|
+
try:
|
|
901
|
+
self.class_cols.remove(col)
|
|
902
|
+
except:
|
|
903
|
+
pass
|
|
904
|
+
|
|
905
|
+
if len(self.class_cols) > 0:
|
|
906
|
+
self.status_name = self.class_cols[0]
|
|
907
|
+
else:
|
|
908
|
+
self.status_name = 'group'
|
|
1578
909
|
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
continue
|
|
1586
|
-
else:
|
|
1587
|
-
maxx = np.max(self.df_tracks.loc[:, signal].to_numpy().flatten())
|
|
1588
|
-
minn = np.min(self.df_tracks.loc[:, signal].to_numpy().flatten())
|
|
1589
|
-
min_values.append(minn)
|
|
1590
|
-
max_values.append(maxx)
|
|
910
|
+
if self.status_name not in self.df_tracks.columns:
|
|
911
|
+
# only create the status column if it does not exist to not erase static classification results
|
|
912
|
+
self.make_status_column()
|
|
913
|
+
else:
|
|
914
|
+
# all good, do nothing
|
|
915
|
+
pass
|
|
1591
916
|
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
917
|
+
all_states = self.df_tracks.loc[:, self.status_name].tolist()
|
|
918
|
+
all_states = np.array(all_states)
|
|
919
|
+
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
920
|
+
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
1596
921
|
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
if signal_choice != "--":
|
|
1603
|
-
#print(f'plot signal {signal_choice} for cell {self.track_of_interest} at frame {current_frame}')
|
|
1604
|
-
if 'TRACK_ID' in self.df_tracks.columns:
|
|
1605
|
-
ydata = self.df_tracks.loc[
|
|
1606
|
-
(self.df_tracks['TRACK_ID'] == self.track_of_interest) &
|
|
1607
|
-
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1608
|
-
else:
|
|
1609
|
-
ydata = self.df_tracks.loc[
|
|
1610
|
-
(self.df_tracks['ID'] == self.track_of_interest) &
|
|
1611
|
-
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1612
|
-
ydata = ydata[ydata == ydata] # remove nan
|
|
1613
|
-
yvalues.extend(ydata)
|
|
1614
|
-
x_pos = np.arange(len(yvalues)) + 1
|
|
1615
|
-
ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3],
|
|
1616
|
-
alpha=1) # Plot red points representing cells
|
|
1617
|
-
self.cell_fcanvas.canvas.draw()
|
|
922
|
+
self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
|
|
923
|
+
self.df_tracks['x_anim'] = self.df_tracks['POSITION_X']
|
|
924
|
+
self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y']
|
|
925
|
+
self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
|
|
926
|
+
self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
|
|
1618
927
|
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
# More than one point in vicinity
|
|
1623
|
-
datax, datay = [self.positions[self.framedata][i, 0] for i in ind], [self.positions[self.framedata][i, 1]
|
|
1624
|
-
for i in ind]
|
|
1625
|
-
msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
|
|
1626
|
-
dist = np.sqrt((np.array(datax) - msx) ** 2 + (np.array(datay) - msy) ** 2)
|
|
1627
|
-
ind = [ind[np.argmin(dist)]]
|
|
1628
|
-
|
|
1629
|
-
if len(ind) > 0 and (len(self.selection) == 0):
|
|
1630
|
-
ind = ind[0]
|
|
1631
|
-
self.selection.append(ind)
|
|
1632
|
-
self.correct_btn.setEnabled(True)
|
|
1633
|
-
self.cancel_btn.setEnabled(True)
|
|
1634
|
-
self.del_shortcut.setEnabled(True)
|
|
1635
|
-
self.no_event_shortcut.setEnabled(True)
|
|
1636
|
-
self.track_of_interest = self.tracks[self.framedata][ind]
|
|
1637
|
-
print(f'You selected cell #{self.track_of_interest}...')
|
|
1638
|
-
self.give_cell_information()
|
|
1639
|
-
if len(self.cell_ax.lines) > 0:
|
|
1640
|
-
self.cell_ax.lines[-1].remove() # Remove the last line (red points) from the plot
|
|
1641
|
-
self.plot_red_points(self.cell_ax)
|
|
928
|
+
self.extract_scatter_from_trajectories()
|
|
929
|
+
if 'TRACK_ID' in self.df_tracks.columns:
|
|
930
|
+
self.track_of_interest = self.df_tracks.dropna(subset='TRACK_ID')['TRACK_ID'].min()
|
|
1642
931
|
else:
|
|
1643
|
-
self.
|
|
932
|
+
self.track_of_interest = self.df_tracks.dropna(subset='ID')['ID'].min()
|
|
1644
933
|
|
|
1645
934
|
self.loc_t = []
|
|
1646
935
|
self.loc_idx = []
|
|
@@ -1650,196 +939,103 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1650
939
|
self.loc_t.append(t)
|
|
1651
940
|
self.loc_idx.append(indices[0])
|
|
1652
941
|
|
|
1653
|
-
self.
|
|
1654
|
-
|
|
1655
|
-
self.previous_color.append(self.colors[t][idx].copy())
|
|
1656
|
-
self.colors[t][idx] = 'lime'
|
|
1657
|
-
|
|
1658
|
-
elif len(ind) > 0 and len(self.selection) == 1:
|
|
1659
|
-
self.cancel_btn.click()
|
|
1660
|
-
else:
|
|
1661
|
-
pass
|
|
1662
|
-
self.draw_frame(self.current_frame)
|
|
1663
|
-
self.fcanvas.canvas.draw()
|
|
1664
|
-
|
|
1665
|
-
def populate_widget(self):
|
|
1666
|
-
|
|
1667
|
-
"""
|
|
1668
|
-
Create the multibox design.
|
|
1669
|
-
|
|
1670
|
-
"""
|
|
1671
|
-
|
|
1672
|
-
self.button_widget = QWidget()
|
|
1673
|
-
main_layout = QHBoxLayout()
|
|
1674
|
-
self.button_widget.setLayout(main_layout)
|
|
1675
|
-
|
|
1676
|
-
main_layout.setContentsMargins(30, 30, 30, 30)
|
|
1677
|
-
|
|
1678
|
-
self.left_panel = QVBoxLayout()
|
|
1679
|
-
self.left_panel.setContentsMargins(30, 5, 30, 5)
|
|
1680
|
-
#self.left_panel.setSpacing(3)
|
|
1681
|
-
|
|
1682
|
-
self.right_panel = QVBoxLayout()
|
|
1683
|
-
|
|
1684
|
-
class_hbox = QHBoxLayout()
|
|
1685
|
-
class_hbox.setContentsMargins(0,0,0,0)
|
|
1686
|
-
class_hbox.setSpacing(0)
|
|
1687
|
-
|
|
1688
|
-
class_hbox.addWidget(QLabel('characteristic \n group: '), 25)
|
|
1689
|
-
self.class_choice_cb = QComboBox()
|
|
1690
|
-
|
|
1691
|
-
cols = np.array(self.df_tracks.columns)
|
|
1692
|
-
self.class_cols = np.array([c.startswith('group') or c.startswith('status') for c in list(self.df_tracks.columns)])
|
|
1693
|
-
self.class_cols = list(cols[self.class_cols])
|
|
1694
|
-
|
|
1695
|
-
try:
|
|
1696
|
-
self.class_cols.remove('group_id')
|
|
1697
|
-
except Exception:
|
|
1698
|
-
pass
|
|
1699
|
-
try:
|
|
1700
|
-
self.class_cols.remove('group_color')
|
|
1701
|
-
except Exception:
|
|
1702
|
-
pass
|
|
942
|
+
self.MinMaxScaler = MinMaxScaler()
|
|
943
|
+
self.columns_to_rescale = list(self.df_tracks.columns)
|
|
1703
944
|
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
945
|
+
cols_to_remove = ['group', 'group_color', 'status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME',
|
|
946
|
+
'x_anim', 'y_anim', 't','dummy','group_color',
|
|
947
|
+
'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
|
|
948
|
+
'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
|
|
949
|
+
'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent', 'ID'] + self.class_cols
|
|
1707
950
|
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
self.add_class_btn.setIconSize(QSize(20, 20))
|
|
1713
|
-
self.add_class_btn.clicked.connect(self.create_new_event_class)
|
|
1714
|
-
class_hbox.addWidget(self.add_class_btn, 5)
|
|
951
|
+
meta = get_experiment_metadata(self.exp_dir)
|
|
952
|
+
if meta is not None:
|
|
953
|
+
keys = list(meta.keys())
|
|
954
|
+
cols_to_remove.extend(keys)
|
|
1715
955
|
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
self.del_class_btn.setIconSize(QSize(20, 20))
|
|
1721
|
-
self.del_class_btn.clicked.connect(self.del_event_class)
|
|
1722
|
-
class_hbox.addWidget(self.del_class_btn, 5)
|
|
956
|
+
labels = get_experiment_labels(self.exp_dir)
|
|
957
|
+
if labels is not None:
|
|
958
|
+
keys = list(labels.keys())
|
|
959
|
+
cols_to_remove.extend(labels)
|
|
1723
960
|
|
|
1724
|
-
|
|
961
|
+
for tr in cols_to_remove:
|
|
962
|
+
try:
|
|
963
|
+
self.columns_to_rescale.remove(tr)
|
|
964
|
+
except:
|
|
965
|
+
pass
|
|
1725
966
|
|
|
1726
|
-
|
|
1727
|
-
|
|
967
|
+
x = self.df_tracks[self.columns_to_rescale].values
|
|
968
|
+
self.MinMaxScaler.fit(x)
|
|
1728
969
|
|
|
970
|
+
|
|
971
|
+
def populate_options_layout(self):
|
|
972
|
+
# clear options hbox
|
|
973
|
+
for i in reversed(range(self.options_hbox.count())):
|
|
974
|
+
self.options_hbox.itemAt(i).widget().setParent(None)
|
|
975
|
+
|
|
1729
976
|
time_option_hbox = QHBoxLayout()
|
|
1730
977
|
time_option_hbox.setContentsMargins(100, 0, 100, 0)
|
|
1731
978
|
time_option_hbox.setSpacing(0)
|
|
1732
979
|
|
|
1733
980
|
self.time_of_interest_label = QLabel('phenotype: ')
|
|
1734
981
|
time_option_hbox.addWidget(self.time_of_interest_label, 30)
|
|
982
|
+
|
|
1735
983
|
self.time_of_interest_le = QLineEdit()
|
|
1736
984
|
self.time_of_interest_le.setValidator(self.int_validator)
|
|
1737
985
|
time_option_hbox.addWidget(self.time_of_interest_le)
|
|
1738
|
-
|
|
1739
|
-
self.
|
|
1740
|
-
self.
|
|
1741
|
-
self.
|
|
1742
|
-
self.
|
|
1743
|
-
self.
|
|
1744
|
-
|
|
1745
|
-
self.
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
self.
|
|
1752
|
-
self.
|
|
1753
|
-
self.
|
|
1754
|
-
self.
|
|
1755
|
-
|
|
1756
|
-
self.
|
|
1757
|
-
|
|
986
|
+
|
|
987
|
+
self.suppr_btn = QPushButton('')
|
|
988
|
+
self.suppr_btn.setStyleSheet(self.button_select_all)
|
|
989
|
+
self.suppr_btn.setIcon(icon(MDI6.delete, color="black"))
|
|
990
|
+
self.suppr_btn.setToolTip("Delete cell")
|
|
991
|
+
self.suppr_btn.setIconSize(QSize(20, 20))
|
|
992
|
+
self.suppr_btn.clicked.connect(self.del_cell)
|
|
993
|
+
time_option_hbox.addWidget(self.suppr_btn)
|
|
994
|
+
|
|
995
|
+
self.options_hbox.addLayout(time_option_hbox)
|
|
996
|
+
|
|
997
|
+
def update_widgets(self):
|
|
998
|
+
|
|
999
|
+
self.class_label.setText('characteristic \n group: ')
|
|
1000
|
+
self.update_class_cb()
|
|
1001
|
+
self.add_class_btn.setToolTip("Add a new characteristic group")
|
|
1002
|
+
self.del_class_btn.setToolTip("Delete a characteristic group")
|
|
1003
|
+
|
|
1004
|
+
self.export_btn.disconnect()
|
|
1005
|
+
self.export_btn.clicked.connect(self.export_measurements)
|
|
1006
|
+
|
|
1007
|
+
def update_class_cb(self):
|
|
1008
|
+
|
|
1009
|
+
self.class_choice_cb.disconnect()
|
|
1010
|
+
self.class_choice_cb.clear()
|
|
1011
|
+
cols = np.array(self.df_tracks.columns)
|
|
1012
|
+
self.class_cols = np.array([c.startswith('group') or c.startswith('status') for c in list(self.df_tracks.columns)])
|
|
1013
|
+
self.class_cols = list(cols[self.class_cols])
|
|
1758
1014
|
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1015
|
+
to_remove = ['group_id', 'group_color', 'class_id', 'class_color', 'status_color']
|
|
1016
|
+
for col in to_remove:
|
|
1017
|
+
try:
|
|
1018
|
+
self.class_cols.remove(col)
|
|
1019
|
+
except Exception:
|
|
1020
|
+
pass
|
|
1021
|
+
|
|
1022
|
+
self.class_choice_cb.addItems(self.class_cols)
|
|
1023
|
+
self.class_choice_cb.currentIndexChanged.connect(self.changed_class)
|
|
1766
1024
|
|
|
1025
|
+
def populate_window(self):
|
|
1026
|
+
|
|
1027
|
+
super().populate_window()
|
|
1028
|
+
# Left panel updates
|
|
1029
|
+
self.populate_options_layout()
|
|
1030
|
+
self.update_widgets()
|
|
1031
|
+
|
|
1767
1032
|
self.annotation_btns_to_hide = [self.time_of_interest_label,
|
|
1768
1033
|
self.time_of_interest_le,
|
|
1769
|
-
self.
|
|
1034
|
+
self.suppr_btn]
|
|
1770
1035
|
self.hide_annotation_buttons()
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
self.del_shortcut = QShortcut(Qt.Key_Delete, self) # QKeySequence("s")
|
|
1774
|
-
self.del_shortcut.activated.connect(self.shortcut_suppr)
|
|
1775
|
-
self.del_shortcut.setEnabled(False)
|
|
1776
|
-
|
|
1777
|
-
self.no_event_shortcut = QShortcut(QKeySequence("n"), self) # QKeySequence("s")
|
|
1778
|
-
self.no_event_shortcut.activated.connect(self.shortcut_no_event)
|
|
1779
|
-
self.no_event_shortcut.setEnabled(False)
|
|
1780
|
-
|
|
1781
|
-
# Cell signals
|
|
1782
|
-
self.cell_fcanvas.setMinimumHeight(int(0.2*self.screen_height))
|
|
1783
|
-
self.left_panel.addWidget(self.cell_fcanvas,90)
|
|
1784
|
-
|
|
1785
|
-
plot_buttons_hbox = QHBoxLayout()
|
|
1786
|
-
plot_buttons_hbox.setContentsMargins(0, 0, 0, 0)
|
|
1787
|
-
self.outliers_check = QCheckBox('Show outliers')
|
|
1788
|
-
self.outliers_check.toggled.connect(self.show_outliers)
|
|
1789
|
-
|
|
1790
|
-
self.normalize_features_btn = QPushButton('')
|
|
1791
|
-
self.normalize_features_btn.setStyleSheet(self.button_select_all)
|
|
1792
|
-
self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
|
|
1793
|
-
self.normalize_features_btn.setIconSize(QSize(25, 25))
|
|
1794
|
-
self.normalize_features_btn.setFixedSize(QSize(30, 30))
|
|
1795
|
-
# self.normalize_features_btn.setShortcut(QKeySequence('n'))
|
|
1796
|
-
self.normalize_features_btn.clicked.connect(self.normalize_features)
|
|
1797
|
-
|
|
1798
|
-
plot_buttons_hbox.addWidget(QLabel(''), 90)
|
|
1799
|
-
plot_buttons_hbox.addWidget(self.outliers_check)
|
|
1800
|
-
plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
|
|
1801
|
-
self.normalized_signals = False
|
|
1802
|
-
|
|
1803
|
-
self.log_btn = QPushButton()
|
|
1804
|
-
self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
|
|
1805
|
-
self.log_btn.setStyleSheet(self.button_select_all)
|
|
1806
|
-
self.log_btn.clicked.connect(self.switch_to_log)
|
|
1807
|
-
plot_buttons_hbox.addWidget(self.log_btn, 5)
|
|
1808
|
-
|
|
1809
|
-
self.left_panel.addLayout(plot_buttons_hbox,5)
|
|
1810
|
-
|
|
1811
|
-
signal_choice_vbox = QVBoxLayout()
|
|
1812
|
-
signal_choice_vbox.setContentsMargins(30, 0, 30, 50)
|
|
1813
|
-
for i in range(len(self.signal_choice_cb)):
|
|
1814
|
-
hlayout = QHBoxLayout()
|
|
1815
|
-
hlayout.addWidget(self.signal_choice_label[i], 20)
|
|
1816
|
-
hlayout.addWidget(self.signal_choice_cb[i], 75)
|
|
1817
|
-
# hlayout.addWidget(self.log_btns[i], 5)
|
|
1818
|
-
signal_choice_vbox.addLayout(hlayout)
|
|
1819
|
-
|
|
1820
|
-
# self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
1821
|
-
# self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
1822
|
-
# self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
|
|
1823
|
-
|
|
1824
|
-
self.left_panel.addLayout(signal_choice_vbox,10)
|
|
1825
|
-
|
|
1826
|
-
btn_hbox = QHBoxLayout()
|
|
1827
|
-
self.save_btn = QPushButton('Save')
|
|
1828
|
-
self.save_btn.setStyleSheet(self.button_style_sheet)
|
|
1829
|
-
self.save_btn.clicked.connect(self.save_trajectories)
|
|
1830
|
-
btn_hbox.addWidget(self.save_btn, 90)
|
|
1831
|
-
|
|
1832
|
-
self.export_btn = QPushButton('')
|
|
1833
|
-
self.export_btn.setStyleSheet(self.button_select_all)
|
|
1834
|
-
self.export_btn.clicked.connect(self.export_measurements)
|
|
1835
|
-
self.export_btn.setIcon(icon(MDI6.export, color="black"))
|
|
1836
|
-
self.export_btn.setIconSize(QSize(25, 25))
|
|
1837
|
-
btn_hbox.addWidget(self.export_btn, 10)
|
|
1838
|
-
self.left_panel.addLayout(btn_hbox,5)
|
|
1839
|
-
|
|
1840
|
-
# Animation
|
|
1036
|
+
|
|
1037
|
+
# Right panel
|
|
1841
1038
|
animation_buttons_box = QHBoxLayout()
|
|
1842
|
-
|
|
1843
1039
|
animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
|
|
1844
1040
|
|
|
1845
1041
|
self.first_frame_btn = QPushButton()
|
|
@@ -1886,7 +1082,7 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1886
1082
|
|
|
1887
1083
|
self.contrast_slider.setSingleStep(0.001)
|
|
1888
1084
|
self.contrast_slider.setTickInterval(0.001)
|
|
1889
|
-
self.contrast_slider.setOrientation(
|
|
1085
|
+
self.contrast_slider.setOrientation(Qt.Horizontal)
|
|
1890
1086
|
self.contrast_slider.setRange(
|
|
1891
1087
|
*[np.nanpercentile(self.img, 0.001), np.nanpercentile(self.img, 99.999)])
|
|
1892
1088
|
self.contrast_slider.setValue(
|
|
@@ -1897,7 +1093,7 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1897
1093
|
self.right_panel.addLayout(contrast_hbox, 5)
|
|
1898
1094
|
self.alpha_slider = QLabeledDoubleSlider()
|
|
1899
1095
|
self.alpha_slider.setSingleStep(0.001)
|
|
1900
|
-
self.alpha_slider.setOrientation(
|
|
1096
|
+
self.alpha_slider.setOrientation(Qt.Horizontal)
|
|
1901
1097
|
self.alpha_slider.setRange(0, 1)
|
|
1902
1098
|
self.alpha_slider.setValue(self.current_alpha)
|
|
1903
1099
|
self.alpha_slider.setDecimals(3)
|
|
@@ -1915,25 +1111,180 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1915
1111
|
self.choose_channel.currentIndexChanged.connect(self.changed_channel)
|
|
1916
1112
|
channel_hbox.addWidget(self.choose_channel)
|
|
1917
1113
|
self.right_panel.addLayout(channel_hbox, 5)
|
|
1114
|
+
|
|
1115
|
+
self.draw_frame(0)
|
|
1116
|
+
self.vmin = self.contrast_slider.value()[0]
|
|
1117
|
+
self.vmax = self.contrast_slider.value()[1]
|
|
1118
|
+
self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
|
|
1119
|
+
|
|
1120
|
+
self.fcanvas.canvas.draw()
|
|
1121
|
+
self.plot_signals()
|
|
1122
|
+
|
|
1123
|
+
def static_image(self):
|
|
1918
1124
|
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
self.button_widget.adjustSize()
|
|
1125
|
+
"""
|
|
1126
|
+
Load an image.
|
|
1922
1127
|
|
|
1923
|
-
|
|
1924
|
-
self.show()
|
|
1128
|
+
"""
|
|
1925
1129
|
|
|
1926
|
-
|
|
1130
|
+
self.framedata = 0
|
|
1131
|
+
self.current_label=self.labels[self.current_frame]
|
|
1132
|
+
self.fig, self.ax = plt.subplots(tight_layout=True)
|
|
1133
|
+
self.fcanvas = FigureCanvas(self.fig, interactive=True)
|
|
1134
|
+
self.ax.clear()
|
|
1135
|
+
# print(self.current_stack.shape)
|
|
1136
|
+
self.im = self.ax.imshow(self.img, cmap='gray')
|
|
1137
|
+
self.status_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker="o",
|
|
1138
|
+
facecolors='none', edgecolors=self.colors[0][:, 0], s=200, picker=True)
|
|
1139
|
+
self.im_mask = self.ax.imshow(np.ma.masked_where(self.current_label == 0, self.current_label),
|
|
1140
|
+
cmap='viridis', interpolation='none',alpha=self.current_alpha,vmin=0,vmax=np.nanmax(self.labels.flatten()))
|
|
1141
|
+
self.ax.set_xticks([])
|
|
1142
|
+
self.ax.set_yticks([])
|
|
1143
|
+
self.ax.set_aspect('equal')
|
|
1144
|
+
|
|
1145
|
+
self.fig.set_facecolor('none') # or 'None'
|
|
1146
|
+
self.fig.canvas.setStyleSheet("background-color: black;")
|
|
1147
|
+
|
|
1148
|
+
self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
|
|
1149
|
+
self.fcanvas.canvas.draw()
|
|
1150
|
+
|
|
1151
|
+
|
|
1152
|
+
def plot_signals(self):
|
|
1153
|
+
|
|
1154
|
+
#try:
|
|
1155
|
+
current_frame = self.current_frame # Assuming you have a variable for the current frame
|
|
1156
|
+
|
|
1157
|
+
yvalues = []
|
|
1158
|
+
all_yvalues = []
|
|
1159
|
+
current_yvalues = []
|
|
1160
|
+
labels = []
|
|
1161
|
+
range_values = []
|
|
1162
|
+
|
|
1163
|
+
for i in range(len(self.signal_choice_cb)):
|
|
1164
|
+
|
|
1165
|
+
signal_choice = self.signal_choice_cb[i].currentText()
|
|
1166
|
+
|
|
1167
|
+
if signal_choice != "--":
|
|
1168
|
+
if 'TRACK_ID' in self.df_tracks.columns:
|
|
1169
|
+
ydata = self.df_tracks.loc[
|
|
1170
|
+
(self.df_tracks['TRACK_ID'] == self.track_of_interest) &
|
|
1171
|
+
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1172
|
+
else:
|
|
1173
|
+
ydata = self.df_tracks.loc[
|
|
1174
|
+
(self.df_tracks['ID'] == self.track_of_interest), signal_choice].to_numpy()
|
|
1175
|
+
all_ydata = self.df_tracks.loc[:, signal_choice].to_numpy()
|
|
1176
|
+
ydataNaN = ydata
|
|
1177
|
+
ydata = ydata[ydata == ydata] # remove nan
|
|
1178
|
+
|
|
1179
|
+
current_ydata = self.df_tracks.loc[
|
|
1180
|
+
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1181
|
+
current_ydata = current_ydata[current_ydata == current_ydata]
|
|
1182
|
+
all_ydata = all_ydata[all_ydata == all_ydata]
|
|
1183
|
+
yvalues.extend(ydataNaN)
|
|
1184
|
+
current_yvalues.append(current_ydata)
|
|
1185
|
+
all_yvalues.append(all_ydata)
|
|
1186
|
+
range_values.extend(all_ydata)
|
|
1187
|
+
labels.append(signal_choice)
|
|
1188
|
+
|
|
1189
|
+
self.cell_ax.clear()
|
|
1190
|
+
if self.log_scale:
|
|
1191
|
+
self.cell_ax.set_yscale('log')
|
|
1192
|
+
else:
|
|
1193
|
+
self.cell_ax.set_yscale('linear')
|
|
1194
|
+
|
|
1195
|
+
if len(yvalues) > 0:
|
|
1196
|
+
try:
|
|
1197
|
+
self.cell_ax.boxplot(all_yvalues, showfliers=self.show_fliers)
|
|
1198
|
+
except Exception as e:
|
|
1199
|
+
print(f"{e=}")
|
|
1200
|
+
|
|
1201
|
+
x_pos = np.arange(len(all_yvalues)) + 1
|
|
1202
|
+
for index, feature in enumerate(current_yvalues):
|
|
1203
|
+
x_values_strip = (index + 1) + np.random.normal(0, 0.04, size=len(
|
|
1204
|
+
feature))
|
|
1205
|
+
self.cell_ax.plot(x_values_strip, feature, marker='o', linestyle='None', color=tab10.colors[0],
|
|
1206
|
+
alpha=0.1)
|
|
1207
|
+
self.cell_ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3], alpha=1)
|
|
1208
|
+
range_values = np.array(range_values)
|
|
1209
|
+
range_values = range_values[range_values==range_values]
|
|
1210
|
+
|
|
1211
|
+
if len(range_values[range_values > 0]) > 0:
|
|
1212
|
+
self.value_magnitude = np.nanmin(range_values[range_values > 0]) - 0.03 * (
|
|
1213
|
+
np.nanmax(range_values[range_values > 0]) - np.nanmin(range_values[range_values > 0]))
|
|
1214
|
+
else:
|
|
1215
|
+
self.value_magnitude = 1
|
|
1216
|
+
|
|
1217
|
+
self.non_log_ymin = np.nanmin(range_values) - 0.03 * (np.nanmax(range_values) - np.nanmin(range_values))
|
|
1218
|
+
self.non_log_ymax = np.nanmax(range_values) + 0.03 * (np.nanmax(range_values) - np.nanmin(range_values))
|
|
1219
|
+
if self.cell_ax.get_yscale() == 'linear':
|
|
1220
|
+
self.cell_ax.set_ylim(self.non_log_ymin, self.non_log_ymax)
|
|
1221
|
+
else:
|
|
1222
|
+
self.cell_ax.set_ylim(self.value_magnitude, self.non_log_ymax)
|
|
1223
|
+
else:
|
|
1224
|
+
self.cell_ax.text(0.5, 0.5, "No data available", horizontalalignment='center',
|
|
1225
|
+
verticalalignment='center', transform=self.cell_ax.transAxes)
|
|
1226
|
+
|
|
1227
|
+
self.cell_fcanvas.canvas.draw()
|
|
1228
|
+
|
|
1229
|
+
def plot_red_points(self, ax):
|
|
1230
|
+
yvalues = []
|
|
1231
|
+
current_frame = self.current_frame
|
|
1232
|
+
for i in range(len(self.signal_choice_cb)):
|
|
1233
|
+
signal_choice = self.signal_choice_cb[i].currentText()
|
|
1234
|
+
if signal_choice != "--":
|
|
1235
|
+
#print(f'plot signal {signal_choice} for cell {self.track_of_interest} at frame {current_frame}')
|
|
1236
|
+
if 'TRACK_ID' in self.df_tracks.columns:
|
|
1237
|
+
ydata = self.df_tracks.loc[
|
|
1238
|
+
(self.df_tracks['TRACK_ID'] == self.track_of_interest) &
|
|
1239
|
+
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1240
|
+
else:
|
|
1241
|
+
ydata = self.df_tracks.loc[
|
|
1242
|
+
(self.df_tracks['ID'] == self.track_of_interest) &
|
|
1243
|
+
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1244
|
+
ydata = ydata[ydata == ydata] # remove nan
|
|
1245
|
+
yvalues.extend(ydata)
|
|
1246
|
+
x_pos = np.arange(len(yvalues)) + 1
|
|
1247
|
+
ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3],
|
|
1248
|
+
alpha=1) # Plot red points representing cells
|
|
1249
|
+
self.cell_fcanvas.canvas.draw()
|
|
1927
1250
|
|
|
1928
|
-
def
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1251
|
+
def select_single_cell(self, index, timepoint):
|
|
1252
|
+
|
|
1253
|
+
self.correct_btn.setEnabled(True)
|
|
1254
|
+
self.cancel_btn.setEnabled(True)
|
|
1255
|
+
self.del_shortcut.setEnabled(True)
|
|
1256
|
+
|
|
1257
|
+
self.track_of_interest = self.tracks[timepoint][index]
|
|
1258
|
+
print(f'You selected cell #{self.track_of_interest}...')
|
|
1259
|
+
self.give_cell_information()
|
|
1260
|
+
|
|
1261
|
+
if len(self.cell_ax.lines) > 0:
|
|
1262
|
+
self.cell_ax.lines[-1].remove() # Remove the last line (red points) from the plot
|
|
1263
|
+
self.plot_red_points(self.cell_ax)
|
|
1264
|
+
else:
|
|
1265
|
+
self.plot_signals()
|
|
1266
|
+
|
|
1267
|
+
self.loc_t = []
|
|
1268
|
+
self.loc_idx = []
|
|
1269
|
+
for t in range(len(self.tracks)):
|
|
1270
|
+
indices = np.where(self.tracks[t] == self.track_of_interest)[0]
|
|
1271
|
+
if len(indices) > 0:
|
|
1272
|
+
self.loc_t.append(t)
|
|
1273
|
+
self.loc_idx.append(indices[0])
|
|
1274
|
+
|
|
1275
|
+
self.previous_color = []
|
|
1276
|
+
for t, idx in zip(self.loc_t, self.loc_idx):
|
|
1277
|
+
self.previous_color.append(self.colors[t][idx].copy())
|
|
1278
|
+
self.colors[t][idx] = 'lime'
|
|
1279
|
+
|
|
1280
|
+
self.draw_frame(self.current_frame)
|
|
1281
|
+
self.fcanvas.canvas.draw()
|
|
1936
1282
|
|
|
1283
|
+
def cancel_selection(self):
|
|
1284
|
+
super().cancel_selection()
|
|
1285
|
+
self.event = None
|
|
1286
|
+
self.draw_frame(self.current_frame)
|
|
1287
|
+
self.fcanvas.canvas.draw()
|
|
1937
1288
|
|
|
1938
1289
|
def export_measurements(self):
|
|
1939
1290
|
|
|
@@ -1994,13 +1345,11 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1994
1345
|
return None
|
|
1995
1346
|
else:
|
|
1996
1347
|
pass
|
|
1348
|
+
|
|
1997
1349
|
self.df_tracks.loc[:, self.target_class] = 0
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
self.class_cols = list(cols[self.class_cols])
|
|
2002
|
-
self.class_cols.remove('group_color')
|
|
2003
|
-
self.class_choice_cb.addItems(self.class_cols)
|
|
1350
|
+
|
|
1351
|
+
self.update_class_cb()
|
|
1352
|
+
|
|
2004
1353
|
idx = self.class_choice_cb.findText(self.target_class)
|
|
2005
1354
|
self.status_name = self.target_class
|
|
2006
1355
|
self.class_choice_cb.setCurrentIndex(idx)
|
|
@@ -2046,7 +1395,7 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2046
1395
|
def create_new_event_class(self):
|
|
2047
1396
|
|
|
2048
1397
|
# display qwidget to name the event
|
|
2049
|
-
self.newClassWidget =
|
|
1398
|
+
self.newClassWidget = CelldetectiveWidget()
|
|
2050
1399
|
self.newClassWidget.setWindowTitle('Create new characteristic group')
|
|
2051
1400
|
|
|
2052
1401
|
layout = QVBoxLayout()
|
|
@@ -2094,7 +1443,6 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2094
1443
|
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
2095
1444
|
|
|
2096
1445
|
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2097
|
-
|
|
2098
1446
|
self.extract_scatter_from_trajectories()
|
|
2099
1447
|
self.give_cell_information()
|
|
2100
1448
|
|
|
@@ -2106,15 +1454,17 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2106
1454
|
self.correct_btn.setText('correct')
|
|
2107
1455
|
self.cancel_btn.setEnabled(False)
|
|
2108
1456
|
self.del_shortcut.setEnabled(False)
|
|
2109
|
-
|
|
1457
|
+
|
|
2110
1458
|
if len(self.selection) > 0:
|
|
2111
1459
|
self.selection.pop(0)
|
|
1460
|
+
|
|
2112
1461
|
self.draw_frame(self.current_frame)
|
|
2113
1462
|
self.fcanvas.canvas.draw()
|
|
2114
1463
|
|
|
2115
1464
|
def assign_color_state(self, state):
|
|
1465
|
+
|
|
2116
1466
|
if np.isnan(state):
|
|
2117
|
-
|
|
1467
|
+
state = "nan"
|
|
2118
1468
|
return self.state_color_map[state]
|
|
2119
1469
|
|
|
2120
1470
|
def draw_frame(self, framedata):
|
|
@@ -2141,45 +1491,26 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2141
1491
|
return (self.im, self.status_scatter,self.im_mask,)
|
|
2142
1492
|
|
|
2143
1493
|
def compute_status_and_colors(self):
|
|
2144
|
-
|
|
1494
|
+
|
|
1495
|
+
self.cancel_selection()
|
|
1496
|
+
|
|
2145
1497
|
if self.class_choice_cb.currentText() == '':
|
|
2146
|
-
|
|
1498
|
+
pass
|
|
2147
1499
|
else:
|
|
2148
1500
|
self.status_name = self.class_choice_cb.currentText()
|
|
2149
1501
|
|
|
2150
|
-
print(f'{self.status_name=}')
|
|
2151
1502
|
if self.status_name not in self.df_tracks.columns:
|
|
2152
|
-
print('
|
|
1503
|
+
print('Creating a new status for visualization...')
|
|
2153
1504
|
self.make_status_column()
|
|
2154
1505
|
else:
|
|
1506
|
+
print(f'Generating per-state colors for the status "{self.status_name}"...')
|
|
2155
1507
|
all_states = self.df_tracks.loc[:, self.status_name].tolist()
|
|
2156
1508
|
all_states = np.array(all_states)
|
|
2157
1509
|
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
2158
|
-
print(f'{self.
|
|
1510
|
+
print(f'Color mapping for "{self.status_name}":')
|
|
1511
|
+
pretty_table(self.state_color_map)
|
|
2159
1512
|
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
def del_event_class(self):
|
|
2163
|
-
|
|
2164
|
-
msgBox = QMessageBox()
|
|
2165
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
2166
|
-
msgBox.setText(
|
|
2167
|
-
f"You are about to delete characteristic group {self.class_choice_cb.currentText()}. Do you still want to proceed?")
|
|
2168
|
-
msgBox.setWindowTitle("Warning")
|
|
2169
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
2170
|
-
returnValue = msgBox.exec()
|
|
2171
|
-
if returnValue == QMessageBox.No:
|
|
2172
|
-
return None
|
|
2173
|
-
else:
|
|
2174
|
-
class_to_delete = self.class_choice_cb.currentText()
|
|
2175
|
-
cols_to_delete = [class_to_delete]
|
|
2176
|
-
for c in cols_to_delete:
|
|
2177
|
-
try:
|
|
2178
|
-
self.df_tracks = self.df_tracks.drop([c], axis=1)
|
|
2179
|
-
except Exception as e:
|
|
2180
|
-
print(e)
|
|
2181
|
-
item_idx = self.class_choice_cb.findText(class_to_delete)
|
|
2182
|
-
self.class_choice_cb.removeItem(item_idx)
|
|
1513
|
+
|
|
2183
1514
|
|
|
2184
1515
|
def make_status_column(self):
|
|
2185
1516
|
if self.status_name == "state_firstdetection":
|
|
@@ -2191,126 +1522,6 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2191
1522
|
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
2192
1523
|
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2193
1524
|
|
|
2194
|
-
def locate_tracks(self):
|
|
2195
|
-
|
|
2196
|
-
"""
|
|
2197
|
-
Locate the tracks.
|
|
2198
|
-
"""
|
|
2199
|
-
|
|
2200
|
-
if not os.path.exists(self.trajectories_path):
|
|
2201
|
-
|
|
2202
|
-
msgBox = QMessageBox()
|
|
2203
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
2204
|
-
msgBox.setText("The trajectories cannot be detected.")
|
|
2205
|
-
msgBox.setWindowTitle("Warning")
|
|
2206
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
2207
|
-
returnValue = msgBox.exec()
|
|
2208
|
-
if returnValue == QMessageBox.Yes:
|
|
2209
|
-
self.close()
|
|
2210
|
-
else:
|
|
2211
|
-
|
|
2212
|
-
# Load and prep tracks
|
|
2213
|
-
self.df_tracks = pd.read_csv(self.trajectories_path)
|
|
2214
|
-
if 'TRACK_ID' in self.df_tracks.columns:
|
|
2215
|
-
self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
|
|
2216
|
-
else:
|
|
2217
|
-
self.df_tracks = self.df_tracks.sort_values(by=['ID', 'FRAME'])
|
|
2218
|
-
|
|
2219
|
-
cols = np.array(self.df_tracks.columns)
|
|
2220
|
-
self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
|
|
2221
|
-
self.class_cols = list(cols[self.class_cols])
|
|
2222
|
-
try:
|
|
2223
|
-
self.class_cols.remove('class_id')
|
|
2224
|
-
except:
|
|
2225
|
-
pass
|
|
2226
|
-
try:
|
|
2227
|
-
self.class_cols.remove('group_color')
|
|
2228
|
-
except:
|
|
2229
|
-
pass
|
|
2230
|
-
if len(self.class_cols) > 0:
|
|
2231
|
-
self.status = self.class_cols[0]
|
|
2232
|
-
|
|
2233
|
-
else:
|
|
2234
|
-
|
|
2235
|
-
self.status_name = 'group'
|
|
2236
|
-
|
|
2237
|
-
if self.status_name not in self.df_tracks.columns:
|
|
2238
|
-
# only create the status column if it does not exist to not erase static classification results
|
|
2239
|
-
self.make_status_column()
|
|
2240
|
-
else:
|
|
2241
|
-
# all good, do nothing
|
|
2242
|
-
pass
|
|
2243
|
-
# else:
|
|
2244
|
-
# if not self.status_name in self.df_tracks.columns:
|
|
2245
|
-
# self.df_tracks[self.status_name] = 0
|
|
2246
|
-
# self.df_tracks['state_color'] = color_from_status(0)
|
|
2247
|
-
# self.df_tracks['class_color'] = color_from_class(1)
|
|
2248
|
-
|
|
2249
|
-
# if not self.class_name in self.df_tracks.columns:
|
|
2250
|
-
# self.df_tracks[self.class_name] = 1
|
|
2251
|
-
# if not self.time_name in self.df_tracks.columns:
|
|
2252
|
-
# self.df_tracks[self.time_name] = -1
|
|
2253
|
-
all_states = self.df_tracks.loc[:, self.status_name].tolist()
|
|
2254
|
-
all_states = np.array(all_states)
|
|
2255
|
-
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
2256
|
-
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2257
|
-
# self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
|
|
2258
|
-
# self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
|
|
2259
|
-
|
|
2260
|
-
self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
|
|
2261
|
-
self.df_tracks['x_anim'] = self.df_tracks['POSITION_X']
|
|
2262
|
-
self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y']
|
|
2263
|
-
self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
|
|
2264
|
-
self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
|
|
2265
|
-
|
|
2266
|
-
self.extract_scatter_from_trajectories()
|
|
2267
|
-
if 'TRACK_ID' in self.df_tracks.columns:
|
|
2268
|
-
self.track_of_interest = self.df_tracks.dropna(subset='TRACK_ID')['TRACK_ID'].min()
|
|
2269
|
-
else:
|
|
2270
|
-
self.track_of_interest = self.df_tracks.dropna(subset='ID')['ID'].min()
|
|
2271
|
-
|
|
2272
|
-
self.loc_t = []
|
|
2273
|
-
self.loc_idx = []
|
|
2274
|
-
for t in range(len(self.tracks)):
|
|
2275
|
-
indices = np.where(self.tracks[t] == self.track_of_interest)[0]
|
|
2276
|
-
if len(indices) > 0:
|
|
2277
|
-
self.loc_t.append(t)
|
|
2278
|
-
self.loc_idx.append(indices[0])
|
|
2279
|
-
|
|
2280
|
-
self.MinMaxScaler = MinMaxScaler()
|
|
2281
|
-
self.columns_to_rescale = list(self.df_tracks.columns)
|
|
2282
|
-
|
|
2283
|
-
# is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
|
|
2284
|
-
# is_number_test = is_number(self.df_tracks.dtypes)
|
|
2285
|
-
# self.columns_to_rescale = [col for t,col in zip(is_number_test,self.df_tracks.columns) if t]
|
|
2286
|
-
# print(self.columns_to_rescale)
|
|
2287
|
-
|
|
2288
|
-
cols_to_remove = ['group', 'group_color', 'status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME',
|
|
2289
|
-
'x_anim', 'y_anim', 't',
|
|
2290
|
-
'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
|
|
2291
|
-
'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
|
|
2292
|
-
'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent', 'ID'] + self.class_cols
|
|
2293
|
-
|
|
2294
|
-
meta = get_experiment_metadata(self.exp_dir)
|
|
2295
|
-
if meta is not None:
|
|
2296
|
-
keys = list(meta.keys())
|
|
2297
|
-
cols_to_remove.extend(keys)
|
|
2298
|
-
|
|
2299
|
-
labels = get_experiment_labels(self.exp_dir)
|
|
2300
|
-
if labels is not None:
|
|
2301
|
-
keys = list(labels.keys())
|
|
2302
|
-
cols_to_remove.extend(labels)
|
|
2303
|
-
|
|
2304
|
-
for tr in cols_to_remove:
|
|
2305
|
-
try:
|
|
2306
|
-
self.columns_to_rescale.remove(tr)
|
|
2307
|
-
except:
|
|
2308
|
-
pass
|
|
2309
|
-
|
|
2310
|
-
# print(f'column {tr} could not be found...')
|
|
2311
|
-
|
|
2312
|
-
x = self.df_tracks[self.columns_to_rescale].values
|
|
2313
|
-
self.MinMaxScaler.fit(x)
|
|
2314
1525
|
|
|
2315
1526
|
def extract_scatter_from_trajectories(self):
|
|
2316
1527
|
|
|
@@ -2329,10 +1540,11 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2329
1540
|
|
|
2330
1541
|
def changed_class(self):
|
|
2331
1542
|
self.status_name = self.class_choice_cb.currentText()
|
|
2332
|
-
self.
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
1543
|
+
if self.status_name!="":
|
|
1544
|
+
self.compute_status_and_colors()
|
|
1545
|
+
self.modify()
|
|
1546
|
+
self.draw_frame(self.current_frame)
|
|
1547
|
+
self.fcanvas.canvas.draw()
|
|
2336
1548
|
|
|
2337
1549
|
def update_frame(self):
|
|
2338
1550
|
"""
|
|
@@ -2348,53 +1560,14 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2348
1560
|
self.modify()
|
|
2349
1561
|
|
|
2350
1562
|
self.draw_frame(self.current_frame)
|
|
1563
|
+
self.vmin = self.contrast_slider.value()[0]
|
|
1564
|
+
self.vmax = self.contrast_slider.value()[1]
|
|
1565
|
+
self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
|
|
1566
|
+
self.give_cell_information()
|
|
1567
|
+
|
|
2351
1568
|
self.fcanvas.canvas.draw()
|
|
2352
1569
|
self.plot_signals()
|
|
2353
1570
|
|
|
2354
|
-
# def load_annotator_config(self):
|
|
2355
|
-
# self.rgb_mode = False
|
|
2356
|
-
# self.log_option = False
|
|
2357
|
-
# self.percentile_mode = True
|
|
2358
|
-
# self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
|
|
2359
|
-
# self.fraction = 0.5955056179775281
|
|
2360
|
-
# #self.anim_interval = 1
|
|
2361
|
-
|
|
2362
|
-
def prepare_stack(self):
|
|
2363
|
-
|
|
2364
|
-
self.img_num_channels = _get_img_num_per_channel(self.channels, self.len_movie, self.nbr_channels)
|
|
2365
|
-
self.current_stack = []
|
|
2366
|
-
for ch in tqdm(self.target_channels, desc="channel"):
|
|
2367
|
-
target_ch_name = ch[0]
|
|
2368
|
-
if self.percentile_mode:
|
|
2369
|
-
normalize_kwargs = {"percentiles": (ch[1], ch[2]), "values": None}
|
|
2370
|
-
else:
|
|
2371
|
-
normalize_kwargs = {"values": (ch[1], ch[2]), "percentiles": None}
|
|
2372
|
-
|
|
2373
|
-
if self.rgb_mode:
|
|
2374
|
-
normalize_kwargs.update({'amplification': 255., 'clip': True})
|
|
2375
|
-
|
|
2376
|
-
chan = []
|
|
2377
|
-
indices = self.img_num_channels[self.channels[np.where(self.channel_names == target_ch_name)][0]]
|
|
2378
|
-
for t in tqdm(range(len(indices)), desc='frame'):
|
|
2379
|
-
if self.rgb_mode:
|
|
2380
|
-
f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=True,
|
|
2381
|
-
normalize_kwargs=normalize_kwargs)
|
|
2382
|
-
f = f.astype(np.uint8)
|
|
2383
|
-
else:
|
|
2384
|
-
f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=False)
|
|
2385
|
-
chan.append(f[:, :, 0])
|
|
2386
|
-
|
|
2387
|
-
self.current_stack.append(chan)
|
|
2388
|
-
|
|
2389
|
-
self.current_stack = np.array(self.current_stack)
|
|
2390
|
-
if self.rgb_mode:
|
|
2391
|
-
self.current_stack = np.moveaxis(self.current_stack, 0, -1)
|
|
2392
|
-
else:
|
|
2393
|
-
self.current_stack = self.current_stack[0]
|
|
2394
|
-
if self.log_option:
|
|
2395
|
-
self.current_stack[np.where((self.current_stack > 0.)&(self.current_stack==self.current_stack))] = np.log(
|
|
2396
|
-
self.current_stack[np.where((self.current_stack > 0.)&(self.current_stack==self.current_stack))])
|
|
2397
|
-
|
|
2398
1571
|
def changed_channel(self):
|
|
2399
1572
|
|
|
2400
1573
|
self.reload_frame()
|
|
@@ -2402,12 +1575,12 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2402
1575
|
*[np.nanpercentile(self.img, 0.001),
|
|
2403
1576
|
np.nanpercentile(self.img, 99.999)])
|
|
2404
1577
|
self.contrast_slider.setValue(
|
|
2405
|
-
[np.nanpercentile(self.img, 1), np.nanpercentile(self.img, 99.99)])
|
|
1578
|
+
[np.nanpercentile(self.img, 0.1), np.nanpercentile(self.img, 99.99)])
|
|
2406
1579
|
self.draw_frame(self.current_frame)
|
|
2407
1580
|
self.fcanvas.canvas.draw()
|
|
2408
1581
|
|
|
2409
1582
|
def save_trajectories(self):
|
|
2410
|
-
|
|
1583
|
+
print(f"Saving trajectories !!")
|
|
2411
1584
|
if self.normalized_signals:
|
|
2412
1585
|
self.normalize_features_btn.click()
|
|
2413
1586
|
if self.selection:
|
|
@@ -2433,17 +1606,14 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2433
1606
|
|
|
2434
1607
|
self.df_tracks.to_csv(self.trajectories_path, index=False)
|
|
2435
1608
|
print('Table successfully exported...')
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
# self.extract_scatter_from_trajectories()
|
|
1609
|
+
|
|
1610
|
+
self.locate_tracks()
|
|
1611
|
+
self.changed_class()
|
|
2440
1612
|
|
|
2441
1613
|
def modify(self):
|
|
2442
|
-
|
|
2443
1614
|
all_states = self.df_tracks.loc[:, self.status_name].tolist()
|
|
2444
1615
|
all_states = np.array(all_states)
|
|
2445
1616
|
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
2446
|
-
print(f'{self.state_color_map=}')
|
|
2447
1617
|
|
|
2448
1618
|
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2449
1619
|
|
|
@@ -2453,84 +1623,6 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2453
1623
|
self.correct_btn.disconnect()
|
|
2454
1624
|
self.correct_btn.clicked.connect(self.show_annotation_buttons)
|
|
2455
1625
|
|
|
2456
|
-
# self.hide_annotation_buttons()
|
|
2457
|
-
# self.correct_btn.setEnabled(False)
|
|
2458
|
-
# self.correct_btn.setText('correct')
|
|
2459
|
-
# self.cancel_btn.setEnabled(False)
|
|
2460
|
-
# self.del_shortcut.setEnabled(False)
|
|
2461
|
-
# self.no_event_shortcut.setEnabled(False)
|
|
2462
|
-
|
|
2463
|
-
def enable_time_of_interest(self):
|
|
2464
|
-
if self.suppr_btn.isChecked():
|
|
2465
|
-
self.time_of_interest_le.setEnabled(False)
|
|
2466
|
-
|
|
2467
|
-
def cancel_selection(self):
|
|
2468
|
-
|
|
2469
|
-
self.hide_annotation_buttons()
|
|
2470
|
-
self.correct_btn.setEnabled(False)
|
|
2471
|
-
self.correct_btn.setText('correct')
|
|
2472
|
-
self.cancel_btn.setEnabled(False)
|
|
2473
|
-
|
|
2474
|
-
try:
|
|
2475
|
-
self.selection.pop(0)
|
|
2476
|
-
except Exception as e:
|
|
2477
|
-
print('Cancel selection: ',e)
|
|
2478
|
-
|
|
2479
|
-
try:
|
|
2480
|
-
for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
|
|
2481
|
-
# print(self.colors[t][idx, 1])
|
|
2482
|
-
self.colors[t][idx, 0] = self.previous_color[k][0]
|
|
2483
|
-
# self.colors[t][idx, 1] = self.previous_color[k][1]
|
|
2484
|
-
except Exception as e:
|
|
2485
|
-
print("cancel_selection: ",f'{e=}')
|
|
2486
|
-
|
|
2487
|
-
self.event = None
|
|
2488
|
-
|
|
2489
|
-
def locate_stack(self):
|
|
2490
|
-
|
|
2491
|
-
"""
|
|
2492
|
-
Locate the target movie.
|
|
2493
|
-
|
|
2494
|
-
"""
|
|
2495
|
-
|
|
2496
|
-
if isinstance(self.pos, str):
|
|
2497
|
-
movies = glob(self.pos + os.sep.join(['movie',f"{self.parent_window.parent_window.movie_prefix}*.tif"]))
|
|
2498
|
-
|
|
2499
|
-
else:
|
|
2500
|
-
msgBox = QMessageBox()
|
|
2501
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
2502
|
-
msgBox.setText("Please select a unique position before launching the wizard...")
|
|
2503
|
-
msgBox.setWindowTitle("Warning")
|
|
2504
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
2505
|
-
returnValue = msgBox.exec()
|
|
2506
|
-
if returnValue == QMessageBox.Ok:
|
|
2507
|
-
self.img = None
|
|
2508
|
-
self.close()
|
|
2509
|
-
return None
|
|
2510
|
-
|
|
2511
|
-
if len(movies) == 0:
|
|
2512
|
-
msgBox = QMessageBox()
|
|
2513
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
2514
|
-
msgBox.setText("No movie is detected in the experiment folder.\nPlease check the stack prefix...")
|
|
2515
|
-
msgBox.setWindowTitle("Warning")
|
|
2516
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
2517
|
-
returnValue = msgBox.exec()
|
|
2518
|
-
if returnValue == QMessageBox.Ok:
|
|
2519
|
-
self.close()
|
|
2520
|
-
else:
|
|
2521
|
-
self.stack_path = movies[0]
|
|
2522
|
-
self.len_movie = self.parent_window.parent_window.len_movie
|
|
2523
|
-
len_movie_auto = auto_load_number_of_frames(self.stack_path)
|
|
2524
|
-
if len_movie_auto is not None:
|
|
2525
|
-
self.len_movie = len_movie_auto
|
|
2526
|
-
exp_config = self.exp_dir + "config.ini"
|
|
2527
|
-
self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
|
|
2528
|
-
self.channel_names = np.array(self.channel_names)
|
|
2529
|
-
self.channels = np.array(self.channels)
|
|
2530
|
-
self.nbr_channels = len(self.channels)
|
|
2531
|
-
self.current_channel = 0
|
|
2532
|
-
self.img = load_frames(0, self.stack_path, normalize_input=False)
|
|
2533
|
-
|
|
2534
1626
|
def reload_frame(self):
|
|
2535
1627
|
|
|
2536
1628
|
"""
|
|
@@ -2538,13 +1630,26 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2538
1630
|
"""
|
|
2539
1631
|
|
|
2540
1632
|
# self.clear_post_threshold_options()
|
|
2541
|
-
|
|
1633
|
+
self.previous_channel = self.current_channel
|
|
2542
1634
|
self.current_channel = self.choose_channel.currentIndex()
|
|
2543
1635
|
|
|
2544
1636
|
t = int(self.frame_slider.value())
|
|
2545
1637
|
idx = t * self.nbr_channels + self.current_channel
|
|
2546
1638
|
self.img = load_frames(idx, self.stack_path, normalize_input=False)
|
|
1639
|
+
|
|
1640
|
+
if self.previous_channel != self.current_channel:
|
|
1641
|
+
# reinitialize intensity bounds
|
|
1642
|
+
epsilon = 0.01
|
|
1643
|
+
self.observed_min_intensity = 0
|
|
1644
|
+
self.observed_max_intensity = 0 + epsilon
|
|
1645
|
+
|
|
2547
1646
|
if self.img is not None:
|
|
1647
|
+
max_img = np.nanmax(self.img)
|
|
1648
|
+
min_img = np.nanmin(self.img)
|
|
1649
|
+
if max_img > self.observed_max_intensity:
|
|
1650
|
+
self.observed_max_intensity = max_img
|
|
1651
|
+
if min_img < self.observed_min_intensity:
|
|
1652
|
+
self.observed_min_intensity = min_img
|
|
2548
1653
|
self.refresh_imshow()
|
|
2549
1654
|
# self.redo_histogram()
|
|
2550
1655
|
else:
|
|
@@ -2558,33 +1663,23 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2558
1663
|
|
|
2559
1664
|
"""
|
|
2560
1665
|
|
|
2561
|
-
self.
|
|
2562
|
-
self.vmax = np.nanpercentile(self.img.flatten(), 99.)
|
|
2563
|
-
|
|
2564
|
-
self.contrast_slider.disconnect()
|
|
2565
|
-
self.contrast_slider.setRange(np.nanmin(self.img), np.nanmax(self.img))
|
|
2566
|
-
self.contrast_slider.setValue([self.vmin, self.vmax])
|
|
2567
|
-
self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
|
|
2568
|
-
|
|
2569
|
-
self.im.set_data(self.img)
|
|
1666
|
+
if self.previous_channel != self.current_channel:
|
|
2570
1667
|
|
|
2571
|
-
|
|
2572
|
-
|
|
1668
|
+
self.vmin = np.nanpercentile(self.img.flatten(), 0.1)
|
|
1669
|
+
self.vmax = np.nanpercentile(self.img.flatten(), 99.99)
|
|
2573
1670
|
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
self.
|
|
2577
|
-
self.
|
|
1671
|
+
self.contrast_slider.disconnect()
|
|
1672
|
+
self.contrast_slider.setRange(np.nanmin(self.img), np.nanmax(self.img))
|
|
1673
|
+
self.contrast_slider.setValue([self.vmin, self.vmax])
|
|
1674
|
+
self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
|
|
2578
1675
|
else:
|
|
2579
|
-
self.
|
|
2580
|
-
self.
|
|
1676
|
+
#self.contrast_slider.disconnect()
|
|
1677
|
+
self.contrast_slider.setRange(self.observed_min_intensity, self.observed_max_intensity)
|
|
1678
|
+
#self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
|
|
1679
|
+
|
|
1680
|
+
self.im.set_data(self.img)
|
|
2581
1681
|
|
|
2582
1682
|
def del_cell(self):
|
|
2583
1683
|
self.time_of_interest_le.setEnabled(False)
|
|
2584
1684
|
self.time_of_interest_le.setText("99")
|
|
2585
1685
|
self.apply_modification()
|
|
2586
|
-
|
|
2587
|
-
def shortcut_suppr(self):
|
|
2588
|
-
self.correct_btn.click()
|
|
2589
|
-
self.del_cell_btn.click()
|
|
2590
|
-
self.correct_btn.click()
|