celldetective 1.0.2.post1__py3-none-any.whl → 1.1.0__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.
Files changed (56) hide show
  1. celldetective/__main__.py +2 -2
  2. celldetective/events.py +2 -44
  3. celldetective/filters.py +4 -5
  4. celldetective/gui/__init__.py +1 -1
  5. celldetective/gui/analyze_block.py +37 -10
  6. celldetective/gui/btrack_options.py +24 -23
  7. celldetective/gui/classifier_widget.py +62 -19
  8. celldetective/gui/configure_new_exp.py +32 -35
  9. celldetective/gui/control_panel.py +115 -81
  10. celldetective/gui/gui_utils.py +674 -396
  11. celldetective/gui/json_readers.py +7 -6
  12. celldetective/gui/layouts.py +755 -0
  13. celldetective/gui/measurement_options.py +168 -487
  14. celldetective/gui/neighborhood_options.py +322 -270
  15. celldetective/gui/plot_measurements.py +1114 -0
  16. celldetective/gui/plot_signals_ui.py +20 -20
  17. celldetective/gui/process_block.py +449 -169
  18. celldetective/gui/retrain_segmentation_model_options.py +27 -26
  19. celldetective/gui/retrain_signal_model_options.py +25 -24
  20. celldetective/gui/seg_model_loader.py +31 -27
  21. celldetective/gui/signal_annotator.py +2326 -2295
  22. celldetective/gui/signal_annotator_options.py +18 -16
  23. celldetective/gui/styles.py +16 -1
  24. celldetective/gui/survival_ui.py +61 -39
  25. celldetective/gui/tableUI.py +60 -23
  26. celldetective/gui/thresholds_gui.py +68 -66
  27. celldetective/gui/viewers.py +596 -0
  28. celldetective/io.py +234 -23
  29. celldetective/measure.py +37 -32
  30. celldetective/neighborhood.py +495 -27
  31. celldetective/preprocessing.py +683 -0
  32. celldetective/scripts/analyze_signals.py +7 -0
  33. celldetective/scripts/measure_cells.py +12 -0
  34. celldetective/scripts/segment_cells.py +5 -0
  35. celldetective/scripts/track_cells.py +11 -0
  36. celldetective/signals.py +221 -98
  37. celldetective/tracking.py +0 -1
  38. celldetective/utils.py +178 -36
  39. celldetective-1.1.0.dist-info/METADATA +305 -0
  40. celldetective-1.1.0.dist-info/RECORD +80 -0
  41. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.0.dist-info}/top_level.txt +1 -0
  42. tests/__init__.py +0 -0
  43. tests/test_events.py +28 -0
  44. tests/test_filters.py +24 -0
  45. tests/test_io.py +70 -0
  46. tests/test_measure.py +141 -0
  47. tests/test_neighborhood.py +70 -0
  48. tests/test_segmentation.py +93 -0
  49. tests/test_signals.py +135 -0
  50. tests/test_tracking.py +164 -0
  51. tests/test_utils.py +71 -0
  52. celldetective-1.0.2.post1.dist-info/METADATA +0 -221
  53. celldetective-1.0.2.post1.dist-info/RECORD +0 -66
  54. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.0.dist-info}/LICENSE +0 -0
  55. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.0.dist-info}/WHEEL +0 -0
  56. {celldetective-1.0.2.post1.dist-info → celldetective-1.1.0.dist-info}/entry_points.txt +0 -0
@@ -1,14 +1,15 @@
1
1
  from PyQt5.QtWidgets import QMainWindow, QComboBox, QLabel, QRadioButton, QLineEdit, QFileDialog, QApplication, \
2
- QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QAction, QShortcut, QLineEdit, QSlider, QCheckBox
2
+ QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QAction, QShortcut, QLineEdit, QSlider, QCheckBox
3
3
  from PyQt5.QtCore import Qt, QSize
4
4
  from PyQt5.QtGui import QKeySequence, QIntValidator
5
5
  from matplotlib.widgets import Slider
6
6
  from tifffile import imread
7
7
 
8
8
  from celldetective.gui.gui_utils import center_window, QHSeperationLine, FilterChoice, color_from_state
9
- from superqt import QLabeledDoubleSlider, QLabeledDoubleRangeSlider, QLabeledSlider
9
+ from superqt import QLabeledDoubleSlider, QLabeledDoubleRangeSlider, QLabeledSlider, QSearchableComboBox
10
10
  from celldetective.utils import extract_experiment_channels, get_software_location, _get_img_num_per_channel
11
- from celldetective.io import auto_load_number_of_frames, load_frames, locate_stack
11
+ from celldetective.io import auto_load_number_of_frames, load_frames, locate_stack, locate_labels, relabel_segmentation, \
12
+ load_napari_data
12
13
  from celldetective.gui.gui_utils import FigureCanvas, color_from_status, color_from_class
13
14
  import json
14
15
  import numpy as np
@@ -25,2364 +26,2394 @@ from matplotlib.animation import FuncAnimation
25
26
  from matplotlib.cm import tab10
26
27
  import pandas as pd
27
28
  from sklearn.preprocessing import MinMaxScaler
29
+ from celldetective.gui import Styles
28
30
 
29
-
30
- class SignalAnnotator(QMainWindow):
31
- """
31
+ class SignalAnnotator(QMainWindow, Styles):
32
+ """
32
33
  UI to set tracking parameters for bTrack.
33
34
 
34
35
  """
35
36
 
36
- def __init__(self, parent=None):
37
-
38
- super().__init__()
39
- self.parent = parent
40
- self.setWindowTitle("Signal annotator")
41
- self.mode = self.parent.mode
42
- self.pos = self.parent.parent.pos
43
- self.exp_dir = self.parent.exp_dir
44
- self.n_signals = 3
45
- self.soft_path = get_software_location()
46
- self.recently_modified = False
47
- self.selection = []
48
- if self.mode == "targets":
49
- self.instructions_path = self.exp_dir + "configs/signal_annotator_config_targets.json"
50
- self.trajectories_path = self.pos + 'output/tables/trajectories_targets.csv'
51
- elif self.mode == "effectors":
52
- self.instructions_path = self.exp_dir + "configs/signal_annotator_config_effectors.json"
53
- self.trajectories_path = self.pos + 'output/tables/trajectories_effectors.csv'
54
-
55
- self.screen_height = self.parent.parent.parent.screen_height
56
- self.screen_width = self.parent.parent.parent.screen_width
57
-
58
- # default params
59
- self.class_name = 'class'
60
- self.time_name = 't0'
61
- self.status_name = 'status'
62
-
63
- center_window(self)
64
-
65
- self.locate_stack()
66
- self.load_annotator_config()
67
- self.locate_tracks()
68
- self.prepare_stack()
69
-
70
- self.generate_signal_choices()
71
- self.frame_lbl = QLabel('frame: ')
72
- self.looped_animation()
73
- self.create_cell_signal_canvas()
74
-
75
- self.populate_widget()
76
-
77
- self.setMinimumWidth(int(0.8 * self.screen_width))
78
- # self.setMaximumHeight(int(0.8*self.screen_height))
79
- self.setMinimumHeight(int(0.8 * self.screen_height))
80
- # self.setMaximumHeight(int(0.8*self.screen_height))
81
-
82
- self.setAttribute(Qt.WA_DeleteOnClose)
83
-
84
- def populate_widget(self):
85
-
86
- """
37
+ def __init__(self, parent_window=None):
38
+
39
+ super().__init__()
40
+ self.parent_window = parent_window
41
+ self.setWindowTitle("Signal annotator")
42
+ self.mode = self.parent_window.mode
43
+ self.pos = self.parent_window.parent_window.pos
44
+ self.exp_dir = self.parent_window.exp_dir
45
+ self.PxToUm = self.parent_window.parent_window.PxToUm
46
+ self.n_signals = 3
47
+ self.soft_path = get_software_location()
48
+ self.recently_modified = False
49
+ self.selection = []
50
+ if self.mode == "targets":
51
+ self.instructions_path = self.exp_dir + "configs/signal_annotator_config_targets.json"
52
+ self.trajectories_path = self.pos + 'output/tables/trajectories_targets.csv'
53
+ elif self.mode == "effectors":
54
+ self.instructions_path = self.exp_dir + "configs/signal_annotator_config_effectors.json"
55
+ self.trajectories_path = self.pos + 'output/tables/trajectories_effectors.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
+
60
+ # default params
61
+ self.class_name = 'class'
62
+ self.time_name = 't0'
63
+ self.status_name = 'status'
64
+
65
+ center_window(self)
66
+
67
+ self.locate_stack()
68
+ self.load_annotator_config()
69
+ self.locate_tracks()
70
+ self.prepare_stack()
71
+
72
+ self.generate_signal_choices()
73
+ self.frame_lbl = QLabel('frame: ')
74
+ self.looped_animation()
75
+ self.create_cell_signal_canvas()
76
+
77
+ self.populate_widget()
78
+
79
+ self.setMinimumWidth(int(0.8 * self.screen_width))
80
+ # self.setMaximumHeight(int(0.8*self.screen_height))
81
+ self.setMinimumHeight(int(0.8 * self.screen_height))
82
+ # self.setMaximumHeight(int(0.8*self.screen_height))
83
+
84
+ self.setAttribute(Qt.WA_DeleteOnClose)
85
+
86
+ def populate_widget(self):
87
+
88
+ """
87
89
  Create the multibox design.
88
90
 
89
91
  """
90
92
 
