celldetective 1.4.0__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/_version.py +1 -1
- celldetective/exceptions.py +11 -0
- celldetective/filters.py +7 -1
- celldetective/gui/InitWindow.py +4 -1
- celldetective/gui/__init__.py +2 -9
- celldetective/gui/about.py +2 -2
- celldetective/gui/base_annotator.py +786 -0
- celldetective/gui/classifier_widget.py +18 -13
- celldetective/gui/configure_new_exp.py +51 -30
- celldetective/gui/control_panel.py +10 -7
- celldetective/gui/{signal_annotator.py → event_annotator.py} +473 -1437
- celldetective/gui/generic_signal_plot.py +2 -1
- celldetective/gui/gui_utils.py +5 -2
- celldetective/gui/help/neighborhood.json +2 -2
- celldetective/gui/layouts.py +21 -11
- celldetective/gui/{signal_annotator2.py → pair_event_annotator.py} +3 -1
- celldetective/gui/process_block.py +129 -91
- celldetective/gui/processes/downloader.py +37 -34
- celldetective/gui/processes/measure_cells.py +14 -8
- celldetective/gui/processes/segment_cells.py +21 -6
- celldetective/gui/processes/track_cells.py +12 -13
- 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} +35 -91
- celldetective/gui/{measurement_options.py → settings/_settings_measurements.py} +28 -81
- celldetective/gui/{neighborhood_options.py → settings/_settings_neighborhood.py} +1 -1
- celldetective/gui/settings/_settings_segmentation.py +49 -0
- celldetective/gui/{retrain_segmentation_model_options.py → settings/_settings_segmentation_model_training.py} +33 -79
- celldetective/gui/{signal_annotator_options.py → settings/_settings_signal_annotator.py} +73 -95
- celldetective/gui/{btrack_options.py → settings/_settings_tracking.py} +64 -87
- celldetective/gui/styles.py +2 -1
- celldetective/gui/survival_ui.py +1 -1
- celldetective/gui/tableUI.py +25 -0
- celldetective/gui/table_ops/__init__.py +0 -0
- celldetective/gui/table_ops/merge_groups.py +118 -0
- celldetective/gui/viewers.py +3 -5
- celldetective/gui/workers.py +0 -2
- celldetective/io.py +98 -55
- celldetective/links/zenodo.json +145 -144
- celldetective/measure.py +31 -26
- celldetective/preprocessing.py +34 -21
- celldetective/regionprops/_regionprops.py +16 -5
- celldetective/scripts/measure_cells.py +5 -5
- celldetective/scripts/measure_relative.py +16 -11
- celldetective/scripts/segment_cells.py +4 -4
- celldetective/scripts/segment_cells_thresholds.py +3 -3
- celldetective/scripts/track_cells.py +7 -7
- celldetective/scripts/train_segmentation_model.py +10 -1
- celldetective/tracking.py +10 -4
- celldetective/utils.py +59 -58
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/METADATA +1 -1
- celldetective-1.4.1.dist-info/RECORD +123 -0
- tests/gui/__init__.py +0 -0
- tests/gui/test_new_project.py +228 -0
- tests/{test_qt.py → gui/test_project.py} +22 -26
- 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.4.0.dist-info/RECORD +0 -131
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/WHEEL +0 -0
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.4.0.dist-info → celldetective-1.4.1.dist-info}/top_level.txt +0 -0
|
@@ -1,213 +1,126 @@
|
|
|
1
|
-
from PyQt5.QtWidgets import QComboBox, QLabel, QRadioButton,
|
|
2
|
-
QPushButton, QVBoxLayout, QHBoxLayout, QMessageBox, QShortcut, QLineEdit, QSlider
|
|
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
|
|
28
24
|
from celldetective.utils import pretty_table
|
|
25
|
+
from celldetective.gui.base_annotator import BaseAnnotator
|
|
29
26
|
|
|
30
|
-
class
|
|
27
|
+
class EventAnnotator(BaseAnnotator):
|
|
31
28
|
"""
|
|
32
29
|
UI to set tracking parameters for bTrack.
|
|
33
30
|
|
|
34
31
|
"""
|
|
35
32
|
|
|
36
|
-
def __init__(self,
|
|
33
|
+
def __init__(self, *args, **kwargs):
|
|
37
34
|
|
|
38
|
-
super().__init__()
|
|
39
|
-
|
|
40
|
-
center_window(self)
|
|
41
|
-
self.proceed = True
|
|
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
|
-
self.instructions_path = self.exp_dir + os.sep.join(['configs', f'signal_annotator_config_{self.mode}.json'])
|
|
55
|
-
self.trajectories_path = self.pos + os.sep.join(['output','tables',f'trajectories_{self.mode}.csv'])
|
|
56
|
-
|
|
57
|
-
self.screen_height = self.parent_window.parent_window.parent_window.screen_height
|
|
58
|
-
self.screen_width = self.parent_window.parent_window.parent_window.screen_width
|
|
59
|
-
#self.setMinimumHeight(int(0.8*self.screen_height))
|
|
60
|
-
self.value_magnitude = 1
|
|
61
37
|
|
|
62
38
|
# default params
|
|
63
39
|
self.class_name = 'class'
|
|
64
40
|
self.time_name = 't0'
|
|
65
41
|
self.status_name = 'status'
|
|
66
42
|
|
|
67
|
-
self.locate_stack()
|
|
43
|
+
# self.locate_stack()
|
|
68
44
|
if not self.proceed:
|
|
69
45
|
self.close()
|
|
70
46
|
else:
|
|
71
|
-
self.load_annotator_config()
|
|
72
|
-
self.locate_tracks()
|
|
47
|
+
#self.load_annotator_config()
|
|
48
|
+
#self.locate_tracks()
|
|
73
49
|
self.prepare_stack()
|
|
74
50
|
|
|
75
|
-
self.generate_signal_choices()
|
|
76
51
|
self.frame_lbl = QLabel('frame: ')
|
|
77
52
|
self.looped_animation()
|
|
78
|
-
self.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
super().resizeEvent(event)
|
|
85
|
-
|
|
86
|
-
try:
|
|
87
|
-
self.cell_fig.tight_layout()
|
|
88
|
-
except:
|
|
89
|
-
pass
|
|
90
|
-
|
|
91
|
-
def populate_widget(self):
|
|
92
|
-
|
|
93
|
-
"""
|
|
94
|
-
Create the multibox design.
|
|
95
|
-
|
|
96
|
-
"""
|
|
97
|
-
|
|
98
|
-
self.button_widget = CelldetectiveWidget()
|
|
99
|
-
main_layout = QHBoxLayout()
|
|
100
|
-
self.button_widget.setLayout(main_layout)
|
|
101
|
-
|
|
102
|
-
main_layout.setContentsMargins(30, 30, 30, 30)
|
|
103
|
-
self.left_panel = QVBoxLayout()
|
|
104
|
-
self.left_panel.setContentsMargins(30, 5, 30, 5)
|
|
105
|
-
self.left_panel.setSpacing(3)
|
|
106
|
-
|
|
107
|
-
self.right_panel = QVBoxLayout()
|
|
108
|
-
|
|
109
|
-
class_hbox = QHBoxLayout()
|
|
110
|
-
class_hbox.setContentsMargins(0,0,0,0)
|
|
111
|
-
class_hbox.addWidget(QLabel('event: '), 25)
|
|
112
|
-
self.class_choice_cb = QComboBox()
|
|
113
|
-
|
|
114
|
-
cols = np.array(self.df_tracks.columns)
|
|
115
|
-
self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
|
|
116
|
-
self.class_cols = list(cols[self.class_cols])
|
|
117
|
-
try:
|
|
118
|
-
self.class_cols.remove('class_id')
|
|
119
|
-
except Exception:
|
|
120
|
-
pass
|
|
121
|
-
try:
|
|
122
|
-
self.class_cols.remove('class_color')
|
|
123
|
-
except Exception:
|
|
124
|
-
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])
|
|
125
59
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class_hbox.addWidget(self.class_choice_cb, 70)
|
|
130
|
-
|
|
131
|
-
self.add_class_btn = QPushButton('')
|
|
132
|
-
self.add_class_btn.setStyleSheet(self.button_select_all)
|
|
133
|
-
self.add_class_btn.setIcon(icon(MDI6.plus, color="black"))
|
|
134
|
-
self.add_class_btn.setToolTip("Add a new event class")
|
|
135
|
-
self.add_class_btn.setIconSize(QSize(20, 20))
|
|
136
|
-
self.add_class_btn.clicked.connect(self.create_new_event_class)
|
|
137
|
-
class_hbox.addWidget(self.add_class_btn, 5)
|
|
138
|
-
|
|
139
|
-
self.del_class_btn = QPushButton('')
|
|
140
|
-
self.del_class_btn.setStyleSheet(self.button_select_all)
|
|
141
|
-
self.del_class_btn.setIcon(icon(MDI6.delete, color="black"))
|
|
142
|
-
self.del_class_btn.setToolTip("Delete an event class")
|
|
143
|
-
self.del_class_btn.setIconSize(QSize(20, 20))
|
|
144
|
-
self.del_class_btn.clicked.connect(self.del_event_class)
|
|
145
|
-
class_hbox.addWidget(self.del_class_btn, 5)
|
|
146
|
-
|
|
147
|
-
self.left_panel.addLayout(class_hbox,5)
|
|
148
|
-
|
|
149
|
-
self.cell_info = QLabel('')
|
|
150
|
-
self.left_panel.addWidget(self.cell_info,10)
|
|
151
|
-
|
|
152
|
-
# Annotation buttons
|
|
153
|
-
options_hbox = QHBoxLayout()
|
|
154
|
-
options_hbox.setContentsMargins(0, 0, 0, 0)
|
|
60
|
+
def init_event_buttons(self):
|
|
61
|
+
|
|
155
62
|
self.event_btn = QRadioButton('event')
|
|
156
63
|
self.event_btn.setStyleSheet(self.button_style_sheet_2)
|
|
157
64
|
self.event_btn.toggled.connect(self.enable_time_of_interest)
|
|
158
|
-
|
|
65
|
+
|
|
159
66
|
self.no_event_btn = QRadioButton('no event')
|
|
160
67
|
self.no_event_btn.setStyleSheet(self.button_style_sheet_2)
|
|
161
68
|
self.no_event_btn.toggled.connect(self.enable_time_of_interest)
|
|
162
|
-
|
|
69
|
+
|
|
163
70
|
self.else_btn = QRadioButton('else')
|
|
164
71
|
self.else_btn.setStyleSheet(self.button_style_sheet_2)
|
|
165
72
|
self.else_btn.toggled.connect(self.enable_time_of_interest)
|
|
166
|
-
|
|
73
|
+
|
|
167
74
|
self.suppr_btn = QRadioButton('remove')
|
|
168
75
|
self.suppr_btn.setToolTip('Mark for deletion. Upon saving, the cell\nwill be removed from the tables.')
|
|
169
76
|
self.suppr_btn.setStyleSheet(self.button_style_sheet_2)
|
|
170
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()
|
|
171
81
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
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
|
|
177
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
|
+
|
|
178
98
|
time_option_hbox = QHBoxLayout()
|
|
179
99
|
time_option_hbox.setContentsMargins(0, 5, 100, 10)
|
|
180
|
-
self.time_of_interest_label = QLabel('time of interest: ')
|
|
181
100
|
time_option_hbox.addWidget(self.time_of_interest_label, 10)
|
|
182
|
-
self.time_of_interest_le = QLineEdit()
|
|
183
101
|
time_option_hbox.addWidget(self.time_of_interest_le, 15)
|
|
184
102
|
time_option_hbox.addWidget(QLabel(''), 75)
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
self.
|
|
190
|
-
|
|
191
|
-
self.correct_btn.setIconSize(QSize(20, 20))
|
|
192
|
-
self.correct_btn.setStyleSheet(self.button_style_sheet)
|
|
193
|
-
self.correct_btn.clicked.connect(self.show_annotation_buttons)
|
|
194
|
-
self.correct_btn.setEnabled(False)
|
|
195
|
-
main_action_hbox.addWidget(self.correct_btn)
|
|
196
|
-
|
|
197
|
-
self.cancel_btn = QPushButton('cancel')
|
|
198
|
-
self.cancel_btn.setStyleSheet(self.button_style_sheet_2)
|
|
199
|
-
self.cancel_btn.setShortcut(QKeySequence("Esc"))
|
|
200
|
-
self.cancel_btn.setEnabled(False)
|
|
201
|
-
self.cancel_btn.clicked.connect(self.cancel_selection)
|
|
202
|
-
main_action_hbox.addWidget(self.cancel_btn)
|
|
203
|
-
self.left_panel.addLayout(main_action_hbox,5)
|
|
204
|
-
|
|
103
|
+
|
|
104
|
+
options_layout.addLayout(btn_hbox)
|
|
105
|
+
options_layout.addLayout(time_option_hbox)
|
|
106
|
+
|
|
107
|
+
self.options_hbox.addLayout(options_layout)
|
|
108
|
+
|
|
205
109
|
self.annotation_btns_to_hide = [self.event_btn, self.no_event_btn,
|
|
206
110
|
self.else_btn, self.time_of_interest_label,
|
|
207
111
|
self.time_of_interest_le, self.suppr_btn]
|
|
208
112
|
self.hide_annotation_buttons()
|
|
209
|
-
|
|
113
|
+
|
|
114
|
+
def populate_window(self):
|
|
115
|
+
|
|
116
|
+
"""
|
|
117
|
+
Create the multibox design.
|
|
210
118
|
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
super().populate_window()
|
|
122
|
+
self.populate_options_layout()
|
|
123
|
+
|
|
211
124
|
self.del_shortcut = QShortcut(Qt.Key_Delete, self) # QKeySequence("s")
|
|
212
125
|
self.del_shortcut.activated.connect(self.shortcut_suppr)
|
|
213
126
|
self.del_shortcut.setEnabled(False)
|
|
@@ -216,68 +129,8 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
216
129
|
self.no_event_shortcut.activated.connect(self.shortcut_no_event)
|
|
217
130
|
self.no_event_shortcut.setEnabled(False)
|
|
218
131
|
|
|
219
|
-
#
|
|
220
|
-
|
|
221
|
-
self.left_panel.addWidget(self.cell_fcanvas, 45)
|
|
222
|
-
|
|
223
|
-
plot_buttons_hbox = QHBoxLayout()
|
|
224
|
-
plot_buttons_hbox.setContentsMargins(0, 0, 0, 0)
|
|
225
|
-
self.normalize_features_btn = QPushButton('')
|
|
226
|
-
self.normalize_features_btn.setStyleSheet(self.button_select_all)
|
|
227
|
-
self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
|
|
228
|
-
self.normalize_features_btn.setIconSize(QSize(25, 25))
|
|
229
|
-
self.normalize_features_btn.setFixedSize(QSize(30, 30))
|
|
230
|
-
# self.normalize_features_btn.setShortcut(QKeySequence('n'))
|
|
231
|
-
self.normalize_features_btn.clicked.connect(self.normalize_features)
|
|
232
|
-
|
|
233
|
-
plot_buttons_hbox.addWidget(QLabel(''), 90)
|
|
234
|
-
plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
|
|
235
|
-
self.normalized_signals = False
|
|
236
|
-
|
|
237
|
-
self.log_btn = QPushButton()
|
|
238
|
-
self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
|
|
239
|
-
self.log_btn.setStyleSheet(self.button_select_all)
|
|
240
|
-
self.log_btn.clicked.connect(self.switch_to_log)
|
|
241
|
-
plot_buttons_hbox.addWidget(self.log_btn, 5)
|
|
242
|
-
|
|
243
|
-
self.export_plot_btn = ExportPlotBtn(self.cell_fig, export_dir = self.exp_dir)
|
|
244
|
-
plot_buttons_hbox.addWidget(self.export_plot_btn, 5)
|
|
245
|
-
|
|
246
|
-
self.left_panel.addLayout(plot_buttons_hbox,5)
|
|
247
|
-
|
|
248
|
-
signal_choice_vbox = QVBoxLayout()
|
|
249
|
-
signal_choice_vbox.setContentsMargins(30, 0, 30, 0)
|
|
250
|
-
for i in range(len(self.signal_choice_cb)):
|
|
251
|
-
hlayout = QHBoxLayout()
|
|
252
|
-
hlayout.addWidget(self.signal_choice_label[i], 20)
|
|
253
|
-
hlayout.addWidget(self.signal_choice_cb[i], 75)
|
|
254
|
-
# hlayout.addWidget(self.log_btns[i], 5)
|
|
255
|
-
signal_choice_vbox.addLayout(hlayout)
|
|
256
|
-
|
|
257
|
-
# self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
258
|
-
# self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
259
|
-
# self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
|
|
260
|
-
|
|
261
|
-
self.left_panel.addLayout(signal_choice_vbox,15)
|
|
262
|
-
|
|
263
|
-
btn_hbox = QHBoxLayout()
|
|
264
|
-
btn_hbox.setContentsMargins(0,10,0,0)
|
|
265
|
-
self.save_btn = QPushButton('Save')
|
|
266
|
-
self.save_btn.setStyleSheet(self.button_style_sheet)
|
|
267
|
-
self.save_btn.clicked.connect(self.save_trajectories)
|
|
268
|
-
btn_hbox.addWidget(self.save_btn, 90)
|
|
269
|
-
|
|
270
|
-
self.export_btn = QPushButton('')
|
|
271
|
-
self.export_btn.setStyleSheet(self.button_select_all)
|
|
272
|
-
self.export_btn.clicked.connect(self.export_signals)
|
|
273
|
-
self.export_btn.setIcon(icon(MDI6.export, color="black"))
|
|
274
|
-
self.export_btn.setIconSize(QSize(25, 25))
|
|
275
|
-
btn_hbox.addWidget(self.export_btn, 10)
|
|
276
|
-
self.left_panel.addLayout(btn_hbox,5)
|
|
277
|
-
|
|
278
|
-
# Animation
|
|
132
|
+
# Right side
|
|
279
133
|
animation_buttons_box = QHBoxLayout()
|
|
280
|
-
|
|
281
134
|
animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
|
|
282
135
|
|
|
283
136
|
self.first_frame_btn = QPushButton()
|
|
@@ -317,7 +170,6 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
317
170
|
animation_buttons_box.addWidget(self.last_frame_btn, 5, alignment=Qt.AlignRight)
|
|
318
171
|
|
|
319
172
|
self.right_panel.addLayout(animation_buttons_box, 5)
|
|
320
|
-
|
|
321
173
|
self.right_panel.addWidget(self.fcanvas, 90)
|
|
322
174
|
|
|
323
175
|
if not self.rgb_mode:
|
|
@@ -335,99 +187,16 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
335
187
|
contrast_hbox.addWidget(QLabel('contrast: '))
|
|
336
188
|
contrast_hbox.addWidget(self.contrast_slider, 90)
|
|
337
189
|
self.right_panel.addLayout(contrast_hbox, 5)
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
# self.interval_slider.setRange(1, 10000)
|
|
346
|
-
# self.interval_slider.setValue(self.speed)
|
|
347
|
-
# self.interval_slider.valueChanged.connect(self.interval_slider_action)
|
|
348
|
-
# speed_hbox.addWidget(QLabel('interval (ms): '))
|
|
349
|
-
# speed_hbox.addWidget(self.interval_slider,90)
|
|
350
|
-
# self.right_panel.addLayout(speed_hbox, 10)
|
|
351
|
-
|
|
352
|
-
# self.populate_left_panel()
|
|
353
|
-
# grid.addLayout(self.left_side, 0, 0, 1, 1)
|
|
354
|
-
|
|
355
|
-
main_layout.addLayout(self.left_panel, 35)
|
|
356
|
-
main_layout.addLayout(self.right_panel, 65)
|
|
357
|
-
self.button_widget.adjustSize()
|
|
358
|
-
|
|
359
|
-
self.compute_status_and_colors(0)
|
|
360
|
-
|
|
361
|
-
self.setCentralWidget(self.button_widget)
|
|
362
|
-
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)
|
|
363
197
|
|
|
364
198
|
QApplication.processEvents()
|
|
365
199
|
|
|
366
|
-
def del_event_class(self):
|
|
367
|
-
|
|
368
|
-
msgBox = QMessageBox()
|
|
369
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
370
|
-
msgBox.setText(
|
|
371
|
-
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?")
|
|
372
|
-
msgBox.setWindowTitle("Warning")
|
|
373
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
374
|
-
returnValue = msgBox.exec()
|
|
375
|
-
if returnValue == QMessageBox.No:
|
|
376
|
-
return None
|
|
377
|
-
else:
|
|
378
|
-
class_to_delete = self.class_choice_cb.currentText()
|
|
379
|
-
time_to_delete = class_to_delete.replace('class', 't')
|
|
380
|
-
status_to_delete = class_to_delete.replace('class', 'status')
|
|
381
|
-
cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
|
|
382
|
-
for c in cols_to_delete:
|
|
383
|
-
try:
|
|
384
|
-
self.df_tracks = self.df_tracks.drop([c], axis=1)
|
|
385
|
-
except Exception as e:
|
|
386
|
-
print(e)
|
|
387
|
-
item_idx = self.class_choice_cb.findText(class_to_delete)
|
|
388
|
-
self.class_choice_cb.removeItem(item_idx)
|
|
389
|
-
|
|
390
|
-
def create_new_event_class(self):
|
|
391
|
-
|
|
392
|
-
# display qwidget to name the event
|
|
393
|
-
self.newClassWidget = CelldetectiveWidget()
|
|
394
|
-
self.newClassWidget.setWindowTitle('Create new event class')
|
|
395
|
-
|
|
396
|
-
layout = QVBoxLayout()
|
|
397
|
-
self.newClassWidget.setLayout(layout)
|
|
398
|
-
name_hbox = QHBoxLayout()
|
|
399
|
-
name_hbox.addWidget(QLabel('event name: '), 25)
|
|
400
|
-
self.class_name_le = QLineEdit('event')
|
|
401
|
-
name_hbox.addWidget(self.class_name_le, 75)
|
|
402
|
-
layout.addLayout(name_hbox)
|
|
403
|
-
|
|
404
|
-
class_labels = ['event', 'no event', 'else']
|
|
405
|
-
layout.addWidget(QLabel('prefill: '))
|
|
406
|
-
radio_box = QHBoxLayout()
|
|
407
|
-
self.class_option_rb = [QRadioButton() for i in range(3)]
|
|
408
|
-
for i, c in enumerate(self.class_option_rb):
|
|
409
|
-
if i == 0:
|
|
410
|
-
c.setChecked(True)
|
|
411
|
-
c.setText(class_labels[i])
|
|
412
|
-
radio_box.addWidget(c, 33, alignment=Qt.AlignCenter)
|
|
413
|
-
layout.addLayout(radio_box)
|
|
414
|
-
|
|
415
|
-
btn_hbox = QHBoxLayout()
|
|
416
|
-
submit_btn = QPushButton('submit')
|
|
417
|
-
cancel_btn = QPushButton('cancel')
|
|
418
|
-
btn_hbox.addWidget(cancel_btn, 50)
|
|
419
|
-
btn_hbox.addWidget(submit_btn, 50)
|
|
420
|
-
layout.addLayout(btn_hbox)
|
|
421
|
-
|
|
422
|
-
submit_btn.clicked.connect(self.write_new_event_class)
|
|
423
|
-
cancel_btn.clicked.connect(self.close_without_new_class)
|
|
424
|
-
|
|
425
|
-
self.newClassWidget.show()
|
|
426
|
-
center_window(self.newClassWidget)
|
|
427
|
-
|
|
428
|
-
# Prefill with class value
|
|
429
|
-
# write in table
|
|
430
|
-
|
|
431
200
|
def write_new_event_class(self):
|
|
432
201
|
|
|
433
202
|
if self.class_name_le.text() == '':
|
|
@@ -472,8 +241,8 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
472
241
|
|
|
473
242
|
self.newClassWidget.close()
|
|
474
243
|
|
|
475
|
-
def close_without_new_class(self):
|
|
476
|
-
|
|
244
|
+
# def close_without_new_class(self):
|
|
245
|
+
# self.newClassWidget.close()
|
|
477
246
|
|
|
478
247
|
def compute_status_and_colors(self, i):
|
|
479
248
|
|
|
@@ -518,36 +287,14 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
518
287
|
|
|
519
288
|
self.fcanvas.canvas.draw()
|
|
520
289
|
|
|
521
|
-
|
|
522
|
-
def contrast_slider_action(self):
|
|
523
|
-
|
|
524
|
-
"""
|
|
525
|
-
Recontrast the imshow as the contrast slider is moved.
|
|
526
|
-
"""
|
|
527
|
-
|
|
528
|
-
self.vmin = self.contrast_slider.value()[0]
|
|
529
|
-
self.vmax = self.contrast_slider.value()[1]
|
|
530
|
-
self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
|
|
531
|
-
self.fcanvas.canvas.draw_idle()
|
|
532
|
-
|
|
533
290
|
def cancel_selection(self):
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
self.correct_btn.setEnabled(False)
|
|
537
|
-
self.correct_btn.setText('correct')
|
|
538
|
-
self.cancel_btn.setEnabled(False)
|
|
539
|
-
|
|
540
|
-
try:
|
|
541
|
-
self.selection.pop(0)
|
|
542
|
-
except Exception as e:
|
|
543
|
-
print(f"L 536 {e=}")
|
|
544
|
-
|
|
291
|
+
|
|
292
|
+
super().cancel_selection()
|
|
545
293
|
try:
|
|
546
294
|
for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
|
|
547
|
-
self.colors[t][idx, 0] = self.previous_color[k][0]
|
|
548
295
|
self.colors[t][idx, 1] = self.previous_color[k][1]
|
|
549
296
|
except Exception as e:
|
|
550
|
-
|
|
297
|
+
pass
|
|
551
298
|
|
|
552
299
|
def hide_annotation_buttons(self):
|
|
553
300
|
|
|
@@ -653,169 +400,6 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
653
400
|
|
|
654
401
|
self.selection.pop(0)
|
|
655
402
|
|
|
656
|
-
# self.fcanvas.canvas.draw()
|
|
657
|
-
|
|
658
|
-
def locate_stack(self):
|
|
659
|
-
|
|
660
|
-
"""
|
|
661
|
-
Locate the target movie.
|
|
662
|
-
|
|
663
|
-
"""
|
|
664
|
-
|
|
665
|
-
movies = glob(self.pos + os.sep.join(["movie", f"{self.parent_window.parent_window.movie_prefix}*.tif"]))
|
|
666
|
-
|
|
667
|
-
if len(movies) == 0:
|
|
668
|
-
msgBox = QMessageBox()
|
|
669
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
670
|
-
msgBox.setText("No movie is detected in the experiment folder.\nPlease check the stack prefix...")
|
|
671
|
-
msgBox.setWindowTitle("Warning")
|
|
672
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
673
|
-
returnValue = msgBox.exec()
|
|
674
|
-
if returnValue == QMessageBox.Ok:
|
|
675
|
-
self.proceed = False
|
|
676
|
-
self.close()
|
|
677
|
-
else:
|
|
678
|
-
self.close()
|
|
679
|
-
else:
|
|
680
|
-
self.stack_path = movies[0]
|
|
681
|
-
self.len_movie = self.parent_window.parent_window.len_movie
|
|
682
|
-
len_movie_auto = auto_load_number_of_frames(self.stack_path)
|
|
683
|
-
if len_movie_auto is not None:
|
|
684
|
-
self.len_movie = len_movie_auto
|
|
685
|
-
exp_config = self.exp_dir + "config.ini"
|
|
686
|
-
self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
|
|
687
|
-
self.channel_names = np.array(self.channel_names)
|
|
688
|
-
self.channels = np.array(self.channels)
|
|
689
|
-
self.nbr_channels = len(self.channels)
|
|
690
|
-
|
|
691
|
-
def locate_tracks(self):
|
|
692
|
-
|
|
693
|
-
"""
|
|
694
|
-
Locate the tracks.
|
|
695
|
-
"""
|
|
696
|
-
|
|
697
|
-
if not os.path.exists(self.trajectories_path):
|
|
698
|
-
|
|
699
|
-
msgBox = QMessageBox()
|
|
700
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
701
|
-
msgBox.setText("The trajectories cannot be detected.")
|
|
702
|
-
msgBox.setWindowTitle("Warning")
|
|
703
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
704
|
-
returnValue = msgBox.exec()
|
|
705
|
-
if returnValue == QMessageBox.Yes:
|
|
706
|
-
self.close()
|
|
707
|
-
else:
|
|
708
|
-
|
|
709
|
-
# Load and prep tracks
|
|
710
|
-
self.df_tracks = pd.read_csv(self.trajectories_path)
|
|
711
|
-
self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
|
|
712
|
-
|
|
713
|
-
cols = np.array(self.df_tracks.columns)
|
|
714
|
-
self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
|
|
715
|
-
self.class_cols = list(cols[self.class_cols])
|
|
716
|
-
try:
|
|
717
|
-
self.class_cols.remove('class_id')
|
|
718
|
-
except:
|
|
719
|
-
pass
|
|
720
|
-
try:
|
|
721
|
-
self.class_cols.remove('class_color')
|
|
722
|
-
except:
|
|
723
|
-
pass
|
|
724
|
-
if len(self.class_cols) > 0:
|
|
725
|
-
self.class_name = self.class_cols[0]
|
|
726
|
-
self.expected_status = 'status'
|
|
727
|
-
suffix = self.class_name.replace('class', '').replace('_', '')
|
|
728
|
-
if suffix != '':
|
|
729
|
-
self.expected_status += '_' + suffix
|
|
730
|
-
self.expected_time = 't_' + suffix
|
|
731
|
-
else:
|
|
732
|
-
self.expected_time = 't0'
|
|
733
|
-
self.time_name = self.expected_time
|
|
734
|
-
self.status_name = self.expected_status
|
|
735
|
-
else:
|
|
736
|
-
self.class_name = 'class'
|
|
737
|
-
self.time_name = 't0'
|
|
738
|
-
self.status_name = 'status'
|
|
739
|
-
|
|
740
|
-
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:
|
|
741
|
-
# only create the status column if it does not exist to not erase static classification results
|
|
742
|
-
self.make_status_column()
|
|
743
|
-
elif self.time_name in self.df_tracks.columns and self.class_name in self.df_tracks.columns:
|
|
744
|
-
# all good, do nothing
|
|
745
|
-
pass
|
|
746
|
-
else:
|
|
747
|
-
if not self.status_name in self.df_tracks.columns:
|
|
748
|
-
self.df_tracks[self.status_name] = 0
|
|
749
|
-
self.df_tracks['status_color'] = color_from_status(0)
|
|
750
|
-
self.df_tracks['class_color'] = color_from_class(1)
|
|
751
|
-
|
|
752
|
-
if not self.class_name in self.df_tracks.columns:
|
|
753
|
-
self.df_tracks[self.class_name] = 1
|
|
754
|
-
if not self.time_name in self.df_tracks.columns:
|
|
755
|
-
self.df_tracks[self.time_name] = -1
|
|
756
|
-
|
|
757
|
-
self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
|
|
758
|
-
self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
|
|
759
|
-
|
|
760
|
-
self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
|
|
761
|
-
self.df_tracks['x_anim'] = self.df_tracks['POSITION_X'] * self.fraction
|
|
762
|
-
self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y'] * self.fraction
|
|
763
|
-
self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
|
|
764
|
-
self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
|
|
765
|
-
|
|
766
|
-
self.extract_scatter_from_trajectories()
|
|
767
|
-
self.track_of_interest = self.df_tracks['TRACK_ID'].min()
|
|
768
|
-
|
|
769
|
-
self.loc_t = []
|
|
770
|
-
self.loc_idx = []
|
|
771
|
-
for t in range(len(self.tracks)):
|
|
772
|
-
indices = np.where(self.tracks[t] == self.track_of_interest)[0]
|
|
773
|
-
if len(indices) > 0:
|
|
774
|
-
self.loc_t.append(t)
|
|
775
|
-
self.loc_idx.append(indices[0])
|
|
776
|
-
|
|
777
|
-
self.MinMaxScaler = MinMaxScaler()
|
|
778
|
-
self.columns_to_rescale = list(self.df_tracks.columns)
|
|
779
|
-
#self.columns_to_rescale = self.df_tracks.select_dtypes(exclude=['object']).columns
|
|
780
|
-
|
|
781
|
-
# is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
|
|
782
|
-
# is_number_test = is_number(self.df_tracks.dtypes)
|
|
783
|
-
# self.columns_to_rescale = [col for t,col in zip(is_number_test,self.df_tracks.columns) if t]
|
|
784
|
-
# print(self.columns_to_rescale)
|
|
785
|
-
|
|
786
|
-
cols_to_remove = ['group', 'group_color', 'status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME',
|
|
787
|
-
'x_anim', 'y_anim', 't','dummy','group_color',
|
|
788
|
-
'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
|
|
789
|
-
'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
|
|
790
|
-
'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent', 'ID'] + self.class_cols
|
|
791
|
-
|
|
792
|
-
meta = get_experiment_metadata(self.exp_dir)
|
|
793
|
-
if meta is not None:
|
|
794
|
-
keys = list(meta.keys())
|
|
795
|
-
cols_to_remove.extend(keys)
|
|
796
|
-
|
|
797
|
-
labels = get_experiment_labels(self.exp_dir)
|
|
798
|
-
if labels is not None:
|
|
799
|
-
keys = list(labels.keys())
|
|
800
|
-
cols_to_remove.extend(labels)
|
|
801
|
-
|
|
802
|
-
cols = np.array(list(self.df_tracks.columns))
|
|
803
|
-
time_cols = np.array([c.startswith('t_') for c in cols])
|
|
804
|
-
time_cols = list(cols[time_cols])
|
|
805
|
-
cols_to_remove += time_cols
|
|
806
|
-
#cols_to_remove.extend(self.df_tracks.select_dtypes(include=['object']).columns)
|
|
807
|
-
|
|
808
|
-
for tr in cols_to_remove:
|
|
809
|
-
try:
|
|
810
|
-
self.columns_to_rescale.remove(tr)
|
|
811
|
-
except:
|
|
812
|
-
pass
|
|
813
|
-
# print(f'column {tr} could not be found...')
|
|
814
|
-
|
|
815
|
-
x = self.df_tracks[self.columns_to_rescale].values
|
|
816
|
-
self.MinMaxScaler.fit(x)
|
|
817
|
-
|
|
818
|
-
# self.loc_t, self.loc_idx = np.where(self.tracks==self.track_of_interest)
|
|
819
403
|
|
|
820
404
|
def make_status_column(self):
|
|
821
405
|
|
|
@@ -947,53 +531,53 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
947
531
|
self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
|
|
948
532
|
self.tracks.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, 'TRACK_ID'].to_numpy())
|
|
949
533
|
|
|
950
|
-
def load_annotator_config(self):
|
|
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
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
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
|
|
997
581
|
|
|
998
582
|
def prepare_stack(self):
|
|
999
583
|
|
|
@@ -1079,76 +663,17 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
1079
663
|
self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
|
|
1080
664
|
self.fcanvas.canvas.draw()
|
|
1081
665
|
|
|
1082
|
-
def
|
|
666
|
+
def select_single_cell(self, index, timepoint):
|
|
1083
667
|
|
|
1084
|
-
self.
|
|
1085
|
-
self.
|
|
1086
|
-
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)
|
|
1087
672
|
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
self.
|
|
1091
|
-
self.
|
|
1092
|
-
self.cell_ax.grid(which='major')
|
|
1093
|
-
self.cell_ax.set_xlabel("time [frame]")
|
|
1094
|
-
self.cell_ax.set_ylabel("signal")
|
|
1095
|
-
|
|
1096
|
-
self.cell_fig.set_facecolor('none') # or 'None'
|
|
1097
|
-
self.cell_fig.canvas.setStyleSheet("background-color: transparent;")
|
|
1098
|
-
|
|
1099
|
-
self.lines = [
|
|
1100
|
-
self.cell_ax.plot([np.linspace(0, self.len_movie - 1, self.len_movie)], [np.zeros((self.len_movie))])[0] for
|
|
1101
|
-
i in range(len(self.signal_choice_cb))]
|
|
1102
|
-
for i in range(len(self.lines)):
|
|
1103
|
-
self.lines[i].set_label(f'signal {i}')
|
|
1104
|
-
|
|
1105
|
-
min_val, max_val = self.cell_ax.get_ylim()
|
|
1106
|
-
self.line_dt, = self.cell_ax.plot([-1, -1], [min_val, max_val], c="k", linestyle="--")
|
|
1107
|
-
|
|
1108
|
-
self.cell_ax.set_xlim(0, self.len_movie)
|
|
1109
|
-
self.cell_ax.legend(fontsize=8)
|
|
1110
|
-
self.cell_fcanvas.canvas.draw()
|
|
1111
|
-
|
|
1112
|
-
self.plot_signals()
|
|
1113
|
-
|
|
1114
|
-
def on_scatter_pick(self, event):
|
|
1115
|
-
|
|
1116
|
-
self.event = event
|
|
1117
|
-
|
|
1118
|
-
self.correct_btn.disconnect()
|
|
1119
|
-
self.correct_btn.clicked.connect(self.show_annotation_buttons)
|
|
1120
|
-
|
|
1121
|
-
ind = event.ind
|
|
1122
|
-
|
|
1123
|
-
if len(ind) > 1:
|
|
1124
|
-
# More than one point in vicinity
|
|
1125
|
-
datax, datay = [self.positions[self.framedata][i, 0] for i in ind], [self.positions[self.framedata][i, 1]
|
|
1126
|
-
for i in ind]
|
|
1127
|
-
msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
|
|
1128
|
-
dist = np.sqrt((np.array(datax) - msx) ** 2 + (np.array(datay) - msy) ** 2)
|
|
1129
|
-
ind = [ind[np.argmin(dist)]]
|
|
1130
|
-
|
|
1131
|
-
if len(ind) > 0 and (len(self.selection) == 0):
|
|
1132
|
-
|
|
1133
|
-
self.selection.append([ind[0],self.framedata])
|
|
1134
|
-
self.select_single_cell(ind[0], self.framedata)
|
|
1135
|
-
|
|
1136
|
-
elif len(ind) > 0 and len(self.selection) == 1:
|
|
1137
|
-
self.cancel_btn.click()
|
|
1138
|
-
else:
|
|
1139
|
-
pass
|
|
1140
|
-
|
|
1141
|
-
def select_single_cell(self, index, timepoint):
|
|
1142
|
-
|
|
1143
|
-
self.correct_btn.setEnabled(True)
|
|
1144
|
-
self.cancel_btn.setEnabled(True)
|
|
1145
|
-
self.del_shortcut.setEnabled(True)
|
|
1146
|
-
self.no_event_shortcut.setEnabled(True)
|
|
1147
|
-
|
|
1148
|
-
self.track_of_interest = self.tracks[timepoint][index]
|
|
1149
|
-
print(f'You selected cell #{self.track_of_interest}...')
|
|
1150
|
-
self.give_cell_information()
|
|
1151
|
-
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()
|
|
1152
677
|
|
|
1153
678
|
self.loc_t = []
|
|
1154
679
|
self.loc_idx = []
|
|
@@ -1164,11 +689,6 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
1164
689
|
self.colors[t][idx] = 'lime'
|
|
1165
690
|
|
|
1166
691
|
|
|
1167
|
-
def shortcut_suppr(self):
|
|
1168
|
-
self.correct_btn.click()
|
|
1169
|
-
self.suppr_btn.click()
|
|
1170
|
-
self.correct_btn.click()
|
|
1171
|
-
|
|
1172
692
|
def shortcut_no_event(self):
|
|
1173
693
|
self.correct_btn.click()
|
|
1174
694
|
self.no_event_btn.click()
|
|
@@ -1237,25 +757,6 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
1237
757
|
self.anim.pause()
|
|
1238
758
|
self.stop_btn.clicked.connect(self.start)
|
|
1239
759
|
|
|
1240
|
-
def start(self):
|
|
1241
|
-
'''
|
|
1242
|
-
Starts interactive animation. Adds the draw frame command to the GUI
|
|
1243
|
-
handler, calls show to start the event loop.
|
|
1244
|
-
'''
|
|
1245
|
-
self.start_btn.setShortcut(QKeySequence(""))
|
|
1246
|
-
|
|
1247
|
-
self.last_frame_btn.setEnabled(True)
|
|
1248
|
-
self.last_frame_btn.clicked.connect(self.set_last_frame)
|
|
1249
|
-
|
|
1250
|
-
self.first_frame_btn.setEnabled(True)
|
|
1251
|
-
self.first_frame_btn.clicked.connect(self.set_first_frame)
|
|
1252
|
-
|
|
1253
|
-
self.start_btn.hide()
|
|
1254
|
-
self.stop_btn.show()
|
|
1255
|
-
|
|
1256
|
-
self.anim.event_source.start()
|
|
1257
|
-
self.stop_btn.clicked.connect(self.stop)
|
|
1258
|
-
|
|
1259
760
|
def give_cell_information(self):
|
|
1260
761
|
|
|
1261
762
|
cell_selected = f"cell: {self.track_of_interest}\n"
|
|
@@ -1273,17 +774,10 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
1273
774
|
self.df_tracks = self.df_tracks.drop(self.df_tracks[self.df_tracks[self.class_name] > 2].index)
|
|
1274
775
|
self.df_tracks.to_csv(self.trajectories_path, index=False)
|
|
1275
776
|
print('Table successfully exported...')
|
|
1276
|
-
self.
|
|
777
|
+
if self.class_choice_cb.currentText()!="":
|
|
778
|
+
self.compute_status_and_colors(0)
|
|
1277
779
|
self.extract_scatter_from_trajectories()
|
|
1278
780
|
|
|
1279
|
-
# self.give_cell_information()
|
|
1280
|
-
|
|
1281
|
-
# def interval_slider_action(self):
|
|
1282
|
-
|
|
1283
|
-
# print(dir(self.anim.event_source))
|
|
1284
|
-
|
|
1285
|
-
# self.anim.event_source.interval = self.interval_slider.value()
|
|
1286
|
-
# self.anim.event_source._timer_set_interval()
|
|
1287
781
|
|
|
1288
782
|
def set_last_frame(self):
|
|
1289
783
|
|
|
@@ -1315,6 +809,11 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
1315
809
|
|
|
1316
810
|
self.first_key = 0
|
|
1317
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
|
+
|
|
1318
817
|
self.anim._drawn_artists = sorted(self.anim._drawn_artists, key=lambda x: x.get_zorder())
|
|
1319
818
|
for a in self.anim._drawn_artists:
|
|
1320
819
|
a.set_visible(True)
|
|
@@ -1328,92 +827,15 @@ class SignalAnnotator(CelldetectiveMainWindow):
|
|
|
1328
827
|
self.stop_btn.clicked.connect(self.start)
|
|
1329
828
|
self.start_btn.setShortcut(QKeySequence("f"))
|
|
1330
829
|
|
|
1331
|
-
def export_signals(self):
|
|
1332
|
-
|
|
1333
|
-
auto_dataset_name = self.pos.split(os.sep)[-4] + '_' + self.pos.split(os.sep)[-2] + '.npy'
|
|
1334
|
-
|
|
1335
|
-
if self.normalized_signals:
|
|
1336
|
-
self.normalize_features_btn.click()
|
|
1337
830
|
|
|
1338
|
-
|
|
1339
|
-
cols = self.df_tracks.columns
|
|
1340
|
-
tracks = np.unique(self.df_tracks["TRACK_ID"].to_numpy())
|
|
1341
|
-
|
|
1342
|
-
for track in tracks:
|
|
1343
|
-
# Add all signals at given track
|
|
1344
|
-
signals = {}
|
|
1345
|
-
for c in cols:
|
|
1346
|
-
signals.update({c: self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, c].to_numpy()})
|
|
1347
|
-
time_of_interest = self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, self.time_name].to_numpy()[0]
|
|
1348
|
-
cclass = self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, self.class_name].to_numpy()[0]
|
|
1349
|
-
signals.update({"time_of_interest": time_of_interest, "class": cclass})
|
|
1350
|
-
# Here auto add all available channels
|
|
1351
|
-
training_set.append(signals)
|
|
831
|
+
class MeasureAnnotator(BaseAnnotator):
|
|
1352
832
|
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
pathsave += ".npy"
|
|
1357
|
-
try:
|
|
1358
|
-
np.save(pathsave, training_set)
|
|
1359
|
-
print(f'File successfully written in {pathsave}.')
|
|
1360
|
-
except Exception as e:
|
|
1361
|
-
print(f"Error {e}...")
|
|
1362
|
-
|
|
1363
|
-
def normalize_features(self):
|
|
1364
|
-
|
|
1365
|
-
x = self.df_tracks[self.columns_to_rescale].values
|
|
1366
|
-
|
|
1367
|
-
if not self.normalized_signals:
|
|
1368
|
-
x = self.MinMaxScaler.transform(x)
|
|
1369
|
-
self.df_tracks[self.columns_to_rescale] = x
|
|
1370
|
-
self.plot_signals()
|
|
1371
|
-
self.normalized_signals = True
|
|
1372
|
-
self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="#1565c0"))
|
|
1373
|
-
self.normalize_features_btn.setIconSize(QSize(25, 25))
|
|
1374
|
-
else:
|
|
1375
|
-
x = self.MinMaxScaler.inverse_transform(x)
|
|
1376
|
-
self.df_tracks[self.columns_to_rescale] = x
|
|
1377
|
-
self.plot_signals()
|
|
1378
|
-
self.normalized_signals = False
|
|
1379
|
-
self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
|
|
1380
|
-
self.normalize_features_btn.setIconSize(QSize(25, 25))
|
|
1381
|
-
|
|
1382
|
-
def switch_to_log(self):
|
|
1383
|
-
|
|
1384
|
-
"""
|
|
1385
|
-
Better would be to create a log(quantity) and plot it...
|
|
1386
|
-
"""
|
|
1387
|
-
|
|
1388
|
-
try:
|
|
1389
|
-
if self.cell_ax.get_yscale()=='linear':
|
|
1390
|
-
ymin,ymax = self.cell_ax.get_ylim()
|
|
1391
|
-
self.cell_ax.set_yscale('log')
|
|
1392
|
-
self.log_btn.setIcon(icon(MDI6.math_log,color="#1565c0"))
|
|
1393
|
-
self.cell_ax.set_ylim(self.value_magnitude, ymax)
|
|
1394
|
-
else:
|
|
1395
|
-
self.cell_ax.set_yscale('linear')
|
|
1396
|
-
self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
|
|
1397
|
-
except Exception as e:
|
|
1398
|
-
print(e)
|
|
1399
|
-
|
|
1400
|
-
#self.cell_ax.autoscale()
|
|
1401
|
-
self.cell_fcanvas.canvas.draw_idle()
|
|
1402
|
-
|
|
1403
|
-
class MeasureAnnotator(SignalAnnotator):
|
|
833
|
+
def __init__(self, *args, **kwargs):
|
|
834
|
+
|
|
835
|
+
super().__init__(read_config=False, *args, **kwargs)
|
|
1404
836
|
|
|
1405
|
-
|
|
837
|
+
self.setWindowTitle("Static annotator")
|
|
1406
838
|
|
|
1407
|
-
SignalAnnotator.__init__(self)
|
|
1408
|
-
self.parent_window = parent_window
|
|
1409
|
-
self.setWindowTitle("Signal annotator")
|
|
1410
|
-
self.mode = self.parent_window.mode
|
|
1411
|
-
self.pos = self.parent_window.parent_window.pos
|
|
1412
|
-
self.exp_dir = self.parent_window.exp_dir
|
|
1413
|
-
self.n_signals = 3
|
|
1414
|
-
self.soft_path = get_software_location()
|
|
1415
|
-
self.recently_modified = False
|
|
1416
|
-
self.selection = []
|
|
1417
839
|
self.int_validator = QIntValidator()
|
|
1418
840
|
self.current_alpha=0.5
|
|
1419
841
|
self.value_magnitude = 1
|
|
@@ -1421,255 +843,93 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1421
843
|
epsilon = 0.01
|
|
1422
844
|
self.observed_min_intensity = 0
|
|
1423
845
|
self.observed_max_intensity = 0 + epsilon
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
self.instructions_path = self.exp_dir + os.sep.join(['configs',f'signal_annotator_config_{self.mode}.json'])
|
|
1427
|
-
self.trajectories_path = self.pos + os.sep.join(['output','tables',f'trajectories_{self.mode}.csv'])
|
|
1428
|
-
|
|
1429
|
-
self.screen_height = self.parent_window.parent_window.parent_window.screen_height
|
|
1430
|
-
self.screen_width = self.parent_window.parent_window.parent_window.screen_width
|
|
846
|
+
|
|
1431
847
|
self.current_frame = 0
|
|
1432
848
|
self.show_fliers = False
|
|
1433
849
|
self.status_name = 'group'
|
|
1434
850
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
self.generate_signal_choices()
|
|
1450
|
-
self.frame_lbl = QLabel('position: ')
|
|
1451
|
-
self.static_image()
|
|
1452
|
-
self.create_cell_signal_canvas()
|
|
1453
|
-
|
|
1454
|
-
self.populate_widget()
|
|
1455
|
-
self.changed_class()
|
|
1456
|
-
|
|
1457
|
-
self.setMinimumWidth(int(0.8 * self.screen_width))
|
|
1458
|
-
# self.setMaximumHeight(int(0.8*self.screen_height))
|
|
1459
|
-
self.setMinimumHeight(int(0.8 * self.screen_height))
|
|
1460
|
-
# self.setMaximumHeight(int(0.8*self.screen_height))
|
|
1461
|
-
|
|
1462
|
-
self.setAttribute(Qt.WA_DeleteOnClose)
|
|
1463
|
-
self.previous_index = None
|
|
1464
|
-
|
|
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])
|
|
1465
865
|
|
|
1466
|
-
|
|
866
|
+
else:
|
|
867
|
+
self.close()
|
|
868
|
+
|
|
869
|
+
def locate_tracks(self):
|
|
1467
870
|
|
|
1468
871
|
"""
|
|
1469
|
-
|
|
1470
|
-
|
|
872
|
+
Locate the tracks.
|
|
1471
873
|
"""
|
|
1472
874
|
|
|
1473
|
-
self.
|
|
1474
|
-
self.current_label=self.labels[self.current_frame]
|
|
1475
|
-
self.fig, self.ax = plt.subplots(tight_layout=True)
|
|
1476
|
-
self.fcanvas = FigureCanvas(self.fig, interactive=True)
|
|
1477
|
-
self.ax.clear()
|
|
1478
|
-
# print(self.current_stack.shape)
|
|
1479
|
-
self.im = self.ax.imshow(self.img, cmap='gray')
|
|
1480
|
-
self.status_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker="o",
|
|
1481
|
-
facecolors='none', edgecolors=self.colors[0][:, 0], s=200, picker=True)
|
|
1482
|
-
self.im_mask = self.ax.imshow(np.ma.masked_where(self.current_label == 0, self.current_label),
|
|
1483
|
-
cmap='viridis', interpolation='none',alpha=self.current_alpha,vmin=0,vmax=np.nanmax(self.labels.flatten()))
|
|
1484
|
-
self.ax.set_xticks([])
|
|
1485
|
-
self.ax.set_yticks([])
|
|
1486
|
-
self.ax.set_aspect('equal')
|
|
1487
|
-
|
|
1488
|
-
self.fig.set_facecolor('none') # or 'None'
|
|
1489
|
-
self.fig.canvas.setStyleSheet("background-color: black;")
|
|
1490
|
-
|
|
1491
|
-
self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
|
|
1492
|
-
self.fcanvas.canvas.draw()
|
|
1493
|
-
|
|
1494
|
-
def create_cell_signal_canvas(self):
|
|
1495
|
-
|
|
1496
|
-
self.cell_fig, self.cell_ax = plt.subplots()
|
|
1497
|
-
self.cell_fcanvas = FigureCanvas(self.cell_fig, interactive=False)
|
|
1498
|
-
self.cell_ax.clear()
|
|
1499
|
-
|
|
1500
|
-
# spacing = 0.5
|
|
1501
|
-
# minorLocator = MultipleLocator(1)
|
|
1502
|
-
# self.cell_ax.xaxis.set_minor_locator(minorLocator)
|
|
1503
|
-
# self.cell_ax.xaxis.set_major_locator(MultipleLocator(5))
|
|
1504
|
-
self.cell_ax.grid(which='major')
|
|
1505
|
-
self.cell_ax.set_xlabel("time [frame]")
|
|
1506
|
-
self.cell_ax.set_ylabel("signal")
|
|
1507
|
-
|
|
1508
|
-
self.cell_fig.set_facecolor('none') # or 'None'
|
|
1509
|
-
self.cell_fig.canvas.setStyleSheet("background-color: transparent;")
|
|
1510
|
-
|
|
1511
|
-
self.lines = [
|
|
1512
|
-
self.cell_ax.plot([np.linspace(0, self.len_movie - 1, self.len_movie)], [np.zeros(self.len_movie)])[0] for
|
|
1513
|
-
i in range(len(self.signal_choice_cb))]
|
|
1514
|
-
for i in range(len(self.lines)):
|
|
1515
|
-
self.lines[i].set_label(f'signal {i}')
|
|
1516
|
-
|
|
1517
|
-
min_val, max_val = self.cell_ax.get_ylim()
|
|
1518
|
-
self.line_dt, = self.cell_ax.plot([-1, -1], [min_val, max_val], c="k", linestyle="--")
|
|
1519
|
-
|
|
1520
|
-
self.cell_ax.set_xlim(0, self.len_movie)
|
|
1521
|
-
self.cell_fcanvas.canvas.draw()
|
|
1522
|
-
|
|
1523
|
-
self.plot_signals()
|
|
1524
|
-
|
|
1525
|
-
def plot_signals(self):
|
|
1526
|
-
|
|
1527
|
-
#try:
|
|
1528
|
-
current_frame = self.current_frame # Assuming you have a variable for the current frame
|
|
1529
|
-
|
|
1530
|
-
yvalues = []
|
|
1531
|
-
all_yvalues = []
|
|
1532
|
-
current_yvalues = []
|
|
1533
|
-
all_median_values = []
|
|
1534
|
-
labels = []
|
|
1535
|
-
range_values = []
|
|
1536
|
-
|
|
1537
|
-
for i in range(len(self.signal_choice_cb)):
|
|
1538
|
-
|
|
1539
|
-
signal_choice = self.signal_choice_cb[i].currentText()
|
|
1540
|
-
|
|
1541
|
-
if signal_choice != "--":
|
|
1542
|
-
if 'TRACK_ID' in self.df_tracks.columns:
|
|
1543
|
-
ydata = self.df_tracks.loc[
|
|
1544
|
-
(self.df_tracks['TRACK_ID'] == self.track_of_interest) &
|
|
1545
|
-
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1546
|
-
else:
|
|
1547
|
-
ydata = self.df_tracks.loc[
|
|
1548
|
-
(self.df_tracks['ID'] == self.track_of_interest), signal_choice].to_numpy()
|
|
1549
|
-
all_ydata = self.df_tracks.loc[:, signal_choice].to_numpy()
|
|
1550
|
-
ydataNaN = ydata
|
|
1551
|
-
ydata = ydata[ydata == ydata] # remove nan
|
|
1552
|
-
|
|
1553
|
-
current_ydata = self.df_tracks.loc[
|
|
1554
|
-
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1555
|
-
current_ydata = current_ydata[current_ydata == current_ydata]
|
|
1556
|
-
all_ydata = all_ydata[all_ydata == all_ydata]
|
|
1557
|
-
yvalues.extend(ydataNaN)
|
|
1558
|
-
current_yvalues.append(current_ydata)
|
|
1559
|
-
all_yvalues.append(all_ydata)
|
|
1560
|
-
range_values.extend(all_ydata)
|
|
1561
|
-
labels.append(signal_choice)
|
|
1562
|
-
|
|
1563
|
-
self.cell_ax.clear()
|
|
1564
|
-
|
|
1565
|
-
if len(yvalues) > 0:
|
|
1566
|
-
try:
|
|
1567
|
-
self.cell_ax.boxplot(all_yvalues, showfliers=self.show_fliers)
|
|
1568
|
-
except Exception as e:
|
|
1569
|
-
print(f"{e=}")
|
|
1570
|
-
ylim = self.cell_ax.get_ylim()
|
|
1571
|
-
self.cell_ax.set_ylim(ylim)
|
|
1572
|
-
x_pos = np.arange(len(all_yvalues)) + 1
|
|
1573
|
-
|
|
1574
|
-
for index, feature in enumerate(current_yvalues):
|
|
1575
|
-
x_values_strip = (index + 1) + np.random.normal(0, 0.04, size=len(
|
|
1576
|
-
feature))
|
|
1577
|
-
self.cell_ax.plot(x_values_strip, feature, marker='o', linestyle='None', color=tab10.colors[0],
|
|
1578
|
-
alpha=0.1)
|
|
1579
|
-
|
|
1580
|
-
self.cell_ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3], alpha=1)
|
|
1581
|
-
|
|
1582
|
-
range_values = np.array(range_values)
|
|
1583
|
-
if len(range_values[range_values==range_values])>0:
|
|
1584
|
-
|
|
1585
|
-
if len(range_values[range_values>0])>0:
|
|
1586
|
-
self.value_magnitude = np.nanmin(range_values[range_values>0]) - 0.03*(np.nanmax(range_values[range_values>0]) - np.nanmin(range_values[range_values>0]))
|
|
1587
|
-
else:
|
|
1588
|
-
self.value_magnitude = 1
|
|
1589
|
-
|
|
1590
|
-
self.non_log_ymin = np.nanmin(range_values) - 0.03*(np.nanmax(range_values) - np.nanmin(range_values))
|
|
1591
|
-
self.non_log_ymax = np.nanmax(range_values) + 0.03*(np.nanmax(range_values) - np.nanmin(range_values))
|
|
1592
|
-
if self.cell_ax.get_yscale()=='linear':
|
|
1593
|
-
self.cell_ax.set_ylim(self.non_log_ymin, self.non_log_ymax)
|
|
1594
|
-
else:
|
|
1595
|
-
self.cell_ax.set_ylim(self.value_magnitude, self.non_log_ymax)
|
|
875
|
+
if not os.path.exists(self.trajectories_path):
|
|
1596
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()
|
|
1597
885
|
else:
|
|
1598
|
-
self.cell_ax.text(0.5, 0.5, "No data available", horizontalalignment='center',
|
|
1599
|
-
verticalalignment='center', transform=self.cell_ax.transAxes)
|
|
1600
886
|
|
|
1601
|
-
|
|
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'])
|
|
1602
893
|
|
|
1603
|
-
|
|
1604
|
-
|
|
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])
|
|
1605
897
|
|
|
1606
|
-
|
|
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'
|
|
1607
909
|
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
continue
|
|
1615
|
-
else:
|
|
1616
|
-
maxx = np.max(self.df_tracks.loc[:, signal].to_numpy().flatten())
|
|
1617
|
-
minn = np.min(self.df_tracks.loc[:, signal].to_numpy().flatten())
|
|
1618
|
-
min_values.append(minn)
|
|
1619
|
-
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
|
|
1620
916
|
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
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)
|
|
1625
921
|
|
|
1626
|
-
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
if signal_choice != "--":
|
|
1632
|
-
#print(f'plot signal {signal_choice} for cell {self.track_of_interest} at frame {current_frame}')
|
|
1633
|
-
if 'TRACK_ID' in self.df_tracks.columns:
|
|
1634
|
-
ydata = self.df_tracks.loc[
|
|
1635
|
-
(self.df_tracks['TRACK_ID'] == self.track_of_interest) &
|
|
1636
|
-
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1637
|
-
else:
|
|
1638
|
-
ydata = self.df_tracks.loc[
|
|
1639
|
-
(self.df_tracks['ID'] == self.track_of_interest) &
|
|
1640
|
-
(self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
|
|
1641
|
-
ydata = ydata[ydata == ydata] # remove nan
|
|
1642
|
-
yvalues.extend(ydata)
|
|
1643
|
-
x_pos = np.arange(len(yvalues)) + 1
|
|
1644
|
-
ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3],
|
|
1645
|
-
alpha=1) # Plot red points representing cells
|
|
1646
|
-
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)
|
|
1647
927
|
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
# More than one point in vicinity
|
|
1652
|
-
datax, datay = [self.positions[self.framedata][i, 0] for i in ind], [self.positions[self.framedata][i, 1]
|
|
1653
|
-
for i in ind]
|
|
1654
|
-
msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
|
|
1655
|
-
dist = np.sqrt((np.array(datax) - msx) ** 2 + (np.array(datay) - msy) ** 2)
|
|
1656
|
-
ind = [ind[np.argmin(dist)]]
|
|
1657
|
-
|
|
1658
|
-
if len(ind) > 0 and (len(self.selection) == 0):
|
|
1659
|
-
ind = ind[0]
|
|
1660
|
-
self.selection.append(ind)
|
|
1661
|
-
self.correct_btn.setEnabled(True)
|
|
1662
|
-
self.cancel_btn.setEnabled(True)
|
|
1663
|
-
self.del_shortcut.setEnabled(True)
|
|
1664
|
-
self.no_event_shortcut.setEnabled(True)
|
|
1665
|
-
self.track_of_interest = self.tracks[self.framedata][ind]
|
|
1666
|
-
print(f'You selected cell #{self.track_of_interest}...')
|
|
1667
|
-
self.give_cell_information()
|
|
1668
|
-
if len(self.cell_ax.lines) > 0:
|
|
1669
|
-
self.cell_ax.lines[-1].remove() # Remove the last line (red points) from the plot
|
|
1670
|
-
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()
|
|
1671
931
|
else:
|
|
1672
|
-
self.
|
|
932
|
+
self.track_of_interest = self.df_tracks.dropna(subset='ID')['ID'].min()
|
|
1673
933
|
|
|
1674
934
|
self.loc_t = []
|
|
1675
935
|
self.loc_idx = []
|
|
@@ -1679,49 +939,80 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1679
939
|
self.loc_t.append(t)
|
|
1680
940
|
self.loc_idx.append(indices[0])
|
|
1681
941
|
|
|
1682
|
-
self.
|
|
1683
|
-
|
|
1684
|
-
self.previous_color.append(self.colors[t][idx].copy())
|
|
1685
|
-
self.colors[t][idx] = 'lime'
|
|
1686
|
-
|
|
1687
|
-
elif len(ind) > 0 and len(self.selection) == 1:
|
|
1688
|
-
self.cancel_btn.click()
|
|
1689
|
-
else:
|
|
1690
|
-
pass
|
|
1691
|
-
self.draw_frame(self.current_frame)
|
|
1692
|
-
self.fcanvas.canvas.draw()
|
|
1693
|
-
|
|
1694
|
-
def populate_widget(self):
|
|
1695
|
-
|
|
1696
|
-
"""
|
|
1697
|
-
Create the multibox design.
|
|
1698
|
-
|
|
1699
|
-
"""
|
|
942
|
+
self.MinMaxScaler = MinMaxScaler()
|
|
943
|
+
self.columns_to_rescale = list(self.df_tracks.columns)
|
|
1700
944
|
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
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
|
|
1704
950
|
|
|
1705
|
-
|
|
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)
|
|
1706
955
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
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)
|
|
1710
960
|
|
|
1711
|
-
|
|
961
|
+
for tr in cols_to_remove:
|
|
962
|
+
try:
|
|
963
|
+
self.columns_to_rescale.remove(tr)
|
|
964
|
+
except:
|
|
965
|
+
pass
|
|
1712
966
|
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
class_hbox.setSpacing(0)
|
|
967
|
+
x = self.df_tracks[self.columns_to_rescale].values
|
|
968
|
+
self.MinMaxScaler.fit(x)
|
|
1716
969
|
|
|
1717
|
-
|
|
1718
|
-
|
|
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
|
+
|
|
976
|
+
time_option_hbox = QHBoxLayout()
|
|
977
|
+
time_option_hbox.setContentsMargins(100, 0, 100, 0)
|
|
978
|
+
time_option_hbox.setSpacing(0)
|
|
1719
979
|
|
|
980
|
+
self.time_of_interest_label = QLabel('phenotype: ')
|
|
981
|
+
time_option_hbox.addWidget(self.time_of_interest_label, 30)
|
|
982
|
+
|
|
983
|
+
self.time_of_interest_le = QLineEdit()
|
|
984
|
+
self.time_of_interest_le.setValidator(self.int_validator)
|
|
985
|
+
time_option_hbox.addWidget(self.time_of_interest_le)
|
|
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()
|
|
1720
1011
|
cols = np.array(self.df_tracks.columns)
|
|
1721
1012
|
self.class_cols = np.array([c.startswith('group') or c.startswith('status') for c in list(self.df_tracks.columns)])
|
|
1722
1013
|
self.class_cols = list(cols[self.class_cols])
|
|
1723
1014
|
|
|
1724
|
-
to_remove = ['group_id','group_color','class_id','class_color']
|
|
1015
|
+
to_remove = ['group_id', 'group_color', 'class_id', 'class_color', 'status_color']
|
|
1725
1016
|
for col in to_remove:
|
|
1726
1017
|
try:
|
|
1727
1018
|
self.class_cols.remove(col)
|
|
@@ -1730,143 +1021,21 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1730
1021
|
|
|
1731
1022
|
self.class_choice_cb.addItems(self.class_cols)
|
|
1732
1023
|
self.class_choice_cb.currentIndexChanged.connect(self.changed_class)
|
|
1733
|
-
class_hbox.addWidget(self.class_choice_cb, 70)
|
|
1734
|
-
|
|
1735
|
-
self.add_class_btn = QPushButton('')
|
|
1736
|
-
self.add_class_btn.setStyleSheet(self.button_select_all)
|
|
1737
|
-
self.add_class_btn.setIcon(icon(MDI6.plus, color="black"))
|
|
1738
|
-
self.add_class_btn.setToolTip("Add a new characteristic group")
|
|
1739
|
-
self.add_class_btn.setIconSize(QSize(20, 20))
|
|
1740
|
-
self.add_class_btn.clicked.connect(self.create_new_event_class)
|
|
1741
|
-
class_hbox.addWidget(self.add_class_btn, 5)
|
|
1742
|
-
|
|
1743
|
-
self.del_class_btn = QPushButton('')
|
|
1744
|
-
self.del_class_btn.setStyleSheet(self.button_select_all)
|
|
1745
|
-
self.del_class_btn.setIcon(icon(MDI6.delete, color="black"))
|
|
1746
|
-
self.del_class_btn.setToolTip("Delete a characteristic group")
|
|
1747
|
-
self.del_class_btn.setIconSize(QSize(20, 20))
|
|
1748
|
-
self.del_class_btn.clicked.connect(self.del_event_class)
|
|
1749
|
-
class_hbox.addWidget(self.del_class_btn, 5)
|
|
1750
|
-
|
|
1751
|
-
self.left_panel.addLayout(class_hbox,5)
|
|
1752
|
-
|
|
1753
|
-
self.cell_info = QLabel('')
|
|
1754
|
-
self.left_panel.addWidget(self.cell_info,5)
|
|
1755
|
-
|
|
1756
|
-
time_option_hbox = QHBoxLayout()
|
|
1757
|
-
time_option_hbox.setContentsMargins(100, 0, 100, 0)
|
|
1758
|
-
time_option_hbox.setSpacing(0)
|
|
1759
|
-
|
|
1760
|
-
self.time_of_interest_label = QLabel('phenotype: ')
|
|
1761
|
-
time_option_hbox.addWidget(self.time_of_interest_label, 30)
|
|
1762
|
-
self.time_of_interest_le = QLineEdit()
|
|
1763
|
-
self.time_of_interest_le.setValidator(self.int_validator)
|
|
1764
|
-
time_option_hbox.addWidget(self.time_of_interest_le)
|
|
1765
|
-
self.del_cell_btn = QPushButton('')
|
|
1766
|
-
self.del_cell_btn.setStyleSheet(self.button_select_all)
|
|
1767
|
-
self.del_cell_btn.setIcon(icon(MDI6.delete, color="black"))
|
|
1768
|
-
self.del_cell_btn.setToolTip("Delete cell")
|
|
1769
|
-
self.del_cell_btn.setIconSize(QSize(20, 20))
|
|
1770
|
-
self.del_cell_btn.clicked.connect(self.del_cell)
|
|
1771
|
-
time_option_hbox.addWidget(self.del_cell_btn)
|
|
1772
|
-
self.left_panel.addLayout(time_option_hbox,5)
|
|
1773
|
-
|
|
1774
|
-
main_action_hbox = QHBoxLayout()
|
|
1775
|
-
main_action_hbox.setContentsMargins(0,0,0,0)
|
|
1776
|
-
main_action_hbox.setSpacing(0)
|
|
1777
|
-
|
|
1778
|
-
self.correct_btn = QPushButton('correct')
|
|
1779
|
-
self.correct_btn.setIcon(icon(MDI6.redo_variant, color="white"))
|
|
1780
|
-
self.correct_btn.setIconSize(QSize(20, 20))
|
|
1781
|
-
self.correct_btn.setStyleSheet(self.button_style_sheet)
|
|
1782
|
-
self.correct_btn.clicked.connect(self.show_annotation_buttons)
|
|
1783
|
-
self.correct_btn.setEnabled(False)
|
|
1784
|
-
main_action_hbox.addWidget(self.correct_btn)
|
|
1785
|
-
|
|
1786
|
-
self.cancel_btn = QPushButton('cancel')
|
|
1787
|
-
self.cancel_btn.setStyleSheet(self.button_style_sheet_2)
|
|
1788
|
-
self.cancel_btn.setShortcut(QKeySequence("Esc"))
|
|
1789
|
-
self.cancel_btn.setEnabled(False)
|
|
1790
|
-
self.cancel_btn.clicked.connect(self.cancel_selection)
|
|
1791
|
-
main_action_hbox.addWidget(self.cancel_btn)
|
|
1792
|
-
self.left_panel.addLayout(main_action_hbox,5)
|
|
1793
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
|
+
|
|
1794
1032
|
self.annotation_btns_to_hide = [self.time_of_interest_label,
|
|
1795
1033
|
self.time_of_interest_le,
|
|
1796
|
-
self.
|
|
1034
|
+
self.suppr_btn]
|
|
1797
1035
|
self.hide_annotation_buttons()
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
self.del_shortcut = QShortcut(Qt.Key_Delete, self) # QKeySequence("s")
|
|
1801
|
-
self.del_shortcut.activated.connect(self.shortcut_suppr)
|
|
1802
|
-
self.del_shortcut.setEnabled(False)
|
|
1803
|
-
|
|
1804
|
-
self.no_event_shortcut = QShortcut(QKeySequence("n"), self) # QKeySequence("s")
|
|
1805
|
-
self.no_event_shortcut.activated.connect(self.shortcut_no_event)
|
|
1806
|
-
self.no_event_shortcut.setEnabled(False)
|
|
1807
|
-
|
|
1808
|
-
# Cell signals
|
|
1809
|
-
self.cell_fcanvas.setMinimumHeight(int(0.2*self.screen_height))
|
|
1810
|
-
self.left_panel.addWidget(self.cell_fcanvas,90)
|
|
1811
|
-
|
|
1812
|
-
plot_buttons_hbox = QHBoxLayout()
|
|
1813
|
-
plot_buttons_hbox.setContentsMargins(0, 0, 0, 0)
|
|
1814
|
-
self.outliers_check = QCheckBox('Show outliers')
|
|
1815
|
-
self.outliers_check.toggled.connect(self.show_outliers)
|
|
1816
|
-
|
|
1817
|
-
self.normalize_features_btn = QPushButton('')
|
|
1818
|
-
self.normalize_features_btn.setStyleSheet(self.button_select_all)
|
|
1819
|
-
self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
|
|
1820
|
-
self.normalize_features_btn.setIconSize(QSize(25, 25))
|
|
1821
|
-
self.normalize_features_btn.setFixedSize(QSize(30, 30))
|
|
1822
|
-
# self.normalize_features_btn.setShortcut(QKeySequence('n'))
|
|
1823
|
-
self.normalize_features_btn.clicked.connect(self.normalize_features)
|
|
1824
|
-
|
|
1825
|
-
plot_buttons_hbox.addWidget(QLabel(''), 90)
|
|
1826
|
-
plot_buttons_hbox.addWidget(self.outliers_check)
|
|
1827
|
-
plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
|
|
1828
|
-
self.normalized_signals = False
|
|
1829
|
-
|
|
1830
|
-
self.log_btn = QPushButton()
|
|
1831
|
-
self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
|
|
1832
|
-
self.log_btn.setStyleSheet(self.button_select_all)
|
|
1833
|
-
self.log_btn.clicked.connect(self.switch_to_log)
|
|
1834
|
-
plot_buttons_hbox.addWidget(self.log_btn, 5)
|
|
1835
|
-
|
|
1836
|
-
self.left_panel.addLayout(plot_buttons_hbox,5)
|
|
1837
|
-
|
|
1838
|
-
signal_choice_vbox = QVBoxLayout()
|
|
1839
|
-
signal_choice_vbox.setContentsMargins(30, 0, 30, 50)
|
|
1840
|
-
for i in range(len(self.signal_choice_cb)):
|
|
1841
|
-
hlayout = QHBoxLayout()
|
|
1842
|
-
hlayout.addWidget(self.signal_choice_label[i], 20)
|
|
1843
|
-
hlayout.addWidget(self.signal_choice_cb[i], 75)
|
|
1844
|
-
# hlayout.addWidget(self.log_btns[i], 5)
|
|
1845
|
-
signal_choice_vbox.addLayout(hlayout)
|
|
1846
|
-
|
|
1847
|
-
# self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
|
|
1848
|
-
# self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
|
|
1849
|
-
# self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
|
|
1850
|
-
|
|
1851
|
-
self.left_panel.addLayout(signal_choice_vbox,10)
|
|
1852
|
-
|
|
1853
|
-
btn_hbox = QHBoxLayout()
|
|
1854
|
-
self.save_btn = QPushButton('Save')
|
|
1855
|
-
self.save_btn.setStyleSheet(self.button_style_sheet)
|
|
1856
|
-
self.save_btn.clicked.connect(self.save_trajectories)
|
|
1857
|
-
btn_hbox.addWidget(self.save_btn, 90)
|
|
1858
|
-
|
|
1859
|
-
self.export_btn = QPushButton('')
|
|
1860
|
-
self.export_btn.setStyleSheet(self.button_select_all)
|
|
1861
|
-
self.export_btn.clicked.connect(self.export_measurements)
|
|
1862
|
-
self.export_btn.setIcon(icon(MDI6.export, color="black"))
|
|
1863
|
-
self.export_btn.setIconSize(QSize(25, 25))
|
|
1864
|
-
btn_hbox.addWidget(self.export_btn, 10)
|
|
1865
|
-
self.left_panel.addLayout(btn_hbox,5)
|
|
1866
|
-
|
|
1867
|
-
# Animation
|
|
1036
|
+
|
|
1037
|
+
# Right panel
|
|
1868
1038
|
animation_buttons_box = QHBoxLayout()
|
|
1869
|
-
|
|
1870
1039
|
animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
|
|
1871
1040
|
|
|
1872
1041
|
self.first_frame_btn = QPushButton()
|
|
@@ -1942,25 +1111,180 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1942
1111
|
self.choose_channel.currentIndexChanged.connect(self.changed_channel)
|
|
1943
1112
|
channel_hbox.addWidget(self.choose_channel)
|
|
1944
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):
|
|
1945
1124
|
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
self.button_widget.adjustSize()
|
|
1125
|
+
"""
|
|
1126
|
+
Load an image.
|
|
1949
1127
|
|
|
1950
|
-
|
|
1951
|
-
self.show()
|
|
1128
|
+
"""
|
|
1952
1129
|
|
|
1953
|
-
|
|
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()
|
|
1954
1250
|
|
|
1955
|
-
def
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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()
|
|
1963
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()
|
|
1964
1288
|
|
|
1965
1289
|
def export_measurements(self):
|
|
1966
1290
|
|
|
@@ -1984,7 +1308,7 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1984
1308
|
print(f"Error {e}...")
|
|
1985
1309
|
|
|
1986
1310
|
def set_next_frame(self):
|
|
1987
|
-
|
|
1311
|
+
|
|
1988
1312
|
self.current_frame = self.current_frame + 1
|
|
1989
1313
|
if self.current_frame > self.len_movie - 1:
|
|
1990
1314
|
self.current_frame == self.len_movie - 1
|
|
@@ -1993,7 +1317,7 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
1993
1317
|
self.start_btn.setShortcut(QKeySequence("f"))
|
|
1994
1318
|
|
|
1995
1319
|
def set_previous_frame(self):
|
|
1996
|
-
|
|
1320
|
+
|
|
1997
1321
|
self.current_frame = self.current_frame - 1
|
|
1998
1322
|
if self.current_frame < 0:
|
|
1999
1323
|
self.current_frame == 0
|
|
@@ -2021,15 +1345,11 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2021
1345
|
return None
|
|
2022
1346
|
else:
|
|
2023
1347
|
pass
|
|
1348
|
+
|
|
2024
1349
|
self.df_tracks.loc[:, self.target_class] = 0
|
|
2025
|
-
self.class_choice_cb.clear()
|
|
2026
|
-
cols = np.array(self.df_tracks.columns)
|
|
2027
|
-
self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
|
|
2028
|
-
|
|
2029
|
-
self.class_cols = list(cols[self.class_cols])
|
|
2030
|
-
self.class_cols.remove('group_color')
|
|
2031
1350
|
|
|
2032
|
-
self.
|
|
1351
|
+
self.update_class_cb()
|
|
1352
|
+
|
|
2033
1353
|
idx = self.class_choice_cb.findText(self.target_class)
|
|
2034
1354
|
self.status_name = self.target_class
|
|
2035
1355
|
self.class_choice_cb.setCurrentIndex(idx)
|
|
@@ -2123,7 +1443,6 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2123
1443
|
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
2124
1444
|
|
|
2125
1445
|
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2126
|
-
|
|
2127
1446
|
self.extract_scatter_from_trajectories()
|
|
2128
1447
|
self.give_cell_information()
|
|
2129
1448
|
|
|
@@ -2135,16 +1454,17 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2135
1454
|
self.correct_btn.setText('correct')
|
|
2136
1455
|
self.cancel_btn.setEnabled(False)
|
|
2137
1456
|
self.del_shortcut.setEnabled(False)
|
|
2138
|
-
|
|
1457
|
+
|
|
2139
1458
|
if len(self.selection) > 0:
|
|
2140
1459
|
self.selection.pop(0)
|
|
1460
|
+
|
|
2141
1461
|
self.draw_frame(self.current_frame)
|
|
2142
1462
|
self.fcanvas.canvas.draw()
|
|
2143
1463
|
|
|
2144
1464
|
def assign_color_state(self, state):
|
|
2145
1465
|
|
|
2146
1466
|
if np.isnan(state):
|
|
2147
|
-
|
|
1467
|
+
state = "nan"
|
|
2148
1468
|
return self.state_color_map[state]
|
|
2149
1469
|
|
|
2150
1470
|
def draw_frame(self, framedata):
|
|
@@ -2171,9 +1491,11 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2171
1491
|
return (self.im, self.status_scatter,self.im_mask,)
|
|
2172
1492
|
|
|
2173
1493
|
def compute_status_and_colors(self):
|
|
1494
|
+
|
|
1495
|
+
self.cancel_selection()
|
|
2174
1496
|
|
|
2175
1497
|
if self.class_choice_cb.currentText() == '':
|
|
2176
|
-
|
|
1498
|
+
pass
|
|
2177
1499
|
else:
|
|
2178
1500
|
self.status_name = self.class_choice_cb.currentText()
|
|
2179
1501
|
|
|
@@ -2189,27 +1511,6 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2189
1511
|
pretty_table(self.state_color_map)
|
|
2190
1512
|
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2191
1513
|
|
|
2192
|
-
def del_event_class(self):
|
|
2193
|
-
|
|
2194
|
-
msgBox = QMessageBox()
|
|
2195
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
2196
|
-
msgBox.setText(
|
|
2197
|
-
f"You are about to delete characteristic group {self.class_choice_cb.currentText()}. Do you still want to proceed?")
|
|
2198
|
-
msgBox.setWindowTitle("Warning")
|
|
2199
|
-
msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
|
|
2200
|
-
returnValue = msgBox.exec()
|
|
2201
|
-
if returnValue == QMessageBox.No:
|
|
2202
|
-
return None
|
|
2203
|
-
else:
|
|
2204
|
-
class_to_delete = self.class_choice_cb.currentText()
|
|
2205
|
-
cols_to_delete = [class_to_delete]
|
|
2206
|
-
for c in cols_to_delete:
|
|
2207
|
-
try:
|
|
2208
|
-
self.df_tracks = self.df_tracks.drop([c], axis=1)
|
|
2209
|
-
except Exception as e:
|
|
2210
|
-
print(e)
|
|
2211
|
-
item_idx = self.class_choice_cb.findText(class_to_delete)
|
|
2212
|
-
self.class_choice_cb.removeItem(item_idx)
|
|
2213
1514
|
|
|
2214
1515
|
def make_status_column(self):
|
|
2215
1516
|
if self.status_name == "state_firstdetection":
|
|
@@ -2221,133 +1522,6 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2221
1522
|
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
2222
1523
|
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2223
1524
|
|
|
2224
|
-
def locate_tracks(self):
|
|
2225
|
-
|
|
2226
|
-
"""
|
|
2227
|
-
Locate the tracks.
|
|
2228
|
-
"""
|
|
2229
|
-
|
|
2230
|
-
if not os.path.exists(self.trajectories_path):
|
|
2231
|
-
|
|
2232
|
-
msgBox = QMessageBox()
|
|
2233
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
2234
|
-
msgBox.setText("The trajectories cannot be detected.")
|
|
2235
|
-
msgBox.setWindowTitle("Warning")
|
|
2236
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
2237
|
-
returnValue = msgBox.exec()
|
|
2238
|
-
if returnValue == QMessageBox.Yes:
|
|
2239
|
-
self.close()
|
|
2240
|
-
else:
|
|
2241
|
-
|
|
2242
|
-
# Load and prep tracks
|
|
2243
|
-
self.df_tracks = pd.read_csv(self.trajectories_path)
|
|
2244
|
-
if 'TRACK_ID' in self.df_tracks.columns:
|
|
2245
|
-
self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
|
|
2246
|
-
else:
|
|
2247
|
-
self.df_tracks = self.df_tracks.sort_values(by=['ID', 'FRAME'])
|
|
2248
|
-
|
|
2249
|
-
cols = np.array(self.df_tracks.columns)
|
|
2250
|
-
self.class_cols = np.array([c.startswith('group') or c.startswith('class') for c in list(self.df_tracks.columns)])
|
|
2251
|
-
self.class_cols = list(cols[self.class_cols])
|
|
2252
|
-
|
|
2253
|
-
to_remove = ['class_id','group_color','class_color']
|
|
2254
|
-
for col in to_remove:
|
|
2255
|
-
try:
|
|
2256
|
-
self.class_cols.remove(col)
|
|
2257
|
-
except:
|
|
2258
|
-
pass
|
|
2259
|
-
# try:
|
|
2260
|
-
# self.class_cols.remove('class_id')
|
|
2261
|
-
# except:
|
|
2262
|
-
# pass
|
|
2263
|
-
# try:
|
|
2264
|
-
# self.class_cols.remove('group_color')
|
|
2265
|
-
# except:
|
|
2266
|
-
# pass
|
|
2267
|
-
if len(self.class_cols) > 0:
|
|
2268
|
-
self.status = self.class_cols[0]
|
|
2269
|
-
|
|
2270
|
-
else:
|
|
2271
|
-
|
|
2272
|
-
self.status_name = 'group'
|
|
2273
|
-
|
|
2274
|
-
if self.status_name not in self.df_tracks.columns:
|
|
2275
|
-
# only create the status column if it does not exist to not erase static classification results
|
|
2276
|
-
self.make_status_column()
|
|
2277
|
-
else:
|
|
2278
|
-
# all good, do nothing
|
|
2279
|
-
pass
|
|
2280
|
-
# else:
|
|
2281
|
-
# if not self.status_name in self.df_tracks.columns:
|
|
2282
|
-
# self.df_tracks[self.status_name] = 0
|
|
2283
|
-
# self.df_tracks['state_color'] = color_from_status(0)
|
|
2284
|
-
# self.df_tracks['class_color'] = color_from_class(1)
|
|
2285
|
-
|
|
2286
|
-
# if not self.class_name in self.df_tracks.columns:
|
|
2287
|
-
# self.df_tracks[self.class_name] = 1
|
|
2288
|
-
# if not self.time_name in self.df_tracks.columns:
|
|
2289
|
-
# self.df_tracks[self.time_name] = -1
|
|
2290
|
-
all_states = self.df_tracks.loc[:, self.status_name].tolist()
|
|
2291
|
-
all_states = np.array(all_states)
|
|
2292
|
-
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
2293
|
-
self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
|
|
2294
|
-
# self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
|
|
2295
|
-
# self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
|
|
2296
|
-
|
|
2297
|
-
self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
|
|
2298
|
-
self.df_tracks['x_anim'] = self.df_tracks['POSITION_X']
|
|
2299
|
-
self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y']
|
|
2300
|
-
self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
|
|
2301
|
-
self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
|
|
2302
|
-
|
|
2303
|
-
self.extract_scatter_from_trajectories()
|
|
2304
|
-
if 'TRACK_ID' in self.df_tracks.columns:
|
|
2305
|
-
self.track_of_interest = self.df_tracks.dropna(subset='TRACK_ID')['TRACK_ID'].min()
|
|
2306
|
-
else:
|
|
2307
|
-
self.track_of_interest = self.df_tracks.dropna(subset='ID')['ID'].min()
|
|
2308
|
-
|
|
2309
|
-
self.loc_t = []
|
|
2310
|
-
self.loc_idx = []
|
|
2311
|
-
for t in range(len(self.tracks)):
|
|
2312
|
-
indices = np.where(self.tracks[t] == self.track_of_interest)[0]
|
|
2313
|
-
if len(indices) > 0:
|
|
2314
|
-
self.loc_t.append(t)
|
|
2315
|
-
self.loc_idx.append(indices[0])
|
|
2316
|
-
|
|
2317
|
-
self.MinMaxScaler = MinMaxScaler()
|
|
2318
|
-
self.columns_to_rescale = list(self.df_tracks.columns)
|
|
2319
|
-
|
|
2320
|
-
# is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
|
|
2321
|
-
# is_number_test = is_number(self.df_tracks.dtypes)
|
|
2322
|
-
# self.columns_to_rescale = [col for t,col in zip(is_number_test,self.df_tracks.columns) if t]
|
|
2323
|
-
# print(self.columns_to_rescale)
|
|
2324
|
-
|
|
2325
|
-
cols_to_remove = ['group', 'group_color', 'status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME',
|
|
2326
|
-
'x_anim', 'y_anim', 't','dummy','group_color',
|
|
2327
|
-
'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
|
|
2328
|
-
'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
|
|
2329
|
-
'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent', 'ID'] + self.class_cols
|
|
2330
|
-
|
|
2331
|
-
meta = get_experiment_metadata(self.exp_dir)
|
|
2332
|
-
if meta is not None:
|
|
2333
|
-
keys = list(meta.keys())
|
|
2334
|
-
cols_to_remove.extend(keys)
|
|
2335
|
-
|
|
2336
|
-
labels = get_experiment_labels(self.exp_dir)
|
|
2337
|
-
if labels is not None:
|
|
2338
|
-
keys = list(labels.keys())
|
|
2339
|
-
cols_to_remove.extend(labels)
|
|
2340
|
-
|
|
2341
|
-
for tr in cols_to_remove:
|
|
2342
|
-
try:
|
|
2343
|
-
self.columns_to_rescale.remove(tr)
|
|
2344
|
-
except:
|
|
2345
|
-
pass
|
|
2346
|
-
|
|
2347
|
-
# print(f'column {tr} could not be found...')
|
|
2348
|
-
|
|
2349
|
-
x = self.df_tracks[self.columns_to_rescale].values
|
|
2350
|
-
self.MinMaxScaler.fit(x)
|
|
2351
1525
|
|
|
2352
1526
|
def extract_scatter_from_trajectories(self):
|
|
2353
1527
|
|
|
@@ -2366,10 +1540,11 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2366
1540
|
|
|
2367
1541
|
def changed_class(self):
|
|
2368
1542
|
self.status_name = self.class_choice_cb.currentText()
|
|
2369
|
-
self.
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
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()
|
|
2373
1548
|
|
|
2374
1549
|
def update_frame(self):
|
|
2375
1550
|
"""
|
|
@@ -2388,54 +1563,11 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2388
1563
|
self.vmin = self.contrast_slider.value()[0]
|
|
2389
1564
|
self.vmax = self.contrast_slider.value()[1]
|
|
2390
1565
|
self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
|
|
1566
|
+
self.give_cell_information()
|
|
2391
1567
|
|
|
2392
1568
|
self.fcanvas.canvas.draw()
|
|
2393
1569
|
self.plot_signals()
|
|
2394
1570
|
|
|
2395
|
-
# def load_annotator_config(self):
|
|
2396
|
-
# self.rgb_mode = False
|
|
2397
|
-
# self.log_option = False
|
|
2398
|
-
# self.percentile_mode = True
|
|
2399
|
-
# self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
|
|
2400
|
-
# self.fraction = 0.5955056179775281
|
|
2401
|
-
# #self.anim_interval = 1
|
|
2402
|
-
|
|
2403
|
-
def prepare_stack(self):
|
|
2404
|
-
|
|
2405
|
-
self.img_num_channels = _get_img_num_per_channel(self.channels, self.len_movie, self.nbr_channels)
|
|
2406
|
-
self.current_stack = []
|
|
2407
|
-
for ch in tqdm(self.target_channels, desc="channel"):
|
|
2408
|
-
target_ch_name = ch[0]
|
|
2409
|
-
if self.percentile_mode:
|
|
2410
|
-
normalize_kwargs = {"percentiles": (ch[1], ch[2]), "values": None}
|
|
2411
|
-
else:
|
|
2412
|
-
normalize_kwargs = {"values": (ch[1], ch[2]), "percentiles": None}
|
|
2413
|
-
|
|
2414
|
-
if self.rgb_mode:
|
|
2415
|
-
normalize_kwargs.update({'amplification': 255., 'clip': True})
|
|
2416
|
-
|
|
2417
|
-
chan = []
|
|
2418
|
-
indices = self.img_num_channels[self.channels[np.where(self.channel_names == target_ch_name)][0]]
|
|
2419
|
-
for t in tqdm(range(len(indices)), desc='frame'):
|
|
2420
|
-
if self.rgb_mode:
|
|
2421
|
-
f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=True,
|
|
2422
|
-
normalize_kwargs=normalize_kwargs)
|
|
2423
|
-
f = f.astype(np.uint8)
|
|
2424
|
-
else:
|
|
2425
|
-
f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=False)
|
|
2426
|
-
chan.append(f[:, :, 0])
|
|
2427
|
-
|
|
2428
|
-
self.current_stack.append(chan)
|
|
2429
|
-
|
|
2430
|
-
self.current_stack = np.array(self.current_stack)
|
|
2431
|
-
if self.rgb_mode:
|
|
2432
|
-
self.current_stack = np.moveaxis(self.current_stack, 0, -1)
|
|
2433
|
-
else:
|
|
2434
|
-
self.current_stack = self.current_stack[0]
|
|
2435
|
-
if self.log_option:
|
|
2436
|
-
self.current_stack[np.where((self.current_stack > 0.)&(self.current_stack==self.current_stack))] = np.log(
|
|
2437
|
-
self.current_stack[np.where((self.current_stack > 0.)&(self.current_stack==self.current_stack))])
|
|
2438
|
-
|
|
2439
1571
|
def changed_channel(self):
|
|
2440
1572
|
|
|
2441
1573
|
self.reload_frame()
|
|
@@ -2443,12 +1575,12 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2443
1575
|
*[np.nanpercentile(self.img, 0.001),
|
|
2444
1576
|
np.nanpercentile(self.img, 99.999)])
|
|
2445
1577
|
self.contrast_slider.setValue(
|
|
2446
|
-
[np.nanpercentile(self.img, 1), np.nanpercentile(self.img, 99.99)])
|
|
1578
|
+
[np.nanpercentile(self.img, 0.1), np.nanpercentile(self.img, 99.99)])
|
|
2447
1579
|
self.draw_frame(self.current_frame)
|
|
2448
1580
|
self.fcanvas.canvas.draw()
|
|
2449
1581
|
|
|
2450
1582
|
def save_trajectories(self):
|
|
2451
|
-
|
|
1583
|
+
print(f"Saving trajectories !!")
|
|
2452
1584
|
if self.normalized_signals:
|
|
2453
1585
|
self.normalize_features_btn.click()
|
|
2454
1586
|
if self.selection:
|
|
@@ -2474,13 +1606,11 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2474
1606
|
|
|
2475
1607
|
self.df_tracks.to_csv(self.trajectories_path, index=False)
|
|
2476
1608
|
print('Table successfully exported...')
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
# self.extract_scatter_from_trajectories()
|
|
1609
|
+
|
|
1610
|
+
self.locate_tracks()
|
|
1611
|
+
self.changed_class()
|
|
2481
1612
|
|
|
2482
1613
|
def modify(self):
|
|
2483
|
-
|
|
2484
1614
|
all_states = self.df_tracks.loc[:, self.status_name].tolist()
|
|
2485
1615
|
all_states = np.array(all_states)
|
|
2486
1616
|
self.state_color_map = color_from_state(all_states, recently_modified=False)
|
|
@@ -2493,84 +1623,6 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2493
1623
|
self.correct_btn.disconnect()
|
|
2494
1624
|
self.correct_btn.clicked.connect(self.show_annotation_buttons)
|
|
2495
1625
|
|
|
2496
|
-
# self.hide_annotation_buttons()
|
|
2497
|
-
# self.correct_btn.setEnabled(False)
|
|
2498
|
-
# self.correct_btn.setText('correct')
|
|
2499
|
-
# self.cancel_btn.setEnabled(False)
|
|
2500
|
-
# self.del_shortcut.setEnabled(False)
|
|
2501
|
-
# self.no_event_shortcut.setEnabled(False)
|
|
2502
|
-
|
|
2503
|
-
def enable_time_of_interest(self):
|
|
2504
|
-
if self.suppr_btn.isChecked():
|
|
2505
|
-
self.time_of_interest_le.setEnabled(False)
|
|
2506
|
-
|
|
2507
|
-
def cancel_selection(self):
|
|
2508
|
-
|
|
2509
|
-
self.hide_annotation_buttons()
|
|
2510
|
-
self.correct_btn.setEnabled(False)
|
|
2511
|
-
self.correct_btn.setText('correct')
|
|
2512
|
-
self.cancel_btn.setEnabled(False)
|
|
2513
|
-
|
|
2514
|
-
try:
|
|
2515
|
-
self.selection.pop(0)
|
|
2516
|
-
except Exception as e:
|
|
2517
|
-
print('Cancel selection: ',e)
|
|
2518
|
-
|
|
2519
|
-
try:
|
|
2520
|
-
for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
|
|
2521
|
-
# print(self.colors[t][idx, 1])
|
|
2522
|
-
self.colors[t][idx, 0] = self.previous_color[k][0]
|
|
2523
|
-
# self.colors[t][idx, 1] = self.previous_color[k][1]
|
|
2524
|
-
except Exception as e:
|
|
2525
|
-
print("cancel_selection: ",f'{e=}')
|
|
2526
|
-
|
|
2527
|
-
self.event = None
|
|
2528
|
-
|
|
2529
|
-
def locate_stack(self):
|
|
2530
|
-
|
|
2531
|
-
"""
|
|
2532
|
-
Locate the target movie.
|
|
2533
|
-
|
|
2534
|
-
"""
|
|
2535
|
-
|
|
2536
|
-
if isinstance(self.pos, str):
|
|
2537
|
-
movies = glob(self.pos + os.sep.join(['movie',f"{self.parent_window.parent_window.movie_prefix}*.tif"]))
|
|
2538
|
-
|
|
2539
|
-
else:
|
|
2540
|
-
msgBox = QMessageBox()
|
|
2541
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
2542
|
-
msgBox.setText("Please select a unique position before launching the wizard...")
|
|
2543
|
-
msgBox.setWindowTitle("Warning")
|
|
2544
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
2545
|
-
returnValue = msgBox.exec()
|
|
2546
|
-
if returnValue == QMessageBox.Ok:
|
|
2547
|
-
self.img = None
|
|
2548
|
-
self.close()
|
|
2549
|
-
return None
|
|
2550
|
-
|
|
2551
|
-
if len(movies) == 0:
|
|
2552
|
-
msgBox = QMessageBox()
|
|
2553
|
-
msgBox.setIcon(QMessageBox.Warning)
|
|
2554
|
-
msgBox.setText("No movie is detected in the experiment folder.\nPlease check the stack prefix...")
|
|
2555
|
-
msgBox.setWindowTitle("Warning")
|
|
2556
|
-
msgBox.setStandardButtons(QMessageBox.Ok)
|
|
2557
|
-
returnValue = msgBox.exec()
|
|
2558
|
-
if returnValue == QMessageBox.Ok:
|
|
2559
|
-
self.close()
|
|
2560
|
-
else:
|
|
2561
|
-
self.stack_path = movies[0]
|
|
2562
|
-
self.len_movie = self.parent_window.parent_window.len_movie
|
|
2563
|
-
len_movie_auto = auto_load_number_of_frames(self.stack_path)
|
|
2564
|
-
if len_movie_auto is not None:
|
|
2565
|
-
self.len_movie = len_movie_auto
|
|
2566
|
-
exp_config = self.exp_dir + "config.ini"
|
|
2567
|
-
self.channel_names, self.channels = extract_experiment_channels(self.exp_dir)
|
|
2568
|
-
self.channel_names = np.array(self.channel_names)
|
|
2569
|
-
self.channels = np.array(self.channels)
|
|
2570
|
-
self.nbr_channels = len(self.channels)
|
|
2571
|
-
self.current_channel = 0
|
|
2572
|
-
self.img = load_frames(0, self.stack_path, normalize_input=False)
|
|
2573
|
-
|
|
2574
1626
|
def reload_frame(self):
|
|
2575
1627
|
|
|
2576
1628
|
"""
|
|
@@ -2613,8 +1665,8 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2613
1665
|
|
|
2614
1666
|
if self.previous_channel != self.current_channel:
|
|
2615
1667
|
|
|
2616
|
-
self.vmin = np.nanpercentile(self.img.flatten(), 1)
|
|
2617
|
-
self.vmax = np.nanpercentile(self.img.flatten(), 99.)
|
|
1668
|
+
self.vmin = np.nanpercentile(self.img.flatten(), 0.1)
|
|
1669
|
+
self.vmax = np.nanpercentile(self.img.flatten(), 99.99)
|
|
2618
1670
|
|
|
2619
1671
|
self.contrast_slider.disconnect()
|
|
2620
1672
|
self.contrast_slider.setRange(np.nanmin(self.img), np.nanmax(self.img))
|
|
@@ -2627,23 +1679,7 @@ class MeasureAnnotator(SignalAnnotator):
|
|
|
2627
1679
|
|
|
2628
1680
|
self.im.set_data(self.img)
|
|
2629
1681
|
|
|
2630
|
-
# self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
|
|
2631
|
-
# self.fcanvas.canvas.draw_idle()
|
|
2632
|
-
|
|
2633
|
-
def show_outliers(self):
|
|
2634
|
-
if self.outliers_check.isChecked():
|
|
2635
|
-
self.show_fliers = True
|
|
2636
|
-
self.plot_signals()
|
|
2637
|
-
else:
|
|
2638
|
-
self.show_fliers = False
|
|
2639
|
-
self.plot_signals()
|
|
2640
|
-
|
|
2641
1682
|
def del_cell(self):
|
|
2642
1683
|
self.time_of_interest_le.setEnabled(False)
|
|
2643
1684
|
self.time_of_interest_le.setText("99")
|
|
2644
1685
|
self.apply_modification()
|
|
2645
|
-
|
|
2646
|
-
def shortcut_suppr(self):
|
|
2647
|
-
self.correct_btn.click()
|
|
2648
|
-
self.del_cell_btn.click()
|
|
2649
|
-
self.correct_btn.click()
|