91
- self.button_widget = QWidget()
92
- main_layout = QHBoxLayout()
93
- self.button_widget.setLayout(main_layout)
94
-
95
- main_layout.setContentsMargins(30, 30, 30, 30)
96
- self.left_panel = QVBoxLayout()
97
- self.left_panel.setContentsMargins(30, 30, 30, 30)
98
- self.left_panel.setSpacing(10)
99
-
100
- self.right_panel = QVBoxLayout()
101
-
102
- class_hbox = QHBoxLayout()
103
- class_hbox.addWidget(QLabel('event: '), 25)
104
- self.class_choice_cb = QComboBox()
105
-
106
- cols = np.array(self.df_tracks.columns)
107
- self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
108
- self.class_cols = list(cols[self.class_cols])
109
- try:
110
- self.class_cols.remove('class_id')
111
- except Exception:
112
- pass
113
- try:
114
- self.class_cols.remove('class_color')
115
- except Exception:
116
- pass
117
-
118
- self.class_choice_cb.addItems(self.class_cols)
119
- self.class_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors)
120
- self.class_choice_cb.setCurrentIndex(0)
121
-
122
- class_hbox.addWidget(self.class_choice_cb, 70)
123
-
124
- self.add_class_btn = QPushButton('')
125
- self.add_class_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
126
- self.add_class_btn.setIcon(icon(MDI6.plus, color="black"))
127
- self.add_class_btn.setToolTip("Add a new event class")
128
- self.add_class_btn.setIconSize(QSize(20, 20))
129
- self.add_class_btn.clicked.connect(self.create_new_event_class)
130
- class_hbox.addWidget(self.add_class_btn, 5)
131
-
132
- self.del_class_btn = QPushButton('')
133
- self.del_class_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
134
- self.del_class_btn.setIcon(icon(MDI6.delete, color="black"))
135
- self.del_class_btn.setToolTip("Delete an event class")
136
- self.del_class_btn.setIconSize(QSize(20, 20))
137
- self.del_class_btn.clicked.connect(self.del_event_class)
138
- class_hbox.addWidget(self.del_class_btn, 5)
139
-
140
- self.left_panel.addLayout(class_hbox)
141
-
142
- self.cell_info = QLabel('')
143
- self.left_panel.addWidget(self.cell_info)
144
-
145
- # Annotation buttons
146
- options_hbox = QHBoxLayout()
147
- options_hbox.setContentsMargins(150, 30, 50, 0)
148
- self.event_btn = QRadioButton('event')
149
- self.event_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
150
- self.event_btn.toggled.connect(self.enable_time_of_interest)
151
-
152
- self.no_event_btn = QRadioButton('no event')
153
- self.no_event_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
154
- self.no_event_btn.toggled.connect(self.enable_time_of_interest)
155
-
156
- self.else_btn = QRadioButton('else')
157
- self.else_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
158
- self.else_btn.toggled.connect(self.enable_time_of_interest)
159
-
160
- self.suppr_btn = QRadioButton('mark for\nsuppression')
161
- self.suppr_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
162
- self.suppr_btn.toggled.connect(self.enable_time_of_interest)
163
-
164
- options_hbox.addWidget(self.event_btn, 25)
165
- options_hbox.addWidget(self.no_event_btn, 25)
166
- options_hbox.addWidget(self.else_btn, 25)
167
- options_hbox.addWidget(self.suppr_btn, 25)
168
- self.left_panel.addLayout(options_hbox)
169
-
170
- time_option_hbox = QHBoxLayout()
171
- time_option_hbox.setContentsMargins(100, 30, 100, 30)
172
- self.time_of_interest_label = QLabel('time of interest: ')
173
- time_option_hbox.addWidget(self.time_of_interest_label, 30)
174
- self.time_of_interest_le = QLineEdit()
175
- time_option_hbox.addWidget(self.time_of_interest_le, 70)
176
- self.left_panel.addLayout(time_option_hbox)
177
-
178
- main_action_hbox = QHBoxLayout()
179
- self.correct_btn = QPushButton('correct')
180
- self.correct_btn.setIcon(icon(MDI6.redo_variant, color="white"))
181
- self.correct_btn.setIconSize(QSize(20, 20))
182
- self.correct_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet)
183
- self.correct_btn.clicked.connect(self.show_annotation_buttons)
184
- self.correct_btn.setEnabled(False)
185
- main_action_hbox.addWidget(self.correct_btn)
186
-
187
- self.cancel_btn = QPushButton('cancel')
188
- self.cancel_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
189
- self.cancel_btn.setShortcut(QKeySequence("Esc"))
190
- self.cancel_btn.setEnabled(False)
191
- self.cancel_btn.clicked.connect(self.cancel_selection)
192
- main_action_hbox.addWidget(self.cancel_btn)
193
- self.left_panel.addLayout(main_action_hbox)
194
-
195
- self.annotation_btns_to_hide = [self.event_btn, self.no_event_btn,
196
- self.else_btn, self.time_of_interest_label,
197
- self.time_of_interest_le, self.suppr_btn]
198
- self.hide_annotation_buttons()
199
- #### End of annotation buttons
200
-
201
- self.del_shortcut = QShortcut(Qt.Key_Delete, self) # QKeySequence("s")
202
- self.del_shortcut.activated.connect(self.shortcut_suppr)
203
- self.del_shortcut.setEnabled(False)
204
-
205
- self.no_event_shortcut = QShortcut(QKeySequence("n"), self) # QKeySequence("s")
206
- self.no_event_shortcut.activated.connect(self.shortcut_no_event)
207
- self.no_event_shortcut.setEnabled(False)
208
-
209
- # Cell signals
210
- self.left_panel.addWidget(self.cell_fcanvas)
211
-
212
- plot_buttons_hbox = QHBoxLayout()
213
- plot_buttons_hbox.setContentsMargins(0, 0, 0, 0)
214
- self.normalize_features_btn = QPushButton('')
215
- self.normalize_features_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
216
- self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
217
- self.normalize_features_btn.setIconSize(QSize(25, 25))
218
- self.normalize_features_btn.setFixedSize(QSize(30, 30))
219
- # self.normalize_features_btn.setShortcut(QKeySequence('n'))
220
- self.normalize_features_btn.clicked.connect(self.normalize_features)
221
-
222
- plot_buttons_hbox.addWidget(QLabel(''), 90)
223
- plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
224
- self.normalized_signals = False
225
-
226
- self.log_btn = QPushButton()
227
- self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
228
- self.log_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
229
- self.log_btn.clicked.connect(self.switch_to_log)
230
- plot_buttons_hbox.addWidget(self.log_btn, 5)
231
-
232
- self.left_panel.addLayout(plot_buttons_hbox)
233
-
234
- signal_choice_vbox = QVBoxLayout()
235
- signal_choice_vbox.setContentsMargins(30, 0, 30, 50)
236
- for i in range(len(self.signal_choice_cb)):
237
- hlayout = QHBoxLayout()
238
- hlayout.addWidget(self.signal_choice_label[i], 20)
239
- hlayout.addWidget(self.signal_choice_cb[i], 75)
240
- # hlayout.addWidget(self.log_btns[i], 5)
241
- signal_choice_vbox.addLayout(hlayout)
242
-
243
- # self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
244
- # self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
245
- # self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
246
-
247
- self.left_panel.addLayout(signal_choice_vbox)
248
-
249
- btn_hbox = QHBoxLayout()
250
- self.save_btn = QPushButton('Save')
251
- self.save_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet)
252
- self.save_btn.clicked.connect(self.save_trajectories)
253
- btn_hbox.addWidget(self.save_btn, 90)
254
-
255
- self.export_btn = QPushButton('')
256
- self.export_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
257
- self.export_btn.clicked.connect(self.export_signals)
258
- self.export_btn.setIcon(icon(MDI6.export, color="black"))
259
- self.export_btn.setIconSize(QSize(25, 25))
260
- btn_hbox.addWidget(self.export_btn, 10)
261
- self.left_panel.addLayout(btn_hbox)
262
-
263
- # Animation
264
- animation_buttons_box = QHBoxLayout()
265
-
266
- animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
267
-
268
- self.first_frame_btn = QPushButton()
269
- self.first_frame_btn.clicked.connect(self.set_first_frame)
270
- self.first_frame_btn.setShortcut(QKeySequence('f'))
271
- self.first_frame_btn.setIcon(icon(MDI6.page_first, color="black"))
272
- self.first_frame_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
273
- self.first_frame_btn.setFixedSize(QSize(60, 60))
274
- self.first_frame_btn.setIconSize(QSize(30, 30))
275
-
276
- self.last_frame_btn = QPushButton()
277
- self.last_frame_btn.clicked.connect(self.set_last_frame)
278
- self.last_frame_btn.setShortcut(QKeySequence('l'))
279
- self.last_frame_btn.setIcon(icon(MDI6.page_last, color="black"))
280
- self.last_frame_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
281
- self.last_frame_btn.setFixedSize(QSize(60, 60))
282
- self.last_frame_btn.setIconSize(QSize(30, 30))
283
-
284
- self.stop_btn = QPushButton()
285
- self.stop_btn.clicked.connect(self.stop)
286
- self.stop_btn.setIcon(icon(MDI6.stop, color="black"))
287
- self.stop_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
288
- self.stop_btn.setFixedSize(QSize(60, 60))
289
- self.stop_btn.setIconSize(QSize(30, 30))
290
-
291
- self.start_btn = QPushButton()
292
- self.start_btn.clicked.connect(self.start)
293
- self.start_btn.setIcon(icon(MDI6.play, color="black"))
294
- self.start_btn.setFixedSize(QSize(60, 60))
295
- self.start_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
296
- self.start_btn.setIconSize(QSize(30, 30))
297
- self.start_btn.hide()
298
-
299
- animation_buttons_box.addWidget(self.first_frame_btn, 5, alignment=Qt.AlignRight)
300
- animation_buttons_box.addWidget(self.stop_btn, 5, alignment=Qt.AlignRight)
301
- animation_buttons_box.addWidget(self.start_btn, 5, alignment=Qt.AlignRight)
302
- animation_buttons_box.addWidget(self.last_frame_btn, 5, alignment=Qt.AlignRight)
303
-
304
- self.right_panel.addLayout(animation_buttons_box, 5)
305
-
306
- self.right_panel.addWidget(self.fcanvas, 90)
307
-
308
- if not self.rgb_mode:
309
- contrast_hbox = QHBoxLayout()
310
- contrast_hbox.setContentsMargins(150, 5, 150, 5)
311
- self.contrast_slider = QLabeledDoubleRangeSlider()
312
- # self.contrast_slider.setSingleStep(0.001)
313
- # self.contrast_slider.setTickInterval(0.001)
314
- self.contrast_slider.setOrientation(1)
315
- print('range: ',
316
- [np.nanpercentile(self.stack.flatten(), 0.001), np.nanpercentile(self.stack.flatten(), 99.999)])
317
- self.contrast_slider.setRange(
318
- *[np.nanpercentile(self.stack.flatten(), 0.001), np.nanpercentile(self.stack.flatten(), 99.999)])
319
- self.contrast_slider.setValue(
320
- [np.percentile(self.stack.flatten(), 1), np.percentile(self.stack.flatten(), 99.99)])
321
- self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
322
- contrast_hbox.addWidget(QLabel('contrast: '))
323
- contrast_hbox.addWidget(self.contrast_slider, 90)
324
- self.right_panel.addLayout(contrast_hbox, 5)
325
-
326
- # speed_hbox = QHBoxLayout()
327
- # speed_hbox.setContentsMargins(150,5,150,5)
328
- # self.interval_slider = QLabeledSlider()
329
- # self.interval_slider.setSingleStep(1)
330
- # self.interval_slider.setTickInterval(1)
331
- # self.interval_slider.setOrientation(1)
332
- # self.interval_slider.setRange(1, 10000)
333
- # self.interval_slider.setValue(self.speed)
334
- # self.interval_slider.valueChanged.connect(self.interval_slider_action)
335
- # speed_hbox.addWidget(QLabel('interval (ms): '))
336
- # speed_hbox.addWidget(self.interval_slider,90)
337
- # self.right_panel.addLayout(speed_hbox, 10)
338
-
339
- # self.populate_left_panel()
340
- # grid.addLayout(self.left_side, 0, 0, 1, 1)
341
-
342
- main_layout.addLayout(self.left_panel, 35)
343
- main_layout.addLayout(self.right_panel, 65)
344
- self.button_widget.adjustSize()
345
-
346
- self.setCentralWidget(self.button_widget)
347
- self.show()
348
-
349
- QApplication.processEvents()
350
-
351
- def del_event_class(self):
352
-
353
- msgBox = QMessageBox()
354
- msgBox.setIcon(QMessageBox.Warning)
355
- msgBox.setText(
356
- 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?")
357
- msgBox.setWindowTitle("Warning")
358
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
359
- returnValue = msgBox.exec()
360
- if returnValue == QMessageBox.No:
361
- return None
362
- else:
363
- class_to_delete = self.class_choice_cb.currentText()
364
- time_to_delete = class_to_delete.replace('class', 't')
365
- status_to_delete = class_to_delete.replace('class', 'status')
366
- cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
367
- for c in cols_to_delete:
368
- try:
369
- self.df_tracks = self.df_tracks.drop([c], axis=1)
370
- except Exception as e:
371
- print(e)
372
- item_idx = self.class_choice_cb.findText(class_to_delete)
373
- self.class_choice_cb.removeItem(item_idx)
374
-
375
- def create_new_event_class(self):
376
-
377
- # display qwidget to name the event
378
- self.newClassWidget = QWidget()
379
- self.newClassWidget.setWindowTitle('Create new event class')
380
-
381
- layout = QVBoxLayout()
382
- self.newClassWidget.setLayout(layout)
383
- name_hbox = QHBoxLayout()
384
- name_hbox.addWidget(QLabel('event name: '), 25)
385
- self.class_name_le = QLineEdit('event')
386
- name_hbox.addWidget(self.class_name_le, 75)
387
- layout.addLayout(name_hbox)
388
-
389
- class_labels = ['event', 'no event', 'else']
390
- layout.addWidget(QLabel('prefill: '))
391
- radio_box = QHBoxLayout()
392
- self.class_option_rb = [QRadioButton() for i in range(3)]
393
- for i, c in enumerate(self.class_option_rb):
394
- if i == 0:
395
- c.setChecked(True)
396
- c.setText(class_labels[i])
397
- radio_box.addWidget(c, 33, alignment=Qt.AlignCenter)
398
- layout.addLayout(radio_box)
399
-
400
- btn_hbox = QHBoxLayout()
401
- submit_btn = QPushButton('submit')
402
- cancel_btn = QPushButton('cancel')
403
- btn_hbox.addWidget(cancel_btn, 50)
404
- btn_hbox.addWidget(submit_btn, 50)
405
- layout.addLayout(btn_hbox)
406
-
407
- submit_btn.clicked.connect(self.write_new_event_class)
408
- cancel_btn.clicked.connect(self.close_without_new_class)
409
-
410
- self.newClassWidget.show()
411
- center_window(self.newClassWidget)
412
-
413
- # Prefill with class value
414
- # write in table
415
-
416
- def write_new_event_class(self):
417
-
418
- if self.class_name_le.text() == '':
419
- self.target_class = 'class'
420
- self.target_time = 't0'
421
- else:
422
- self.target_class = 'class_' + self.class_name_le.text()
423
- self.target_time = 't_' + self.class_name_le.text()
424
-
425
- if self.target_class in list(self.df_tracks.columns):
426
-
427
- msgBox = QMessageBox()
428
- msgBox.setIcon(QMessageBox.Warning)
429
- msgBox.setText(
430
- "This event name already exists. If you proceed,\nall annotated data will be rewritten. Do you wish to continue?")
431
- msgBox.setWindowTitle("Warning")
432
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
433
- returnValue = msgBox.exec()
434
- if returnValue == QMessageBox.No:
435
- return None
436
- else:
437
- pass
438
-
439
- fill_option = np.where([c.isChecked() for c in self.class_option_rb])[0][0]
440
- self.df_tracks.loc[:, self.target_class] = fill_option
441
- if fill_option == 0:
442
- self.df_tracks.loc[:, self.target_time] = 0.1
443
- else:
444
- self.df_tracks.loc[:, self.target_time] = -1
445
-
446
- self.class_choice_cb.clear()
447
- cols = np.array(self.df_tracks.columns)
448
- self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
449
- self.class_cols = list(cols[self.class_cols])
450
- self.class_cols.remove('class_id')
451
- self.class_cols.remove('class_color')
452
- self.class_choice_cb.addItems(self.class_cols)
453
- idx = self.class_choice_cb.findText(self.target_class)
454
- self.class_choice_cb.setCurrentIndex(idx)
455
-
456
- self.newClassWidget.close()
457
-
458
- def close_without_new_class(self):
459
- self.newClassWidget.close()
460
-
461
- def compute_status_and_colors(self, i):
462
- self.class_name = self.class_choice_cb.currentText()
463
- self.expected_status = 'status'
464
- suffix = self.class_name.replace('class', '').replace('_', '', 1)
465
- if suffix != '':
466
- self.expected_status += '_' + suffix
467
- self.expected_time = 't_' + suffix
468
- else:
469
- self.expected_time = 't0'
470
- self.time_name = self.expected_time
471
- self.status_name = self.expected_status
472
-
473
- print('selection and expected names: ', self.class_name, self.expected_time, self.expected_status)
474
-
475
- 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:
476
- # only create the status column if it does not exist to not erase static classification results
477
- self.make_status_column()
478
- elif self.time_name in self.df_tracks.columns and self.class_name in self.df_tracks.columns:
479
- # all good, do nothing
480
- pass
481
- else:
482
- if not self.status_name in self.df_tracks.columns:
483
- self.df_tracks[self.status_name] = 0
484
- self.df_tracks['status_color'] = color_from_status(0)
485
- self.df_tracks['class_color'] = color_from_class(1)
486
-
487
- if not self.class_name in self.df_tracks.columns:
488
- self.df_tracks[self.class_name] = 1
489
- if not self.time_name in self.df_tracks.columns:
490
- self.df_tracks[self.time_name] = -1
491
-
492
- self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
493
- self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
494
-
495
- self.extract_scatter_from_trajectories()
496
-
497
- def contrast_slider_action(self):
498
-
499
- """
93
+ self.button_widget = QWidget()
94
+ main_layout = QHBoxLayout()
95
+ self.button_widget.setLayout(main_layout)
96
+
97
+ main_layout.setContentsMargins(30, 30, 30, 30)
98
+ self.left_panel = QVBoxLayout()
99
+ self.left_panel.setContentsMargins(30, 30, 30, 30)
100
+ self.left_panel.setSpacing(10)
101
+
102
+ self.right_panel = QVBoxLayout()
103
+
104
+ class_hbox = QHBoxLayout()
105
+ class_hbox.addWidget(QLabel('event: '), 25)
106
+ self.class_choice_cb = QComboBox()
107
+
108
+ cols = np.array(self.df_tracks.columns)
109
+ self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
110
+ self.class_cols = list(cols[self.class_cols])
111
+ try:
112
+ self.class_cols.remove('class_id')
113
+ except Exception:
114
+ pass
115
+ try:
116
+ self.class_cols.remove('class_color')
117
+ except Exception:
118
+ pass
119
+
120
+ self.class_choice_cb.addItems(self.class_cols)
121
+ self.class_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors)
122
+ self.class_choice_cb.setCurrentIndex(0)
123
+
124
+ class_hbox.addWidget(self.class_choice_cb, 70)
125
+
126
+ self.add_class_btn = QPushButton('')
127
+ self.add_class_btn.setStyleSheet(self.button_select_all)
128
+ self.add_class_btn.setIcon(icon(MDI6.plus, color="black"))
129
+ self.add_class_btn.setToolTip("Add a new event class")
130
+ self.add_class_btn.setIconSize(QSize(20, 20))
131
+ self.add_class_btn.clicked.connect(self.create_new_event_class)
132
+ class_hbox.addWidget(self.add_class_btn, 5)
133
+
134
+ self.del_class_btn = QPushButton('')
135
+ self.del_class_btn.setStyleSheet(self.button_select_all)
136
+ self.del_class_btn.setIcon(icon(MDI6.delete, color="black"))
137
+ self.del_class_btn.setToolTip("Delete an event class")
138
+ self.del_class_btn.setIconSize(QSize(20, 20))
139
+ self.del_class_btn.clicked.connect(self.del_event_class)
140
+ class_hbox.addWidget(self.del_class_btn, 5)
141
+
142
+ self.left_panel.addLayout(class_hbox)
143
+
144
+ self.cell_info = QLabel('')
145
+ self.left_panel.addWidget(self.cell_info)
146
+
147
+ # Annotation buttons
148
+ options_hbox = QHBoxLayout()
149
+ options_hbox.setContentsMargins(150, 30, 50, 0)
150
+ self.event_btn = QRadioButton('event')
151
+ self.event_btn.setStyleSheet(self.button_style_sheet_2)
152
+ self.event_btn.toggled.connect(self.enable_time_of_interest)
153
+
154
+ self.no_event_btn = QRadioButton('no event')
155
+ self.no_event_btn.setStyleSheet(self.button_style_sheet_2)
156
+ self.no_event_btn.toggled.connect(self.enable_time_of_interest)
157
+
158
+ self.else_btn = QRadioButton('else')
159
+ self.else_btn.setStyleSheet(self.button_style_sheet_2)
160
+ self.else_btn.toggled.connect(self.enable_time_of_interest)
161
+
162
+ self.suppr_btn = QRadioButton('mark for\nsuppression')
163
+ self.suppr_btn.setStyleSheet(self.button_style_sheet_2)
164
+ self.suppr_btn.toggled.connect(self.enable_time_of_interest)
165
+
166
+ options_hbox.addWidget(self.event_btn, 25)
167
+ options_hbox.addWidget(self.no_event_btn, 25)
168
+ options_hbox.addWidget(self.else_btn, 25)
169
+ options_hbox.addWidget(self.suppr_btn, 25)
170
+ self.left_panel.addLayout(options_hbox)
171
+
172
+ time_option_hbox = QHBoxLayout()
173
+ time_option_hbox.setContentsMargins(100, 30, 100, 30)
174
+ self.time_of_interest_label = QLabel('time of interest: ')
175
+ time_option_hbox.addWidget(self.time_of_interest_label, 30)
176
+ self.time_of_interest_le = QLineEdit()
177
+ time_option_hbox.addWidget(self.time_of_interest_le, 70)
178
+ self.left_panel.addLayout(time_option_hbox)
179
+
180
+ main_action_hbox = QHBoxLayout()
181
+ self.correct_btn = QPushButton('correct')
182
+ self.correct_btn.setIcon(icon(MDI6.redo_variant, color="white"))
183
+ self.correct_btn.setIconSize(QSize(20, 20))
184
+ self.correct_btn.setStyleSheet(self.button_style_sheet)
185
+ self.correct_btn.clicked.connect(self.show_annotation_buttons)
186
+ self.correct_btn.setEnabled(False)
187
+ main_action_hbox.addWidget(self.correct_btn)
188
+
189
+ self.cancel_btn = QPushButton('cancel')
190
+ self.cancel_btn.setStyleSheet(self.button_style_sheet_2)
191
+ self.cancel_btn.setShortcut(QKeySequence("Esc"))
192
+ self.cancel_btn.setEnabled(False)
193
+ self.cancel_btn.clicked.connect(self.cancel_selection)
194
+ main_action_hbox.addWidget(self.cancel_btn)
195
+ self.left_panel.addLayout(main_action_hbox)
196
+
197
+ self.annotation_btns_to_hide = [self.event_btn, self.no_event_btn,
198
+ self.else_btn, self.time_of_interest_label,
199
+ self.time_of_interest_le, self.suppr_btn]
200
+ self.hide_annotation_buttons()
201
+ #### End of annotation buttons
202
+
203
+ self.del_shortcut = QShortcut(Qt.Key_Delete, self) # QKeySequence("s")
204
+ self.del_shortcut.activated.connect(self.shortcut_suppr)
205
+ self.del_shortcut.setEnabled(False)
206
+
207
+ self.no_event_shortcut = QShortcut(QKeySequence("n"), self) # QKeySequence("s")
208
+ self.no_event_shortcut.activated.connect(self.shortcut_no_event)
209
+ self.no_event_shortcut.setEnabled(False)
210
+
211
+ # Cell signals
212
+ self.left_panel.addWidget(self.cell_fcanvas)
213
+
214
+ plot_buttons_hbox = QHBoxLayout()
215
+ plot_buttons_hbox.setContentsMargins(0, 0, 0, 0)
216
+ self.normalize_features_btn = QPushButton('')
217
+ self.normalize_features_btn.setStyleSheet(self.button_select_all)
218
+ self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
219
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
220
+ self.normalize_features_btn.setFixedSize(QSize(30, 30))
221
+ # self.normalize_features_btn.setShortcut(QKeySequence('n'))
222
+ self.normalize_features_btn.clicked.connect(self.normalize_features)
223
+
224
+ plot_buttons_hbox.addWidget(QLabel(''), 90)
225
+ plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
226
+ self.normalized_signals = False
227
+
228
+ self.log_btn = QPushButton()
229
+ self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
230
+ self.log_btn.setStyleSheet(self.button_select_all)
231
+ self.log_btn.clicked.connect(self.switch_to_log)
232
+ plot_buttons_hbox.addWidget(self.log_btn, 5)
233
+
234
+ self.left_panel.addLayout(plot_buttons_hbox)
235
+
236
+ signal_choice_vbox = QVBoxLayout()
237
+ signal_choice_vbox.setContentsMargins(30, 0, 30, 50)
238
+ for i in range(len(self.signal_choice_cb)):
239
+ hlayout = QHBoxLayout()
240
+ hlayout.addWidget(self.signal_choice_label[i], 20)
241
+ hlayout.addWidget(self.signal_choice_cb[i], 75)
242
+ # hlayout.addWidget(self.log_btns[i], 5)
243
+ signal_choice_vbox.addLayout(hlayout)
244
+
245
+ # self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
246
+ # self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
247
+ # self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
248
+
249
+ self.left_panel.addLayout(signal_choice_vbox)
250
+
251
+ btn_hbox = QHBoxLayout()
252
+ self.save_btn = QPushButton('Save')
253
+ self.save_btn.setStyleSheet(self.button_style_sheet)
254
+ self.save_btn.clicked.connect(self.save_trajectories)
255
+ btn_hbox.addWidget(self.save_btn, 90)
256
+
257
+ self.export_btn = QPushButton('')
258
+ self.export_btn.setStyleSheet(self.button_select_all)
259
+ self.export_btn.clicked.connect(self.export_signals)
260
+ self.export_btn.setIcon(icon(MDI6.export, color="black"))
261
+ self.export_btn.setIconSize(QSize(25, 25))
262
+ btn_hbox.addWidget(self.export_btn, 10)
263
+ self.left_panel.addLayout(btn_hbox)
264
+
265
+ # Animation
266
+ animation_buttons_box = QHBoxLayout()
267
+
268
+ animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
269
+
270
+ self.first_frame_btn = QPushButton()
271
+ self.first_frame_btn.clicked.connect(self.set_first_frame)
272
+ self.first_frame_btn.setShortcut(QKeySequence('f'))
273
+ self.first_frame_btn.setIcon(icon(MDI6.page_first, color="black"))
274
+ self.first_frame_btn.setStyleSheet(self.button_select_all)
275
+ self.first_frame_btn.setFixedSize(QSize(60, 60))
276
+ self.first_frame_btn.setIconSize(QSize(30, 30))
277
+
278
+ self.last_frame_btn = QPushButton()
279
+ self.last_frame_btn.clicked.connect(self.set_last_frame)
280
+ self.last_frame_btn.setShortcut(QKeySequence('l'))
281
+ self.last_frame_btn.setIcon(icon(MDI6.page_last, color="black"))
282
+ self.last_frame_btn.setStyleSheet(self.button_select_all)
283
+ self.last_frame_btn.setFixedSize(QSize(60, 60))
284
+ self.last_frame_btn.setIconSize(QSize(30, 30))
285
+
286
+ self.stop_btn = QPushButton()
287
+ self.stop_btn.clicked.connect(self.stop)
288
+ self.stop_btn.setIcon(icon(MDI6.stop, color="black"))
289
+ self.stop_btn.setStyleSheet(self.button_select_all)
290
+ self.stop_btn.setFixedSize(QSize(60, 60))
291
+ self.stop_btn.setIconSize(QSize(30, 30))
292
+
293
+ self.start_btn = QPushButton()
294
+ self.start_btn.clicked.connect(self.start)
295
+ self.start_btn.setIcon(icon(MDI6.play, color="black"))
296
+ self.start_btn.setFixedSize(QSize(60, 60))
297
+ self.start_btn.setStyleSheet(self.button_select_all)
298
+ self.start_btn.setIconSize(QSize(30, 30))
299
+ self.start_btn.hide()
300
+
301
+ animation_buttons_box.addWidget(self.first_frame_btn, 5, alignment=Qt.AlignRight)
302
+ animation_buttons_box.addWidget(self.stop_btn, 5, alignment=Qt.AlignRight)
303
+ animation_buttons_box.addWidget(self.start_btn, 5, alignment=Qt.AlignRight)
304
+ animation_buttons_box.addWidget(self.last_frame_btn, 5, alignment=Qt.AlignRight)
305
+
306
+ self.right_panel.addLayout(animation_buttons_box, 5)
307
+
308
+ self.right_panel.addWidget(self.fcanvas, 90)
309
+
310
+ if not self.rgb_mode:
311
+ contrast_hbox = QHBoxLayout()
312
+ contrast_hbox.setContentsMargins(150, 5, 150, 5)
313
+ self.contrast_slider = QLabeledDoubleRangeSlider()
314
+ self.contrast_slider.setSingleStep(0.001)
315
+ self.contrast_slider.setTickInterval(0.001)
316
+ self.contrast_slider.setOrientation(1)
317
+ print(self.stack.shape, np.unique(self.stack))
318
+ print('range: ',
319
+ [np.nanpercentile(self.stack, 0.001), np.nanpercentile(self.stack, 99.999)])
320
+ self.contrast_slider.setRange(
321
+ *[np.nanpercentile(self.stack, 0.001), np.nanpercentile(self.stack, 99.999)])
322
+ self.contrast_slider.setValue(
323
+ [np.nanpercentile(self.stack, 1), np.nanpercentile(self.stack, 99.99)])
324
+ self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
325
+ contrast_hbox.addWidget(QLabel('contrast: '))
326
+ contrast_hbox.addWidget(self.contrast_slider, 90)
327
+ self.right_panel.addLayout(contrast_hbox, 5)
328
+
329
+ # speed_hbox = QHBoxLayout()
330
+ # speed_hbox.setContentsMargins(150,5,150,5)
331
+ # self.interval_slider = QLabeledSlider()
332
+ # self.interval_slider.setSingleStep(1)
333
+ # self.interval_slider.setTickInterval(1)
334
+ # self.interval_slider.setOrientation(1)
335
+ # self.interval_slider.setRange(1, 10000)
336
+ # self.interval_slider.setValue(self.speed)
337
+ # self.interval_slider.valueChanged.connect(self.interval_slider_action)
338
+ # speed_hbox.addWidget(QLabel('interval (ms): '))
339
+ # speed_hbox.addWidget(self.interval_slider,90)
340
+ # self.right_panel.addLayout(speed_hbox, 10)
341
+
342
+ # self.populate_left_panel()
343
+ # grid.addLayout(self.left_side, 0, 0, 1, 1)
344
+
345
+ main_layout.addLayout(self.left_panel, 35)
346
+ main_layout.addLayout(self.right_panel, 65)
347
+ self.button_widget.adjustSize()
348
+
349
+ self.setCentralWidget(self.button_widget)
350
+ self.show()
351
+
352
+ QApplication.processEvents()
353
+
354
+ def del_event_class(self):
355
+
356
+ msgBox = QMessageBox()
357
+ msgBox.setIcon(QMessageBox.Warning)
358
+ msgBox.setText(
359
+ 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?")
360
+ msgBox.setWindowTitle("Warning")
361
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
362
+ returnValue = msgBox.exec()
363
+ if returnValue == QMessageBox.No:
364
+ return None
365
+ else:
366
+ class_to_delete = self.class_choice_cb.currentText()
367
+ time_to_delete = class_to_delete.replace('class', 't')
368
+ status_to_delete = class_to_delete.replace('class', 'status')
369
+ cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
370
+ for c in cols_to_delete:
371
+ try:
372
+ self.df_tracks = self.df_tracks.drop([c], axis=1)
373
+ except Exception as e:
374
+ print(e)
375
+ item_idx = self.class_choice_cb.findText(class_to_delete)
376
+ self.class_choice_cb.removeItem(item_idx)
377
+
378
+ def create_new_event_class(self):
379
+
380
+ # display qwidget to name the event
381
+ self.newClassWidget = QWidget()
382
+ self.newClassWidget.setWindowTitle('Create new event class')
383
+
384
+ layout = QVBoxLayout()
385
+ self.newClassWidget.setLayout(layout)
386
+ name_hbox = QHBoxLayout()
387
+ name_hbox.addWidget(QLabel('event name: '), 25)
388
+ self.class_name_le = QLineEdit('event')
389
+ name_hbox.addWidget(self.class_name_le, 75)
390
+ layout.addLayout(name_hbox)
391
+
392
+ class_labels = ['event', 'no event', 'else']
393
+ layout.addWidget(QLabel('prefill: '))
394
+ radio_box = QHBoxLayout()
395
+ self.class_option_rb = [QRadioButton() for i in range(3)]
396
+ for i, c in enumerate(self.class_option_rb):
397
+ if i == 0:
398
+ c.setChecked(True)
399
+ c.setText(class_labels[i])
400
+ radio_box.addWidget(c, 33, alignment=Qt.AlignCenter)
401
+ layout.addLayout(radio_box)
402
+
403
+ btn_hbox = QHBoxLayout()
404
+ submit_btn = QPushButton('submit')
405
+ cancel_btn = QPushButton('cancel')
406
+ btn_hbox.addWidget(cancel_btn, 50)
407
+ btn_hbox.addWidget(submit_btn, 50)
408
+ layout.addLayout(btn_hbox)
409
+
410
+ submit_btn.clicked.connect(self.write_new_event_class)
411
+ cancel_btn.clicked.connect(self.close_without_new_class)
412
+
413
+ self.newClassWidget.show()
414
+ center_window(self.newClassWidget)
415
+
416
+ # Prefill with class value
417
+ # write in table
418
+
419
+ def write_new_event_class(self):
420
+
421
+ if self.class_name_le.text() == '':
422
+ self.target_class = 'class'
423
+ self.target_time = 't0'
424
+ else:
425
+ self.target_class = 'class_' + self.class_name_le.text()
426
+ self.target_time = 't_' + self.class_name_le.text()
427
+
428
+ if self.target_class in list(self.df_tracks.columns):
429
+
430
+ msgBox = QMessageBox()
431
+ msgBox.setIcon(QMessageBox.Warning)
432
+ msgBox.setText(
433
+ "This event name already exists. If you proceed,\nall annotated data will be rewritten. Do you wish to continue?")
434
+ msgBox.setWindowTitle("Warning")
435
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
436
+ returnValue = msgBox.exec()
437
+ if returnValue == QMessageBox.No:
438
+ return None
439
+ else:
440
+ pass
441
+
442
+ fill_option = np.where([c.isChecked() for c in self.class_option_rb])[0][0]
443
+ self.df_tracks.loc[:, self.target_class] = fill_option
444
+ if fill_option == 0:
445
+ self.df_tracks.loc[:, self.target_time] = 0.1
446
+ else:
447
+ self.df_tracks.loc[:, self.target_time] = -1
448
+
449
+ self.class_choice_cb.clear()
450
+ cols = np.array(self.df_tracks.columns)
451
+ self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
452
+ self.class_cols = list(cols[self.class_cols])
453
+ self.class_cols.remove('class_id')
454
+ self.class_cols.remove('class_color')
455
+ self.class_choice_cb.addItems(self.class_cols)
456
+ idx = self.class_choice_cb.findText(self.target_class)
457
+ self.class_choice_cb.setCurrentIndex(idx)
458
+
459
+ self.newClassWidget.close()
460
+
461
+ def close_without_new_class(self):
462
+ self.newClassWidget.close()
463
+
464
+ def compute_status_and_colors(self, i):
465
+ self.class_name = self.class_choice_cb.currentText()
466
+ self.expected_status = 'status'
467
+ suffix = self.class_name.replace('class', '').replace('_', '', 1)
468
+ if suffix != '':
469
+ self.expected_status += '_' + suffix
470
+ self.expected_time = 't_' + suffix
471
+ else:
472
+ self.expected_time = 't0'
473
+ self.time_name = self.expected_time
474
+ self.status_name = self.expected_status
475
+
476
+ print('selection and expected names: ', self.class_name, self.expected_time, self.expected_status)
477
+
478
+ 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:
479
+ # only create the status column if it does not exist to not erase static classification results
480
+ self.make_status_column()
481
+ elif self.time_name in self.df_tracks.columns and self.class_name in self.df_tracks.columns:
482
+ # all good, do nothing
483
+ pass
484
+ else:
485
+ if not self.status_name in self.df_tracks.columns:
486
+ self.df_tracks[self.status_name] = 0
487
+ self.df_tracks['status_color'] = color_from_status(0)
488
+ self.df_tracks['class_color'] = color_from_class(1)
489
+
490
+ if not self.class_name in self.df_tracks.columns:
491
+ self.df_tracks[self.class_name] = 1
492
+ if not self.time_name in self.df_tracks.columns:
493
+ self.df_tracks[self.time_name] = -1
494
+
495
+ self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
496
+ self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
497
+
498
+ self.extract_scatter_from_trajectories()
499
+
500
+ def contrast_slider_action(self):
501
+
502
+ """
500
503
  Recontrast the imshow as the contrast slider is moved.
501
504
  """
502
505
 
503
- self.vmin = self.contrast_slider.value()[0]
504
- self.vmax = self.contrast_slider.value()[1]
505
- self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
506
- self.fcanvas.canvas.draw_idle()
507
-
508
- def cancel_selection(self):
509
-
510
- self.hide_annotation_buttons()
511
- self.correct_btn.setEnabled(False)
512
- self.correct_btn.setText('correct')
513
- self.cancel_btn.setEnabled(False)
514
-
515
- try:
516
- self.selection.pop(0)
517
- except Exception as e:
518
- print(e)
519
-
520
- try:
521
- for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
522
- self.colors[t][idx, 0] = self.previous_color[k][0]
523
- self.colors[t][idx, 1] = self.previous_color[k][1]
524
- except Exception as e:
525
- print(f'{e=}')
526
-
527
- def hide_annotation_buttons(self):
528
-
529
- for a in self.annotation_btns_to_hide:
530
- a.hide()
531
- for b in [self.event_btn, self.no_event_btn, self.else_btn, self.suppr_btn]:
532
- b.setChecked(False)
533
- self.time_of_interest_label.setEnabled(False)
534
- self.time_of_interest_le.setText('')
535
- self.time_of_interest_le.setEnabled(False)
536
-
537
- def enable_time_of_interest(self):
538
-
539
- if self.event_btn.isChecked():
540
- self.time_of_interest_label.setEnabled(True)
541
- self.time_of_interest_le.setEnabled(True)
542
- else:
543
- self.time_of_interest_label.setEnabled(False)
544
- self.time_of_interest_le.setEnabled(False)
545
-
546
- def show_annotation_buttons(self):
547
-
548
- for a in self.annotation_btns_to_hide:
549
- a.show()
550
-
551
- cclass = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.class_name].to_numpy()[0]
552
- t0 = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.time_name].to_numpy()[0]
553
-
554
- if cclass == 0:
555
- self.event_btn.setChecked(True)
556
- self.time_of_interest_le.setText(str(t0))
557
- elif cclass == 1:
558
- self.no_event_btn.setChecked(True)
559
- elif cclass == 2:
560
- self.else_btn.setChecked(True)
561
- elif cclass > 2:
562
- self.suppr_btn.setChecked(True)
563
-
564
- self.enable_time_of_interest()
565
- self.correct_btn.setText('submit')
566
-
567
- self.correct_btn.disconnect()
568
- self.correct_btn.clicked.connect(self.apply_modification)
569
-
570
- def apply_modification(self):
571
-
572
- t0 = -1
573
- if self.event_btn.isChecked():
574
- cclass = 0
575
- try:
576
- t0 = float(self.time_of_interest_le.text().replace(',', '.'))
577
- self.line_dt.set_xdata([t0, t0])
578
- self.cell_fcanvas.canvas.draw_idle()
579
- except Exception as e:
580
- print(e)
581
- t0 = -1
582
- cclass = 2
583
- elif self.no_event_btn.isChecked():
584
- cclass = 1
585
- elif self.else_btn.isChecked():
586
- cclass = 2
587
- elif self.suppr_btn.isChecked():
588
- cclass = 42
589
-
590
- self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.class_name] = cclass
591
- self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.time_name] = t0
592
-
593
- indices = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.class_name].index
594
- timeline = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, 'FRAME'].to_numpy()
595
- status = np.zeros_like(timeline)
596
- if t0 > 0:
597
- status[timeline >= t0] = 1.
598
- if cclass == 2:
599
- status[:] = 2
600
- if cclass > 2:
601
- status[:] = 42
602
- status_color = [color_from_status(s, recently_modified=True) for s in status]
603
- class_color = [color_from_class(cclass, recently_modified=True) for i in range(len(status))]
604
-
605
- self.df_tracks.loc[indices, self.status_name] = status
606
- self.df_tracks.loc[indices, 'status_color'] = status_color
607
- self.df_tracks.loc[indices, 'class_color'] = class_color
608
-
609
- # self.make_status_column()
610
- self.extract_scatter_from_trajectories()
611
- self.give_cell_information()
612
-
613
- self.correct_btn.disconnect()
614
- self.correct_btn.clicked.connect(self.show_annotation_buttons)
615
- # self.cancel_btn.click()
616
-
617
- self.hide_annotation_buttons()
618
- self.correct_btn.setEnabled(False)
619
- self.correct_btn.setText('correct')
620
- self.cancel_btn.setEnabled(False)
621
- self.del_shortcut.setEnabled(False)
622
- self.no_event_shortcut.setEnabled(False)
623
-
624
- self.selection.pop(0)
625
-
626
- # self.fcanvas.canvas.draw()
627
-
628
- def locate_stack(self):
629
-
630
- """
506
+ self.vmin = self.contrast_slider.value()[0]
507
+ self.vmax = self.contrast_slider.value()[1]
508
+ self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
509
+ self.fcanvas.canvas.draw_idle()
510
+
511
+ def cancel_selection(self):
512
+
513
+ self.hide_annotation_buttons()
514
+ self.correct_btn.setEnabled(False)
515
+ self.correct_btn.setText('correct')
516
+ self.cancel_btn.setEnabled(False)
517
+
518
+ try:
519
+ self.selection.pop(0)
520
+ except Exception as e:
521
+ print(e)
522
+
523
+ try:
524
+ for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
525
+ self.colors[t][idx, 0] = self.previous_color[k][0]
526
+ self.colors[t][idx, 1] = self.previous_color[k][1]
527
+ except Exception as e:
528
+ print(f'{e=}')
529
+
530
+ def hide_annotation_buttons(self):
531
+
532
+ for a in self.annotation_btns_to_hide:
533
+ a.hide()
534
+ for b in [self.event_btn, self.no_event_btn, self.else_btn, self.suppr_btn]:
535
+ b.setChecked(False)
536
+ self.time_of_interest_label.setEnabled(False)
537
+ self.time_of_interest_le.setText('')
538
+ self.time_of_interest_le.setEnabled(False)
539
+
540
+ def enable_time_of_interest(self):
541
+
542
+ if self.event_btn.isChecked():
543
+ self.time_of_interest_label.setEnabled(True)
544
+ self.time_of_interest_le.setEnabled(True)
545
+ else:
546
+ self.time_of_interest_label.setEnabled(False)
547
+ self.time_of_interest_le.setEnabled(False)
548
+
549
+ def show_annotation_buttons(self):
550
+
551
+ for a in self.annotation_btns_to_hide:
552
+ a.show()
553
+
554
+ cclass = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.class_name].to_numpy()[0]
555
+ t0 = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.time_name].to_numpy()[0]
556
+
557
+ if cclass == 0:
558
+ self.event_btn.setChecked(True)
559
+ self.time_of_interest_le.setText(str(t0))
560
+ elif cclass == 1:
561
+ self.no_event_btn.setChecked(True)
562
+ elif cclass == 2:
563
+ self.else_btn.setChecked(True)
564
+ elif cclass > 2:
565
+ self.suppr_btn.setChecked(True)
566
+
567
+ self.enable_time_of_interest()
568
+ self.correct_btn.setText('submit')
569
+
570
+ self.correct_btn.disconnect()
571
+ self.correct_btn.clicked.connect(self.apply_modification)
572
+
573
+ def apply_modification(self):
574
+
575
+ t0 = -1
576
+ if self.event_btn.isChecked():
577
+ cclass = 0
578
+ try:
579
+ t0 = float(self.time_of_interest_le.text().replace(',', '.'))
580
+ self.line_dt.set_xdata([t0, t0])
581
+ self.cell_fcanvas.canvas.draw_idle()
582
+ except Exception as e:
583
+ print(e)
584
+ t0 = -1
585
+ cclass = 2
586
+ elif self.no_event_btn.isChecked():
587
+ cclass = 1
588
+ elif self.else_btn.isChecked():
589
+ cclass = 2
590
+ elif self.suppr_btn.isChecked():
591
+ cclass = 42
592
+
593
+ self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.class_name] = cclass
594
+ self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.time_name] = t0
595
+
596
+ indices = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.class_name].index
597
+ timeline = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, 'FRAME'].to_numpy()
598
+ status = np.zeros_like(timeline)
599
+ if t0 > 0:
600
+ status[timeline >= t0] = 1.
601
+ if cclass == 2:
602
+ status[:] = 2
603
+ if cclass > 2:
604
+ status[:] = 42
605
+ status_color = [color_from_status(s, recently_modified=True) for s in status]
606
+ class_color = [color_from_class(cclass, recently_modified=True) for i in range(len(status))]
607
+
608
+ self.df_tracks.loc[indices, self.status_name] = status
609
+ self.df_tracks.loc[indices, 'status_color'] = status_color
610
+ self.df_tracks.loc[indices, 'class_color'] = class_color
611
+
612
+ # self.make_status_column()
613
+ self.extract_scatter_from_trajectories()
614
+ self.give_cell_information()
615
+
616
+ self.correct_btn.disconnect()
617
+ self.correct_btn.clicked.connect(self.show_annotation_buttons)
618
+ # self.cancel_btn.click()
619
+
620
+ self.hide_annotation_buttons()
621
+ self.correct_btn.setEnabled(False)
622
+ self.correct_btn.setText('correct')
623
+ self.cancel_btn.setEnabled(False)
624
+ self.del_shortcut.setEnabled(False)
625
+ self.no_event_shortcut.setEnabled(False)
626
+
627
+ self.selection.pop(0)
628
+
629
+ # self.fcanvas.canvas.draw()
630
+
631
+ def locate_stack(self):
632
+
633
+ """
631
634
  Locate the target movie.
632
635
 
633
636
  """
634
637
 
635
- movies = glob(self.pos + f"movie/{self.parent.parent.movie_prefix}*.tif")
636
-
637
- if len(movies) == 0:
638
- msgBox = QMessageBox()
639
- msgBox.setIcon(QMessageBox.Warning)
640
- msgBox.setText("No movies are detected in the experiment folder. Cannot load an image to test Haralick.")
641
- msgBox.setWindowTitle("Warning")
642
- msgBox.setStandardButtons(QMessageBox.Ok)
643
- returnValue = msgBox.exec()
644
- if returnValue == QMessageBox.Yes:
645
- self.close()
646
- else:
647
- self.stack_path = movies[0]
648
- self.len_movie = self.parent.parent.len_movie
649
- len_movie_auto = auto_load_number_of_frames(self.stack_path)
650
- if len_movie_auto is not None:
651
- self.len_movie = len_movie_auto
652
- exp_config = self.exp_dir + "config.ini"
653
- self.channel_names, self.channels = extract_experiment_channels(exp_config)
654
- self.channel_names = np.array(self.channel_names)
655
- self.channels = np.array(self.channels)
656
- self.nbr_channels = len(self.channels)
657
-
658
- def locate_tracks(self):
659
-
660
- """
638
+ movies = glob(self.pos + f"movie/{self.parent_window.parent_window.movie_prefix}*.tif")
639
+
640
+ if len(movies) == 0:
641
+ msgBox = QMessageBox()
642
+ msgBox.setIcon(QMessageBox.Warning)
643
+ msgBox.setText("No movies are detected in the experiment folder. Cannot load an image to test Haralick.")
644
+ msgBox.setWindowTitle("Warning")
645
+ msgBox.setStandardButtons(QMessageBox.Ok)
646
+ returnValue = msgBox.exec()
647
+ if returnValue == QMessageBox.Yes:
648
+ self.close()
649
+ else:
650
+ self.stack_path = movies[0]
651
+ self.len_movie = self.parent_window.parent_window.len_movie
652
+ len_movie_auto = auto_load_number_of_frames(self.stack_path)
653
+ if len_movie_auto is not None:
654
+ self.len_movie = len_movie_auto
655
+ exp_config = self.exp_dir + "config.ini"
656
+ self.channel_names, self.channels = extract_experiment_channels(exp_config)
657
+ self.channel_names = np.array(self.channel_names)
658
+ self.channels = np.array(self.channels)
659
+ self.nbr_channels = len(self.channels)
660
+
661
+ def locate_tracks(self):
662
+
663
+ """
661
664
  Locate the tracks.
662
665
  """
663
666
 
664
- if not os.path.exists(self.trajectories_path):
665
-
666
- msgBox = QMessageBox()
667
- msgBox.setIcon(QMessageBox.Warning)
668
- msgBox.setText("The trajectories cannot be detected.")
669
- msgBox.setWindowTitle("Warning")
670
- msgBox.setStandardButtons(QMessageBox.Ok)
671
- returnValue = msgBox.exec()
672
- if returnValue == QMessageBox.Yes:
673
- self.close()
674
- else:
675
-
676
- # Load and prep tracks
677
- self.df_tracks = pd.read_csv(self.trajectories_path)
678
- self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
679
-
680
- cols = np.array(self.df_tracks.columns)
681
- self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
682
- self.class_cols = list(cols[self.class_cols])
683
- try:
684
- self.class_cols.remove('class_id')
685
- except:
686
- pass
687
- try:
688
- self.class_cols.remove('class_color')
689
- except:
690
- pass
691
- if len(self.class_cols) > 0:
692
- self.class_name = self.class_cols[0]
693
- self.expected_status = 'status'
694
- suffix = self.class_name.replace('class', '').replace('_', '')
695
- if suffix != '':
696
- self.expected_status += '_' + suffix
697
- self.expected_time = 't_' + suffix
698
- else:
699
- self.expected_time = 't0'
700
- self.time_name = self.expected_time
701
- self.status_name = self.expected_status
702
- else:
703
- self.class_name = 'class'
704
- self.time_name = 't0'
705
- self.status_name = 'status'
706
-
707
- 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:
708
- # only create the status column if it does not exist to not erase static classification results
709
- self.make_status_column()
710
- elif self.time_name in self.df_tracks.columns and self.class_name in self.df_tracks.columns:
711
- # all good, do nothing
712
- pass
713
- else:
714
- if not self.status_name in self.df_tracks.columns:
715
- self.df_tracks[self.status_name] = 0
716
- self.df_tracks['status_color'] = color_from_status(0)
717
- self.df_tracks['class_color'] = color_from_class(1)
718
-
719
- if not self.class_name in self.df_tracks.columns:
720
- self.df_tracks[self.class_name] = 1
721
- if not self.time_name in self.df_tracks.columns:
722
- self.df_tracks[self.time_name] = -1
723
-
724
- self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
725
- self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
726
-
727
- self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
728
- self.df_tracks['x_anim'] = self.df_tracks['POSITION_X'] * self.fraction
729
- self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y'] * self.fraction
730
- self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
731
- self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
732
-
733
- self.extract_scatter_from_trajectories()
734
- self.track_of_interest = self.df_tracks['TRACK_ID'].min()
735
-
736
- self.loc_t = []
737
- self.loc_idx = []
738
- for t in range(len(self.tracks)):
739
- indices = np.where(self.tracks[t] == self.track_of_interest)[0]
740
- if len(indices) > 0:
741
- self.loc_t.append(t)
742
- self.loc_idx.append(indices[0])
743
-
744
- self.MinMaxScaler = MinMaxScaler()
745
- self.columns_to_rescale = list(self.df_tracks.columns)
746
-
747
- # is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
748
- # is_number_test = is_number(self.df_tracks.dtypes)
749
- # self.columns_to_rescale = [col for t,col in zip(is_number_test,self.df_tracks.columns) if t]
750
- # print(self.columns_to_rescale)
751
-
752
- cols_to_remove = ['status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME', 'x_anim', 'y_anim', 't',
753
- 'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
754
- 'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
755
- 'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent'] + self.class_cols
756
- cols = np.array(list(self.df_tracks.columns))
757
- time_cols = np.array([c.startswith('t_') for c in cols])
758
- time_cols = list(cols[time_cols])
759
- cols_to_remove += time_cols
760
-
761
- for tr in cols_to_remove:
762
- try:
763
- self.columns_to_rescale.remove(tr)
764
- except:
765
- pass
766
- # print(f'column {tr} could not be found...')
767
-
768
- x = self.df_tracks[self.columns_to_rescale].values
769
- self.MinMaxScaler.fit(x)
770
-
771
- # self.loc_t, self.loc_idx = np.where(self.tracks==self.track_of_interest)
772
-
773
- def make_status_column(self):
774
- print(self.class_name, self.time_name, self.status_name)
775
- print('remaking the status column')
776
- for tid, group in self.df_tracks.groupby('TRACK_ID'):
777
-
778
- indices = group.index
779
- t0 = group[self.time_name].to_numpy()[0]
780
- cclass = group[self.class_name].to_numpy()[0]
781
- timeline = group['FRAME'].to_numpy()
782
- status = np.zeros_like(timeline)
783
- if t0 > 0:
784
- status[timeline >= t0] = 1.
785
- if cclass == 2:
786
- status[:] = 2
787
- if cclass > 2:
788
- status[:] = 42
789
- status_color = [color_from_status(s) for s in status]
790
- class_color = [color_from_class(cclass) for i in range(len(status))]
791
-
792
- self.df_tracks.loc[indices, self.status_name] = status
793
- self.df_tracks.loc[indices, 'status_color'] = status_color
794
- self.df_tracks.loc[indices, 'class_color'] = class_color
795
-
796
- def generate_signal_choices(self):
797
-
798
- self.signal_choice_cb = [QComboBox() for i in range(self.n_signals)]
799
- self.signal_choice_label = [QLabel(f'signal {i + 1}: ') for i in range(self.n_signals)]
800
- # self.log_btns = [QPushButton() for i in range(self.n_signals)]
801
-
802
- signals = list(self.df_tracks.columns)
803
- print(signals)
804
- to_remove = ['TRACK_ID', 'FRAME', 'x_anim', 'y_anim', 't', 'state', 'generation', 'root', 'parent', 'class_id',
805
- 'class', 't0', 'POSITION_X', 'POSITION_Y', 'position', 'well', 'well_index', 'well_name',
806
- 'pos_name', 'index','class_color','status_color']
807
-
808
- for c in to_remove:
809
- if c in signals:
810
- signals.remove(c)
811
-
812
- for i in range(len(self.signal_choice_cb)):
813
- self.signal_choice_cb[i].addItems(['--'] + signals)
814
- self.signal_choice_cb[i].setCurrentIndex(i + 1)
815
- self.signal_choice_cb[i].currentIndexChanged.connect(self.plot_signals)
816
-
817
- def plot_signals(self):
818
-
819
- try:
820
- yvalues = []
821
- for i in range(len(self.signal_choice_cb)):
822
-
823
- signal_choice = self.signal_choice_cb[i].currentText()
824
- self.lines[i].set_label(signal_choice)
825
-
826
- if signal_choice == "--":
827
- self.lines[i].set_xdata([])
828
- self.lines[i].set_ydata([])
829
- else:
830
- print(f'plot signal {signal_choice} for cell {self.track_of_interest}')
831
- xdata = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, 'FRAME'].to_numpy()
832
- ydata = self.df_tracks.loc[
833
- self.df_tracks['TRACK_ID'] == self.track_of_interest, signal_choice].to_numpy()
834
-
835
- xdata = xdata[ydata == ydata] # remove nan
836
- ydata = ydata[ydata == ydata]
837
-
838
- yvalues.extend(ydata)
839
- self.lines[i].set_xdata(xdata)
840
- self.lines[i].set_ydata(ydata)
841
- self.lines[i].set_color(tab10(i / 3.))
842
-
843
- self.configure_ylims()
844
-
845
- min_val, max_val = self.cell_ax.get_ylim()
846
- t0 = \
847
- self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.expected_time].to_numpy()[
848
- 0]
849
- self.line_dt.set_xdata([t0, t0])
850
- self.line_dt.set_ydata([min_val, max_val])
851
-
852
- self.cell_ax.legend()
853
- self.cell_fcanvas.canvas.draw()
854
- except Exception as e:
855
- print(f"{e=}")
856
-
857
- def extract_scatter_from_trajectories(self):
858
-
859
- self.positions = []
860
- self.colors = []
861
- self.tracks = []
862
-
863
- for t in np.arange(self.len_movie):
864
- self.positions.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['x_anim', 'y_anim']].to_numpy())
865
- self.colors.append(
866
- self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
867
- self.tracks.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, 'TRACK_ID'].to_numpy())
868
-
869
- def load_annotator_config(self):
870
-
871
- """
667
+ if not os.path.exists(self.trajectories_path):
668
+
669
+ msgBox = QMessageBox()
670
+ msgBox.setIcon(QMessageBox.Warning)
671
+ msgBox.setText("The trajectories cannot be detected.")
672
+ msgBox.setWindowTitle("Warning")
673
+ msgBox.setStandardButtons(QMessageBox.Ok)
674
+ returnValue = msgBox.exec()
675
+ if returnValue == QMessageBox.Yes:
676
+ self.close()
677
+ else:
678
+
679
+ # Load and prep tracks
680
+ self.df_tracks = pd.read_csv(self.trajectories_path)
681
+ self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
682
+
683
+ cols = np.array(self.df_tracks.columns)
684
+ self.class_cols = np.array([c.startswith('class') for c in list(self.df_tracks.columns)])
685
+ self.class_cols = list(cols[self.class_cols])
686
+ try:
687
+ self.class_cols.remove('class_id')
688
+ except:
689
+ pass
690
+ try:
691
+ self.class_cols.remove('class_color')
692
+ except:
693
+ pass
694
+ if len(self.class_cols) > 0:
695
+ self.class_name = self.class_cols[0]
696
+ self.expected_status = 'status'
697
+ suffix = self.class_name.replace('class', '').replace('_', '')
698
+ if suffix != '':
699
+ self.expected_status += '_' + suffix
700
+ self.expected_time = 't_' + suffix
701
+ else:
702
+ self.expected_time = 't0'
703
+ self.time_name = self.expected_time
704
+ self.status_name = self.expected_status
705
+ else:
706
+ self.class_name = 'class'
707
+ self.time_name = 't0'
708
+ self.status_name = 'status'
709
+
710
+ 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:
711
+ # only create the status column if it does not exist to not erase static classification results
712
+ self.make_status_column()
713
+ elif self.time_name in self.df_tracks.columns and self.class_name in self.df_tracks.columns:
714
+ # all good, do nothing
715
+ pass
716
+ else:
717
+ if not self.status_name in self.df_tracks.columns:
718
+ self.df_tracks[self.status_name] = 0
719
+ self.df_tracks['status_color'] = color_from_status(0)
720
+ self.df_tracks['class_color'] = color_from_class(1)
721
+
722
+ if not self.class_name in self.df_tracks.columns:
723
+ self.df_tracks[self.class_name] = 1
724
+ if not self.time_name in self.df_tracks.columns:
725
+ self.df_tracks[self.time_name] = -1
726
+
727
+ self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
728
+ self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
729
+
730
+ self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
731
+ self.df_tracks['x_anim'] = self.df_tracks['POSITION_X'] * self.fraction
732
+ self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y'] * self.fraction
733
+ self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
734
+ self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
735
+
736
+ self.extract_scatter_from_trajectories()
737
+ self.track_of_interest = self.df_tracks['TRACK_ID'].min()
738
+
739
+ self.loc_t = []
740
+ self.loc_idx = []
741
+ for t in range(len(self.tracks)):
742
+ indices = np.where(self.tracks[t] == self.track_of_interest)[0]
743
+ if len(indices) > 0:
744
+ self.loc_t.append(t)
745
+ self.loc_idx.append(indices[0])
746
+
747
+ self.MinMaxScaler = MinMaxScaler()
748
+ self.columns_to_rescale = list(self.df_tracks.columns)
749
+
750
+ # is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
751
+ # is_number_test = is_number(self.df_tracks.dtypes)
752
+ # self.columns_to_rescale = [col for t,col in zip(is_number_test,self.df_tracks.columns) if t]
753
+ # print(self.columns_to_rescale)
754
+
755
+ cols_to_remove = ['status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME', 'x_anim', 'y_anim', 't',
756
+ 'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
757
+ 'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
758
+ 'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent'] + self.class_cols
759
+ cols = np.array(list(self.df_tracks.columns))
760
+ time_cols = np.array([c.startswith('t_') for c in cols])
761
+ time_cols = list(cols[time_cols])
762
+ cols_to_remove += time_cols
763
+
764
+ for tr in cols_to_remove:
765
+ try:
766
+ self.columns_to_rescale.remove(tr)
767
+ except:
768
+ pass
769
+ # print(f'column {tr} could not be found...')
770
+
771
+ x = self.df_tracks[self.columns_to_rescale].values
772
+ self.MinMaxScaler.fit(x)
773
+
774
+ # self.loc_t, self.loc_idx = np.where(self.tracks==self.track_of_interest)
775
+
776
+ def make_status_column(self):
777
+ print(self.class_name, self.time_name, self.status_name)
778
+ print('remaking the status column')
779
+ for tid, group in self.df_tracks.groupby('TRACK_ID'):
780
+
781
+ indices = group.index
782
+ t0 = group[self.time_name].to_numpy()[0]
783
+ cclass = group[self.class_name].to_numpy()[0]
784
+ timeline = group['FRAME'].to_numpy()
785
+ status = np.zeros_like(timeline)
786
+ if t0 > 0:
787
+ status[timeline >= t0] = 1.
788
+ if cclass == 2:
789
+ status[:] = 2
790
+ if cclass > 2:
791
+ status[:] = 42
792
+ status_color = [color_from_status(s) for s in status]
793
+ class_color = [color_from_class(cclass) for i in range(len(status))]
794
+
795
+ self.df_tracks.loc[indices, self.status_name] = status
796
+ self.df_tracks.loc[indices, 'status_color'] = status_color
797
+ self.df_tracks.loc[indices, 'class_color'] = class_color
798
+
799
+ def generate_signal_choices(self):
800
+
801
+ self.signal_choice_cb = [QSearchableComboBox() for i in range(self.n_signals)]
802
+ self.signal_choice_label = [QLabel(f'signal {i + 1}: ') for i in range(self.n_signals)]
803
+ # self.log_btns = [QPushButton() for i in range(self.n_signals)]
804
+
805
+ signals = list(self.df_tracks.columns)
806
+ print(signals)
807
+ to_remove = ['TRACK_ID', 'FRAME', 'x_anim', 'y_anim', 't', 'state', 'generation', 'root', 'parent', 'class_id',
808
+ 'class', 't0', 'POSITION_X', 'POSITION_Y', 'position', 'well', 'well_index', 'well_name',
809
+ 'pos_name', 'index','class_color','status_color']
810
+
811
+ for c in to_remove:
812
+ if c in signals:
813
+ signals.remove(c)
814
+
815
+ for i in range(len(self.signal_choice_cb)):
816
+ self.signal_choice_cb[i].addItems(['--'] + signals)
817
+ self.signal_choice_cb[i].setCurrentIndex(i + 1)
818
+ self.signal_choice_cb[i].currentIndexChanged.connect(self.plot_signals)
819
+
820
+ def plot_signals(self):
821
+
822
+ try:
823
+ yvalues = []
824
+ for i in range(len(self.signal_choice_cb)):
825
+
826
+ signal_choice = self.signal_choice_cb[i].currentText()
827
+ self.lines[i].set_label(signal_choice)
828
+
829
+ if signal_choice == "--":
830
+ self.lines[i].set_xdata([])
831
+ self.lines[i].set_ydata([])
832
+ else:
833
+ print(f'plot signal {signal_choice} for cell {self.track_of_interest}')
834
+ xdata = self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, 'FRAME'].to_numpy()
835
+ ydata = self.df_tracks.loc[
836
+ self.df_tracks['TRACK_ID'] == self.track_of_interest, signal_choice].to_numpy()
837
+
838
+ xdata = xdata[ydata == ydata] # remove nan
839
+ ydata = ydata[ydata == ydata]
840
+
841
+ yvalues.extend(ydata)
842
+ self.lines[i].set_xdata(xdata)
843
+ self.lines[i].set_ydata(ydata)
844
+ self.lines[i].set_color(tab10(i / 3.))
845
+
846
+ self.configure_ylims()
847
+
848
+ min_val, max_val = self.cell_ax.get_ylim()
849
+ t0 = \
850
+ self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.expected_time].to_numpy()[
851
+ 0]
852
+ self.line_dt.set_xdata([t0, t0])
853
+ self.line_dt.set_ydata([min_val, max_val])
854
+
855
+ self.cell_ax.legend()
856
+ self.cell_fcanvas.canvas.draw()
857
+ except Exception as e:
858
+ print(f"{e=}")
859
+
860
+ def extract_scatter_from_trajectories(self):
861
+
862
+ self.positions = []
863
+ self.colors = []
864
+ self.tracks = []
865
+
866
+ for t in np.arange(self.len_movie):
867
+ self.positions.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['x_anim', 'y_anim']].to_numpy())
868
+ self.colors.append(
869
+ self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
870
+ self.tracks.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, 'TRACK_ID'].to_numpy())
871
+
872
+ def load_annotator_config(self):
873
+
874
+ """
872
875
  Load settings from config or set default values.
873
876
  """
874
877
 
875
- print('Reading instructions..')
876
- if os.path.exists(self.instructions_path):
877
- with open(self.instructions_path, 'r') as f:
878
-
879
- instructions = json.load(f)
880
- print(f'Reading instructions: {instructions}')
881
-
882
- if 'rgb_mode' in instructions:
883
- self.rgb_mode = instructions['rgb_mode']
884
- else:
885
- self.rgb_mode = False
886
-
887
- if 'percentile_mode' in instructions:
888
- self.percentile_mode = instructions['percentile_mode']
889
- else:
890
- self.percentile_mode = True
891
-
892
- if 'channels' in instructions:
893
- self.target_channels = instructions['channels']
894
- else:
895
- self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
896
-
897
- if 'fraction' in instructions:
898
- self.fraction = float(instructions['fraction'])
899
- else:
900
- self.fraction = 0.25
901
-
902
- if 'interval' in instructions:
903
- self.anim_interval = int(instructions['interval'])
904
- else:
905
- self.anim_interval = 1
906
-
907
- if 'log' in instructions:
908
- self.log_option = instructions['log']
909
- else:
910
- self.log_option = False
911
- else:
912
- self.rgb_mode = False
913
- self.log_option = False
914
- self.percentile_mode = True
915
- self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
916
- self.fraction = 0.25
917
- self.anim_interval = 1
918
-
919
- def prepare_stack(self):
920
-
921
- self.img_num_channels = _get_img_num_per_channel(self.channels, self.len_movie, self.nbr_channels)
922
- self.stack = []
923
- for ch in tqdm(self.target_channels, desc="channel"):
924
- target_ch_name = ch[0]
925
- if self.percentile_mode:
926
- normalize_kwargs = {"percentiles": (ch[1], ch[2]), "values": None}
927
- else:
928
- normalize_kwargs = {"values": (ch[1], ch[2]), "percentiles": None}
929
-
930
- if self.rgb_mode:
931
- normalize_kwargs.update({'amplification': 255., 'clip': True})
932
-
933
- chan = []
934
- indices = self.img_num_channels[self.channels[np.where(self.channel_names == target_ch_name)][0]]
935
- for t in tqdm(range(len(indices)), desc='frame'):
936
- if self.rgb_mode:
937
- f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=True,
938
- normalize_kwargs=normalize_kwargs)
939
- f = f.astype(np.uint8)
940
- else:
941
- f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=False)
942
- chan.append(f[:, :, 0])
943
-
944
- self.stack.append(chan)
945
-
946
- self.stack = np.array(self.stack)
947
- if self.rgb_mode:
948
- self.stack = np.moveaxis(self.stack, 0, -1)
949
- else:
950
- self.stack = self.stack[0]
951
- if self.log_option:
952
- self.stack[np.where(self.stack > 0.)] = np.log(self.stack[np.where(self.stack > 0.)])
953
-
954
- print(f'Load stack of shape: {self.stack.shape}.')
955
-
956
- def closeEvent(self, event):
957
-
958
- self.stop()
959
- # result = QMessageBox.question(self,
960
- # "Confirm Exit...",
961
- # "Are you sure you want to exit ?",
962
- # QMessageBox.Yes| QMessageBox.No,
963
- # )
964
- del self.stack
965
- gc.collect()
966
-
967
- def looped_animation(self):
968
-
969
- """
878
+ print('Reading instructions..')
879
+ if os.path.exists(self.instructions_path):
880
+ with open(self.instructions_path, 'r') as f:
881
+
882
+ instructions = json.load(f)
883
+ print(f'Reading instructions: {instructions}')
884
+
885
+ if 'rgb_mode' in instructions:
886
+ self.rgb_mode = instructions['rgb_mode']
887
+ else:
888
+ self.rgb_mode = False
889
+
890
+ if 'percentile_mode' in instructions:
891
+ self.percentile_mode = instructions['percentile_mode']
892
+ else:
893
+ self.percentile_mode = True
894
+
895
+ if 'channels' in instructions:
896
+ self.target_channels = instructions['channels']
897
+ else:
898
+ self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
899
+
900
+ if 'fraction' in instructions:
901
+ self.fraction = float(instructions['fraction'])
902
+ else:
903
+ self.fraction = 0.25
904
+
905
+ if 'interval' in instructions:
906
+ self.anim_interval = int(instructions['interval'])
907
+ else:
908
+ self.anim_interval = 1
909
+
910
+ if 'log' in instructions:
911
+ self.log_option = instructions['log']
912
+ else:
913
+ self.log_option = False
914
+ else:
915
+ self.rgb_mode = False
916
+ self.log_option = False
917
+ self.percentile_mode = True
918
+ self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
919
+ self.fraction = 0.25
920
+ self.anim_interval = 1
921
+
922
+ def prepare_stack(self):
923
+
924
+ self.img_num_channels = _get_img_num_per_channel(self.channels, self.len_movie, self.nbr_channels)
925
+ self.stack = []
926
+ for ch in tqdm(self.target_channels, desc="channel"):
927
+ target_ch_name = ch[0]
928
+ if self.percentile_mode:
929
+ normalize_kwargs = {"percentiles": (ch[1], ch[2]), "values": None}
930
+ else:
931
+ normalize_kwargs = {"values": (ch[1], ch[2]), "percentiles": None}
932
+
933
+ if self.rgb_mode:
934
+ normalize_kwargs.update({'amplification': 255., 'clip': True})
935
+
936
+ chan = []
937
+ indices = self.img_num_channels[self.channels[np.where(self.channel_names == target_ch_name)][0]]
938
+ for t in tqdm(range(len(indices)), desc='frame'):
939
+ if self.rgb_mode:
940
+ f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=True,
941
+ normalize_kwargs=normalize_kwargs)
942
+ f = f.astype(np.uint8)
943
+ else:
944
+ f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=False)
945
+
946
+ chan.append(f[:, :, 0])
947
+
948
+ self.stack.append(chan)
949
+
950
+ self.stack = np.array(self.stack)
951
+ if self.rgb_mode:
952
+ self.stack = np.moveaxis(self.stack, 0, -1)
953
+ else:
954
+ self.stack = self.stack[0]
955
+ if self.log_option:
956
+ self.stack[np.where(self.stack > 0.)] = np.log(self.stack[np.where(self.stack > 0.)])
957
+
958
+ print(f'Load stack of shape: {self.stack.shape}.')
959
+
960
+ def closeEvent(self, event):
961
+
962
+ self.stop()
963
+ # result = QMessageBox.question(self,
964
+ # "Confirm Exit...",
965
+ # "Are you sure you want to exit ?",
966
+ # QMessageBox.Yes| QMessageBox.No,
967
+ # )
968
+ del self.stack
969
+ gc.collect()
970
+
971
+ def looped_animation(self):
972
+
973
+ """
970
974
  Load an image.
971
975
 
972
976
  """
973
977
 
974
- self.framedata = 0
975
-
976
- self.fig, self.ax = plt.subplots(tight_layout=True)
977
- self.fcanvas = FigureCanvas(self.fig, interactive=True)
978
- self.ax.clear()
979
-
980
- self.im = self.ax.imshow(self.stack[0], cmap='gray')
981
- self.status_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker="x",
982
- c=self.colors[0][:, 1], s=50, picker=True, pickradius=100)
983
- self.class_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker='o',
984
- facecolors='none', edgecolors=self.colors[0][:, 0], s=200)
985
-
986
- self.ax.set_xticks([])
987
- self.ax.set_yticks([])
988
- self.ax.set_aspect('equal')
989
-
990
- self.fig.set_facecolor('none') # or 'None'
991
- self.fig.canvas.setStyleSheet("background-color: black;")
992
-
993
- self.anim = FuncAnimation(
994
- self.fig,
995
- self.draw_frame,
996
- frames=self.len_movie, # better would be to cast np.arange(len(movie)) in case frame column is incomplete
997
- interval=self.anim_interval, # in ms
998
- blit=True,
999
- )
1000
-
1001
- self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
1002
- self.fcanvas.canvas.draw()
1003
-
1004
- def create_cell_signal_canvas(self):
1005
-
1006
- self.cell_fig, self.cell_ax = plt.subplots()
1007
- self.cell_fcanvas = FigureCanvas(self.cell_fig, interactive=False)
1008
- self.cell_ax.clear()
1009
-
1010
- spacing = 0.5
1011
- minorLocator = MultipleLocator(1)
1012
- self.cell_ax.xaxis.set_minor_locator(minorLocator)
1013
- self.cell_ax.xaxis.set_major_locator(MultipleLocator(5))
1014
- self.cell_ax.grid(which='major')
1015
- self.cell_ax.set_xlabel("time [frame]")
1016
- self.cell_ax.set_ylabel("signal")
1017
-
1018
- self.cell_fig.set_facecolor('none') # or 'None'
1019
- self.cell_fig.canvas.setStyleSheet("background-color: transparent;")
1020
-
1021
- self.lines = [
1022
- self.cell_ax.plot([np.linspace(0, self.len_movie - 1, self.len_movie)], [np.zeros((self.len_movie))])[0] for
1023
- i in range(len(self.signal_choice_cb))]
1024
- for i in range(len(self.lines)):
1025
- self.lines[i].set_label(f'signal {i}')
1026
-
1027
- min_val, max_val = self.cell_ax.get_ylim()
1028
- self.line_dt, = self.cell_ax.plot([-1, -1], [min_val, max_val], c="k", linestyle="--")
1029
-
1030
- self.cell_ax.set_xlim(0, self.len_movie)
1031
- self.cell_ax.legend()
1032
- self.cell_fcanvas.canvas.draw()
1033
-
1034
- self.plot_signals()
1035
-
1036
- def on_scatter_pick(self, event):
1037
-
1038
- ind = event.ind
1039
-
1040
- if len(ind) > 1:
1041
- # More than one point in vicinity
1042
- datax, datay = [self.positions[self.framedata][i, 0] for i in ind], [self.positions[self.framedata][i, 1]
1043
- for i in ind]
1044
- msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
1045
- dist = np.sqrt((np.array(datax) - msx) ** 2 + (np.array(datay) - msy) ** 2)
1046
- ind = [ind[np.argmin(dist)]]
1047
-
1048
- if len(ind) > 0 and (len(self.selection) == 0):
1049
- ind = ind[0]
1050
- self.selection.append(ind)
1051
- self.correct_btn.setEnabled(True)
1052
- self.cancel_btn.setEnabled(True)
1053
- self.del_shortcut.setEnabled(True)
1054
- self.no_event_shortcut.setEnabled(True)
1055
-
1056
- self.track_of_interest = self.tracks[self.framedata][ind]
1057
- print(f'You selected track {self.track_of_interest}.')
1058
- self.give_cell_information()
1059
- self.plot_signals()
1060
-
1061
- self.loc_t = []
1062
- self.loc_idx = []
1063
- for t in range(len(self.tracks)):
1064
- indices = np.where(self.tracks[t] == self.track_of_interest)[0]
1065
- if len(indices) > 0:
1066
- self.loc_t.append(t)
1067
- self.loc_idx.append(indices[0])
1068
-
1069
- self.previous_color = []
1070
- for t, idx in zip(self.loc_t, self.loc_idx):
1071
- self.previous_color.append(self.colors[t][idx].copy())
1072
- self.colors[t][idx] = 'lime'
1073
-
1074
- elif len(ind) > 0 and len(self.selection) == 1:
1075
- self.cancel_btn.click()
1076
- else:
1077
- pass
1078
-
1079
- def shortcut_suppr(self):
1080
- self.correct_btn.click()
1081
- self.suppr_btn.click()
1082
- self.correct_btn.click()
1083
-
1084
- def shortcut_no_event(self):
1085
- self.correct_btn.click()
1086
- self.no_event_btn.click()
1087
- self.correct_btn.click()
1088
-
1089
- def configure_ylims(self):
1090
-
1091
- try:
1092
- min_values = []
1093
- max_values = []
1094
- for i in range(len(self.signal_choice_cb)):
1095
- signal = self.signal_choice_cb[i].currentText()
1096
- if signal == '--':
1097
- continue
1098
- else:
1099
- maxx = np.nanpercentile(self.df_tracks.loc[:, signal].to_numpy().flatten(), 99)
1100
- minn = np.nanpercentile(self.df_tracks.loc[:, signal].to_numpy().flatten(), 1)
1101
- min_values.append(minn)
1102
- max_values.append(maxx)
1103
-
1104
- if len(min_values) > 0:
1105
- self.cell_ax.set_ylim(np.amin(min_values), np.amax(max_values))
1106
- except Exception as e:
1107
- print(e)
1108
-
1109
- def draw_frame(self, framedata):
1110
-
1111
- """
978
+ self.framedata = 0
979
+
980
+ self.fig, self.ax = plt.subplots(tight_layout=True)
981
+ self.fcanvas = FigureCanvas(self.fig, interactive=True)
982
+ self.ax.clear()
983
+
984
+ self.im = self.ax.imshow(self.stack[0], cmap='gray', interpolation='none')
985
+ self.status_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker="x",
986
+ c=self.colors[0][:, 1], s=50, picker=True, pickradius=100)
987
+ self.class_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker='o',
988
+ facecolors='none', edgecolors=self.colors[0][:, 0], s=200)
989
+
990
+
991
+ self.ax.set_xticks([])
992
+ self.ax.set_yticks([])
993
+ self.ax.set_aspect('equal')
994
+
995
+ self.fig.set_facecolor('none') # or 'None'
996
+ self.fig.canvas.setStyleSheet("background-color: black;")
997
+
998
+ self.anim = FuncAnimation(
999
+ self.fig,
1000
+ self.draw_frame,
1001
+ frames=self.len_movie, # better would be to cast np.arange(len(movie)) in case frame column is incomplete
1002
+ interval=self.anim_interval, # in ms
1003
+ blit=True,
1004
+ )
1005
+
1006
+ self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
1007
+ self.fcanvas.canvas.draw()
1008
+
1009
+ def create_cell_signal_canvas(self):
1010
+
1011
+ self.cell_fig, self.cell_ax = plt.subplots()
1012
+ self.cell_fcanvas = FigureCanvas(self.cell_fig, interactive=False)
1013
+ self.cell_ax.clear()
1014
+
1015
+ spacing = 0.5
1016
+ minorLocator = MultipleLocator(1)
1017
+ self.cell_ax.xaxis.set_minor_locator(minorLocator)
1018
+ self.cell_ax.xaxis.set_major_locator(MultipleLocator(5))
1019
+ self.cell_ax.grid(which='major')
1020
+ self.cell_ax.set_xlabel("time [frame]")
1021
+ self.cell_ax.set_ylabel("signal")
1022
+
1023
+ self.cell_fig.set_facecolor('none') # or 'None'
1024
+ self.cell_fig.canvas.setStyleSheet("background-color: transparent;")
1025
+
1026
+ self.lines = [
1027
+ self.cell_ax.plot([np.linspace(0, self.len_movie - 1, self.len_movie)], [np.zeros((self.len_movie))])[0] for
1028
+ i in range(len(self.signal_choice_cb))]
1029
+ for i in range(len(self.lines)):
1030
+ self.lines[i].set_label(f'signal {i}')
1031
+
1032
+ min_val, max_val = self.cell_ax.get_ylim()
1033
+ self.line_dt, = self.cell_ax.plot([-1, -1], [min_val, max_val], c="k", linestyle="--")
1034
+
1035
+ self.cell_ax.set_xlim(0, self.len_movie)
1036
+ self.cell_ax.legend()
1037
+ self.cell_fcanvas.canvas.draw()
1038
+
1039
+ self.plot_signals()
1040
+
1041
+ def on_scatter_pick(self, event):
1042
+
1043
+ ind = event.ind
1044
+
1045
+ if len(ind) > 1:
1046
+ # More than one point in vicinity
1047
+ datax, datay = [self.positions[self.framedata][i, 0] for i in ind], [self.positions[self.framedata][i, 1]
1048
+ for i in ind]
1049
+ msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
1050
+ dist = np.sqrt((np.array(datax) - msx) ** 2 + (np.array(datay) - msy) ** 2)
1051
+ ind = [ind[np.argmin(dist)]]
1052
+
1053
+ if len(ind) > 0 and (len(self.selection) == 0):
1054
+ ind = ind[0]
1055
+ self.selection.append(ind)
1056
+ self.correct_btn.setEnabled(True)
1057
+ self.cancel_btn.setEnabled(True)
1058
+ self.del_shortcut.setEnabled(True)
1059
+ self.no_event_shortcut.setEnabled(True)
1060
+
1061
+ self.track_of_interest = self.tracks[self.framedata][ind]
1062
+ print(f'You selected track {self.track_of_interest}.')
1063
+ self.give_cell_information()
1064
+ self.plot_signals()
1065
+
1066
+ self.loc_t = []
1067
+ self.loc_idx = []
1068
+ for t in range(len(self.tracks)):
1069
+ indices = np.where(self.tracks[t] == self.track_of_interest)[0]
1070
+ if len(indices) > 0:
1071
+ self.loc_t.append(t)
1072
+ self.loc_idx.append(indices[0])
1073
+
1074
+ self.previous_color = []
1075
+ for t, idx in zip(self.loc_t, self.loc_idx):
1076
+ self.previous_color.append(self.colors[t][idx].copy())
1077
+ self.colors[t][idx] = 'lime'
1078
+
1079
+ elif len(ind) > 0 and len(self.selection) == 1:
1080
+ self.cancel_btn.click()
1081
+ else:
1082
+ pass
1083
+
1084
+ def shortcut_suppr(self):
1085
+ self.correct_btn.click()
1086
+ self.suppr_btn.click()
1087
+ self.correct_btn.click()
1088
+
1089
+ def shortcut_no_event(self):
1090
+ self.correct_btn.click()
1091
+ self.no_event_btn.click()
1092
+ self.correct_btn.click()
1093
+
1094
+ def configure_ylims(self):
1095
+
1096
+ try:
1097
+ min_values = []
1098
+ max_values = []
1099
+ for i in range(len(self.signal_choice_cb)):
1100
+ signal = self.signal_choice_cb[i].currentText()
1101
+ if signal == '--':
1102
+ continue
1103
+ else:
1104
+ maxx = np.nanpercentile(self.df_tracks.loc[:, signal].to_numpy().flatten(), 99)
1105
+ minn = np.nanpercentile(self.df_tracks.loc[:, signal].to_numpy().flatten(), 1)
1106
+ min_values.append(minn)
1107
+ max_values.append(maxx)
1108
+
1109
+ if len(min_values) > 0:
1110
+ self.cell_ax.set_ylim(np.amin(min_values), np.amax(max_values))
1111
+ except Exception as e:
1112
+ print(e)
1113
+
1114
+ def draw_frame(self, framedata):
1115
+
1116
+ """
1112
1117
  Update plot elements at each timestep of the loop.
1113
1118
  """
1114
1119
 
1115
- self.framedata = framedata
1116
- self.frame_lbl.setText(f'frame: {self.framedata}')
1117
- self.im.set_array(self.stack[self.framedata])
1118
- self.status_scatter.set_offsets(self.positions[self.framedata])
1119
- self.status_scatter.set_color(self.colors[self.framedata][:, 1])
1120
+ self.framedata = framedata
1121
+ self.frame_lbl.setText(f'frame: {self.framedata}')
1122
+ self.im.set_array(self.stack[self.framedata])
1123
+ self.status_scatter.set_offsets(self.positions[self.framedata])
1124
+ self.status_scatter.set_color(self.colors[self.framedata][:, 1])
1120
1125
 
1121
- self.class_scatter.set_offsets(self.positions[self.framedata])
1122
- self.class_scatter.set_edgecolor(self.colors[self.framedata][:, 0])
1126
+ self.class_scatter.set_offsets(self.positions[self.framedata])
1127
+ self.class_scatter.set_edgecolor(self.colors[self.framedata][:, 0])
1123
1128
 
1124
- return (self.im, self.status_scatter, self.class_scatter,)
1129
+ return (self.im, self.status_scatter, self.class_scatter,)
1125
1130
 
1126
- def stop(self):
1127
- # # On stop we disconnect all of our events.
1128
- self.stop_btn.hide()
1129
- self.start_btn.show()
1130
- self.anim.pause()
1131
- self.stop_btn.clicked.connect(self.start)
1131
+ def stop(self):
1132
+ # # On stop we disconnect all of our events.
1133
+ self.stop_btn.hide()
1134
+ self.start_btn.show()
1135
+ self.anim.pause()
1136
+ self.stop_btn.clicked.connect(self.start)
1132
1137
 
1133
- def start(self):
1134
- '''
1138
+ def start(self):
1139
+ '''
1135
1140
  Starts interactive animation. Adds the draw frame command to the GUI
1136
1141
  handler, calls show to start the event loop.
1137
1142
  '''
1138
- self.start_btn.setShortcut(QKeySequence(""))
1143
+ self.start_btn.setShortcut(QKeySequence(""))
1139
1144
 
1140
- self.last_frame_btn.setEnabled(True)
1141
- self.last_frame_btn.clicked.connect(self.set_last_frame)
1145
+ self.last_frame_btn.setEnabled(True)
1146
+ self.last_frame_btn.clicked.connect(self.set_last_frame)
1142
1147
 
1143
- self.first_frame_btn.setEnabled(True)
1144
- self.first_frame_btn.clicked.connect(self.set_first_frame)
1148
+ self.first_frame_btn.setEnabled(True)
1149
+ self.first_frame_btn.clicked.connect(self.set_first_frame)
1145
1150
 
1146
- self.start_btn.hide()
1147
- self.stop_btn.show()
1151
+ self.start_btn.hide()
1152
+ self.stop_btn.show()
1148
1153
 
1149
- self.anim.event_source.start()
1150
- self.stop_btn.clicked.connect(self.stop)
1154
+ self.anim.event_source.start()
1155
+ self.stop_btn.clicked.connect(self.stop)
1151
1156
 
1152
- def give_cell_information(self):
1157
+ def give_cell_information(self):
1153
1158
 
1154
- cell_selected = f"cell: {self.track_of_interest}\n"
1155
- cell_class = f"class: {self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.class_name].to_numpy()[0]}\n"
1156
- cell_time = f"time of interest: {self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.time_name].to_numpy()[0]}\n"
1157
- self.cell_info.setText(cell_selected + cell_class + cell_time)
1159
+ cell_selected = f"cell: {self.track_of_interest}\n"
1160
+ cell_class = f"class: {self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.class_name].to_numpy()[0]}\n"
1161
+ cell_time = f"time of interest: {self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.time_name].to_numpy()[0]}\n"
1162
+ self.cell_info.setText(cell_selected + cell_class + cell_time)
1158
1163
 
1159
- def save_trajectories(self):
1164
+ def save_trajectories(self):
1160
1165
 
1161
- if self.normalized_signals:
1162
- self.normalize_features_btn.click()
1163
- if self.selection:
1164
- self.cancel_selection()
1166
+ if self.normalized_signals:
1167
+ self.normalize_features_btn.click()
1168
+ if self.selection:
1169
+ self.cancel_selection()
1165
1170
 
1166
- self.df_tracks = self.df_tracks.drop(self.df_tracks[self.df_tracks[self.class_name] > 2].index)
1167
- self.df_tracks.to_csv(self.trajectories_path, index=False)
1168
- print('table saved.')
1169
- self.extract_scatter_from_trajectories()
1171
+ self.df_tracks = self.df_tracks.drop(self.df_tracks[self.df_tracks[self.class_name] > 2].index)
1172
+ self.df_tracks.to_csv(self.trajectories_path, index=False)
1173
+ print('table saved.')
1174
+ self.extract_scatter_from_trajectories()
1170
1175
 
1171
- # self.give_cell_information()
1176
+ # self.give_cell_information()
1172
1177
 
1173
- # def interval_slider_action(self):
1174
-
1175
- # print(dir(self.anim.event_source))
1176
-
1177
- # self.anim.event_source.interval = self.interval_slider.value()
1178
- # self.anim.event_source._timer_set_interval()
1178
+ # def interval_slider_action(self):
1179
+
1180
+ # print(dir(self.anim.event_source))
1181
+
1182
+ # self.anim.event_source.interval = self.interval_slider.value()
1183
+ # self.anim.event_source._timer_set_interval()
1179
1184
 
1180
- def set_last_frame(self):
1185
+ def set_last_frame(self):
1181
1186
 
1182
- self.last_frame_btn.setEnabled(False)
1183
- self.last_frame_btn.disconnect()
1187
+ self.last_frame_btn.setEnabled(False)
1188
+ self.last_frame_btn.disconnect()
1184
1189
 
1185
- self.last_key = len(self.stack) - 1
1186
- while len(np.where(self.stack[self.last_key].flatten() == 0)[0]) > 0.99 * len(
1187
- self.stack[self.last_key].flatten()):
1188
- self.last_key -= 1
1189
- print(f'Last frame is {len(self.stack) - 1}; last not black is {self.last_key}')
1190
- self.anim._drawn_artists = self.draw_frame(self.last_key)
1191
- self.anim._drawn_artists = sorted(self.anim._drawn_artists, key=lambda x: x.get_zorder())
1192
- for a in self.anim._drawn_artists:
1193
- a.set_visible(True)
1190
+ self.last_key = len(self.stack) - 1
1191
+ while len(np.where(self.stack[self.last_key].flatten() == 0)[0]) > 0.99 * len(
1192
+ self.stack[self.last_key].flatten()):
1193
+ self.last_key -= 1
1194
+ print(f'Last frame is {len(self.stack) - 1}; last not black is {self.last_key}')
1195
+ self.anim._drawn_artists = self.draw_frame(self.last_key)
1196
+ self.anim._drawn_artists = sorted(self.anim._drawn_artists, key=lambda x: x.get_zorder())
1197
+ for a in self.anim._drawn_artists:
1198
+ a.set_visible(True)
1194
1199
 
1195
- self.fig.canvas.draw()
1196
- self.anim.event_source.stop()
1200
+ self.fig.canvas.draw()
1201
+ self.anim.event_source.stop()
1197
1202
 
1198
- # self.cell_plot.draw()
1199
- self.stop_btn.hide()
1200
- self.start_btn.show()
1201
- self.stop_btn.clicked.connect(self.start)
1202
- self.start_btn.setShortcut(QKeySequence("l"))
1203
+ # self.cell_plot.draw()
1204
+ self.stop_btn.hide()
1205
+ self.start_btn.show()
1206
+ self.stop_btn.clicked.connect(self.start)
1207
+ self.start_btn.setShortcut(QKeySequence("l"))
1203
1208
 
1204
- def set_first_frame(self):
1209
+ def set_first_frame(self):
1205
1210
 
1206
- self.first_frame_btn.setEnabled(False)
1207
- self.first_frame_btn.disconnect()
1211
+ self.first_frame_btn.setEnabled(False)
1212
+ self.first_frame_btn.disconnect()
1208
1213
 
1209
- self.first_key = 0
1210
- print(f'First frame is {0}')
1211
- self.anim._drawn_artists = self.draw_frame(0)
1212
- self.anim._drawn_artists = sorted(self.anim._drawn_artists, key=lambda x: x.get_zorder())
1213
- for a in self.anim._drawn_artists:
1214
- a.set_visible(True)
1214
+ self.first_key = 0
1215
+ print(f'First frame is {0}')
1216
+ self.anim._drawn_artists = self.draw_frame(0)
1217
+ self.anim._drawn_artists = sorted(self.anim._drawn_artists, key=lambda x: x.get_zorder())
1218
+ for a in self.anim._drawn_artists:
1219
+ a.set_visible(True)
1215
1220
 
1216
- self.fig.canvas.draw()
1217
- self.anim.event_source.stop()
1221
+ self.fig.canvas.draw()
1222
+ self.anim.event_source.stop()
1218
1223
 
1219
- # self.cell_plot.draw()
1220
- self.stop_btn.hide()
1221
- self.start_btn.show()
1222
- self.stop_btn.clicked.connect(self.start)
1223
- self.start_btn.setShortcut(QKeySequence("f"))
1224
+ # self.cell_plot.draw()
1225
+ self.stop_btn.hide()
1226
+ self.start_btn.show()
1227
+ self.stop_btn.clicked.connect(self.start)
1228
+ self.start_btn.setShortcut(QKeySequence("f"))
1224
1229
 
1225
- def export_signals(self):
1230
+ def export_signals(self):
1226
1231
 
1227
- auto_dataset_name = self.pos.split(os.sep)[-4] + '_' + self.pos.split(os.sep)[-2] + '.npy'
1232
+ auto_dataset_name = self.pos.split(os.sep)[-4] + '_' + self.pos.split(os.sep)[-2] + '.npy'
1228
1233
 
1229
- if self.normalized_signals:
1230
- self.normalize_features_btn.click()
1234
+ if self.normalized_signals:
1235
+ self.normalize_features_btn.click()
1231
1236
 
1232
- training_set = []
1233
- cols = self.df_tracks.columns
1234
- tracks = np.unique(self.df_tracks["TRACK_ID"].to_numpy())
1237
+ training_set = []
1238
+ cols = self.df_tracks.columns
1239
+ tracks = np.unique(self.df_tracks["TRACK_ID"].to_numpy())
1235
1240
 
1236
- for track in tracks:
1237
- # Add all signals at given track
1238
- signals = {}
1239
- for c in cols:
1240
- signals.update({c: self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, c].to_numpy()})
1241
- time_of_interest = self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, self.time_name].to_numpy()[0]
1242
- cclass = self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, self.class_name].to_numpy()[0]
1243
- signals.update({"time_of_interest": time_of_interest, "class": cclass})
1244
- # Here auto add all available channels
1245
- training_set.append(signals)
1246
-
1247
- print(training_set)
1248
-
1249
- pathsave = QFileDialog.getSaveFileName(self, "Select file name", self.exp_dir + auto_dataset_name, ".npy")[0]
1250
- if pathsave != '':
1251
- if not pathsave.endswith(".npy"):
1252
- pathsave += ".npy"
1253
- try:
1254
- np.save(pathsave, training_set)
1255
- print(f'File successfully written in {pathsave}.')
1256
- except Exception as e:
1257
- print(f"Error {e}...")
1258
-
1259
- def normalize_features(self):
1260
-
1261
- x = self.df_tracks[self.columns_to_rescale].values
1262
-
1263
- if not self.normalized_signals:
1264
- x = self.MinMaxScaler.transform(x)
1265
- self.df_tracks[self.columns_to_rescale] = x
1266
- self.plot_signals()
1267
- self.normalized_signals = True
1268
- self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="#1565c0"))
1269
- self.normalize_features_btn.setIconSize(QSize(25, 25))
1270
- else:
1271
- x = self.MinMaxScaler.inverse_transform(x)
1272
- self.df_tracks[self.columns_to_rescale] = x
1273
- self.plot_signals()
1274
- self.normalized_signals = False
1275
- self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
1276
- self.normalize_features_btn.setIconSize(QSize(25, 25))
1277
-
1278
- def switch_to_log(self):
1279
-
1280
- """
1241
+ for track in tracks:
1242
+ # Add all signals at given track
1243
+ signals = {}
1244
+ for c in cols:
1245
+ signals.update({c: self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, c].to_numpy()})
1246
+ time_of_interest = self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, self.time_name].to_numpy()[0]
1247
+ cclass = self.df_tracks.loc[self.df_tracks["TRACK_ID"] == track, self.class_name].to_numpy()[0]
1248
+ signals.update({"time_of_interest": time_of_interest, "class": cclass})
1249
+ # Here auto add all available channels
1250
+ training_set.append(signals)
1251
+
1252
+ print(training_set)
1253
+
1254
+ pathsave = QFileDialog.getSaveFileName(self, "Select file name", self.exp_dir + auto_dataset_name, ".npy")[0]
1255
+ if pathsave != '':
1256
+ if not pathsave.endswith(".npy"):
1257
+ pathsave += ".npy"
1258
+ try:
1259
+ np.save(pathsave, training_set)
1260
+ print(f'File successfully written in {pathsave}.')
1261
+ except Exception as e:
1262
+ print(f"Error {e}...")
1263
+
1264
+ def normalize_features(self):
1265
+
1266
+ x = self.df_tracks[self.columns_to_rescale].values
1267
+
1268
+ if not self.normalized_signals:
1269
+ x = self.MinMaxScaler.transform(x)
1270
+ self.df_tracks[self.columns_to_rescale] = x
1271
+ self.plot_signals()
1272
+ self.normalized_signals = True
1273
+ self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="#1565c0"))
1274
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
1275
+ else:
1276
+ x = self.MinMaxScaler.inverse_transform(x)
1277
+ self.df_tracks[self.columns_to_rescale] = x
1278
+ self.plot_signals()
1279
+ self.normalized_signals = False
1280
+ self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
1281
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
1282
+
1283
+ def switch_to_log(self):
1284
+
1285
+ """
1281
1286
  Better would be to create a log(quantity) and plot it...
1282
1287
  """
1283
1288
 
1284
- try:
1285
- if self.cell_ax.get_yscale() == 'linear':
1286
- self.cell_ax.set_yscale('log')
1287
- self.log_btn.setIcon(icon(MDI6.math_log, color="#1565c0"))
1288
- else:
1289
- self.cell_ax.set_yscale('linear')
1290
- self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
1291
- except Exception as e:
1292
- print(e)
1289
+ try:
1290
+ if self.cell_ax.get_yscale() == 'linear':
1291
+ self.cell_ax.set_yscale('log')
1292
+ self.log_btn.setIcon(icon(MDI6.math_log, color="#1565c0"))
1293
+ else:
1294
+ self.cell_ax.set_yscale('linear')
1295
+ self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
1296
+ except Exception as e:
1297
+ print(e)
1293
1298
 
1294
- # self.cell_ax.autoscale()
1295
- self.cell_fcanvas.canvas.draw_idle()
1299
+ # self.cell_ax.autoscale()
1300
+ self.cell_fcanvas.canvas.draw_idle()
1296
1301
 
1297
1302
 
1298
1303
  class MeasureAnnotator(SignalAnnotator):
1299
- def __init__(self, parent=None):
1300
- QMainWindow.__init__(self)
1301
- self.parent = parent
1302
- self.setWindowTitle("Signal annotator")
1303
- self.mode = self.parent.mode
1304
- self.pos = self.parent.parent.pos
1305
- self.exp_dir = self.parent.exp_dir
1306
- self.n_signals = 3
1307
- self.soft_path = get_software_location()
1308
- self.recently_modified = False
1309
- self.selection = []
1310
- self.int_validator = QIntValidator()
1311
- if self.mode == "targets":
1312
- self.instructions_path = self.exp_dir + "configs/signal_annotator_config_targets.json"
1313
- self.trajectories_path = self.pos + 'output/tables/trajectories_targets.csv'
1314
- elif self.mode == "effectors":
1315
- self.instructions_path = self.exp_dir + "configs/signal_annotator_config_effectors.json"
1316
- self.trajectories_path = self.pos + 'output/tables/trajectories_effectors.csv'
1317
-
1318
- self.screen_height = self.parent.parent.parent.screen_height
1319
- self.screen_width = self.parent.parent.parent.screen_width
1320
- self.current_frame = 0
1321
- self.show_fliers = False
1322
- self.status_name = 'group'
1323
-
1324
- center_window(self)
1325
-
1326
- self.locate_stack()
1327
- self.current_channel = 0
1328
-
1329
- self.locate_tracks()
1330
-
1331
- self.generate_signal_choices()
1332
- self.frame_lbl = QLabel('position: ')
1333
- self.static_image()
1334
- self.create_cell_signal_canvas()
1335
-
1336
- self.populate_widget()
1337
- self.changed_class()
1338
-
1339
- self.setMinimumWidth(int(0.8 * self.screen_width))
1340
- # self.setMaximumHeight(int(0.8*self.screen_height))
1341
- self.setMinimumHeight(int(0.8 * self.screen_height))
1342
- # self.setMaximumHeight(int(0.8*self.screen_height))
1343
-
1344
- self.setAttribute(Qt.WA_DeleteOnClose)
1345
- self.previous_index = None
1346
-
1347
- def static_image(self):
1348
-
1349
- """
1350
- Load an image.
1351
-
1352
- """
1353
-
1354
- self.framedata = 0
1355
-
1356
- self.fig, self.ax = plt.subplots(tight_layout=True)
1357
- self.fcanvas = FigureCanvas(self.fig, interactive=True)
1358
- self.ax.clear()
1359
- # print(self.current_stack.shape)
1360
- self.im = self.ax.imshow(self.img, cmap='gray')
1361
- self.status_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker="o",
1362
- facecolors='none', edgecolors=self.colors[0][:, 0], s=200, picker=True)
1363
- self.ax.set_xticks([])
1364
- self.ax.set_yticks([])
1365
- self.ax.set_aspect('equal')
1366
-
1367
- self.fig.set_facecolor('none') # or 'None'
1368
- self.fig.canvas.setStyleSheet("background-color: black;")
1369
-
1370
- self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
1371
- self.fcanvas.canvas.draw()
1372
-
1373
- def create_cell_signal_canvas(self):
1374
-
1375
- self.cell_fig, self.cell_ax = plt.subplots()
1376
- self.cell_fcanvas = FigureCanvas(self.cell_fig, interactive=False)
1377
- self.cell_ax.clear()
1378
-
1379
- spacing = 0.5
1380
- minorLocator = MultipleLocator(1)
1381
- self.cell_ax.xaxis.set_minor_locator(minorLocator)
1382
- self.cell_ax.xaxis.set_major_locator(MultipleLocator(5))
1383
- self.cell_ax.grid(which='major')
1384
- self.cell_ax.set_xlabel("time [frame]")
1385
- self.cell_ax.set_ylabel("signal")
1386
-
1387
- self.cell_fig.set_facecolor('none') # or 'None'
1388
- self.cell_fig.canvas.setStyleSheet("background-color: transparent;")
1389
-
1390
- self.lines = [
1391
- self.cell_ax.plot([np.linspace(0, self.len_movie - 1, self.len_movie)], [np.zeros(self.len_movie)])[0] for
1392
- i in range(len(self.signal_choice_cb))]
1393
- for i in range(len(self.lines)):
1394
- self.lines[i].set_label(f'signal {i}')
1395
-
1396
- min_val, max_val = self.cell_ax.get_ylim()
1397
- self.line_dt, = self.cell_ax.plot([-1, -1], [min_val, max_val], c="k", linestyle="--")
1398
-
1399
- self.cell_ax.set_xlim(0, self.len_movie)
1400
- self.cell_fcanvas.canvas.draw()
1401
-
1402
- self.plot_signals()
1403
-
1404
- def plot_signals(self):
1405
- try:
1406
- current_frame = self.current_frame # Assuming you have a variable for the current frame
1407
- yvalues = []
1408
- all_yvalues = []
1409
- current_yvalues = []
1410
- all_median_values = []
1411
- labels = []
1412
- for i in range(len(self.signal_choice_cb)):
1413
- signal_choice = self.signal_choice_cb[i].currentText()
1414
-
1415
- if signal_choice != "--":
1416
- if 'TRACK_ID' in self.df_tracks.columns:
1417
- ydata = self.df_tracks.loc[
1418
- (self.df_tracks['TRACK_ID'] == self.track_of_interest) &
1419
- (self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
1420
- else:
1421
- ydata = self.df_tracks.loc[
1422
- (self.df_tracks['ID'] == self.track_of_interest), signal_choice].to_numpy()
1423
- all_ydata = self.df_tracks.loc[:, signal_choice].to_numpy()
1424
- ydata = ydata[ydata == ydata] # remove nan
1425
- current_ydata = self.df_tracks.loc[
1426
- (self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
1427
- current_ydata = current_ydata[current_ydata == current_ydata]
1428
- all_ydata = all_ydata[all_ydata == all_ydata]
1429
- yvalues.extend(ydata)
1430
- current_yvalues.append(current_ydata)
1431
- all_yvalues.append(all_ydata)
1432
- labels.append(signal_choice)
1433
-
1434
- self.cell_ax.clear()
1435
-
1436
- if len(yvalues) > 0:
1437
- self.cell_ax.boxplot(all_yvalues, showfliers=self.show_fliers)
1438
- ylim = self.cell_ax.get_ylim()
1439
- self.cell_ax.set_ylim(ylim)
1440
- x_pos = np.arange(len(all_yvalues)) + 1
1441
-
1442
- for index, feature in enumerate(current_yvalues):
1443
- x_values_strip = (index + 1) + np.random.normal(0, 0.04, size=len(
1444
- feature))
1445
- self.cell_ax.plot(x_values_strip, feature, marker='o', linestyle='None', color=tab10.colors[0],
1446
- alpha=0.1)
1447
- self.cell_ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3], alpha=1)
1448
-
1449
-
1450
- else:
1451
- self.cell_ax.text(0.5, 0.5, "No data available", horizontalalignment='center',
1452
- verticalalignment='center', transform=self.cell_ax.transAxes)
1453
-
1454
- self.cell_fcanvas.canvas.draw()
1455
-
1456
- except Exception as e:
1457
- print(f"{e=}")
1458
-
1459
- def configure_ylims(self):
1460
-
1461
- try:
1462
- min_values = []
1463
- max_values = []
1464
- for i in range(len(self.signal_choice_cb)):
1465
- signal = self.signal_choice_cb[i].currentText()
1466
- if signal == '--':
1467
- continue
1468
- else:
1469
- maxx = np.max(self.df_tracks.loc[:, signal].to_numpy().flatten())
1470
- minn = np.min(self.df_tracks.loc[:, signal].to_numpy().flatten())
1471
- min_values.append(minn)
1472
- max_values.append(maxx)
1473
-
1474
- if len(min_values) > 0:
1475
- self.cell_ax.set_ylim(np.amin(min_values), np.amax(max_values))
1476
- except Exception as e:
1477
- print(e)
1478
-
1479
- def plot_red_points(self, ax):
1480
- yvalues = []
1481
- current_frame = self.current_frame
1482
- for i in range(len(self.signal_choice_cb)):
1483
- signal_choice = self.signal_choice_cb[i].currentText()
1484
- if signal_choice != "--":
1485
- print(f'plot signal {signal_choice} for cell {self.track_of_interest} at frame {current_frame}')
1486
- if 'TRACK_ID' in self.df_tracks.columns:
1487
- ydata = self.df_tracks.loc[
1488
- (self.df_tracks['TRACK_ID'] == self.track_of_interest) &
1489
- (self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
1490
- else:
1491
- ydata = self.df_tracks.loc[
1492
- (self.df_tracks['ID'] == self.track_of_interest) &
1493
- (self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
1494
- ydata = ydata[ydata == ydata] # remove nan
1495
- yvalues.extend(ydata)
1496
- x_pos = np.arange(len(yvalues)) + 1
1497
- ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3],
1498
- alpha=1) # Plot red points representing cells
1499
- self.cell_fcanvas.canvas.draw()
1500
-
1501
- def on_scatter_pick(self, event):
1502
- ind = event.ind
1503
- if len(ind) > 1:
1504
- # More than one point in vicinity
1505
- datax, datay = [self.positions[self.framedata][i, 0] for i in ind], [self.positions[self.framedata][i, 1]
1506
- for i in ind]
1507
- msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
1508
- dist = np.sqrt((np.array(datax) - msx) ** 2 + (np.array(datay) - msy) ** 2)
1509
- ind = [ind[np.argmin(dist)]]
1510
-
1511
- if len(ind) > 0 and (len(self.selection) == 0):
1512
- ind = ind[0]
1513
- self.selection.append(ind)
1514
- self.correct_btn.setEnabled(True)
1515
- self.cancel_btn.setEnabled(True)
1516
- self.del_shortcut.setEnabled(True)
1517
- self.no_event_shortcut.setEnabled(True)
1518
- self.track_of_interest = self.tracks[self.framedata][ind]
1519
- print(f'You selected track {self.track_of_interest}.')
1520
- self.give_cell_information()
1521
- if len(self.cell_ax.lines) > 0:
1522
- self.cell_ax.lines[-1].remove() # Remove the last line (red points) from the plot
1523
- self.plot_red_points(self.cell_ax)
1524
- else:
1525
- self.plot_signals()
1526
-
1527
- self.loc_t = []
1528
- self.loc_idx = []
1529
- for t in range(len(self.tracks)):
1530
- indices = np.where(self.tracks[t] == self.track_of_interest)[0]
1531
- if len(indices) > 0:
1532
- self.loc_t.append(t)
1533
- self.loc_idx.append(indices[0])
1534
-
1535
- self.previous_color = []
1536
- for t, idx in zip(self.loc_t, self.loc_idx):
1537
- self.previous_color.append(self.colors[t][idx].copy())
1538
- self.colors[t][idx] = 'lime'
1539
-
1540
- elif len(ind) > 0 and len(self.selection) == 1:
1541
- self.cancel_btn.click()
1542
- else:
1543
- pass
1544
- self.draw_frame(self.current_frame)
1545
- self.fcanvas.canvas.draw()
1546
-
1547
- def populate_widget(self):
1548
-
1549
- """
1550
- Create the multibox design.
1551
-
1552
- """
1553
-
1554
- self.button_widget = QWidget()
1555
- main_layout = QHBoxLayout()
1556
- self.button_widget.setLayout(main_layout)
1557
-
1558
- main_layout.setContentsMargins(30, 30, 30, 30)
1559
- self.left_panel = QVBoxLayout()
1560
- self.left_panel.setContentsMargins(30, 30, 30, 30)
1561
- self.left_panel.setSpacing(10)
1562
-
1563
- self.right_panel = QVBoxLayout()
1564
-
1565
- class_hbox = QHBoxLayout()
1566
- class_hbox.addWidget(QLabel('characteristic \n group: '), 25)
1567
- self.class_choice_cb = QComboBox()
1568
-
1569
- cols = np.array(self.df_tracks.columns)
1570
- self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
1571
- self.class_cols = list(cols[self.class_cols])
1572
- try:
1573
- self.class_cols.remove('group_id')
1574
- except Exception:
1575
- pass
1576
- try:
1577
- self.class_cols.remove('group_color')
1578
- except Exception:
1579
- pass
1580
-
1581
- self.class_choice_cb.addItems(self.class_cols)
1582
- self.class_choice_cb.currentIndexChanged.connect(self.changed_class)
1583
- class_hbox.addWidget(self.class_choice_cb, 70)
1584
-
1585
- self.add_class_btn = QPushButton('')
1586
- self.add_class_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1587
- self.add_class_btn.setIcon(icon(MDI6.plus, color="black"))
1588
- self.add_class_btn.setToolTip("Add a new characteristic group")
1589
- self.add_class_btn.setIconSize(QSize(20, 20))
1590
- self.add_class_btn.clicked.connect(self.create_new_event_class)
1591
- class_hbox.addWidget(self.add_class_btn, 5)
1592
-
1593
- self.del_class_btn = QPushButton('')
1594
- self.del_class_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1595
- self.del_class_btn.setIcon(icon(MDI6.delete, color="black"))
1596
- self.del_class_btn.setToolTip("Delete a characteristic group")
1597
- self.del_class_btn.setIconSize(QSize(20, 20))
1598
- self.del_class_btn.clicked.connect(self.del_event_class)
1599
- class_hbox.addWidget(self.del_class_btn, 5)
1600
-
1601
- self.left_panel.addLayout(class_hbox)
1602
-
1603
- self.cell_info = QLabel('')
1604
- self.left_panel.addWidget(self.cell_info)
1605
-
1606
- time_option_hbox = QHBoxLayout()
1607
- time_option_hbox.setContentsMargins(100, 30, 100, 30)
1608
- self.time_of_interest_label = QLabel('phenotype: ')
1609
- time_option_hbox.addWidget(self.time_of_interest_label, 30)
1610
- self.time_of_interest_le = QLineEdit()
1611
- self.time_of_interest_le.setValidator(self.int_validator)
1612
- time_option_hbox.addWidget(self.time_of_interest_le)
1613
- self.del_cell_btn = QPushButton('')
1614
- self.del_cell_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1615
- self.del_cell_btn.setIcon(icon(MDI6.delete, color="black"))
1616
- self.del_cell_btn.setToolTip("Delete cell")
1617
- self.del_cell_btn.setIconSize(QSize(20, 20))
1618
- self.del_cell_btn.clicked.connect(self.del_cell)
1619
- time_option_hbox.addWidget(self.del_cell_btn)
1620
- self.left_panel.addLayout(time_option_hbox)
1621
-
1622
- main_action_hbox = QHBoxLayout()
1623
- self.correct_btn = QPushButton('correct')
1624
- self.correct_btn.setIcon(icon(MDI6.redo_variant, color="white"))
1625
- self.correct_btn.setIconSize(QSize(20, 20))
1626
- self.correct_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet)
1627
- self.correct_btn.clicked.connect(self.show_annotation_buttons)
1628
- self.correct_btn.setEnabled(False)
1629
- main_action_hbox.addWidget(self.correct_btn)
1630
-
1631
- self.cancel_btn = QPushButton('cancel')
1632
- self.cancel_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet_2)
1633
- self.cancel_btn.setShortcut(QKeySequence("Esc"))
1634
- self.cancel_btn.setEnabled(False)
1635
- self.cancel_btn.clicked.connect(self.cancel_selection)
1636
- main_action_hbox.addWidget(self.cancel_btn)
1637
- self.left_panel.addLayout(main_action_hbox)
1638
-
1639
- self.annotation_btns_to_hide = [self.time_of_interest_label,
1640
- self.time_of_interest_le,
1641
- self.del_cell_btn]
1642
- self.hide_annotation_buttons()
1643
- #### End of annotation buttons
1644
-
1645
- self.del_shortcut = QShortcut(Qt.Key_Delete, self) # QKeySequence("s")
1646
- self.del_shortcut.activated.connect(self.shortcut_suppr)
1647
- self.del_shortcut.setEnabled(False)
1648
-
1649
- self.no_event_shortcut = QShortcut(QKeySequence("n"), self) # QKeySequence("s")
1650
- self.no_event_shortcut.activated.connect(self.shortcut_no_event)
1651
- self.no_event_shortcut.setEnabled(False)
1652
-
1653
- # Cell signals
1654
- self.left_panel.addWidget(self.cell_fcanvas)
1655
-
1656
- plot_buttons_hbox = QHBoxLayout()
1657
- plot_buttons_hbox.setContentsMargins(0, 0, 0, 0)
1658
- self.outliers_check = QCheckBox('Show outliers')
1659
- self.outliers_check.toggled.connect(self.show_outliers)
1660
-
1661
- self.normalize_features_btn = QPushButton('')
1662
- self.normalize_features_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1663
- self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
1664
- self.normalize_features_btn.setIconSize(QSize(25, 25))
1665
- self.normalize_features_btn.setFixedSize(QSize(30, 30))
1666
- # self.normalize_features_btn.setShortcut(QKeySequence('n'))
1667
- self.normalize_features_btn.clicked.connect(self.normalize_features)
1668
-
1669
- plot_buttons_hbox.addWidget(QLabel(''), 90)
1670
- plot_buttons_hbox.addWidget(self.outliers_check)
1671
- plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
1672
- self.normalized_signals = False
1673
-
1674
- self.log_btn = QPushButton()
1675
- self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
1676
- self.log_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1677
- self.log_btn.clicked.connect(self.switch_to_log)
1678
- plot_buttons_hbox.addWidget(self.log_btn, 5)
1679
-
1680
- self.left_panel.addLayout(plot_buttons_hbox)
1681
-
1682
- signal_choice_vbox = QVBoxLayout()
1683
- signal_choice_vbox.setContentsMargins(30, 0, 30, 50)
1684
- for i in range(len(self.signal_choice_cb)):
1685
- hlayout = QHBoxLayout()
1686
- hlayout.addWidget(self.signal_choice_label[i], 20)
1687
- hlayout.addWidget(self.signal_choice_cb[i], 75)
1688
- # hlayout.addWidget(self.log_btns[i], 5)
1689
- signal_choice_vbox.addLayout(hlayout)
1690
-
1691
- # self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
1692
- # self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
1693
- # self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
1694
-
1695
- self.left_panel.addLayout(signal_choice_vbox)
1696
-
1697
- btn_hbox = QHBoxLayout()
1698
- self.save_btn = QPushButton('Save')
1699
- self.save_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet)
1700
- self.save_btn.clicked.connect(self.save_trajectories)
1701
- btn_hbox.addWidget(self.save_btn, 90)
1702
- self.left_panel.addLayout(btn_hbox)
1703
-
1704
- # Animation
1705
- animation_buttons_box = QHBoxLayout()
1706
-
1707
- animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
1708
-
1709
- self.first_frame_btn = QPushButton()
1710
- self.first_frame_btn.clicked.connect(self.set_previous_frame)
1711
- self.first_frame_btn.setShortcut(QKeySequence('f'))
1712
- self.first_frame_btn.setIcon(icon(MDI6.page_first, color="black"))
1713
- self.first_frame_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1714
- self.first_frame_btn.setFixedSize(QSize(60, 60))
1715
- self.first_frame_btn.setIconSize(QSize(30, 30))
1716
-
1717
- self.last_frame_btn = QPushButton()
1718
- self.last_frame_btn.clicked.connect(self.set_next_frame)
1719
- self.last_frame_btn.setShortcut(QKeySequence('l'))
1720
- self.last_frame_btn.setIcon(icon(MDI6.page_last, color="black"))
1721
- self.last_frame_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1722
- self.last_frame_btn.setFixedSize(QSize(60, 60))
1723
- self.last_frame_btn.setIconSize(QSize(30, 30))
1724
-
1725
- self.frame_slider = QSlider(Qt.Horizontal)
1726
- self.frame_slider.setFixedSize(200, 30)
1727
- self.frame_slider.setRange(0, self.len_movie - 1)
1728
- self.frame_slider.setValue(0)
1729
- self.frame_slider.valueChanged.connect(self.update_frame)
1730
-
1731
- self.start_btn = QPushButton()
1732
- self.start_btn.clicked.connect(self.start)
1733
- self.start_btn.setIcon(icon(MDI6.play, color="black"))
1734
- self.start_btn.setFixedSize(QSize(60, 60))
1735
- self.start_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
1736
- self.start_btn.setIconSize(QSize(30, 30))
1737
- self.start_btn.hide()
1738
-
1739
- animation_buttons_box.addWidget(self.first_frame_btn, 5, alignment=Qt.AlignRight)
1740
- animation_buttons_box.addWidget(self.frame_slider, 5, alignment=Qt.AlignCenter)
1741
- animation_buttons_box.addWidget(self.last_frame_btn, 5, alignment=Qt.AlignLeft)
1742
-
1743
- self.right_panel.addLayout(animation_buttons_box, 5)
1744
-
1745
- self.right_panel.addWidget(self.fcanvas, 90)
1746
-
1747
- contrast_hbox = QHBoxLayout()
1748
- contrast_hbox.setContentsMargins(150, 5, 150, 5)
1749
- self.contrast_slider = QLabeledDoubleRangeSlider()
1750
-
1751
- # self.contrast_slider.setSingleStep(0.001)
1752
- # self.contrast_slider.setTickInterval(0.001)
1753
- self.contrast_slider.setOrientation(1)
1754
- print('range: ',
1755
- [np.nanpercentile(self.img.flatten(), 0.001), np.nanpercentile(self.img.flatten(), 99.999)])
1756
- self.contrast_slider.setRange(
1757
- *[np.nanpercentile(self.img.flatten(), 0.001), np.nanpercentile(self.img.flatten(), 99.999)])
1758
- self.contrast_slider.setValue(
1759
- [np.percentile(self.img.flatten(), 1), np.percentile(self.img.flatten(), 99.99)])
1760
- self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
1761
- contrast_hbox.addWidget(QLabel('contrast: '))
1762
- contrast_hbox.addWidget(self.contrast_slider, 90)
1763
- self.right_panel.addLayout(contrast_hbox, 5)
1764
-
1765
- channel_hbox = QHBoxLayout()
1766
- self.choose_channel = QComboBox()
1767
- self.choose_channel.addItems(self.channel_names)
1768
- self.choose_channel.currentIndexChanged.connect(self.changed_channel)
1769
- channel_hbox.addWidget(self.choose_channel)
1770
- self.right_panel.addLayout(channel_hbox, 5)
1771
-
1772
- main_layout.addLayout(self.left_panel, 35)
1773
- main_layout.addLayout(self.right_panel, 65)
1774
- self.button_widget.adjustSize()
1775
-
1776
- self.setCentralWidget(self.button_widget)
1777
- self.show()
1778
-
1779
- QApplication.processEvents()
1780
-
1781
- def closeEvent(self, event):
1782
- # result = QMessageBox.question(self,
1783
- # "Confirm Exit...",
1784
- # "Are you sure you want to exit ?",
1785
- # QMessageBox.Yes| QMessageBox.No,
1786
- # )
1787
- # del self.img
1788
- gc.collect()
1789
-
1790
- def set_next_frame(self):
1791
-
1792
- self.current_frame = self.current_frame + 1
1793
- if self.current_frame > self.len_movie - 1:
1794
- self.current_frame == self.len_movie - 1
1795
- self.frame_slider.setValue(self.current_frame)
1796
- self.update_frame()
1797
- self.start_btn.setShortcut(QKeySequence("f"))
1798
-
1799
- def set_previous_frame(self):
1800
-
1801
- self.current_frame = self.current_frame - 1
1802
- if self.current_frame < 0:
1803
- self.current_frame == 0
1804
- self.frame_slider.setValue(self.current_frame)
1805
- self.update_frame()
1806
-
1807
- self.start_btn.setShortcut(QKeySequence("l"))
1808
-
1809
- def write_new_event_class(self):
1810
-
1811
- if self.class_name_le.text() == '':
1812
- self.target_class = 'group'
1813
- else:
1814
- self.target_class = 'group_' + self.class_name_le.text()
1815
-
1816
- if self.target_class in list(self.df_tracks.columns):
1817
- msgBox = QMessageBox()
1818
- msgBox.setIcon(QMessageBox.Warning)
1819
- msgBox.setText(
1820
- "This characteristic group name already exists. If you proceed,\nall annotated data will be rewritten. Do you wish to continue?")
1821
- msgBox.setWindowTitle("Warning")
1822
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1823
- returnValue = msgBox.exec()
1824
- if returnValue == QMessageBox.No:
1825
- return None
1826
- else:
1827
- pass
1828
- self.df_tracks.loc[:, self.target_class] = 0
1829
- self.class_choice_cb.clear()
1830
- cols = np.array(self.df_tracks.columns)
1831
- self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
1832
- self.class_cols = list(cols[self.class_cols])
1833
- self.class_cols.remove('group_color')
1834
- self.class_choice_cb.addItems(self.class_cols)
1835
- idx = self.class_choice_cb.findText(self.target_class)
1836
- self.status_name = self.target_class
1837
- self.class_choice_cb.setCurrentIndex(idx)
1838
- self.newClassWidget.close()
1839
-
1840
- def hide_annotation_buttons(self):
1841
-
1842
- for a in self.annotation_btns_to_hide:
1843
- a.hide()
1844
- self.time_of_interest_label.setEnabled(False)
1845
- self.time_of_interest_le.setText('')
1846
- self.time_of_interest_le.setEnabled(False)
1847
-
1848
- def show_annotation_buttons(self):
1849
-
1850
- for a in self.annotation_btns_to_hide:
1851
- a.show()
1852
-
1853
- self.time_of_interest_label.setEnabled(True)
1854
- self.time_of_interest_le.setEnabled(True)
1855
- self.correct_btn.setText('submit')
1856
-
1857
- self.correct_btn.disconnect()
1858
- self.correct_btn.clicked.connect(self.apply_modification)
1859
-
1860
- def give_cell_information(self):
1861
-
1862
- cell_selected = f"cell: {self.track_of_interest}\n"
1863
- if 'TRACK_ID' in self.df_tracks.columns:
1864
- cell_status = f"phenotype: {self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.status_name].to_numpy()[0]}\n"
1865
- else:
1866
- cell_status = f"phenotype: {self.df_tracks.loc[self.df_tracks['ID'] == self.track_of_interest, self.status_name].to_numpy()[0]}\n"
1867
- self.cell_info.setText(cell_selected + cell_status)
1868
-
1869
- def create_new_event_class(self):
1870
-
1871
- # display qwidget to name the event
1872
- self.newClassWidget = QWidget()
1873
- self.newClassWidget.setWindowTitle('Create new characteristic group')
1874
-
1875
- layout = QVBoxLayout()
1876
- self.newClassWidget.setLayout(layout)
1877
- name_hbox = QHBoxLayout()
1878
- name_hbox.addWidget(QLabel('group name: '), 25)
1879
- self.class_name_le = QLineEdit('group')
1880
- name_hbox.addWidget(self.class_name_le, 75)
1881
- layout.addLayout(name_hbox)
1882
-
1883
- btn_hbox = QHBoxLayout()
1884
- submit_btn = QPushButton('submit')
1885
- cancel_btn = QPushButton('cancel')
1886
- btn_hbox.addWidget(cancel_btn, 50)
1887
- btn_hbox.addWidget(submit_btn, 50)
1888
- layout.addLayout(btn_hbox)
1889
-
1890
- submit_btn.clicked.connect(self.write_new_event_class)
1891
- cancel_btn.clicked.connect(self.close_without_new_class)
1892
-
1893
- self.newClassWidget.show()
1894
- center_window(self.newClassWidget)
1895
-
1896
- def apply_modification(self):
1897
- if self.time_of_interest_le.text() != "":
1898
- status = int(self.time_of_interest_le.text())
1899
- else:
1900
- status = 0
1901
- if "TRACK_ID" in self.df_tracks.columns:
1902
- self.df_tracks.loc[(self.df_tracks['TRACK_ID'] == self.track_of_interest) & (
1903
- self.df_tracks['FRAME'] == self.current_frame), self.status_name] = status
1904
-
1905
- indices = self.df_tracks.index[(self.df_tracks['TRACK_ID'] == self.track_of_interest) & (
1906
- self.df_tracks['FRAME'] == self.current_frame)]
1907
- else:
1908
- self.df_tracks.loc[(self.df_tracks['ID'] == self.track_of_interest) & (
1909
- self.df_tracks['FRAME'] == self.current_frame), self.status_name] = status
1910
-
1911
- indices = self.df_tracks.index[(self.df_tracks['ID'] == self.track_of_interest) & (
1912
- self.df_tracks['FRAME'] == self.current_frame)]
1913
-
1914
- self.df_tracks.loc[indices, self.status_name] = status
1915
- all_states = self.df_tracks.loc[:, self.status_name].tolist()
1916
- all_states = np.array(all_states)
1917
- self.state_color_map = color_from_state(all_states, recently_modified=False)
1918
-
1919
- self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
1920
-
1921
- self.extract_scatter_from_trajectories()
1922
- self.give_cell_information()
1923
-
1924
- self.correct_btn.disconnect()
1925
- self.correct_btn.clicked.connect(self.show_annotation_buttons)
1926
-
1927
- self.hide_annotation_buttons()
1928
- self.correct_btn.setEnabled(False)
1929
- self.correct_btn.setText('correct')
1930
- self.cancel_btn.setEnabled(False)
1931
- self.del_shortcut.setEnabled(False)
1932
- self.no_event_shortcut.setEnabled(False)
1933
- if len(self.selection) > 0:
1934
- self.selection.pop(0)
1935
- self.draw_frame(self.current_frame)
1936
- self.fcanvas.canvas.draw()
1937
-
1938
- def assign_color_state(self, state):
1939
- if np.isnan(state):
1940
- pass
1941
- else:
1942
- return self.state_color_map[state]
1943
-
1944
- def draw_frame(self, framedata):
1945
-
1946
- """
1947
- Update plot elements at each timestep of the loop.
1948
- """
1949
-
1950
- self.framedata = framedata
1951
- self.frame_lbl.setText(f'position: {self.framedata}')
1952
- self.im.set_array(self.img)
1953
- self.status_scatter.set_offsets(self.positions[self.framedata])
1954
- self.status_scatter.set_edgecolors(self.colors[self.framedata][:, 0])
1955
-
1956
- return (self.im, self.status_scatter,)
1957
-
1958
- def compute_status_and_colors(self):
1959
- if self.class_choice_cb.currentText() == '':
1960
- self.status_name=self.target_class
1961
- else:
1962
- self.status_name = self.class_choice_cb.currentText()
1963
- print('selection and expected names: ', self.status_name)
1964
-
1965
- if self.status_name not in self.df_tracks.columns:
1966
- self.make_status_column()
1967
- else:
1968
- all_states = self.df_tracks.loc[:, self.status_name].tolist()
1969
- all_states = np.array(all_states)
1970
- self.state_color_map = color_from_state(all_states, recently_modified=False)
1971
- self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
1972
-
1973
- def del_event_class(self):
1974
-
1975
- msgBox = QMessageBox()
1976
- msgBox.setIcon(QMessageBox.Warning)
1977
- msgBox.setText(
1978
- f"You are about to delete characteristic group {self.class_choice_cb.currentText()}. Do you still want to proceed?")
1979
- msgBox.setWindowTitle("Warning")
1980
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1981
- returnValue = msgBox.exec()
1982
- if returnValue == QMessageBox.No:
1983
- return None
1984
- else:
1985
- class_to_delete = self.class_choice_cb.currentText()
1986
- cols_to_delete = [class_to_delete]
1987
- for c in cols_to_delete:
1988
- try:
1989
- self.df_tracks = self.df_tracks.drop([c], axis=1)
1990
- except Exception as e:
1991
- print(e)
1992
- item_idx = self.class_choice_cb.findText(class_to_delete)
1993
- self.class_choice_cb.removeItem(item_idx)
1994
-
1995
- def make_status_column(self):
1996
- if self.status_name == "state_firstdetection":
1997
- pass
1998
- else:
1999
- print('remaking the group column')
2000
- self.df_tracks.loc[:, self.status_name] = 0
2001
- all_states = self.df_tracks.loc[:, self.status_name].tolist()
2002
- all_states = np.array(all_states)
2003
- self.state_color_map = color_from_state(all_states, recently_modified=False)
2004
- self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
2005
-
2006
- def locate_tracks(self):
2007
-
2008
- """
2009
- Locate the tracks.
2010
- """
2011
-
2012
- if not os.path.exists(self.trajectories_path):
2013
-
2014
- msgBox = QMessageBox()
2015
- msgBox.setIcon(QMessageBox.Warning)
2016
- msgBox.setText("The trajectories cannot be detected.")
2017
- msgBox.setWindowTitle("Warning")
2018
- msgBox.setStandardButtons(QMessageBox.Ok)
2019
- returnValue = msgBox.exec()
2020
- if returnValue == QMessageBox.Yes:
2021
- self.close()
2022
- else:
2023
-
2024
- # Load and prep tracks
2025
- self.df_tracks = pd.read_csv(self.trajectories_path)
2026
- if 'TRACK_ID' in self.df_tracks.columns:
2027
- self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
2028
- else:
2029
- self.df_tracks = self.df_tracks.sort_values(by=['ID', 'FRAME'])
2030
-
2031
- cols = np.array(self.df_tracks.columns)
2032
- self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
2033
- self.class_cols = list(cols[self.class_cols])
2034
- try:
2035
- self.class_cols.remove('class_id')
2036
- except:
2037
- pass
2038
- try:
2039
- self.class_cols.remove('group_color')
2040
- except:
2041
- pass
2042
- if len(self.class_cols) > 0:
2043
- self.status = self.class_cols[0]
2044
-
2045
- else:
2046
-
2047
- self.status_name = 'group'
2048
-
2049
- if self.status_name not in self.df_tracks.columns:
2050
- # only create the status column if it does not exist to not erase static classification results
2051
- self.make_status_column()
2052
- else:
2053
- # all good, do nothing
2054
- pass
2055
- # else:
2056
- # if not self.status_name in self.df_tracks.columns:
2057
- # self.df_tracks[self.status_name] = 0
2058
- # self.df_tracks['state_color'] = color_from_status(0)
2059
- # self.df_tracks['class_color'] = color_from_class(1)
2060
-
2061
- # if not self.class_name in self.df_tracks.columns:
2062
- # self.df_tracks[self.class_name] = 1
2063
- # if not self.time_name in self.df_tracks.columns:
2064
- # self.df_tracks[self.time_name] = -1
2065
- all_states = self.df_tracks.loc[:, self.status_name].tolist()
2066
- all_states = np.array(all_states)
2067
- self.state_color_map = color_from_state(all_states, recently_modified=False)
2068
- self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
2069
- # self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
2070
- # self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
2071
-
2072
- self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
2073
- self.df_tracks['x_anim'] = self.df_tracks['POSITION_X']
2074
- self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y']
2075
- self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
2076
- self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
2077
-
2078
- self.extract_scatter_from_trajectories()
2079
- if 'TRACK_ID' in self.df_tracks.columns:
2080
- self.track_of_interest = self.df_tracks['TRACK_ID'].min()
2081
- else:
2082
- self.track_of_interest = self.df_tracks['ID'].min()
2083
-
2084
- self.loc_t = []
2085
- self.loc_idx = []
2086
- for t in range(len(self.tracks)):
2087
- indices = np.where(self.tracks[t] == self.track_of_interest)[0]
2088
- if len(indices) > 0:
2089
- self.loc_t.append(t)
2090
- self.loc_idx.append(indices[0])
2091
-
2092
- self.MinMaxScaler = MinMaxScaler()
2093
- self.columns_to_rescale = list(self.df_tracks.columns)
2094
-
2095
- # is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
2096
- # is_number_test = is_number(self.df_tracks.dtypes)
2097
- # self.columns_to_rescale = [col for t,col in zip(is_number_test,self.df_tracks.columns) if t]
2098
- # print(self.columns_to_rescale)
2099
-
2100
- cols_to_remove = ['group', 'group_color', 'status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME',
2101
- 'x_anim', 'y_anim', 't',
2102
- 'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
2103
- 'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
2104
- 'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent', 'ID'] + self.class_cols
2105
- cols = np.array(list(self.df_tracks.columns))
2106
- for tr in cols_to_remove:
2107
- try:
2108
- self.columns_to_rescale.remove(tr)
2109
- except:
2110
- pass
2111
- # print(f'column {tr} could not be found...')
2112
-
2113
- x = self.df_tracks[self.columns_to_rescale].values
2114
- self.MinMaxScaler.fit(x)
2115
-
2116
- def extract_scatter_from_trajectories(self):
2117
-
2118
- self.positions = []
2119
- self.colors = []
2120
- self.tracks = []
2121
-
2122
- for t in np.arange(self.len_movie):
2123
- self.positions.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['POSITION_X', 'POSITION_Y']].to_numpy())
2124
- self.colors.append(
2125
- self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['group_color']].to_numpy())
2126
- if 'TRACK_ID' in self.df_tracks.columns:
2127
- self.tracks.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, 'TRACK_ID'].to_numpy())
2128
- else:
2129
- self.tracks.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, 'ID'].to_numpy())
2130
-
2131
- def changed_class(self):
2132
- self.status_name = self.class_choice_cb.currentText()
2133
- self.compute_status_and_colors()
2134
- self.modify()
2135
- self.draw_frame(self.current_frame)
2136
- self.fcanvas.canvas.draw()
2137
-
2138
- def update_frame(self):
2139
- """
2140
- Update the displayed frame.
2141
- """
2142
- self.current_frame = self.frame_slider.value()
2143
- self.reload_frame()
2144
- if 'ID' in self.df_tracks.columns:
2145
- self.track_of_interest = self.df_tracks[self.df_tracks['FRAME'] == self.current_frame]['ID'].min()
2146
- self.modify()
2147
- self.draw_frame(self.current_frame)
2148
- self.fcanvas.canvas.draw()
2149
- self.plot_signals()
2150
-
2151
- # def load_annotator_config(self):
2152
- # self.rgb_mode = False
2153
- # self.log_option = False
2154
- # self.percentile_mode = True
2155
- # self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
2156
- # self.fraction = 0.5955056179775281
2157
- # #self.anim_interval = 1
2158
-
2159
- def prepare_stack(self):
2160
-
2161
- self.img_num_channels = _get_img_num_per_channel(self.channels, self.len_movie, self.nbr_channels)
2162
- self.current_stack = []
2163
- for ch in tqdm(self.target_channels, desc="channel"):
2164
- target_ch_name = ch[0]
2165
- if self.percentile_mode:
2166
- normalize_kwargs = {"percentiles": (ch[1], ch[2]), "values": None}
2167
- else:
2168
- normalize_kwargs = {"values": (ch[1], ch[2]), "percentiles": None}
2169
-
2170
- if self.rgb_mode:
2171
- normalize_kwargs.update({'amplification': 255., 'clip': True})
2172
-
2173
- chan = []
2174
- indices = self.img_num_channels[self.channels[np.where(self.channel_names == target_ch_name)][0]]
2175
- for t in tqdm(range(len(indices)), desc='frame'):
2176
- if self.rgb_mode:
2177
- f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=True,
2178
- normalize_kwargs=normalize_kwargs)
2179
- f = f.astype(np.uint8)
2180
- else:
2181
- f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=False)
2182
- chan.append(f[:, :, 0])
2183
-
2184
- self.current_stack.append(chan)
2185
-
2186
- self.current_stack = np.array(self.current_stack)
2187
- if self.rgb_mode:
2188
- self.current_stack = np.moveaxis(self.current_stack, 0, -1)
2189
- else:
2190
- self.current_stack = self.current_stack[0]
2191
- if self.log_option:
2192
- self.current_stack[np.where(self.current_stack > 0.)] = np.log(
2193
- self.current_stack[np.where(self.current_stack > 0.)])
2194
-
2195
- print(f'Load stack of shape: {self.current_stack.shape}.')
2196
-
2197
- def changed_channel(self):
2198
-
2199
- self.reload_frame()
2200
- self.contrast_slider.setRange(
2201
- *[np.nanpercentile(self.img.flatten(), 0.001),
2202
- np.nanpercentile(self.img.flatten(), 99.999)])
2203
- self.contrast_slider.setValue(
2204
- [np.percentile(self.img.flatten(), 1), np.percentile(self.img.flatten(), 99.99)])
2205
- self.draw_frame(self.current_frame)
2206
- self.fcanvas.canvas.draw()
2207
-
2208
- def save_trajectories(self):
2209
-
2210
- if self.normalized_signals:
2211
- self.normalize_features_btn.click()
2212
- if self.selection:
2213
- self.cancel_selection()
2214
- self.df_tracks = self.df_tracks.drop(self.df_tracks[self.df_tracks[self.status_name] == 99].index)
2215
- #color_column = str(self.status_name) + "_color"
2216
- try:
2217
- self.df_tracks.drop(columns='', inplace=True)
2218
- except:
2219
- pass
2220
- try:
2221
- self.df_tracks.drop(columns='group_color', inplace=True)
2222
- except:
2223
- pass
2224
- try:
2225
- self.df_tracks.drop(columns='x_anim', inplace=True)
2226
- except:
2227
- pass
2228
- try:
2229
- self.df_tracks.drop(columns='y_anim', inplace=True)
2230
- except:
2231
- pass
2232
-
2233
- self.df_tracks.to_csv(self.trajectories_path, index=False)
2234
- print('table saved.')
2235
- self.update_frame()
2236
-
2237
-
2238
- # self.extract_scatter_from_trajectories()
2239
-
2240
- def modify(self):
2241
- all_states = self.df_tracks.loc[:, self.status_name].tolist()
2242
- all_states = np.array(all_states)
2243
- self.state_color_map = color_from_state(all_states, recently_modified=False)
2244
-
2245
- self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
2246
-
2247
- self.extract_scatter_from_trajectories()
2248
- self.give_cell_information()
2249
-
2250
- self.correct_btn.disconnect()
2251
- self.correct_btn.clicked.connect(self.show_annotation_buttons)
2252
-
2253
- # self.hide_annotation_buttons()
2254
- # self.correct_btn.setEnabled(False)
2255
- # self.correct_btn.setText('correct')
2256
- # self.cancel_btn.setEnabled(False)
2257
- # self.del_shortcut.setEnabled(False)
2258
- # self.no_event_shortcut.setEnabled(False)
2259
-
2260
- def enable_time_of_interest(self):
2261
- if self.suppr_btn.isChecked():
2262
- self.time_of_interest_le.setEnabled(False)
2263
-
2264
- def cancel_selection(self):
2265
-
2266
- self.hide_annotation_buttons()
2267
- self.correct_btn.setEnabled(False)
2268
- self.correct_btn.setText('correct')
2269
- self.cancel_btn.setEnabled(False)
2270
-
2271
- try:
2272
- self.selection.pop(0)
2273
- except Exception as e:
2274
- print(e)
2275
-
2276
- try:
2277
- for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
2278
- # print(self.colors[t][idx, 1])
2279
- self.colors[t][idx, 0] = self.previous_color[k][0]
2280
- # self.colors[t][idx, 1] = self.previous_color[k][1]
2281
- except Exception as e:
2282
- print(f'{e=}')
2283
-
2284
- def locate_stack(self):
2285
-
2286
- """
2287
- Locate the target movie.
2288
-
2289
- """
2290
-
2291
- print("this is the loaded position: ", self.pos)
2292
- if isinstance(self.pos, str):
2293
- movies = glob(self.pos + f"movie/{self.parent.parent.movie_prefix}*.tif")
2294
-
2295
- else:
2296
- msgBox = QMessageBox()
2297
- msgBox.setIcon(QMessageBox.Warning)
2298
- msgBox.setText("Please select a unique position before launching the wizard...")
2299
- msgBox.setWindowTitle("Warning")
2300
- msgBox.setStandardButtons(QMessageBox.Ok)
2301
- returnValue = msgBox.exec()
2302
- if returnValue == QMessageBox.Ok:
2303
- self.img = None
2304
- self.close()
2305
- return None
2306
-
2307
- if len(movies) == 0:
2308
- msgBox = QMessageBox()
2309
- msgBox.setIcon(QMessageBox.Warning)
2310
- msgBox.setText("No movies are detected in the experiment folder. Cannot load an image to test Haralick.")
2311
- msgBox.setWindowTitle("Warning")
2312
- msgBox.setStandardButtons(QMessageBox.Ok)
2313
- returnValue = msgBox.exec()
2314
- if returnValue == QMessageBox.Yes:
2315
- self.close()
2316
- else:
2317
- self.stack_path = movies[0]
2318
- self.len_movie = self.parent.parent.len_movie
2319
- len_movie_auto = auto_load_number_of_frames(self.stack_path)
2320
- if len_movie_auto is not None:
2321
- self.len_movie = len_movie_auto
2322
- exp_config = self.exp_dir + "config.ini"
2323
- self.channel_names, self.channels = extract_experiment_channels(exp_config)
2324
- self.channel_names = np.array(self.channel_names)
2325
- self.channels = np.array(self.channels)
2326
- self.nbr_channels = len(self.channels)
2327
- self.current_channel = 0
2328
- self.img = load_frames(0, self.stack_path, normalize_input=False)
2329
- print(self.img.shape)
2330
- print(f'{self.stack_path} successfully located.')
2331
-
2332
- def reload_frame(self):
2333
-
2334
- """
2335
- Load the frame from the current channel and time choice. Show imshow, update histogram.
2336
- """
2337
-
2338
- # self.clear_post_threshold_options()
2339
-
2340
- self.current_channel = self.choose_channel.currentIndex()
2341
-
2342
- t = int(self.frame_slider.value())
2343
- idx = t * self.nbr_channels + self.current_channel
2344
- self.img = load_frames(idx, self.stack_path, normalize_input=False)
2345
- if self.img is not None:
2346
- self.refresh_imshow()
2347
- # self.redo_histogram()
2348
- else:
2349
- print('Frame could not be loaded...')
2350
-
2351
- def refresh_imshow(self):
2352
-
2353
- """
2354
-
2355
- Update the imshow based on the current frame selection.
2356
-
2357
- """
2358
-
2359
- self.vmin = np.nanpercentile(self.img.flatten(), 1)
2360
- self.vmax = np.nanpercentile(self.img.flatten(), 99.)
2361
-
2362
- self.contrast_slider.disconnect()
2363
- self.contrast_slider.setRange(np.amin(self.img), np.amax(self.img))
2364
- self.contrast_slider.setValue([self.vmin, self.vmax])
2365
- self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
2366
-
2367
- self.im.set_data(self.img)
2368
-
2369
- # self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
2370
- # self.fcanvas.canvas.draw_idle()
2371
-
2372
- def show_outliers(self):
2373
- if self.outliers_check.isChecked():
2374
- self.show_fliers = True
2375
- self.plot_signals()
2376
- else:
2377
- self.show_fliers = False
2378
- self.plot_signals()
2379
-
2380
- def del_cell(self):
2381
- self.time_of_interest_le.setEnabled(False)
2382
- self.time_of_interest_le.setText("99")
2383
- self.apply_modification()
2384
-
2385
- def shortcut_suppr(self):
2386
- self.correct_btn.click()
2387
- self.del_cell_btn.click()
2388
- self.correct_btn.click()
1304
+ def __init__(self, parent_window=None):
1305
+ QMainWindow.__init__(self)
1306
+ self.parent_window = parent_window
1307
+ self.setWindowTitle("Signal annotator")
1308
+ self.mode = self.parent_window.mode
1309
+ self.pos = self.parent_window.parent_window.pos
1310
+ self.exp_dir = self.parent_window.exp_dir
1311
+ self.n_signals = 3
1312
+ self.soft_path = get_software_location()
1313
+ self.recently_modified = False
1314
+ self.selection = []
1315
+ self.int_validator = QIntValidator()
1316
+ self.current_alpha=0.5
1317
+ if self.mode == "targets":
1318
+ self.instructions_path = self.exp_dir + "configs/signal_annotator_config_targets.json"
1319
+ self.trajectories_path = self.pos + 'output/tables/trajectories_targets.csv'
1320
+ elif self.mode == "effectors":
1321
+ self.instructions_path = self.exp_dir + "configs/signal_annotator_config_effectors.json"
1322
+ self.trajectories_path = self.pos + 'output/tables/trajectories_effectors.csv'
1323
+
1324
+ self.screen_height = self.parent_window.parent_window.parent_window.screen_height
1325
+ self.screen_width = self.parent_window.parent_window.parent_window.screen_width
1326
+ self.current_frame = 0
1327
+ self.show_fliers = False
1328
+ self.status_name = 'group'
1329
+
1330
+ center_window(self)
1331
+
1332
+ self.locate_stack()
1333
+ data, properties, graph, labels, _ = load_napari_data(self.pos, prefix=None, population=self.mode,return_stack=False)
1334
+ self.labels = relabel_segmentation(labels,data,properties)
1335
+ self.current_channel = 0
1336
+
1337
+ self.locate_tracks()
1338
+
1339
+ self.generate_signal_choices()
1340
+ self.frame_lbl = QLabel('position: ')
1341
+ self.static_image()
1342
+ self.create_cell_signal_canvas()
1343
+
1344
+ self.populate_widget()
1345
+ self.changed_class()
1346
+
1347
+ self.setMinimumWidth(int(0.8 * self.screen_width))
1348
+ # self.setMaximumHeight(int(0.8*self.screen_height))
1349
+ self.setMinimumHeight(int(0.8 * self.screen_height))
1350
+ # self.setMaximumHeight(int(0.8*self.screen_height))
1351
+
1352
+ self.setAttribute(Qt.WA_DeleteOnClose)
1353
+ self.previous_index = None
1354
+
1355
+ def static_image(self):
1356
+
1357
+ """
1358
+ Load an image.
1359
+
1360
+ """
1361
+
1362
+ self.framedata = 0
1363
+ self.current_label=self.labels[self.current_frame]
1364
+ self.fig, self.ax = plt.subplots(tight_layout=True)
1365
+ self.fcanvas = FigureCanvas(self.fig, interactive=True)
1366
+ self.ax.clear()
1367
+ # print(self.current_stack.shape)
1368
+ self.im = self.ax.imshow(self.img, cmap='gray')
1369
+ self.status_scatter = self.ax.scatter(self.positions[0][:, 0], self.positions[0][:, 1], marker="o",
1370
+ facecolors='none', edgecolors=self.colors[0][:, 0], s=200, picker=True)
1371
+ self.im_mask = self.ax.imshow(np.ma.masked_where(self.current_label == 0, self.current_label),
1372
+ cmap='viridis', interpolation='none',alpha=self.current_alpha,vmin=0,vmax=np.nanmax(self.labels.flatten()))
1373
+ self.ax.set_xticks([])
1374
+ self.ax.set_yticks([])
1375
+ self.ax.set_aspect('equal')
1376
+
1377
+ self.fig.set_facecolor('none') # or 'None'
1378
+ self.fig.canvas.setStyleSheet("background-color: black;")
1379
+
1380
+ self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
1381
+ self.fcanvas.canvas.draw()
1382
+
1383
+ def create_cell_signal_canvas(self):
1384
+
1385
+ self.cell_fig, self.cell_ax = plt.subplots()
1386
+ self.cell_fcanvas = FigureCanvas(self.cell_fig, interactive=False)
1387
+ self.cell_ax.clear()
1388
+
1389
+ spacing = 0.5
1390
+ minorLocator = MultipleLocator(1)
1391
+ self.cell_ax.xaxis.set_minor_locator(minorLocator)
1392
+ self.cell_ax.xaxis.set_major_locator(MultipleLocator(5))
1393
+ self.cell_ax.grid(which='major')
1394
+ self.cell_ax.set_xlabel("time [frame]")
1395
+ self.cell_ax.set_ylabel("signal")
1396
+
1397
+ self.cell_fig.set_facecolor('none') # or 'None'
1398
+ self.cell_fig.canvas.setStyleSheet("background-color: transparent;")
1399
+
1400
+ self.lines = [
1401
+ self.cell_ax.plot([np.linspace(0, self.len_movie - 1, self.len_movie)], [np.zeros(self.len_movie)])[0] for
1402
+ i in range(len(self.signal_choice_cb))]
1403
+ for i in range(len(self.lines)):
1404
+ self.lines[i].set_label(f'signal {i}')
1405
+
1406
+ min_val, max_val = self.cell_ax.get_ylim()
1407
+ self.line_dt, = self.cell_ax.plot([-1, -1], [min_val, max_val], c="k", linestyle="--")
1408
+
1409
+ self.cell_ax.set_xlim(0, self.len_movie)
1410
+ self.cell_fcanvas.canvas.draw()
1411
+
1412
+ self.plot_signals()
1413
+
1414
+ def plot_signals(self):
1415
+ try:
1416
+ current_frame = self.current_frame # Assuming you have a variable for the current frame
1417
+ yvalues = []
1418
+ all_yvalues = []
1419
+ current_yvalues = []
1420
+ all_median_values = []
1421
+ labels = []
1422
+ for i in range(len(self.signal_choice_cb)):
1423
+ signal_choice = self.signal_choice_cb[i].currentText()
1424
+
1425
+ if signal_choice != "--":
1426
+ if 'TRACK_ID' in self.df_tracks.columns:
1427
+ ydata = self.df_tracks.loc[
1428
+ (self.df_tracks['TRACK_ID'] == self.track_of_interest) &
1429
+ (self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
1430
+ else:
1431
+ ydata = self.df_tracks.loc[
1432
+ (self.df_tracks['ID'] == self.track_of_interest), signal_choice].to_numpy()
1433
+ all_ydata = self.df_tracks.loc[:, signal_choice].to_numpy()
1434
+ ydata = ydata[ydata == ydata] # remove nan
1435
+ current_ydata = self.df_tracks.loc[
1436
+ (self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
1437
+ current_ydata = current_ydata[current_ydata == current_ydata]
1438
+ all_ydata = all_ydata[all_ydata == all_ydata]
1439
+ yvalues.extend(ydata)
1440
+ current_yvalues.append(current_ydata)
1441
+ all_yvalues.append(all_ydata)
1442
+ labels.append(signal_choice)
1443
+
1444
+ self.cell_ax.clear()
1445
+
1446
+ if len(yvalues) > 0:
1447
+ self.cell_ax.boxplot(all_yvalues, showfliers=self.show_fliers)
1448
+ ylim = self.cell_ax.get_ylim()
1449
+ self.cell_ax.set_ylim(ylim)
1450
+ x_pos = np.arange(len(all_yvalues)) + 1
1451
+
1452
+ for index, feature in enumerate(current_yvalues):
1453
+ x_values_strip = (index + 1) + np.random.normal(0, 0.04, size=len(
1454
+ feature))
1455
+ self.cell_ax.plot(x_values_strip, feature, marker='o', linestyle='None', color=tab10.colors[0],
1456
+ alpha=0.1)
1457
+ self.cell_ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3], alpha=1)
1458
+
1459
+
1460
+ else:
1461
+ self.cell_ax.text(0.5, 0.5, "No data available", horizontalalignment='center',
1462
+ verticalalignment='center', transform=self.cell_ax.transAxes)
1463
+
1464
+ self.cell_fcanvas.canvas.draw()
1465
+
1466
+ except Exception as e:
1467
+ print(f"{e=}")
1468
+
1469
+ def configure_ylims(self):
1470
+
1471
+ try:
1472
+ min_values = []
1473
+ max_values = []
1474
+ for i in range(len(self.signal_choice_cb)):
1475
+ signal = self.signal_choice_cb[i].currentText()
1476
+ if signal == '--':
1477
+ continue
1478
+ else:
1479
+ maxx = np.max(self.df_tracks.loc[:, signal].to_numpy().flatten())
1480
+ minn = np.min(self.df_tracks.loc[:, signal].to_numpy().flatten())
1481
+ min_values.append(minn)
1482
+ max_values.append(maxx)
1483
+
1484
+ if len(min_values) > 0:
1485
+ self.cell_ax.set_ylim(np.amin(min_values), np.amax(max_values))
1486
+ except Exception as e:
1487
+ print(e)
1488
+
1489
+ def plot_red_points(self, ax):
1490
+ yvalues = []
1491
+ current_frame = self.current_frame
1492
+ for i in range(len(self.signal_choice_cb)):
1493
+ signal_choice = self.signal_choice_cb[i].currentText()
1494
+ if signal_choice != "--":
1495
+ print(f'plot signal {signal_choice} for cell {self.track_of_interest} at frame {current_frame}')
1496
+ if 'TRACK_ID' in self.df_tracks.columns:
1497
+ ydata = self.df_tracks.loc[
1498
+ (self.df_tracks['TRACK_ID'] == self.track_of_interest) &
1499
+ (self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
1500
+ else:
1501
+ ydata = self.df_tracks.loc[
1502
+ (self.df_tracks['ID'] == self.track_of_interest) &
1503
+ (self.df_tracks['FRAME'] == current_frame), signal_choice].to_numpy()
1504
+ ydata = ydata[ydata == ydata] # remove nan
1505
+ yvalues.extend(ydata)
1506
+ x_pos = np.arange(len(yvalues)) + 1
1507
+ ax.plot(x_pos, yvalues, marker='H', linestyle='None', color=tab10.colors[3],
1508
+ alpha=1) # Plot red points representing cells
1509
+ self.cell_fcanvas.canvas.draw()
1510
+
1511
+ def on_scatter_pick(self, event):
1512
+ ind = event.ind
1513
+ if len(ind) > 1:
1514
+ # More than one point in vicinity
1515
+ datax, datay = [self.positions[self.framedata][i, 0] for i in ind], [self.positions[self.framedata][i, 1]
1516
+ for i in ind]
1517
+ msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
1518
+ dist = np.sqrt((np.array(datax) - msx) ** 2 + (np.array(datay) - msy) ** 2)
1519
+ ind = [ind[np.argmin(dist)]]
1520
+
1521
+ if len(ind) > 0 and (len(self.selection) == 0):
1522
+ ind = ind[0]
1523
+ self.selection.append(ind)
1524
+ self.correct_btn.setEnabled(True)
1525
+ self.cancel_btn.setEnabled(True)
1526
+ self.del_shortcut.setEnabled(True)
1527
+ self.no_event_shortcut.setEnabled(True)
1528
+ self.track_of_interest = self.tracks[self.framedata][ind]
1529
+ print(f'You selected track {self.track_of_interest}.')
1530
+ self.give_cell_information()
1531
+ if len(self.cell_ax.lines) > 0:
1532
+ self.cell_ax.lines[-1].remove() # Remove the last line (red points) from the plot
1533
+ self.plot_red_points(self.cell_ax)
1534
+ else:
1535
+ self.plot_signals()
1536
+
1537
+ self.loc_t = []
1538
+ self.loc_idx = []
1539
+ for t in range(len(self.tracks)):
1540
+ indices = np.where(self.tracks[t] == self.track_of_interest)[0]
1541
+ if len(indices) > 0:
1542
+ self.loc_t.append(t)
1543
+ self.loc_idx.append(indices[0])
1544
+
1545
+ self.previous_color = []
1546
+ for t, idx in zip(self.loc_t, self.loc_idx):
1547
+ self.previous_color.append(self.colors[t][idx].copy())
1548
+ self.colors[t][idx] = 'lime'
1549
+
1550
+ elif len(ind) > 0 and len(self.selection) == 1:
1551
+ self.cancel_btn.click()
1552
+ else:
1553
+ pass
1554
+ self.draw_frame(self.current_frame)
1555
+ self.fcanvas.canvas.draw()
1556
+
1557
+ def populate_widget(self):
1558
+
1559
+ """
1560
+ Create the multibox design.
1561
+
1562
+ """
1563
+
1564
+ self.button_widget = QWidget()
1565
+ main_layout = QHBoxLayout()
1566
+ self.button_widget.setLayout(main_layout)
1567
+
1568
+ main_layout.setContentsMargins(30, 30, 30, 30)
1569
+ self.left_panel = QVBoxLayout()
1570
+ self.left_panel.setContentsMargins(30, 30, 30, 30)
1571
+ self.left_panel.setSpacing(10)
1572
+
1573
+ self.right_panel = QVBoxLayout()
1574
+
1575
+ class_hbox = QHBoxLayout()
1576
+ class_hbox.addWidget(QLabel('characteristic \n group: '), 25)
1577
+ self.class_choice_cb = QComboBox()
1578
+
1579
+ cols = np.array(self.df_tracks.columns)
1580
+ self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
1581
+ self.class_cols = list(cols[self.class_cols])
1582
+ try:
1583
+ self.class_cols.remove('group_id')
1584
+ except Exception:
1585
+ pass
1586
+ try:
1587
+ self.class_cols.remove('group_color')
1588
+ except Exception:
1589
+ pass
1590
+
1591
+ self.class_choice_cb.addItems(self.class_cols)
1592
+ self.class_choice_cb.currentIndexChanged.connect(self.changed_class)
1593
+ class_hbox.addWidget(self.class_choice_cb, 70)
1594
+
1595
+ self.add_class_btn = QPushButton('')
1596
+ self.add_class_btn.setStyleSheet(self.button_select_all)
1597
+ self.add_class_btn.setIcon(icon(MDI6.plus, color="black"))
1598
+ self.add_class_btn.setToolTip("Add a new characteristic group")
1599
+ self.add_class_btn.setIconSize(QSize(20, 20))
1600
+ self.add_class_btn.clicked.connect(self.create_new_event_class)
1601
+ class_hbox.addWidget(self.add_class_btn, 5)
1602
+
1603
+ self.del_class_btn = QPushButton('')
1604
+ self.del_class_btn.setStyleSheet(self.button_select_all)
1605
+ self.del_class_btn.setIcon(icon(MDI6.delete, color="black"))
1606
+ self.del_class_btn.setToolTip("Delete a characteristic group")
1607
+ self.del_class_btn.setIconSize(QSize(20, 20))
1608
+ self.del_class_btn.clicked.connect(self.del_event_class)
1609
+ class_hbox.addWidget(self.del_class_btn, 5)
1610
+
1611
+ self.left_panel.addLayout(class_hbox)
1612
+
1613
+ self.cell_info = QLabel('')
1614
+ self.left_panel.addWidget(self.cell_info)
1615
+
1616
+ time_option_hbox = QHBoxLayout()
1617
+ time_option_hbox.setContentsMargins(100, 30, 100, 30)
1618
+ self.time_of_interest_label = QLabel('phenotype: ')
1619
+ time_option_hbox.addWidget(self.time_of_interest_label, 30)
1620
+ self.time_of_interest_le = QLineEdit()
1621
+ self.time_of_interest_le.setValidator(self.int_validator)
1622
+ time_option_hbox.addWidget(self.time_of_interest_le)
1623
+ self.del_cell_btn = QPushButton('')
1624
+ self.del_cell_btn.setStyleSheet(self.button_select_all)
1625
+ self.del_cell_btn.setIcon(icon(MDI6.delete, color="black"))
1626
+ self.del_cell_btn.setToolTip("Delete cell")
1627
+ self.del_cell_btn.setIconSize(QSize(20, 20))
1628
+ self.del_cell_btn.clicked.connect(self.del_cell)
1629
+ time_option_hbox.addWidget(self.del_cell_btn)
1630
+ self.left_panel.addLayout(time_option_hbox)
1631
+
1632
+ main_action_hbox = QHBoxLayout()
1633
+ self.correct_btn = QPushButton('correct')
1634
+ self.correct_btn.setIcon(icon(MDI6.redo_variant, color="white"))
1635
+ self.correct_btn.setIconSize(QSize(20, 20))
1636
+ self.correct_btn.setStyleSheet(self.button_style_sheet)
1637
+ self.correct_btn.clicked.connect(self.show_annotation_buttons)
1638
+ self.correct_btn.setEnabled(False)
1639
+ main_action_hbox.addWidget(self.correct_btn)
1640
+
1641
+ self.cancel_btn = QPushButton('cancel')
1642
+ self.cancel_btn.setStyleSheet(self.button_style_sheet_2)
1643
+ self.cancel_btn.setShortcut(QKeySequence("Esc"))
1644
+ self.cancel_btn.setEnabled(False)
1645
+ self.cancel_btn.clicked.connect(self.cancel_selection)
1646
+ main_action_hbox.addWidget(self.cancel_btn)
1647
+ self.left_panel.addLayout(main_action_hbox)
1648
+
1649
+ self.annotation_btns_to_hide = [self.time_of_interest_label,
1650
+ self.time_of_interest_le,
1651
+ self.del_cell_btn]
1652
+ self.hide_annotation_buttons()
1653
+ #### End of annotation buttons
1654
+
1655
+ self.del_shortcut = QShortcut(Qt.Key_Delete, self) # QKeySequence("s")
1656
+ self.del_shortcut.activated.connect(self.shortcut_suppr)
1657
+ self.del_shortcut.setEnabled(False)
1658
+
1659
+ self.no_event_shortcut = QShortcut(QKeySequence("n"), self) # QKeySequence("s")
1660
+ self.no_event_shortcut.activated.connect(self.shortcut_no_event)
1661
+ self.no_event_shortcut.setEnabled(False)
1662
+
1663
+ # Cell signals
1664
+ self.left_panel.addWidget(self.cell_fcanvas)
1665
+
1666
+ plot_buttons_hbox = QHBoxLayout()
1667
+ plot_buttons_hbox.setContentsMargins(0, 0, 0, 0)
1668
+ self.outliers_check = QCheckBox('Show outliers')
1669
+ self.outliers_check.toggled.connect(self.show_outliers)
1670
+
1671
+ self.normalize_features_btn = QPushButton('')
1672
+ self.normalize_features_btn.setStyleSheet(self.button_select_all)
1673
+ self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
1674
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
1675
+ self.normalize_features_btn.setFixedSize(QSize(30, 30))
1676
+ # self.normalize_features_btn.setShortcut(QKeySequence('n'))
1677
+ self.normalize_features_btn.clicked.connect(self.normalize_features)
1678
+
1679
+ plot_buttons_hbox.addWidget(QLabel(''), 90)
1680
+ plot_buttons_hbox.addWidget(self.outliers_check)
1681
+ plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
1682
+ self.normalized_signals = False
1683
+
1684
+ self.log_btn = QPushButton()
1685
+ self.log_btn.setIcon(icon(MDI6.math_log, color="black"))
1686
+ self.log_btn.setStyleSheet(self.button_select_all)
1687
+ self.log_btn.clicked.connect(self.switch_to_log)
1688
+ plot_buttons_hbox.addWidget(self.log_btn, 5)
1689
+
1690
+ self.left_panel.addLayout(plot_buttons_hbox)
1691
+
1692
+ signal_choice_vbox = QVBoxLayout()
1693
+ signal_choice_vbox.setContentsMargins(30, 0, 30, 50)
1694
+ for i in range(len(self.signal_choice_cb)):
1695
+ hlayout = QHBoxLayout()
1696
+ hlayout.addWidget(self.signal_choice_label[i], 20)
1697
+ hlayout.addWidget(self.signal_choice_cb[i], 75)
1698
+ # hlayout.addWidget(self.log_btns[i], 5)
1699
+ signal_choice_vbox.addLayout(hlayout)
1700
+
1701
+ # self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
1702
+ # self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
1703
+ # self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
1704
+
1705
+ self.left_panel.addLayout(signal_choice_vbox)
1706
+
1707
+ btn_hbox = QHBoxLayout()
1708
+ self.save_btn = QPushButton('Save')
1709
+ self.save_btn.setStyleSheet(self.button_style_sheet)
1710
+ self.save_btn.clicked.connect(self.save_trajectories)
1711
+ btn_hbox.addWidget(self.save_btn, 90)
1712
+ self.left_panel.addLayout(btn_hbox)
1713
+
1714
+ # Animation
1715
+ animation_buttons_box = QHBoxLayout()
1716
+
1717
+ animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
1718
+
1719
+ self.first_frame_btn = QPushButton()
1720
+ self.first_frame_btn.clicked.connect(self.set_previous_frame)
1721
+ self.first_frame_btn.setShortcut(QKeySequence('f'))
1722
+ self.first_frame_btn.setIcon(icon(MDI6.page_first, color="black"))
1723
+ self.first_frame_btn.setStyleSheet(self.button_select_all)
1724
+ self.first_frame_btn.setFixedSize(QSize(60, 60))
1725
+ self.first_frame_btn.setIconSize(QSize(30, 30))
1726
+
1727
+ self.last_frame_btn = QPushButton()
1728
+ self.last_frame_btn.clicked.connect(self.set_next_frame)
1729
+ self.last_frame_btn.setShortcut(QKeySequence('l'))
1730
+ self.last_frame_btn.setIcon(icon(MDI6.page_last, color="black"))
1731
+ self.last_frame_btn.setStyleSheet(self.button_select_all)
1732
+ self.last_frame_btn.setFixedSize(QSize(60, 60))
1733
+ self.last_frame_btn.setIconSize(QSize(30, 30))
1734
+
1735
+ self.frame_slider = QSlider(Qt.Horizontal)
1736
+ self.frame_slider.setFixedSize(200, 30)
1737
+ self.frame_slider.setRange(0, self.len_movie - 1)
1738
+ self.frame_slider.setValue(0)
1739
+ self.frame_slider.valueChanged.connect(self.update_frame)
1740
+
1741
+ self.start_btn = QPushButton()
1742
+ self.start_btn.clicked.connect(self.start)
1743
+ self.start_btn.setIcon(icon(MDI6.play, color="black"))
1744
+ self.start_btn.setFixedSize(QSize(60, 60))
1745
+ self.start_btn.setStyleSheet(self.button_select_all)
1746
+ self.start_btn.setIconSize(QSize(30, 30))
1747
+ self.start_btn.hide()
1748
+
1749
+ animation_buttons_box.addWidget(self.first_frame_btn, 5, alignment=Qt.AlignRight)
1750
+ animation_buttons_box.addWidget(self.frame_slider, 5, alignment=Qt.AlignCenter)
1751
+ animation_buttons_box.addWidget(self.last_frame_btn, 5, alignment=Qt.AlignLeft)
1752
+
1753
+ self.right_panel.addLayout(animation_buttons_box, 5)
1754
+
1755
+ self.right_panel.addWidget(self.fcanvas, 90)
1756
+
1757
+ contrast_hbox = QHBoxLayout()
1758
+ contrast_hbox.setContentsMargins(150, 5, 150, 5)
1759
+ self.contrast_slider = QLabeledDoubleRangeSlider()
1760
+
1761
+ self.contrast_slider.setSingleStep(0.001)
1762
+ self.contrast_slider.setTickInterval(0.001)
1763
+ self.contrast_slider.setOrientation(1)
1764
+ print('range: ',
1765
+ [np.nanpercentile(self.img.flatten(), 0.001), np.nanpercentile(self.img.flatten(), 99.999)])
1766
+ self.contrast_slider.setRange(
1767
+ *[np.nanpercentile(self.img, 0.001), np.nanpercentile(self.img, 99.999)])
1768
+ self.contrast_slider.setValue(
1769
+ [np.nanpercentile(self.img, 1), np.nanpercentile(self.img, 99.99)])
1770
+ self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
1771
+ contrast_hbox.addWidget(QLabel('contrast: '))
1772
+ contrast_hbox.addWidget(self.contrast_slider, 90)
1773
+ self.right_panel.addLayout(contrast_hbox, 5)
1774
+ self.alpha_slider = QLabeledDoubleSlider()
1775
+ self.alpha_slider.setSingleStep(0.001)
1776
+ self.alpha_slider.setOrientation(1)
1777
+ self.alpha_slider.setRange(0, 1)
1778
+ self.alpha_slider.setValue(self.current_alpha)
1779
+ self.alpha_slider.setDecimals(3)
1780
+ self.alpha_slider.valueChanged.connect(self.set_transparency)
1781
+
1782
+ slider_alpha_hbox = QHBoxLayout()
1783
+ slider_alpha_hbox.setContentsMargins(150, 5, 150, 5)
1784
+ slider_alpha_hbox.addWidget(QLabel('transparency: '), 10)
1785
+ slider_alpha_hbox.addWidget(self.alpha_slider, 90)
1786
+ self.right_panel.addLayout(slider_alpha_hbox)
1787
+
1788
+ channel_hbox = QHBoxLayout()
1789
+ self.choose_channel = QComboBox()
1790
+ self.choose_channel.addItems(self.channel_names)
1791
+ self.choose_channel.currentIndexChanged.connect(self.changed_channel)
1792
+ channel_hbox.addWidget(self.choose_channel)
1793
+ self.right_panel.addLayout(channel_hbox, 5)
1794
+
1795
+ main_layout.addLayout(self.left_panel, 35)
1796
+ main_layout.addLayout(self.right_panel, 65)
1797
+ self.button_widget.adjustSize()
1798
+
1799
+ self.setCentralWidget(self.button_widget)
1800
+ self.show()
1801
+
1802
+ QApplication.processEvents()
1803
+
1804
+ def closeEvent(self, event):
1805
+ # result = QMessageBox.question(self,
1806
+ # "Confirm Exit...",
1807
+ # "Are you sure you want to exit ?",
1808
+ # QMessageBox.Yes| QMessageBox.No,
1809
+ # )
1810
+ # del self.img
1811
+ gc.collect()
1812
+
1813
+ def set_next_frame(self):
1814
+
1815
+ self.current_frame = self.current_frame + 1
1816
+ if self.current_frame > self.len_movie - 1:
1817
+ self.current_frame == self.len_movie - 1
1818
+ self.frame_slider.setValue(self.current_frame)
1819
+ self.update_frame()
1820
+ self.start_btn.setShortcut(QKeySequence("f"))
1821
+
1822
+ def set_previous_frame(self):
1823
+
1824
+ self.current_frame = self.current_frame - 1
1825
+ if self.current_frame < 0:
1826
+ self.current_frame == 0
1827
+ self.frame_slider.setValue(self.current_frame)
1828
+ self.update_frame()
1829
+
1830
+ self.start_btn.setShortcut(QKeySequence("l"))
1831
+
1832
+ def write_new_event_class(self):
1833
+
1834
+ if self.class_name_le.text() == '':
1835
+ self.target_class = 'group'
1836
+ else:
1837
+ self.target_class = 'group_' + self.class_name_le.text()
1838
+
1839
+ if self.target_class in list(self.df_tracks.columns):
1840
+ msgBox = QMessageBox()
1841
+ msgBox.setIcon(QMessageBox.Warning)
1842
+ msgBox.setText(
1843
+ "This characteristic group name already exists. If you proceed,\nall annotated data will be rewritten. Do you wish to continue?")
1844
+ msgBox.setWindowTitle("Warning")
1845
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1846
+ returnValue = msgBox.exec()
1847
+ if returnValue == QMessageBox.No:
1848
+ return None
1849
+ else:
1850
+ pass
1851
+ self.df_tracks.loc[:, self.target_class] = 0
1852
+ self.class_choice_cb.clear()
1853
+ cols = np.array(self.df_tracks.columns)
1854
+ self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
1855
+ self.class_cols = list(cols[self.class_cols])
1856
+ self.class_cols.remove('group_color')
1857
+ self.class_choice_cb.addItems(self.class_cols)
1858
+ idx = self.class_choice_cb.findText(self.target_class)
1859
+ self.status_name = self.target_class
1860
+ self.class_choice_cb.setCurrentIndex(idx)
1861
+ self.newClassWidget.close()
1862
+
1863
+ def hide_annotation_buttons(self):
1864
+
1865
+ for a in self.annotation_btns_to_hide:
1866
+ a.hide()
1867
+ self.time_of_interest_label.setEnabled(False)
1868
+ self.time_of_interest_le.setText('')
1869
+ self.time_of_interest_le.setEnabled(False)
1870
+
1871
+ def set_transparency(self):
1872
+ self.current_alpha = self.alpha_slider.value()
1873
+ self.im_mask.set_alpha(self.current_alpha)
1874
+ self.fcanvas.canvas.draw()
1875
+
1876
+ def show_annotation_buttons(self):
1877
+
1878
+ for a in self.annotation_btns_to_hide:
1879
+ a.show()
1880
+
1881
+ self.time_of_interest_label.setEnabled(True)
1882
+ self.time_of_interest_le.setEnabled(True)
1883
+ self.correct_btn.setText('submit')
1884
+
1885
+ self.correct_btn.disconnect()
1886
+ self.correct_btn.clicked.connect(self.apply_modification)
1887
+
1888
+ def give_cell_information(self):
1889
+
1890
+ cell_selected = f"cell: {self.track_of_interest}\n"
1891
+ if 'TRACK_ID' in self.df_tracks.columns:
1892
+ cell_status = f"phenotype: {self.df_tracks.loc[self.df_tracks['TRACK_ID'] == self.track_of_interest, self.status_name].to_numpy()[0]}\n"
1893
+ else:
1894
+ cell_status = f"phenotype: {self.df_tracks.loc[self.df_tracks['ID'] == self.track_of_interest, self.status_name].to_numpy()[0]}\n"
1895
+ self.cell_info.setText(cell_selected + cell_status)
1896
+
1897
+ def create_new_event_class(self):
1898
+
1899
+ # display qwidget to name the event
1900
+ self.newClassWidget = QWidget()
1901
+ self.newClassWidget.setWindowTitle('Create new characteristic group')
1902
+
1903
+ layout = QVBoxLayout()
1904
+ self.newClassWidget.setLayout(layout)
1905
+ name_hbox = QHBoxLayout()
1906
+ name_hbox.addWidget(QLabel('group name: '), 25)
1907
+ self.class_name_le = QLineEdit('group')
1908
+ name_hbox.addWidget(self.class_name_le, 75)
1909
+ layout.addLayout(name_hbox)
1910
+
1911
+ btn_hbox = QHBoxLayout()
1912
+ submit_btn = QPushButton('submit')
1913
+ cancel_btn = QPushButton('cancel')
1914
+ btn_hbox.addWidget(cancel_btn, 50)
1915
+ btn_hbox.addWidget(submit_btn, 50)
1916
+ layout.addLayout(btn_hbox)
1917
+
1918
+ submit_btn.clicked.connect(self.write_new_event_class)
1919
+ cancel_btn.clicked.connect(self.close_without_new_class)
1920
+
1921
+ self.newClassWidget.show()
1922
+ center_window(self.newClassWidget)
1923
+
1924
+ def apply_modification(self):
1925
+ if self.time_of_interest_le.text() != "":
1926
+ status = int(self.time_of_interest_le.text())
1927
+ else:
1928
+ status = 0
1929
+ if "TRACK_ID" in self.df_tracks.columns:
1930
+ self.df_tracks.loc[(self.df_tracks['TRACK_ID'] == self.track_of_interest) & (
1931
+ self.df_tracks['FRAME'] == self.current_frame), self.status_name] = status
1932
+
1933
+ indices = self.df_tracks.index[(self.df_tracks['TRACK_ID'] == self.track_of_interest) & (
1934
+ self.df_tracks['FRAME'] == self.current_frame)]
1935
+ else:
1936
+ self.df_tracks.loc[(self.df_tracks['ID'] == self.track_of_interest) & (
1937
+ self.df_tracks['FRAME'] == self.current_frame), self.status_name] = status
1938
+
1939
+ indices = self.df_tracks.index[(self.df_tracks['ID'] == self.track_of_interest) & (
1940
+ self.df_tracks['FRAME'] == self.current_frame)]
1941
+
1942
+ self.df_tracks.loc[indices, self.status_name] = status
1943
+ all_states = self.df_tracks.loc[:, self.status_name].tolist()
1944
+ all_states = np.array(all_states)
1945
+ self.state_color_map = color_from_state(all_states, recently_modified=False)
1946
+
1947
+ self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
1948
+
1949
+ self.extract_scatter_from_trajectories()
1950
+ self.give_cell_information()
1951
+
1952
+ self.correct_btn.disconnect()
1953
+ self.correct_btn.clicked.connect(self.show_annotation_buttons)
1954
+
1955
+ self.hide_annotation_buttons()
1956
+ self.correct_btn.setEnabled(False)
1957
+ self.correct_btn.setText('correct')
1958
+ self.cancel_btn.setEnabled(False)
1959
+ self.del_shortcut.setEnabled(False)
1960
+ self.no_event_shortcut.setEnabled(False)
1961
+ if len(self.selection) > 0:
1962
+ self.selection.pop(0)
1963
+ self.draw_frame(self.current_frame)
1964
+ self.fcanvas.canvas.draw()
1965
+
1966
+ def assign_color_state(self, state):
1967
+ if np.isnan(state):
1968
+ pass
1969
+ else:
1970
+ return self.state_color_map[state]
1971
+
1972
+ def draw_frame(self, framedata):
1973
+
1974
+ """
1975
+ Update plot elements at each timestep of the loop.
1976
+ """
1977
+ self.framedata = framedata
1978
+ self.frame_lbl.setText(f'position: {self.framedata}')
1979
+ self.im.set_array(self.img)
1980
+ self.status_scatter.set_offsets(self.positions[self.framedata])
1981
+ self.status_scatter.set_edgecolors(self.colors[self.framedata][:, 0])
1982
+ self.current_label = self.labels[self.current_frame]
1983
+ self.im_mask.remove()
1984
+ self.im_mask = self.ax.imshow(np.ma.masked_where(self.current_label == 0, self.current_label),
1985
+ cmap='viridis', interpolation='none',alpha=self.current_alpha,vmin=0,vmax=np.nanmax(self.labels.flatten()))
1986
+
1987
+ return (self.im, self.status_scatter,self.im_mask,)
1988
+
1989
+ def compute_status_and_colors(self):
1990
+ if self.class_choice_cb.currentText() == '':
1991
+ self.status_name=self.target_class
1992
+ else:
1993
+ self.status_name = self.class_choice_cb.currentText()
1994
+ print('selection and expected names: ', self.status_name)
1995
+
1996
+ if self.status_name not in self.df_tracks.columns:
1997
+ self.make_status_column()
1998
+ else:
1999
+ all_states = self.df_tracks.loc[:, self.status_name].tolist()
2000
+ all_states = np.array(all_states)
2001
+ self.state_color_map = color_from_state(all_states, recently_modified=False)
2002
+ self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
2003
+
2004
+ def del_event_class(self):
2005
+
2006
+ msgBox = QMessageBox()
2007
+ msgBox.setIcon(QMessageBox.Warning)
2008
+ msgBox.setText(
2009
+ f"You are about to delete characteristic group {self.class_choice_cb.currentText()}. Do you still want to proceed?")
2010
+ msgBox.setWindowTitle("Warning")
2011
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
2012
+ returnValue = msgBox.exec()
2013
+ if returnValue == QMessageBox.No:
2014
+ return None
2015
+ else:
2016
+ class_to_delete = self.class_choice_cb.currentText()
2017
+ cols_to_delete = [class_to_delete]
2018
+ for c in cols_to_delete:
2019
+ try:
2020
+ self.df_tracks = self.df_tracks.drop([c], axis=1)
2021
+ except Exception as e:
2022
+ print(e)
2023
+ item_idx = self.class_choice_cb.findText(class_to_delete)
2024
+ self.class_choice_cb.removeItem(item_idx)
2025
+
2026
+ def make_status_column(self):
2027
+ if self.status_name == "state_firstdetection":
2028
+ pass
2029
+ else:
2030
+ print('remaking the group column')
2031
+ self.df_tracks.loc[:, self.status_name] = 0
2032
+ all_states = self.df_tracks.loc[:, self.status_name].tolist()
2033
+ all_states = np.array(all_states)
2034
+ self.state_color_map = color_from_state(all_states, recently_modified=False)
2035
+ self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
2036
+
2037
+ def locate_tracks(self):
2038
+
2039
+ """
2040
+ Locate the tracks.
2041
+ """
2042
+
2043
+ if not os.path.exists(self.trajectories_path):
2044
+
2045
+ msgBox = QMessageBox()
2046
+ msgBox.setIcon(QMessageBox.Warning)
2047
+ msgBox.setText("The trajectories cannot be detected.")
2048
+ msgBox.setWindowTitle("Warning")
2049
+ msgBox.setStandardButtons(QMessageBox.Ok)
2050
+ returnValue = msgBox.exec()
2051
+ if returnValue == QMessageBox.Yes:
2052
+ self.close()
2053
+ else:
2054
+
2055
+ # Load and prep tracks
2056
+ self.df_tracks = pd.read_csv(self.trajectories_path)
2057
+ if 'TRACK_ID' in self.df_tracks.columns:
2058
+ self.df_tracks = self.df_tracks.sort_values(by=['TRACK_ID', 'FRAME'])
2059
+ else:
2060
+ self.df_tracks = self.df_tracks.sort_values(by=['ID', 'FRAME'])
2061
+
2062
+ cols = np.array(self.df_tracks.columns)
2063
+ self.class_cols = np.array([c.startswith('group') for c in list(self.df_tracks.columns)])
2064
+ self.class_cols = list(cols[self.class_cols])
2065
+ try:
2066
+ self.class_cols.remove('class_id')
2067
+ except:
2068
+ pass
2069
+ try:
2070
+ self.class_cols.remove('group_color')
2071
+ except:
2072
+ pass
2073
+ if len(self.class_cols) > 0:
2074
+ self.status = self.class_cols[0]
2075
+
2076
+ else:
2077
+
2078
+ self.status_name = 'group'
2079
+
2080
+ if self.status_name not in self.df_tracks.columns:
2081
+ # only create the status column if it does not exist to not erase static classification results
2082
+ self.make_status_column()
2083
+ else:
2084
+ # all good, do nothing
2085
+ pass
2086
+ # else:
2087
+ # if not self.status_name in self.df_tracks.columns:
2088
+ # self.df_tracks[self.status_name] = 0
2089
+ # self.df_tracks['state_color'] = color_from_status(0)
2090
+ # self.df_tracks['class_color'] = color_from_class(1)
2091
+
2092
+ # if not self.class_name in self.df_tracks.columns:
2093
+ # self.df_tracks[self.class_name] = 1
2094
+ # if not self.time_name in self.df_tracks.columns:
2095
+ # self.df_tracks[self.time_name] = -1
2096
+ all_states = self.df_tracks.loc[:, self.status_name].tolist()
2097
+ all_states = np.array(all_states)
2098
+ self.state_color_map = color_from_state(all_states, recently_modified=False)
2099
+ self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
2100
+ # self.df_tracks['status_color'] = [color_from_status(i) for i in self.df_tracks[self.status_name].to_numpy()]
2101
+ # self.df_tracks['class_color'] = [color_from_class(i) for i in self.df_tracks[self.class_name].to_numpy()]
2102
+
2103
+ self.df_tracks = self.df_tracks.dropna(subset=['POSITION_X', 'POSITION_Y'])
2104
+ self.df_tracks['x_anim'] = self.df_tracks['POSITION_X']
2105
+ self.df_tracks['y_anim'] = self.df_tracks['POSITION_Y']
2106
+ self.df_tracks['x_anim'] = self.df_tracks['x_anim'].astype(int)
2107
+ self.df_tracks['y_anim'] = self.df_tracks['y_anim'].astype(int)
2108
+
2109
+ self.extract_scatter_from_trajectories()
2110
+ if 'TRACK_ID' in self.df_tracks.columns:
2111
+ self.track_of_interest = self.df_tracks['TRACK_ID'].min()
2112
+ else:
2113
+ self.track_of_interest = self.df_tracks['ID'].min()
2114
+
2115
+ self.loc_t = []
2116
+ self.loc_idx = []
2117
+ for t in range(len(self.tracks)):
2118
+ indices = np.where(self.tracks[t] == self.track_of_interest)[0]
2119
+ if len(indices) > 0:
2120
+ self.loc_t.append(t)
2121
+ self.loc_idx.append(indices[0])
2122
+
2123
+ self.MinMaxScaler = MinMaxScaler()
2124
+ self.columns_to_rescale = list(self.df_tracks.columns)
2125
+
2126
+ # is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
2127
+ # is_number_test = is_number(self.df_tracks.dtypes)
2128
+ # self.columns_to_rescale = [col for t,col in zip(is_number_test,self.df_tracks.columns) if t]
2129
+ # print(self.columns_to_rescale)
2130
+
2131
+ cols_to_remove = ['group', 'group_color', 'status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME',
2132
+ 'x_anim', 'y_anim', 't',
2133
+ 'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
2134
+ 'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
2135
+ 'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent', 'ID'] + self.class_cols
2136
+ cols = np.array(list(self.df_tracks.columns))
2137
+ for tr in cols_to_remove:
2138
+ try:
2139
+ self.columns_to_rescale.remove(tr)
2140
+ except:
2141
+ pass
2142
+ # print(f'column {tr} could not be found...')
2143
+
2144
+ x = self.df_tracks[self.columns_to_rescale].values
2145
+ self.MinMaxScaler.fit(x)
2146
+
2147
+ def extract_scatter_from_trajectories(self):
2148
+
2149
+ self.positions = []
2150
+ self.colors = []
2151
+ self.tracks = []
2152
+
2153
+ for t in np.arange(self.len_movie):
2154
+ self.positions.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['POSITION_X', 'POSITION_Y']].to_numpy())
2155
+ self.colors.append(
2156
+ self.df_tracks.loc[self.df_tracks['FRAME'] == t, ['group_color']].to_numpy())
2157
+ if 'TRACK_ID' in self.df_tracks.columns:
2158
+ self.tracks.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, 'TRACK_ID'].to_numpy())
2159
+ else:
2160
+ self.tracks.append(self.df_tracks.loc[self.df_tracks['FRAME'] == t, 'ID'].to_numpy())
2161
+
2162
+ def changed_class(self):
2163
+ self.status_name = self.class_choice_cb.currentText()
2164
+ self.compute_status_and_colors()
2165
+ self.modify()
2166
+ self.draw_frame(self.current_frame)
2167
+ self.fcanvas.canvas.draw()
2168
+
2169
+ def update_frame(self):
2170
+ """
2171
+ Update the displayed frame.
2172
+ """
2173
+ self.current_frame = self.frame_slider.value()
2174
+ self.reload_frame()
2175
+ if 'ID' in self.df_tracks.columns:
2176
+ self.track_of_interest = self.df_tracks[self.df_tracks['FRAME'] == self.current_frame]['ID'].min()
2177
+ self.modify()
2178
+ self.draw_frame(self.current_frame)
2179
+ self.fcanvas.canvas.draw()
2180
+ self.plot_signals()
2181
+
2182
+ # def load_annotator_config(self):
2183
+ # self.rgb_mode = False
2184
+ # self.log_option = False
2185
+ # self.percentile_mode = True
2186
+ # self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
2187
+ # self.fraction = 0.5955056179775281
2188
+ # #self.anim_interval = 1
2189
+
2190
+ def prepare_stack(self):
2191
+
2192
+ self.img_num_channels = _get_img_num_per_channel(self.channels, self.len_movie, self.nbr_channels)
2193
+ self.current_stack = []
2194
+ for ch in tqdm(self.target_channels, desc="channel"):
2195
+ target_ch_name = ch[0]
2196
+ if self.percentile_mode:
2197
+ normalize_kwargs = {"percentiles": (ch[1], ch[2]), "values": None}
2198
+ else:
2199
+ normalize_kwargs = {"values": (ch[1], ch[2]), "percentiles": None}
2200
+
2201
+ if self.rgb_mode:
2202
+ normalize_kwargs.update({'amplification': 255., 'clip': True})
2203
+
2204
+ chan = []
2205
+ indices = self.img_num_channels[self.channels[np.where(self.channel_names == target_ch_name)][0]]
2206
+ for t in tqdm(range(len(indices)), desc='frame'):
2207
+ if self.rgb_mode:
2208
+ f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=True,
2209
+ normalize_kwargs=normalize_kwargs)
2210
+ f = f.astype(np.uint8)
2211
+ else:
2212
+ f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=False)
2213
+ chan.append(f[:, :, 0])
2214
+
2215
+ self.current_stack.append(chan)
2216
+
2217
+ self.current_stack = np.array(self.current_stack)
2218
+ if self.rgb_mode:
2219
+ self.current_stack = np.moveaxis(self.current_stack, 0, -1)
2220
+ else:
2221
+ self.current_stack = self.current_stack[0]
2222
+ if self.log_option:
2223
+ self.current_stack[np.where(self.current_stack > 0.)] = np.log(
2224
+ self.current_stack[np.where(self.current_stack > 0.)])
2225
+
2226
+ print(f'Load stack of shape: {self.current_stack.shape}.')
2227
+
2228
+ def changed_channel(self):
2229
+
2230
+ self.reload_frame()
2231
+ self.contrast_slider.setRange(
2232
+ *[np.nanpercentile(self.img, 0.001),
2233
+ np.nanpercentile(self.img, 99.999)])
2234
+ self.contrast_slider.setValue(
2235
+ [np.nanpercentile(self.img, 1), np.nanpercentile(self.img, 99.99)])
2236
+ self.draw_frame(self.current_frame)
2237
+ self.fcanvas.canvas.draw()
2238
+
2239
+ def save_trajectories(self):
2240
+
2241
+ if self.normalized_signals:
2242
+ self.normalize_features_btn.click()
2243
+ if self.selection:
2244
+ self.cancel_selection()
2245
+ self.df_tracks = self.df_tracks.drop(self.df_tracks[self.df_tracks[self.status_name] == 99].index)
2246
+ #color_column = str(self.status_name) + "_color"
2247
+ try:
2248
+ self.df_tracks.drop(columns='', inplace=True)
2249
+ except:
2250
+ pass
2251
+ try:
2252
+ self.df_tracks.drop(columns='group_color', inplace=True)
2253
+ except:
2254
+ pass
2255
+ try:
2256
+ self.df_tracks.drop(columns='x_anim', inplace=True)
2257
+ except:
2258
+ pass
2259
+ try:
2260
+ self.df_tracks.drop(columns='y_anim', inplace=True)
2261
+ except:
2262
+ pass
2263
+
2264
+ self.df_tracks.to_csv(self.trajectories_path, index=False)
2265
+ print('table saved.')
2266
+ self.update_frame()
2267
+
2268
+
2269
+ # self.extract_scatter_from_trajectories()
2270
+
2271
+ def modify(self):
2272
+ all_states = self.df_tracks.loc[:, self.status_name].tolist()
2273
+ all_states = np.array(all_states)
2274
+ self.state_color_map = color_from_state(all_states, recently_modified=False)
2275
+
2276
+ self.df_tracks['group_color'] = self.df_tracks[self.status_name].apply(self.assign_color_state)
2277
+
2278
+ self.extract_scatter_from_trajectories()
2279
+ self.give_cell_information()
2280
+
2281
+ self.correct_btn.disconnect()
2282
+ self.correct_btn.clicked.connect(self.show_annotation_buttons)
2283
+
2284
+ # self.hide_annotation_buttons()
2285
+ # self.correct_btn.setEnabled(False)
2286
+ # self.correct_btn.setText('correct')
2287
+ # self.cancel_btn.setEnabled(False)
2288
+ # self.del_shortcut.setEnabled(False)
2289
+ # self.no_event_shortcut.setEnabled(False)
2290
+
2291
+ def enable_time_of_interest(self):
2292
+ if self.suppr_btn.isChecked():
2293
+ self.time_of_interest_le.setEnabled(False)
2294
+
2295
+ def cancel_selection(self):
2296
+
2297
+ self.hide_annotation_buttons()
2298
+ self.correct_btn.setEnabled(False)
2299
+ self.correct_btn.setText('correct')
2300
+ self.cancel_btn.setEnabled(False)
2301
+
2302
+ try:
2303
+ self.selection.pop(0)
2304
+ except Exception as e:
2305
+ print(e)
2306
+
2307
+ try:
2308
+ for k, (t, idx) in enumerate(zip(self.loc_t, self.loc_idx)):
2309
+ # print(self.colors[t][idx, 1])
2310
+ self.colors[t][idx, 0] = self.previous_color[k][0]
2311
+ # self.colors[t][idx, 1] = self.previous_color[k][1]
2312
+ except Exception as e:
2313
+ print(f'{e=}')
2314
+
2315
+ def locate_stack(self):
2316
+
2317
+ """
2318
+ Locate the target movie.
2319
+
2320
+ """
2321
+
2322
+ print("this is the loaded position: ", self.pos)
2323
+ if isinstance(self.pos, str):
2324
+ movies = glob(self.pos + f"movie/{self.parent_window.parent_window.movie_prefix}*.tif")
2325
+
2326
+ else:
2327
+ msgBox = QMessageBox()
2328
+ msgBox.setIcon(QMessageBox.Warning)
2329
+ msgBox.setText("Please select a unique position before launching the wizard...")
2330
+ msgBox.setWindowTitle("Warning")
2331
+ msgBox.setStandardButtons(QMessageBox.Ok)
2332
+ returnValue = msgBox.exec()
2333
+ if returnValue == QMessageBox.Ok:
2334
+ self.img = None
2335
+ self.close()
2336
+ return None
2337
+
2338
+ if len(movies) == 0:
2339
+ msgBox = QMessageBox()
2340
+ msgBox.setIcon(QMessageBox.Warning)
2341
+ msgBox.setText("No movies are detected in the experiment folder. Cannot load an image to test Haralick.")
2342
+ msgBox.setWindowTitle("Warning")
2343
+ msgBox.setStandardButtons(QMessageBox.Ok)
2344
+ returnValue = msgBox.exec()
2345
+ if returnValue == QMessageBox.Yes:
2346
+ self.close()
2347
+ else:
2348
+ self.stack_path = movies[0]
2349
+ self.len_movie = self.parent_window.parent_window.len_movie
2350
+ len_movie_auto = auto_load_number_of_frames(self.stack_path)
2351
+ if len_movie_auto is not None:
2352
+ self.len_movie = len_movie_auto
2353
+ exp_config = self.exp_dir + "config.ini"
2354
+ self.channel_names, self.channels = extract_experiment_channels(exp_config)
2355
+ self.channel_names = np.array(self.channel_names)
2356
+ self.channels = np.array(self.channels)
2357
+ self.nbr_channels = len(self.channels)
2358
+ self.current_channel = 0
2359
+ self.img = load_frames(0, self.stack_path, normalize_input=False)
2360
+ print(self.img.shape)
2361
+ print(f'{self.stack_path} successfully located.')
2362
+
2363
+ def reload_frame(self):
2364
+
2365
+ """
2366
+ Load the frame from the current channel and time choice. Show imshow, update histogram.
2367
+ """
2368
+
2369
+ # self.clear_post_threshold_options()
2370
+
2371
+ self.current_channel = self.choose_channel.currentIndex()
2372
+
2373
+ t = int(self.frame_slider.value())
2374
+ idx = t * self.nbr_channels + self.current_channel
2375
+ self.img = load_frames(idx, self.stack_path, normalize_input=False)
2376
+ if self.img is not None:
2377
+ self.refresh_imshow()
2378
+ # self.redo_histogram()
2379
+ else:
2380
+ print('Frame could not be loaded...')
2381
+
2382
+ def refresh_imshow(self):
2383
+
2384
+ """
2385
+
2386
+ Update the imshow based on the current frame selection.
2387
+
2388
+ """
2389
+
2390
+ self.vmin = np.nanpercentile(self.img.flatten(), 1)
2391
+ self.vmax = np.nanpercentile(self.img.flatten(), 99.)
2392
+
2393
+ self.contrast_slider.disconnect()
2394
+ self.contrast_slider.setRange(np.nanmin(self.img), np.nanmax(self.img))
2395
+ self.contrast_slider.setValue([self.vmin, self.vmax])
2396
+ self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
2397
+
2398
+ self.im.set_data(self.img)
2399
+
2400
+ # self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
2401
+ # self.fcanvas.canvas.draw_idle()
2402
+
2403
+ def show_outliers(self):
2404
+ if self.outliers_check.isChecked():
2405
+ self.show_fliers = True
2406
+ self.plot_signals()
2407
+ else:
2408
+ self.show_fliers = False
2409
+ self.plot_signals()
2410
+
2411
+ def del_cell(self):
2412
+ self.time_of_interest_le.setEnabled(False)
2413
+ self.time_of_interest_le.setText("99")
2414
+ self.apply_modification()
2415
+
2416
+ def shortcut_suppr(self):
2417
+ self.correct_btn.click()
2418
+ self.del_cell_btn.click()
2419
+ self.correct_btn.click()