celldetective 1.1.1.post3__py3-none-any.whl → 1.2.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 (42) hide show
  1. celldetective/__init__.py +2 -1
  2. celldetective/__main__.py +17 -0
  3. celldetective/extra_properties.py +62 -34
  4. celldetective/gui/__init__.py +1 -0
  5. celldetective/gui/analyze_block.py +2 -1
  6. celldetective/gui/classifier_widget.py +18 -10
  7. celldetective/gui/control_panel.py +57 -6
  8. celldetective/gui/layouts.py +14 -11
  9. celldetective/gui/neighborhood_options.py +21 -13
  10. celldetective/gui/plot_signals_ui.py +39 -11
  11. celldetective/gui/process_block.py +413 -95
  12. celldetective/gui/retrain_segmentation_model_options.py +17 -4
  13. celldetective/gui/retrain_signal_model_options.py +106 -6
  14. celldetective/gui/signal_annotator.py +110 -30
  15. celldetective/gui/signal_annotator2.py +2708 -0
  16. celldetective/gui/signal_annotator_options.py +3 -1
  17. celldetective/gui/survival_ui.py +15 -6
  18. celldetective/gui/tableUI.py +248 -43
  19. celldetective/io.py +598 -416
  20. celldetective/measure.py +919 -969
  21. celldetective/models/pair_signal_detection/blank +0 -0
  22. celldetective/neighborhood.py +482 -340
  23. celldetective/preprocessing.py +81 -61
  24. celldetective/relative_measurements.py +648 -0
  25. celldetective/scripts/analyze_signals.py +1 -1
  26. celldetective/scripts/measure_cells.py +28 -8
  27. celldetective/scripts/measure_relative.py +103 -0
  28. celldetective/scripts/segment_cells.py +5 -5
  29. celldetective/scripts/track_cells.py +4 -1
  30. celldetective/scripts/train_segmentation_model.py +23 -18
  31. celldetective/scripts/train_signal_model.py +33 -0
  32. celldetective/segmentation.py +67 -29
  33. celldetective/signals.py +402 -8
  34. celldetective/tracking.py +8 -2
  35. celldetective/utils.py +144 -12
  36. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/METADATA +8 -8
  37. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/RECORD +42 -38
  38. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/WHEEL +1 -1
  39. tests/test_segmentation.py +1 -1
  40. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/LICENSE +0 -0
  41. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/entry_points.txt +0 -0
  42. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,2708 @@
1
+ import copy
2
+
3
+ from PyQt5.QtWidgets import QMainWindow, QComboBox, QLabel, QRadioButton, QLineEdit, QFileDialog, QApplication, \
4
+ QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QAction, QShortcut, QLineEdit, QTabWidget, \
5
+ QButtonGroup, QGridLayout, QSlider, QCheckBox, QToolButton
6
+ from PyQt5.QtCore import Qt, QSize
7
+ from PyQt5.QtGui import QKeySequence
8
+ from matplotlib.collections import LineCollection
9
+ from celldetective.gui import SignalAnnotator, Styles
10
+ from celldetective.gui.gui_utils import center_window, QHSeperationLine, FilterChoice
11
+ from superqt import QLabeledDoubleSlider, QLabeledDoubleRangeSlider, QLabeledSlider, QSearchableComboBox
12
+ from celldetective.utils import extract_experiment_channels, get_software_location, _get_img_num_per_channel
13
+ from celldetective.io import auto_load_number_of_frames, load_frames
14
+ from celldetective.gui.gui_utils import FigureCanvas, color_from_status, color_from_class
15
+ import json
16
+ import numpy as np
17
+ from superqt.fonticon import icon
18
+ from fonticon_mdi6 import MDI6
19
+ import os
20
+ from glob import glob
21
+ from natsort import natsorted
22
+ import matplotlib.pyplot as plt
23
+ from matplotlib.ticker import MultipleLocator
24
+ from tqdm import tqdm
25
+ import gc
26
+ from matplotlib.animation import FuncAnimation
27
+ from matplotlib.cm import tab10
28
+ import pandas as pd
29
+ from sklearn.preprocessing import MinMaxScaler
30
+ from functools import partial
31
+ from pandas.api.types import is_numeric_dtype
32
+
33
+ class SignalAnnotator2(QMainWindow,Styles):
34
+
35
+ """
36
+ UI to set tracking parameters for bTrack.
37
+
38
+ """
39
+
40
+ def __init__(self, parent=None):
41
+
42
+ super().__init__()
43
+ self.parent_window = parent
44
+ self.setWindowTitle("Signal annotator")
45
+
46
+ self.pos = self.parent_window.parent_window.pos
47
+ self.exp_dir = self.parent_window.exp_dir
48
+ print(f'{self.pos=} {self.exp_dir=}')
49
+
50
+ self.soft_path = get_software_location()
51
+ self.recently_modified = False
52
+ self.n_signals = 3
53
+ self.target_selection = []
54
+ self.effector_selection = []
55
+
56
+ self.reference_selection = []
57
+ self.neighbor_selection = []
58
+ self.pair_selection = []
59
+ self.neighbor_loc_t = []; self.neighbor_loc_idx = [];
60
+ self.reference_loc_t = []; self.reference_loc_idx = [];
61
+ self.reference_loc_t_not_picked = []; self.reference_loc_idx_not_picked = [];
62
+ self.neigh_cell_loc_t = []; self.neigh_cell_loc_idx = [];
63
+
64
+ self.reference_track_of_interest = None
65
+ self.neighbor_track_of_interest = None
66
+ self.value_magnitude = 1
67
+
68
+ self.cols_to_remove = ['REFERENCE_ID', 'NEIGHBOR_ID', 'FRAME', 't0_arrival', 'TRACK_ID', 'class_color', 'status_color',
69
+ 'FRAME', 'x_anim', 'y_anim', 't', 'state', 'generation', 'root', 'parent', 'class_id', 'class',
70
+ 't0', 'POSITION_X', 'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name',
71
+ 'index', 'relxy', 'tc', 'nk', 'concentration', 'antibody', 'cell_type', 'pharmaceutical_agent',
72
+ 'reference_population', 'neighbor_population']
73
+
74
+
75
+ # Read instructions from target block for now...
76
+ self.mode = "neighborhood"
77
+ self.instructions_path = self.exp_dir + os.sep.join(['configs', 'signal_annotator_config_neighborhood.json'])
78
+
79
+ # default params
80
+ self.target_class_name = 'class'
81
+ self.target_time_name = 't0'
82
+ self.target_status_name = 'status'
83
+
84
+ center_window(self)
85
+
86
+ # Locate stack
87
+ self.locate_stack()
88
+ self.load_annotator_config()
89
+
90
+ # Locate tracks
91
+ self.locate_target_tracks()
92
+ self.locate_effector_tracks()
93
+
94
+ self.dataframes = {
95
+ 'targets': self.df_targets,
96
+ 'effectors': self.df_effectors,
97
+ }
98
+
99
+ self.neighborhood_cols = []
100
+ if self.df_targets is not None:
101
+ self.neighborhood_cols.extend(['target_ref_'+c for c in list(self.df_targets.columns) if c.startswith('neighborhood')])
102
+ if self.df_effectors is not None:
103
+ print(self.df_effectors.columns)
104
+ self.neighborhood_cols.extend(['effector_ref_'+c for c in list(self.df_effectors.columns) if c.startswith('neighborhood')])
105
+ print(f"The following neighborhoods were detected: {self.neighborhood_cols=}")
106
+ self.locate_relative_tracks()
107
+
108
+ # Prepare stack
109
+ self.prepare_stack()
110
+
111
+ self.generate_signal_choices()
112
+ self.frame_lbl = QLabel('frame: ')
113
+ self.looped_animation()
114
+ self.create_cell_signal_canvas()
115
+
116
+ self.populate_widget()
117
+ self.fill_signal_choices()
118
+
119
+ self.reference_pop_option_buttons[0].setChecked(True)
120
+ self.neighbor_pop_option_buttons[1].setChecked(True)
121
+ self.relative_pop_option_buttons[2].setChecked(True)
122
+ #self.plot_signals()
123
+
124
+ self.give_reference_cell_information()
125
+ self.give_neighbor_cell_information()
126
+ self.give_pair_information()
127
+
128
+ # Widget settings
129
+ self.screen_height = self.parent_window.parent_window.parent_window.screen_height
130
+ self.screen_width = self.parent_window.parent_window.parent_window.screen_width
131
+ self.setMinimumWidth(int(0.8*self.screen_width))
132
+ self.setMinimumHeight(int(0.8*self.screen_height))
133
+
134
+ #self.cell_fcanvas.setMinimumHeight(int(0.3*self.screen_height))
135
+
136
+ self.setAttribute(Qt.WA_DeleteOnClose)
137
+
138
+ def populate_widget(self):
139
+
140
+ """
141
+ Create the multibox design.
142
+
143
+ """
144
+
145
+ self.button_widget = QWidget()
146
+ main_layout = QHBoxLayout()
147
+ self.button_widget.setLayout(main_layout)
148
+
149
+ main_layout.setContentsMargins(30,30,30,30)
150
+ self.left_panel = QVBoxLayout()
151
+ self.left_panel.setContentsMargins(30,30,30,30)
152
+ self.left_panel.setSpacing(10)
153
+
154
+ self.right_panel = QVBoxLayout()
155
+
156
+ #NEIGHBORHOOD
157
+ neigh_hbox = QHBoxLayout()
158
+ neigh_hbox.addWidget(QLabel('neighborhood: '), 25)
159
+ self.neighborhood_choice_cb = QComboBox()
160
+ self.neighborhood_choice_cb.addItems(self.neighborhood_cols)
161
+ self.neighborhood_choice_cb.setCurrentIndex(0)
162
+ neigh_hbox.addWidget(self.neighborhood_choice_cb, 75)
163
+ self.left_panel.addLayout(neigh_hbox)
164
+
165
+ self.reference_cell_info = QLabel('')
166
+ self.pair_info = QLabel('')
167
+ self.neighbor_cell_info= QLabel('')
168
+
169
+ class_hbox = QHBoxLayout()
170
+ class_hbox.addWidget(QLabel('interaction event: '), 25)
171
+
172
+ subclass_hbox = QHBoxLayout()
173
+ self.relative_class_choice_cb = QComboBox()
174
+ self.relative_class_choice_cb.addItems(self.relative_class_cols)
175
+ self.relative_class_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_pair)
176
+ self.relative_class_choice_cb.setCurrentIndex(0)
177
+
178
+ subclass_hbox.addWidget(self.relative_class_choice_cb, 90)
179
+
180
+ self.set_reference_and_neighbor_populations()
181
+ self.neighborhood_choice_cb.currentIndexChanged.connect(self.neighborhood_changed)
182
+ self.compute_status_and_colors_pair()
183
+
184
+ self.relative_add_class_btn = QPushButton('')
185
+ self.relative_add_class_btn.setStyleSheet(self.button_select_all)
186
+ self.relative_add_class_btn.setIcon(icon(MDI6.plus,color="black"))
187
+ self.relative_add_class_btn.setToolTip("Add a new interaction event class")
188
+ self.relative_add_class_btn.setIconSize(QSize(20, 20))
189
+ self.relative_add_class_btn.clicked.connect(self.create_new_relative_event_class)
190
+ subclass_hbox.addWidget(self.relative_add_class_btn, 5)
191
+
192
+ self.relative_del_class_btn = QPushButton('')
193
+ self.relative_del_class_btn.setStyleSheet(self.button_select_all)
194
+ self.relative_del_class_btn.setIcon(icon(MDI6.delete,color="black"))
195
+ self.relative_del_class_btn.setToolTip("Delete an interaction event class")
196
+ self.relative_del_class_btn.setIconSize(QSize(20, 20))
197
+ self.relative_del_class_btn.clicked.connect(self.del_relative_event_class)
198
+ subclass_hbox.addWidget(self.relative_del_class_btn, 5)
199
+ class_hbox.addLayout(subclass_hbox, 75)
200
+
201
+
202
+ self.left_panel.addLayout(class_hbox)
203
+
204
+ self.cell_events_hbox = QHBoxLayout()
205
+ self.cell_events_hbox.addWidget(QLabel('reference event: '), 25)
206
+ self.reference_event_choice_cb = QComboBox()
207
+ self.cell_events_hbox.addWidget(self.reference_event_choice_cb, 75)
208
+
209
+ #if 'self' not in self.neighborhood_choice_cb.currentText():
210
+ self.neigh_cell_events_hbox = QHBoxLayout()
211
+ self.neigh_lab=QLabel('neighbor event: ')
212
+ self.neigh_cell_events_hbox.addWidget(self.neigh_lab, 25)
213
+ self.neighbor_event_choice_cb = QComboBox()
214
+ self.neigh_cell_events_hbox.addWidget(self.neighbor_event_choice_cb, 75)
215
+ self.fill_class_cbs()
216
+
217
+ #self.left_panel.addLayout(self.cell_events_hbox)
218
+
219
+ # Text information about selected cells
220
+ self.cell_info_hbox = QHBoxLayout()
221
+ self.cell_info_hbox.setContentsMargins(30,30,30,30)
222
+
223
+ reference_layout = QVBoxLayout()
224
+ reference_layout.addWidget(self.reference_cell_info)
225
+ reference_layout.addLayout(self.cell_events_hbox)
226
+
227
+ neighbor_layout = QVBoxLayout()
228
+ neighbor_layout.addWidget(self.neighbor_cell_info)
229
+ neighbor_layout.addLayout(self.neigh_cell_events_hbox)
230
+
231
+ self.cell_info_hbox.addLayout(reference_layout, 33)
232
+ self.cell_info_hbox.addWidget(self.pair_info, 33, alignment=Qt.AlignCenter)
233
+ self.cell_info_hbox.addLayout(neighbor_layout, 33)
234
+
235
+ self.left_panel.addLayout(self.cell_info_hbox)
236
+
237
+ # Annotation buttons
238
+ options_hbox = QHBoxLayout()
239
+ options_hbox.setContentsMargins(150, 30, 50, 0)
240
+ self.event_btn = QRadioButton('event')
241
+ self.event_btn.setStyleSheet(self.button_style_sheet_2)
242
+ self.event_btn.toggled.connect(self.enable_time_of_interest)
243
+
244
+ self.no_event_btn = QRadioButton('no event')
245
+ self.no_event_btn.setStyleSheet(self.button_style_sheet_2)
246
+ self.no_event_btn.toggled.connect(self.enable_time_of_interest)
247
+
248
+ self.else_btn = QRadioButton('else')
249
+ self.else_btn.setStyleSheet(self.button_style_sheet_2)
250
+ self.else_btn.toggled.connect(self.enable_time_of_interest)
251
+
252
+ self.suppr_btn = QRadioButton('mark for\nsuppression')
253
+ self.suppr_btn.setStyleSheet(self.button_style_sheet_2)
254
+ self.suppr_btn.toggled.connect(self.enable_time_of_interest)
255
+
256
+ options_hbox.addWidget(self.event_btn, 25)
257
+ options_hbox.addWidget(self.no_event_btn, 25)
258
+ options_hbox.addWidget(self.else_btn, 25)
259
+ options_hbox.addWidget(self.suppr_btn, 25)
260
+ self.left_panel.addLayout(options_hbox)
261
+
262
+ time_option_hbox = QHBoxLayout()
263
+ time_option_hbox.setContentsMargins(100, 30, 100, 30)
264
+ self.time_of_interest_label = QLabel('time of interest: ')
265
+ time_option_hbox.addWidget(self.time_of_interest_label, 30)
266
+ self.time_of_interest_le = QLineEdit()
267
+ time_option_hbox.addWidget(self.time_of_interest_le, 70)
268
+ self.left_panel.addLayout(time_option_hbox)
269
+
270
+ main_action_hbox = QHBoxLayout()
271
+ self.correct_btn = QPushButton('correct')
272
+ self.correct_btn.setIcon(icon(MDI6.redo_variant, color="white"))
273
+ self.correct_btn.setIconSize(QSize(20, 20))
274
+ self.correct_btn.setStyleSheet(self.button_style_sheet)
275
+ self.correct_btn.clicked.connect(self.show_annotation_buttons)
276
+ self.correct_btn.setEnabled(False)
277
+ main_action_hbox.addWidget(self.correct_btn)
278
+
279
+ self.cancel_btn = QPushButton('cancel')
280
+ self.cancel_btn.setStyleSheet(self.button_style_sheet_2)
281
+ self.cancel_btn.setShortcut(QKeySequence("Esc"))
282
+ self.cancel_btn.setEnabled(False)
283
+ self.cancel_btn.clicked.connect(self.cancel_selection)
284
+ main_action_hbox.addWidget(self.cancel_btn)
285
+ self.left_panel.addLayout(main_action_hbox)
286
+
287
+ self.annotation_btns_to_hide = [self.event_btn, self.no_event_btn,
288
+ self.else_btn, self.time_of_interest_label,
289
+ self.time_of_interest_le, self.suppr_btn]
290
+ self.hide_annotation_buttons()
291
+
292
+ self.del_shortcut = QShortcut(Qt.Key_Delete, self) #QKeySequence("s")
293
+ self.del_shortcut.activated.connect(self.shortcut_suppr)
294
+ self.del_shortcut.setEnabled(False)
295
+
296
+ self.no_event_shortcut = QShortcut(QKeySequence("n"), self) #QKeySequence("s")
297
+ self.no_event_shortcut.activated.connect(self.shortcut_no_event)
298
+ self.no_event_shortcut.setEnabled(False)
299
+
300
+
301
+ # Cell signals
302
+ self.left_panel.addWidget(self.cell_fcanvas)
303
+
304
+ plot_buttons_hbox = QHBoxLayout()
305
+ plot_buttons_hbox.setContentsMargins(0,0,0,0)
306
+ self.normalize_features_btn = QPushButton('')
307
+ self.normalize_features_btn.setStyleSheet(self.button_select_all)
308
+ self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical,color="black"))
309
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
310
+ self.normalize_features_btn.setFixedSize(QSize(30, 30))
311
+ #self.normalize_features_btn.setShortcut(QKeySequence('n'))
312
+ self.normalize_features_btn.clicked.connect(self.normalize_features)
313
+
314
+ plot_buttons_hbox.addWidget(QLabel(''), 90)
315
+ plot_buttons_hbox.addWidget(self.normalize_features_btn, 5)
316
+ self.normalized_signals = False
317
+
318
+ self.log_btn = QPushButton()
319
+ self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
320
+ self.log_btn.setStyleSheet(self.button_select_all)
321
+ self.log_btn.clicked.connect(self.switch_to_log)
322
+ plot_buttons_hbox.addWidget(self.log_btn, 5)
323
+
324
+ self.left_panel.addLayout(plot_buttons_hbox)
325
+
326
+ signal_choice_grid = QVBoxLayout()
327
+ signal_choice_grid.setContentsMargins(30,0,30,50)
328
+
329
+ header_layout = QHBoxLayout()
330
+ header_layout.addWidget(QLabel('reference'), 23, alignment=Qt.AlignCenter)
331
+ header_layout.addWidget(QLabel('neighbor'), 23, alignment=Qt.AlignCenter)
332
+ header_layout.addWidget(QLabel('pair'), 23, alignment=Qt.AlignCenter)
333
+ header_layout.addWidget(QLabel(''), 30, alignment=Qt.AlignCenter)
334
+ signal_choice_grid.addLayout(header_layout)
335
+
336
+ for i in range(self.n_signals):
337
+
338
+ h_layout = QHBoxLayout()
339
+ h_layout.addWidget(self.reference_pop_option_buttons[i], 23, alignment=Qt.AlignCenter)
340
+ h_layout.addWidget(self.neighbor_pop_option_buttons[i], 23, alignment=Qt.AlignCenter)
341
+ h_layout.addWidget(self.relative_pop_option_buttons[i], 23, alignment=Qt.AlignCenter)
342
+ h_layout.addWidget(self.signal_choices[i], 30)
343
+ signal_choice_grid.addLayout(h_layout)
344
+
345
+ # signal_choice_vbox = QVBoxLayout()
346
+ # signal_choice_vbox.setContentsMargins(30,0,30,50)
347
+ # for i in range(len(self.signal_choices)):
348
+ # signal_choice_grid.addWidget(self.signal_choices[i],i+1,3)
349
+ # # hlayout = QHBoxLayout()
350
+ # #
351
+ # # #hlayout.addLayout(self.signal_labels[i], 20)
352
+ # # #hlayout.addLayout(self.signal_choices[i], 75)
353
+ # # # if i==0:
354
+ # # # hlayout.addWidget(self.signal_choices[i], 75,alignment=Qt.AlignBottom)
355
+ # # # else:
356
+ # # hlayout.addWidget(self.signal_choices[i], 75)
357
+ # # #hlayout.addWidget(self.log_btns[i], 5)
358
+ # # signal_choice_vbox.addLayout(hlayout)
359
+
360
+ # self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
361
+ # self.log_btns[i].setStyleSheet(self.parent.parent.parent.button_select_all)
362
+ # self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
363
+ #signal_choice_hbox.addLayout(signal_choice_vbox,alignment=Qt.AlignCenter)
364
+
365
+ self.left_panel.addLayout(signal_choice_grid)
366
+
367
+ btn_hbox = QHBoxLayout()
368
+ self.save_btn = QPushButton('Save')
369
+ self.save_btn.setStyleSheet(self.button_style_sheet)
370
+ self.save_btn.clicked.connect(self.save_trajectories)
371
+ btn_hbox.addWidget(self.save_btn, 90)
372
+
373
+ self.export_btn = QPushButton('')
374
+ self.export_btn.setStyleSheet(self.button_select_all)
375
+ self.export_btn.clicked.connect(self.export_signals)
376
+ self.export_btn.setIcon(icon(MDI6.export,color="black"))
377
+ self.export_btn.setIconSize(QSize(25, 25))
378
+ btn_hbox.addWidget(self.export_btn, 10)
379
+ self.left_panel.addLayout(btn_hbox)
380
+
381
+ # Animation
382
+ animation_buttons_box = QHBoxLayout()
383
+
384
+
385
+ animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
386
+
387
+ self.first_frame_btn = QPushButton()
388
+ self.first_frame_btn.clicked.connect(self.set_first_frame)
389
+ self.first_frame_btn.setShortcut(QKeySequence('f'))
390
+ self.first_frame_btn.setIcon(icon(MDI6.page_first,color="black"))
391
+ self.first_frame_btn.setStyleSheet(self.button_select_all)
392
+ self.first_frame_btn.setFixedSize(QSize(60, 60))
393
+ self.first_frame_btn.setIconSize(QSize(30, 30))
394
+
395
+
396
+
397
+ self.last_frame_btn = QPushButton()
398
+ self.last_frame_btn.clicked.connect(self.set_last_frame)
399
+ self.last_frame_btn.setShortcut(QKeySequence('l'))
400
+ self.last_frame_btn.setIcon(icon(MDI6.page_last,color="black"))
401
+ self.last_frame_btn.setStyleSheet(self.button_select_all)
402
+ self.last_frame_btn.setFixedSize(QSize(60, 60))
403
+ self.last_frame_btn.setIconSize(QSize(30, 30))
404
+
405
+ self.stop_btn = QPushButton()
406
+ self.stop_btn.clicked.connect(self.stop)
407
+ self.stop_btn.setIcon(icon(MDI6.stop,color="black"))
408
+ self.stop_btn.setStyleSheet(self.button_select_all)
409
+ self.stop_btn.setFixedSize(QSize(60, 60))
410
+ self.stop_btn.setIconSize(QSize(30, 30))
411
+
412
+
413
+ self.start_btn = QPushButton()
414
+ self.start_btn.clicked.connect(self.start)
415
+ self.start_btn.setIcon(icon(MDI6.play,color="black"))
416
+ self.start_btn.setFixedSize(QSize(60, 60))
417
+ self.start_btn.setStyleSheet(self.button_select_all)
418
+ self.start_btn.setIconSize(QSize(30, 30))
419
+ self.start_btn.hide()
420
+
421
+ animation_buttons_box.addWidget(self.first_frame_btn, 5, alignment=Qt.AlignRight)
422
+ animation_buttons_box.addWidget(self.stop_btn,5, alignment=Qt.AlignRight)
423
+ animation_buttons_box.addWidget(self.start_btn,5, alignment=Qt.AlignRight)
424
+ animation_buttons_box.addWidget(self.last_frame_btn, 5, alignment=Qt.AlignRight)
425
+
426
+
427
+ self.right_panel.addLayout(animation_buttons_box, 5)
428
+
429
+
430
+ self.right_panel.addWidget(self.fcanvas, 90)
431
+
432
+ if not self.rgb_mode:
433
+ contrast_hbox = QHBoxLayout()
434
+ contrast_hbox.setContentsMargins(150,5,150,5)
435
+ self.contrast_slider = QLabeledDoubleRangeSlider()
436
+ # self.contrast_slider.setSingleStep(0.001)
437
+ # self.contrast_slider.setTickInterval(0.001)
438
+ self.contrast_slider.setOrientation(1)
439
+ print('range: ', [np.nanpercentile(self.stack.flatten(), 0.001), np.nanpercentile(self.stack.flatten(), 99.999)])
440
+ self.contrast_slider.setRange(
441
+ *[np.nanpercentile(self.stack, 0.001), np.nanpercentile(self.stack, 99.999)])
442
+ self.contrast_slider.setValue(
443
+ [np.nanpercentile(self.stack, 1), np.nanpercentile(self.stack, 99.99)])
444
+ self.contrast_slider.valueChanged.connect(self.contrast_slider_action)
445
+ contrast_hbox.addWidget(QLabel('contrast: '))
446
+ contrast_hbox.addWidget(self.contrast_slider,90)
447
+ self.right_panel.addLayout(contrast_hbox, 5)
448
+
449
+ # speed_hbox = QHBoxLayout()
450
+ # speed_hbox.setContentsMargins(150,5,150,5)
451
+ # self.interval_slider = QLabeledSlider()
452
+ # self.interval_slider.setSingleStep(1)
453
+ # self.interval_slider.setTickInterval(1)
454
+ # self.interval_slider.setOrientation(1)
455
+ # self.interval_slider.setRange(1, 10000)
456
+ # self.interval_slider.setValue(self.speed)
457
+ # self.interval_slider.valueChanged.connect(self.interval_slider_action)
458
+ # speed_hbox.addWidget(QLabel('interval (ms): '))
459
+ # speed_hbox.addWidget(self.interval_slider,90)
460
+ # self.right_panel.addLayout(speed_hbox, 10)
461
+
462
+ #self.selected_populationulate_left_panel()
463
+ #grid.addLayout(self.left_side, 0, 0, 1, 1)
464
+
465
+ main_layout.addLayout(self.left_panel, 35)
466
+ main_layout.addLayout(self.right_panel, 65)
467
+ self.button_widget.adjustSize()
468
+ self.compute_status_and_colors_reference()
469
+
470
+
471
+ self.setCentralWidget(self.button_widget)
472
+ self.show()
473
+
474
+ QApplication.processEvents()
475
+
476
+ def fill_class_cbs(self):
477
+
478
+ cols_to_remove = ['class_id', 'class_color']
479
+
480
+ try:
481
+ self.reference_event_choice_cb.disconnect()
482
+ except:
483
+ pass
484
+ self.reference_event_choice_cb.clear()
485
+ df_reference = self.dataframes[self.reference_population]
486
+ reference_class_cols = [c for c in list(df_reference.columns) if c.startswith('class')]
487
+ for c in cols_to_remove:
488
+ try:
489
+ reference_class_cols.remove(c)
490
+ except:
491
+ pass
492
+ self.reference_event_choice_cb.addItems(reference_class_cols)
493
+ self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
494
+
495
+ try:
496
+ self.neighbor_event_choice_cb.disconnect()
497
+ except:
498
+ pass
499
+ self.neighbor_event_choice_cb.clear()
500
+ df_neighbors = self.dataframes[self.neighbor_population]
501
+ neighbor_class_cols = [c for c in list(df_neighbors.columns) if c.startswith('class')]
502
+ for c in cols_to_remove:
503
+ try:
504
+ neighbor_class_cols.remove(c)
505
+ except:
506
+ pass
507
+ self.neighbor_event_choice_cb.addItems(neighbor_class_cols)
508
+ self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
509
+
510
+
511
+ def del_target_event_class(self):
512
+
513
+ msgBox = QMessageBox()
514
+ msgBox.setIcon(QMessageBox.Warning)
515
+ msgBox.setText(f"You are about to delete event class {self.target_class_choice_cb.currentText()}. The associated time and\nstatus will also be deleted. Do you still want to proceed?")
516
+ msgBox.setWindowTitle("Warning")
517
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
518
+ returnValue = msgBox.exec()
519
+ if returnValue == QMessageBox.No:
520
+ return None
521
+ else:
522
+ class_to_delete = self.target_class_choice_cb.currentText()
523
+ time_to_delete = class_to_delete.replace('class','t')
524
+ status_to_delete = class_to_delete.replace('class', 'status')
525
+ cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
526
+ for c in cols_to_delete:
527
+ try:
528
+ self.df_targets = self.df_targets.drop([c], axis=1)
529
+ except Exception as e:
530
+ print(e)
531
+ item_idx = self.target_class_choice_cb.findText(class_to_delete)
532
+ self.target_class_choice_cb.removeItem(item_idx)
533
+
534
+ def del_effector_event_class(self):
535
+
536
+ msgBox = QMessageBox()
537
+ msgBox.setIcon(QMessageBox.Warning)
538
+ msgBox.setText(f"You are about to delete event class {self.effector_class_choice_cb.currentText()}. The associated time and\nstatus will also be deleted. Do you still want to proceed?")
539
+ msgBox.setWindowTitle("Warning")
540
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
541
+ returnValue = msgBox.exec()
542
+ if returnValue == QMessageBox.No:
543
+ return None
544
+ else:
545
+ class_to_delete = self.effector_class_choice_cb.currentText()
546
+ time_to_delete = class_to_delete.replace('class','t')
547
+ status_to_delete = class_to_delete.replace('class', 'status')
548
+ cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
549
+ for c in cols_to_delete:
550
+ try:
551
+ self.df_effectors = self.df_effectors.drop([c], axis=1)
552
+ except Exception as e:
553
+ print(e)
554
+ item_idx = self.effector_class_choice_cb.findText(class_to_delete)
555
+ self.effector_class_choice_cb.removeItem(item_idx)
556
+
557
+ def del_relative_event_class(self):
558
+
559
+ msgBox = QMessageBox()
560
+ msgBox.setIcon(QMessageBox.Warning)
561
+ msgBox.setText(f"You are about to delete event class {self.relative_class_choice_cb.currentText()}. The associated time and\nstatus will also be deleted. Do you still want to proceed?")
562
+ msgBox.setWindowTitle("Warning")
563
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
564
+ returnValue = msgBox.exec()
565
+ if returnValue == QMessageBox.No:
566
+ return None
567
+ else:
568
+ class_to_delete = self.relative_class_choice_cb.currentText()
569
+ time_to_delete = class_to_delete.replace('class','t')
570
+ status_to_delete = class_to_delete.replace('class', 'status')
571
+ cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
572
+ for c in cols_to_delete:
573
+ try:
574
+ self.df_relative = self.df_relative.drop([c], axis=1)
575
+ except Exception as e:
576
+ print(e)
577
+ item_idx = self.relative_class_choice_cb.findText(class_to_delete)
578
+ self.relative_class_choice_cb.removeItem(item_idx)
579
+
580
+ def update_cell_events(self):
581
+ if 'self' in self.current_neighborhood:
582
+ try:
583
+ self.neighbor_event_choice_cb.hide()
584
+ self.neigh_lab.hide()
585
+ except:
586
+ pass
587
+ self.reference_event_choice_cb.disconnect()
588
+ self.reference_event_choice_cb.clear()
589
+ if self.reference_population=='targets':
590
+ self.reference_event_choice_cb.addItems(self.target_class_cols)
591
+ self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
592
+ else:
593
+ self.reference_event_choice_cb.addItems(self.effector_class_cols)
594
+ self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
595
+
596
+ else:
597
+ try:
598
+ self.neighbor_event_choice_cb.show()
599
+ self.neigh_lab.show()
600
+ except:
601
+ pass
602
+ self.reference_event_choice_cb.disconnect()
603
+ self.reference_event_choice_cb.clear()
604
+
605
+ if self.reference_population=='targets':
606
+ self.reference_event_choice_cb.addItems(self.target_class_cols)
607
+ self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
608
+
609
+ else:
610
+ self.reference_event_choice_cb.addItems(self.effector_class_cols)
611
+ self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
612
+
613
+ self.neighbor_event_choice_cb.disconnect()
614
+ self.neighbor_event_choice_cb.clear()
615
+
616
+ if self.neighbor_population=='targets':
617
+ self.neighbor_event_choice_cb.addItems(self.target_class_cols)
618
+ self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
619
+
620
+ else:
621
+ self.neighbor_event_choice_cb.addItems(self.effector_class_cols)
622
+ self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
623
+
624
+
625
+
626
+ def create_new_relative_event_class(self):
627
+
628
+ # display qwidget to name the event
629
+ self.newClassWidget = QWidget()
630
+ self.newClassWidget.setWindowTitle('Create new event class')
631
+
632
+ layout = QVBoxLayout()
633
+ self.newClassWidget.setLayout(layout)
634
+ name_hbox = QHBoxLayout()
635
+ name_hbox.addWidget(QLabel('event name: '), 25)
636
+ self.relative_class_name_le = QLineEdit('event')
637
+ name_hbox.addWidget(self.relative_class_name_le, 75)
638
+ layout.addLayout(name_hbox)
639
+
640
+ class_labels = ['event', 'no event', 'else']
641
+ layout.addWidget(QLabel('prefill: '))
642
+ radio_box = QHBoxLayout()
643
+ self.class_option_rb = [QRadioButton() for i in range(3)]
644
+ for i,c in enumerate(self.class_option_rb):
645
+ if i==0:
646
+ c.setChecked(True)
647
+ c.setText(class_labels[i])
648
+ radio_box.addWidget(c, 33, alignment=Qt.AlignCenter)
649
+ layout.addLayout(radio_box)
650
+
651
+ btn_hbox = QHBoxLayout()
652
+ submit_btn = QPushButton('submit')
653
+ cancel_btn = QPushButton('cancel')
654
+ btn_hbox.addWidget(cancel_btn, 50)
655
+ btn_hbox.addWidget(submit_btn, 50)
656
+ layout.addLayout(btn_hbox)
657
+ submit_btn.clicked.connect(self.write_new_relative_event_class)
658
+ cancel_btn.clicked.connect(self.close_without_new_class)
659
+
660
+ self.newClassWidget.show()
661
+ center_window(self.newClassWidget)
662
+
663
+ def write_new_relative_event_class(self):
664
+
665
+ if self.relative_class_name_le.text()=='':
666
+ self.relative_class = 'class'
667
+ self.relative_time = 't0'
668
+ self.relative_status = 'status'
669
+ else:
670
+ self.relative_class = 'class_'+self.relative_class_name_le.text()
671
+ self.relative_status = self.relative_class.replace('class','status')
672
+ self.relative_time = 't0_'+self.relative_class_name_le.text()
673
+
674
+ if self.relative_class in list(self.df_relative.columns):
675
+
676
+ msgBox = QMessageBox()
677
+ msgBox.setIcon(QMessageBox.Warning)
678
+ msgBox.setText("This event name already exists. If you proceed,\nall annotated data will be rewritten. Do you wish to continue?")
679
+ msgBox.setWindowTitle("Warning")
680
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
681
+ returnValue = msgBox.exec()
682
+ if returnValue == QMessageBox.No:
683
+ return None
684
+ else:
685
+ pass
686
+
687
+ fill_option = np.where([c.isChecked() for c in self.class_option_rb])[0][0]
688
+ self.df_relative.loc[(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population),self.relative_class] = fill_option
689
+ if fill_option==0:
690
+ self.df_relative.loc[(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population),self.relative_time] = 0.1
691
+ else:
692
+ self.df_relative.loc[(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population),self.relative_time] = -1
693
+ self.relative_class_choice_cb.disconnect()
694
+ self.relative_class_choice_cb.clear()
695
+ cols = np.array(self.df_relative.columns)
696
+ self.relative_class_cols = np.array([c.startswith('class') for c in list(self.df_relative.columns)])
697
+ self.relative_class_cols = list(cols[self.relative_class_cols])
698
+ try:
699
+ self.relative_class_cols.remove('class_color')
700
+ self.relative_class_cols.remove('class_id')
701
+ except:
702
+ pass
703
+ self.relative_class_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_pair)
704
+ self.relative_class_choice_cb.addItems(self.relative_class_cols)
705
+ idx = self.relative_class_choice_cb.findText(self.relative_class)
706
+ self.relative_class_choice_cb.setCurrentIndex(idx)
707
+
708
+ self.pair_class_name = self.relative_class
709
+
710
+ self.pair_time_name = self.relative_time
711
+ self.pair_status_name = self.relative_status
712
+
713
+ self.newClassWidget.close()
714
+
715
+
716
+ def close_without_new_class(self):
717
+
718
+ self.newClassWidget.close()
719
+
720
+
721
+ def compute_status_and_colors_reference(self):
722
+
723
+ df_reference = self.dataframes[self.reference_population]
724
+ self.reference_class_name = self.reference_event_choice_cb.currentText()
725
+ self.expected_reference_status = 'status_'
726
+ suffix = self.reference_class_name.replace('class','').replace('_','')
727
+ if suffix!='':
728
+ self.expected_reference_status+='_'+suffix
729
+ self.expected_reference_time = 't_'+suffix
730
+ else:
731
+ self.expected_reference_time = 't0'
732
+
733
+ self.reference_time_name = self.expected_reference_time
734
+ self.reference_status_name = self.expected_reference_status
735
+
736
+ if self.reference_time_name in list(df_reference.columns) and self.reference_class_name in list(df_reference.columns) and not self.reference_status_name in list(df_reference.columns):
737
+ # only create the status column if it does not exist to not erase static classification results
738
+ self.make_reference_status_column()
739
+ elif self.reference_time_name in list(df_reference.columns) and self.reference_class_name in list(df_reference.columns):
740
+ # all good, do nothing
741
+ pass
742
+ else:
743
+ if not self.reference_status_name in list(df_reference.columns):
744
+ df_reference[self.reference_status_name] = 0
745
+ df_reference['status_color'] = color_from_status(0)
746
+ df_reference['class_color'] = color_from_class(1)
747
+
748
+ if not self.reference_class_name in list(df_reference.columns):
749
+ df_reference[self.reference_class_name] = 1
750
+ if not self.reference_time_name in list(df_reference.columns):
751
+ df_reference[self.reference_time_name] = -1
752
+
753
+ df_reference['status_color'] = [color_from_status(i) for i in df_reference[self.reference_status_name].to_numpy()]
754
+ df_reference['class_color'] = [color_from_class(i) for i in df_reference[self.reference_class_name].to_numpy()]
755
+
756
+ if self.reference_population=='targets':
757
+ self.extract_scatter_from_target_trajectories()
758
+ else:
759
+ self.extract_scatter_from_effector_trajectories()
760
+
761
+
762
+ def compute_status_and_colors_neighbor(self):
763
+
764
+ df_neighbors = self.dataframes[self.neighbor_population]
765
+ self.neighbor_class_name = self.neighbor_event_choice_cb.currentText()
766
+ self.expected_neighbor_status = 'status_'
767
+ suffix = self.neighbor_class_name.replace('class','').replace('_','')
768
+ if suffix!='':
769
+ self.expected_neighbor_status+='_'+suffix
770
+ self.expected_neighbor_time = 't_'+suffix
771
+ else:
772
+ self.expected_neighbor_time = 't0'
773
+
774
+ self.neighbor_time_name = self.expected_neighbor_time
775
+ self.neighbor_status_name = self.expected_neighbor_status
776
+
777
+ if self.neighbor_time_name in list(df_neighbors.columns) and self.neighbor_class_name in list(df_neighbors.columns) and not self.neighbor_status_name in list(df_neighbors.columns):
778
+ # only create the status column if it does not exist to not erase static classification results
779
+ self.make_neighbor_status_column()
780
+ elif self.neighbor_time_name in list(df_neighbors.columns) and self.neighbor_class_name in list(df_neighbors.columns):
781
+ # all good, do nothing
782
+ pass
783
+ else:
784
+ if not self.neighbor_status_name in list(df_neighbors.columns):
785
+ df_neighbors[self.neighbor_status_name] = 0
786
+ df_neighbors['status_color'] = color_from_status(0)
787
+ df_neighbors['class_color'] = color_from_class(1)
788
+
789
+ if not self.neighbor_class_name in list(df_neighbors.columns):
790
+ df_neighbors[self.neighbor_class_name] = 1
791
+ if not self.neighbor_time_name in list(df_neighbors.columns):
792
+ df_neighbors[self.neighbor_time_name] = -1
793
+
794
+ df_neighbors['status_color'] = [color_from_status(i) for i in df_neighbors[self.neighbor_status_name].to_numpy()]
795
+ df_neighbors['class_color'] = [color_from_class(i) for i in df_neighbors[self.neighbor_class_name].to_numpy()]
796
+
797
+ if self.neighbor_population=='targets':
798
+ self.extract_scatter_from_target_trajectories()
799
+ else:
800
+ self.extract_scatter_from_effector_trajectories()
801
+
802
+ # if self.df_effectors is not None:
803
+ # if self.reference_population=='effectors':
804
+ # self.effector_class_name = self.reference_event_choice_cb.currentText()
805
+ # elif self.neighbor_population == 'effectors':
806
+ # self.effector_class_name = self.neighbor_event_choice_cb.currentText()
807
+ # else:
808
+ # self.effector_class_name=''
809
+ # #self.effector_class_name = self.effector_class_choice_cb.currentText()
810
+ # self.effector_expected_status = 'status'
811
+ # suffix = self.effector_class_name.replace('class','').replace('_','')
812
+ # if suffix!='':
813
+ # self.effector_expected_status+='_'+suffix
814
+ # self.effector_expected_time = 't_'+suffix
815
+ # else:
816
+ # self.effector_expected_time = 't0'
817
+
818
+ # self.effector_time_name = self.effector_expected_time
819
+ # self.effector_status_name = self.effector_expected_status
820
+
821
+ # print('selection and expected names: ', self.effector_class_name, self.effector_expected_time, self.effector_expected_status)
822
+
823
+ # if self.effector_time_name in self.df_effectors.columns and self.effector_class_name in self.df_effectors.columns and not self.effector_status_name in self.df_effectors.columns:
824
+ # # only create the status column if it does not exist to not erase static classification results
825
+ # self.make_effector_status_column()
826
+ # elif self.effector_time_name in self.df_effectors.columns and self.effector_class_name in self.df_effectors.columns:
827
+ # # all good, do nothing
828
+ # pass
829
+ # else:
830
+ # if not self.effector_status_name in self.df_effectors.columns:
831
+ # self.df_effectors[self.effector_status_name] = 0
832
+ # self.df_effectors['status_color'] = color_from_status(0)
833
+ # self.df_effectors['class_color'] = color_from_class(1)
834
+
835
+ # if not self.effector_class_name in self.df_effectors.columns:
836
+ # self.df_effectors[self.effector_class_name] = 1
837
+ # if not self.effector_time_name in self.df_effectors.columns:
838
+ # self.df_effectors[self.effector_time_name] = -1
839
+
840
+ # self.df_effectors['status_color'] = [color_from_status(i) for i in self.df_effectors[self.effector_status_name].to_numpy()]
841
+ # self.df_effectors['class_color'] = [color_from_class(i) for i in self.df_effectors[self.effector_class_name].to_numpy()]
842
+
843
+ # self.extract_scatter_from_effector_trajectories()
844
+
845
+ def compute_status_and_colors_pair(self):
846
+
847
+ self.pair_class_name = self.relative_class_choice_cb.currentText()
848
+ print(f'{self.pair_class_name=}')
849
+
850
+ self.pair_expected_status = 'status'
851
+ suffix = self.pair_class_name.replace('class','').replace('_','',1)
852
+ if suffix!='':
853
+ self.pair_expected_status+='_'+suffix
854
+ self.pair_expected_time = 't0_'+suffix
855
+ if not self.pair_expected_time in list(self.df_relative.columns):
856
+ self.pair_expected_time = 't_'+suffix
857
+ else:
858
+ self.pair_expected_time = 't0'
859
+
860
+ self.pair_time_name = self.pair_expected_time
861
+ self.pair_status_name = self.pair_expected_status
862
+
863
+ if self.pair_time_name in self.df_relative.columns and self.pair_class_name in self.df_relative.columns and not self.pair_status_name in self.df_relative.columns:
864
+ # only create the status column if it does not exist to not erase static classification results
865
+ self.make_relative_status_column()
866
+ elif self.pair_time_name in self.df_relative.columns and self.pair_class_name in self.df_relative.columns:
867
+ # all good, do nothing
868
+ pass
869
+ else:
870
+ if not self.pair_status_name in self.df_relative.columns:
871
+ self.df_relative[self.pair_status_name] = 0
872
+ self.df_relative['status_color'] = color_from_status(0)
873
+ self.df_relative['class_color'] = color_from_class(1)
874
+
875
+ if not self.pair_class_name in self.df_relative.columns:
876
+ self.df_relative[self.pair_time_name] = 1
877
+ if not self.pair_time_name in self.df_relative.columns:
878
+ self.df_relative[self.pair_time_name] = -1
879
+
880
+ self.df_relative['status_color'] = [color_from_status(i) for i in self.df_relative[self.pair_status_name].to_numpy()]
881
+ self.df_relative['class_color'] = [color_from_class(i) for i in self.df_relative[self.pair_class_name].to_numpy()]
882
+
883
+ self.extract_scatter_from_lines()
884
+ self.give_pair_information()
885
+ self.plot_signals()
886
+
887
+ def contrast_slider_action(self):
888
+
889
+ """
890
+ Recontrast the imshow as the contrast slider is moved.
891
+ """
892
+
893
+ self.vmin = self.contrast_slider.value()[0]
894
+ self.vmax = self.contrast_slider.value()[1]
895
+ self.im.set_clim(vmin=self.vmin, vmax=self.vmax)
896
+ self.fcanvas.canvas.draw_idle()
897
+
898
+
899
+ def cancel_selection(self):
900
+
901
+ print('Canceling selection...')
902
+
903
+ self.hide_annotation_buttons()
904
+ self.correct_btn.setEnabled(False)
905
+ self.correct_btn.setText('correct')
906
+ self.cancel_btn.setEnabled(False)
907
+ self.correct_btn.disconnect()
908
+ self.correct_btn.clicked.connect(self.show_annotation_buttons)
909
+
910
+ self.reference_selection = []
911
+ self.reference_track_of_interest = None
912
+ self.give_reference_cell_information()
913
+
914
+ if len(self.pair_selection) > 0:
915
+ self.cancel_pair_selection()
916
+
917
+ if self.df_targets is not None:
918
+ self.target_selection = []
919
+ if self.df_effectors is not None:
920
+ self.effector_selection = []
921
+
922
+ _, _, neighbor_colors, initial_neighbor_colors = self.get_neighbor_sets()
923
+ _, _, reference_colors, initial_reference_colors = self.get_reference_sets()
924
+
925
+ for k, (t,idx) in enumerate(zip(self.neighbor_loc_t, self.neighbor_loc_idx)):
926
+ neighbor_colors[t][idx,0] = initial_neighbor_colors[k][0]
927
+ neighbor_colors[t][idx,1] = initial_neighbor_colors[k][1]
928
+
929
+ #for (t,idx) in (zip(self.neighbor_loc_t_not_picked,self.target_loc_idx_not_picked)):
930
+ # neighbor_colors[t][idx, 0] = initial_neighbor_colors[k][0]
931
+ # neighbor_colors[t][idx, 1] = initial_neighbor_colors[k][1]
932
+
933
+ for t in range(len(neighbor_colors)):
934
+ for ind in range(len(neighbor_colors[t])):
935
+ neighbor_colors[t][ind] = initial_neighbor_colors[t][ind]
936
+
937
+ for k, (t,idx) in enumerate(zip(self.reference_loc_t, self.reference_loc_idx)):
938
+ reference_colors[t][idx,0] = initial_reference_colors[k][0]
939
+ reference_colors[t][idx,1] = initial_reference_colors[k][1]
940
+
941
+ for (t,idx) in (zip(self.reference_loc_t_not_picked,self.reference_loc_idx_not_picked)):
942
+ reference_colors[t][idx, 0] = initial_reference_colors[t][idx,0]
943
+ reference_colors[t][idx, 1] = initial_reference_colors[t][idx,1]
944
+
945
+ for t in range(len(reference_colors)):
946
+ for ind in range(len(reference_colors[t])):
947
+ reference_colors[t][ind] = initial_reference_colors[t][ind]
948
+
949
+ self.lines_data={}
950
+ self.lines_list=[]
951
+ self.lines_plot=[]
952
+
953
+ self.selected_population = None
954
+
955
+ for i in range(self.n_signals):
956
+ self.reference_pop_option_buttons[i].setEnabled(False)
957
+
958
+ self.plot_signals()
959
+
960
+
961
+ def hide_annotation_buttons(self):
962
+
963
+ for a in self.annotation_btns_to_hide:
964
+ a.hide()
965
+ for b in [self.event_btn, self.no_event_btn, self.else_btn, self.suppr_btn]:
966
+ b.setChecked(False)
967
+ self.time_of_interest_label.setEnabled(False)
968
+ self.time_of_interest_le.setText('')
969
+ self.time_of_interest_le.setEnabled(False)
970
+
971
+
972
+ def enable_time_of_interest(self):
973
+
974
+ if self.event_btn.isChecked():
975
+ self.time_of_interest_label.setEnabled(True)
976
+ self.time_of_interest_le.setEnabled(True)
977
+ else:
978
+ self.time_of_interest_label.setEnabled(False)
979
+ self.time_of_interest_le.setEnabled(False)
980
+
981
+ def cancel_pair_selection(self):
982
+
983
+ # Unselect and recolor pair line
984
+ self.pair_selection = []
985
+ for t in range(len(self.lines_colors_status)):
986
+ for idx in range(len(self.lines_colors_status[t])):
987
+ if self.lines_colors_status[t][idx,2] == 'lime':
988
+ self.lines_colors_status[t][idx,2]=self.initial_lines_colors_status[t][idx,2]
989
+ self.lines_colors_class[t][idx,2]=self.initial_lines_colors_class[t][idx,2]
990
+
991
+ # Unselect and recolor neighbor
992
+ self.neighbor_selection =[]
993
+ self.neighbor_track_of_interest = None
994
+ _, _, colors_neigh, _ = self.get_neighbor_sets()
995
+ for k,(t,idx) in enumerate(zip(self.neigh_cell_loc_t,self.neigh_cell_loc_idx)):
996
+ colors_neigh[t][idx, 0] = self.neigh_previous_color[k][0]
997
+ colors_neigh[t][idx, 1] = self.neigh_previous_color[k][1]
998
+ self.give_neighbor_cell_information()
999
+ self.give_pair_information()
1000
+
1001
+ for i in range(self.n_signals):
1002
+ self.neighbor_pop_option_buttons[i].setEnabled(False)
1003
+ self.relative_pop_option_buttons[i].setEnabled(False)
1004
+ option = self.signal_pop_button_groups[i].checkedId()
1005
+ if option!=0:
1006
+ self.lines[i].set_xdata([])
1007
+ self.lines[i].set_ydata([])
1008
+ self.line_dt.set_xdata([])
1009
+ self.line_dt.set_ydata([])
1010
+ self.lines[i].set_label('')
1011
+
1012
+ self.correct_btn.setEnabled(False)
1013
+ self.cancel_btn.setEnabled(False)
1014
+
1015
+
1016
+ def apply_modification(self):
1017
+
1018
+ # Plot the new time
1019
+ t0 = -1
1020
+ if self.event_btn.isChecked():
1021
+ try:
1022
+ cclass = 0
1023
+ t0 = float(self.time_of_interest_le.text().replace(',', '.'))
1024
+ self.line_dt.set_xdata([t0, t0])
1025
+ self.cell_fcanvas.canvas.draw_idle()
1026
+ except Exception as e:
1027
+ print(e)
1028
+ t0 = -1
1029
+ cclass = 2
1030
+
1031
+ elif self.no_event_btn.isChecked():
1032
+ cclass = 1
1033
+
1034
+ elif self.else_btn.isChecked():
1035
+ cclass = 2
1036
+
1037
+ elif self.suppr_btn.isChecked():
1038
+ cclass = 42
1039
+
1040
+ pair_filter = (self.df_relative['REFERENCE_ID'] == self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest)&(self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population)&(~self.df_relative['status_'+self.current_neighborhood].isnull())
1041
+
1042
+ self.df_relative.loc[pair_filter, self.pair_class_name] = cclass
1043
+ self.df_relative.loc[pair_filter, self.pair_time_name] = t0
1044
+ timeline = self.df_relative.loc[pair_filter, 'FRAME'].to_numpy()
1045
+
1046
+ status = np.zeros_like(timeline)
1047
+ if t0 > 0:
1048
+ status[timeline >= t0] = 1.
1049
+ if cclass == 2:
1050
+ status[:] = 2
1051
+ if cclass > 2:
1052
+ status[:] = 42
1053
+
1054
+ status_color = [color_from_status(s, recently_modified=True) for s in status]
1055
+ class_color = [color_from_class(cclass, recently_modified=True) for i in range(len(status))]
1056
+
1057
+ self.df_relative.loc[pair_filter, self.pair_status_name] = status
1058
+ self.df_relative.loc[pair_filter, 'status_color'] = status_color
1059
+ self.df_relative.loc[pair_filter, 'class_color'] = class_color
1060
+
1061
+ # self.make_status_column()
1062
+ self.extract_scatter_from_lines()
1063
+ self.give_reference_cell_information()
1064
+ self.give_neighbor_cell_information()
1065
+ self.give_pair_information()
1066
+
1067
+ self.correct_btn.disconnect()
1068
+ self.correct_btn.clicked.connect(self.show_annotation_buttons)
1069
+ # self.cancel_btn.click()
1070
+
1071
+ self.hide_annotation_buttons()
1072
+ self.correct_btn.setEnabled(False)
1073
+ self.correct_btn.setText('correct')
1074
+ self.cancel_btn.setEnabled(False)
1075
+ self.del_shortcut.setEnabled(False)
1076
+ self.no_event_shortcut.setEnabled(False)
1077
+
1078
+ self.pair_selection=[]
1079
+ self.neighbor_selection = []
1080
+ self.neighbor_track_of_interest = None
1081
+ # but keep reference
1082
+
1083
+ #self.make_status_column()
1084
+ self.extract_scatter_from_target_trajectories()
1085
+ self.extract_scatter_from_effector_trajectories()
1086
+
1087
+ self.recolor_selection()
1088
+ self.trace_neighbors()
1089
+
1090
+ def locate_stack(self):
1091
+
1092
+ """
1093
+ Locate the target movie.
1094
+
1095
+ """
1096
+
1097
+ movies = glob(self.pos + f"movie/{self.parent_window.parent_window.movie_prefix}*.tif")
1098
+
1099
+ if len(movies)==0:
1100
+ msgBox = QMessageBox()
1101
+ msgBox.setIcon(QMessageBox.Warning)
1102
+ msgBox.setText("No movies are detected in the experiment folder. Cannot load an image to test Haralick.")
1103
+ msgBox.setWindowTitle("Warning")
1104
+ msgBox.setStandardButtons(QMessageBox.Ok)
1105
+ returnValue = msgBox.exec()
1106
+ if returnValue == QMessageBox.Yes:
1107
+ self.close()
1108
+ else:
1109
+ self.stack_path = movies[0]
1110
+ self.len_movie = self.parent_window.parent_window.len_movie
1111
+ len_movie_auto = auto_load_number_of_frames(self.stack_path)
1112
+ if len_movie_auto is not None:
1113
+ self.len_movie = len_movie_auto
1114
+ exp_config = self.exp_dir +"config.ini"
1115
+ self.channel_names, self.channels = extract_experiment_channels(exp_config)
1116
+ self.channel_names = np.array(self.channel_names)
1117
+ self.channels = np.array(self.channels)
1118
+ self.nbr_channels = len(self.channels)
1119
+
1120
+ def locate_target_tracks(self):
1121
+
1122
+ population = 'targets'
1123
+ self.target_trajectories_path = self.pos + os.sep.join(['output','tables', f'trajectories_{population}.pkl'])
1124
+ if not os.path.exists(self.target_trajectories_path):
1125
+ self.target_trajectories_path = self.target_trajectories_path.replace('.pkl','.csv')
1126
+
1127
+ if not os.path.exists(self.target_trajectories_path):
1128
+
1129
+ msgBox = QMessageBox()
1130
+ msgBox.setIcon(QMessageBox.Warning)
1131
+ msgBox.setText("The target trajectories cannot be detected...")
1132
+ msgBox.setWindowTitle("Warning")
1133
+ msgBox.setStandardButtons(QMessageBox.Ok)
1134
+ returnValue = msgBox.exec()
1135
+ self.df_targets = None
1136
+
1137
+ else:
1138
+
1139
+ # Load and prep tracks
1140
+ if self.target_trajectories_path.endswith('.pkl'):
1141
+ self.df_targets = np.load(self.target_trajectories_path, allow_pickle=True)
1142
+ else:
1143
+ self.df_targets = pd.read_csv(self.target_trajectories_path)
1144
+
1145
+ self.df_targets = self.df_targets.sort_values(by=['TRACK_ID', 'FRAME'])
1146
+
1147
+ cols = np.array(self.df_targets.columns)
1148
+ self.target_class_cols = [c for c in list(self.df_targets.columns) if c.startswith('class')]
1149
+
1150
+ try:
1151
+ self.target_class_cols.remove('class_id')
1152
+ except:
1153
+ pass
1154
+ try:
1155
+ self.target_class_cols.remove('class_color')
1156
+ except:
1157
+ pass
1158
+
1159
+ if len(self.target_class_cols)>0:
1160
+
1161
+ self.target_class_name = self.target_class_cols[0]
1162
+ self.target_expected_status = 'status'
1163
+ suffix = self.target_class_name.replace('class','').replace('_','')
1164
+ if suffix!='':
1165
+ self.target_expected_status+='_'+suffix
1166
+ self.target_expected_time = 't_'+suffix
1167
+ else:
1168
+ self.target_expected_time = 't0'
1169
+ self.target_time_name = self.target_expected_time
1170
+ self.target_status_name = self.target_expected_status
1171
+ else:
1172
+ self.target_class_name = 'class'
1173
+ self.target_time_name = 't0'
1174
+ self.target_status_name = 'status'
1175
+
1176
+ if self.target_time_name in self.df_targets.columns and self.target_class_name in self.df_targets.columns and not self.target_status_name in self.df_targets.columns:
1177
+ # only create the status column if it does not exist to not erase static classification results
1178
+ pass
1179
+ #self.make_target_status_column()
1180
+ elif self.target_time_name in self.df_targets.columns and self.target_class_name in self.df_targets.columns:
1181
+ # all good, do nothing
1182
+ pass
1183
+ else:
1184
+ if not self.target_status_name in self.df_targets.columns:
1185
+ self.df_targets[self.target_status_name] = 0
1186
+ self.df_targets['status_color'] = color_from_status(0)
1187
+ self.df_targets['class_color'] = color_from_class(1)
1188
+
1189
+ if not self.target_class_name in self.df_targets.columns:
1190
+ self.df_targets[self.target_class_name] = 1
1191
+ if not self.target_time_name in self.df_targets.columns:
1192
+ self.df_targets[self.target_time_name] = -1
1193
+
1194
+ self.df_targets['status_color'] = color_from_status(2) #[color_from_status(i) for i in self.df_targets[self.target_status_name].to_numpy()]
1195
+ self.df_targets['class_color'] = color_from_status(2) #[color_from_class(i) for i in self.df_targets[self.target_class_name].to_numpy()]
1196
+
1197
+ self.df_targets = self.df_targets.dropna(subset=['POSITION_X', 'POSITION_Y'])
1198
+ self.df_targets['x_anim'] = self.df_targets['POSITION_X'] * self.fraction
1199
+ self.df_targets['y_anim'] = self.df_targets['POSITION_Y'] * self.fraction
1200
+ self.df_targets['x_anim'] = self.df_targets['x_anim'].astype(int)
1201
+ self.df_targets['y_anim'] = self.df_targets['y_anim'].astype(int)
1202
+
1203
+ self.extract_scatter_from_target_trajectories()
1204
+ self.target_track_of_interest = self.df_targets['TRACK_ID'].min()
1205
+
1206
+ self.loc_t = []
1207
+ self.loc_idx = []
1208
+ for t in range(len(self.target_tracks)):
1209
+ indices = np.where(self.target_tracks[t]==self.target_track_of_interest)[0]
1210
+ if len(indices)>0:
1211
+ self.loc_t.append(t)
1212
+ self.loc_idx.append(indices[0])
1213
+
1214
+ self.MinMaxScaler_targets = MinMaxScaler()
1215
+ self.target_columns = list(self.df_targets.columns)
1216
+ cols_to_remove = [c for c in self.cols_to_remove if c in self.target_columns] + self.target_class_cols
1217
+ time_cols = [c for c in self.target_columns if c.startswith('t_')]
1218
+ cols_to_remove += time_cols
1219
+ neigh_cols = [c for c in self.target_columns if c.startswith('neighborhood_')]
1220
+ cols_to_remove += neigh_cols
1221
+
1222
+ for col in cols_to_remove:
1223
+ try:
1224
+ self.target_columns.remove(col)
1225
+ except:
1226
+ pass
1227
+
1228
+ x = self.df_targets[self.target_columns].values
1229
+ self.MinMaxScaler_targets.fit(x)
1230
+
1231
+ def locate_effector_tracks(self):
1232
+
1233
+ population = 'effectors'
1234
+ self.effector_trajectories_path = self.pos + os.sep.join(['output','tables',f'trajectories_{population}.pkl'])
1235
+ if not os.path.exists(self.effector_trajectories_path):
1236
+ self.effector_trajectories_path = self.effector_trajectories_path.replace('.pkl','.csv')
1237
+
1238
+ if not os.path.exists(self.effector_trajectories_path):
1239
+
1240
+ msgBox = QMessageBox()
1241
+ msgBox.setIcon(QMessageBox.Warning)
1242
+ msgBox.setText("The effector trajectories cannot be detected...")
1243
+ msgBox.setWindowTitle("Warning")
1244
+ msgBox.setStandardButtons(QMessageBox.Ok)
1245
+ returnValue = msgBox.exec()
1246
+ self.df_effectors = None
1247
+ else:
1248
+ # Load and prep tracks
1249
+ if self.effector_trajectories_path.endswith('.pkl'):
1250
+ self.df_effectors = np.load(self.effector_trajectories_path, allow_pickle=True)
1251
+ else:
1252
+ self.df_effectors = pd.read_csv(self.effector_trajectories_path)
1253
+
1254
+ try:
1255
+ self.df_effectors = self.df_effectors.sort_values(by=['TRACK_ID', 'FRAME'])
1256
+ except:
1257
+ self.df_effectors = self.df_effectors.sort_values(by=['ID', 'FRAME'])
1258
+
1259
+
1260
+ cols = np.array(self.df_effectors.columns)
1261
+ self.effector_class_cols = np.array([c.startswith('class') for c in list(self.df_effectors.columns)])
1262
+ self.effector_class_cols = list(cols[self.effector_class_cols])
1263
+ try:
1264
+ self.effector_class_cols.remove('class_id')
1265
+ except:
1266
+ pass
1267
+ try:
1268
+ self.effector_class_cols.remove('class_color')
1269
+ except:
1270
+ pass
1271
+ if len(self.effector_class_cols)>0:
1272
+ self.effector_class_name = self.effector_class_cols[0]
1273
+ self.effector_expected_status = 'status'
1274
+ suffix = self.effector_class_name.replace('class','').replace('_','')
1275
+ if suffix!='':
1276
+ self.effector_expected_status+='_'+suffix
1277
+ self.effector_expected_time = 't_'+suffix
1278
+ else:
1279
+ self.effector_expected_time = 't0'
1280
+ self.effector_time_name = self.effector_expected_time
1281
+ self.effector_status_name = self.effector_expected_status
1282
+ else:
1283
+ self.effector_class_name = 'class'
1284
+ self.effector_time_name = 't0'
1285
+ self.effector_status_name = 'status'
1286
+
1287
+ if self.effector_time_name in self.df_effectors.columns and self.effector_class_name in self.df_effectors.columns and not self.effector_status_name in self.df_effectors.columns:
1288
+ # only create the status column if it does not exist to not erase static classification results
1289
+ pass
1290
+ #self.make_effector_status_column()
1291
+ elif self.effector_time_name in self.df_effectors.columns and self.effector_class_name in self.df_effectors.columns:
1292
+ # all good, do nothing
1293
+ pass
1294
+ else:
1295
+ if not self.effector_status_name in self.df_effectors.columns:
1296
+ self.df_effectors[self.effector_status_name] = 0
1297
+ self.df_effectors['status_color'] = color_from_status(0)
1298
+ self.df_effectors['class_color'] = color_from_class(1)
1299
+
1300
+ if not self.effector_class_name in self.df_effectors.columns:
1301
+ self.df_effectors[self.effector_class_name] = 1
1302
+ if not self.effector_time_name in self.df_effectors.columns:
1303
+ self.df_effectors[self.effector_time_name] = -1
1304
+
1305
+ self.df_effectors['status_color'] = color_from_status(2) #[color_from_status(i) for i in self.df_effectors[self.effector_status_name].to_numpy()]
1306
+ self.df_effectors['class_color'] = color_from_status(2) #[color_from_class(i) for i in self.df_effectors[self.effector_class_name].to_numpy()]
1307
+
1308
+
1309
+ self.df_effectors = self.df_effectors.dropna(subset=['POSITION_X', 'POSITION_Y'])
1310
+ self.df_effectors['x_anim'] = self.df_effectors['POSITION_X'] * self.fraction
1311
+ self.df_effectors['y_anim'] = self.df_effectors['POSITION_Y'] * self.fraction
1312
+ self.df_effectors['x_anim'] = self.df_effectors['x_anim'].astype(int)
1313
+ self.df_effectors['y_anim'] = self.df_effectors['y_anim'].astype(int)
1314
+
1315
+ self.extract_scatter_from_effector_trajectories()
1316
+ try:
1317
+ self.effector_track_of_interest = self.df_effectors['TRACK_ID'].min()
1318
+ except:
1319
+ self.effector_track_of_interest = self.df_effectors['ID'].min()
1320
+
1321
+
1322
+ self.loc_t = []
1323
+ self.loc_idx = []
1324
+ for t in range(len(self.effector_tracks)):
1325
+ indices = np.where(self.effector_tracks[t]==self.effector_track_of_interest)[0]
1326
+ if len(indices)>0:
1327
+ self.loc_t.append(t)
1328
+ self.loc_idx.append(indices[0])
1329
+
1330
+ self.MinMaxScaler_effectors = MinMaxScaler()
1331
+ self.effector_columns = list(self.df_effectors.columns)
1332
+ cols_to_remove = [c for c in self.cols_to_remove if c in self.effector_columns] + self.effector_class_cols
1333
+ time_cols = [c for c in self.effector_columns if c.startswith('t_')]
1334
+ cols_to_remove += time_cols
1335
+ neigh_cols = [c for c in self.effector_columns if c.startswith('neighborhood_')]
1336
+ cols_to_remove += neigh_cols
1337
+
1338
+ for col in cols_to_remove:
1339
+ try:
1340
+ self.effector_columns.remove(col)
1341
+ except:
1342
+ pass
1343
+
1344
+ x = self.df_effectors[self.effector_columns].to_numpy()
1345
+ print(self.effector_columns, x, x.shape)
1346
+ self.MinMaxScaler_effectors.fit(x)
1347
+
1348
+
1349
+ # def make_effector_status_column(self):
1350
+ # print('remaking the status column for the effectors')
1351
+ # for tid, group in self.df_effectors.groupby('TRACK_ID'):
1352
+
1353
+ # indices = group.index
1354
+ # t0 = group[self.].to_numpy()[0]
1355
+ # cclass = group[self.class_name].to_numpy()[0]
1356
+ # timeline = group['FRAME'].to_numpy()
1357
+ # status = np.zeros_like(timeline)
1358
+ # if t0 > 0:
1359
+ # status[timeline >= t0] = 1.
1360
+ # if cclass == 2:
1361
+ # status[:] = 2
1362
+ # if cclass > 2:
1363
+ # status[:] = 42
1364
+ # status_color = [color_from_status(s) for s in status]
1365
+ # class_color = [color_from_class(cclass) for i in range(len(status))]
1366
+
1367
+ # self.df_tracks.loc[indices, self.status_name] = status
1368
+ # self.df_tracks.loc[indices, 'status_color'] = status_color
1369
+ # self.df_tracks.loc[indices, 'class_color'] = class_color
1370
+
1371
+
1372
+
1373
+ def locate_relative_tracks(self):
1374
+
1375
+ population = 'relative'
1376
+ self.relative_trajectories_path = self.pos + os.sep.join(['output','tables','trajectories_pairs.csv'])
1377
+
1378
+ if not os.path.exists(self.relative_trajectories_path):
1379
+
1380
+ msgBox = QMessageBox()
1381
+ msgBox.setIcon(QMessageBox.Warning)
1382
+ msgBox.setText("The pair measurements cannot be detected... Please measure the pairs first.")
1383
+ msgBox.setWindowTitle("Warning")
1384
+ msgBox.setStandardButtons(QMessageBox.Ok)
1385
+ returnValue = msgBox.exec()
1386
+ self.close()
1387
+ else:
1388
+ # Load and prep tracks
1389
+ self.df_relative = pd.read_csv(self.relative_trajectories_path)
1390
+ print(self.df_relative.columns)
1391
+ self.df_relative= self.df_relative.sort_values(by=['REFERENCE_ID','NEIGHBOR_ID','reference_population','neighbor_population','FRAME'])
1392
+ self.relative_cols = np.array(self.df_relative.columns)
1393
+
1394
+ self.relative_class_cols = [c for c in list(self.df_relative.columns) if c.startswith('class')]
1395
+
1396
+ if len(self.relative_class_cols) > 0:
1397
+ self.relative_class_name = self.relative_class_cols[0]
1398
+ self.relative_expected_status = 'status'
1399
+ suffix = self.relative_class_name.replace('class', '').replace('_', '')
1400
+ if suffix != '':
1401
+ self.relative_expected_status += '_' + suffix
1402
+ self.relative_expected_time = 't_' + suffix
1403
+ else:
1404
+ self.relative_expected_time = 't0_arrival'
1405
+ self.relative_time_name = self.relative_expected_time
1406
+ self.relative_status_name = self.relative_expected_status
1407
+ else:
1408
+ self.relative_class_name = 'class'
1409
+ self.relative_time_name = 't0'
1410
+ self.relative_status_name = 'status'
1411
+
1412
+
1413
+ self.MinMaxScaler_pairs = MinMaxScaler()
1414
+ self.pair_columns = list(self.df_relative.columns)
1415
+ cols_to_remove = [c for c in self.cols_to_remove if c in self.pair_columns] + self.relative_class_cols
1416
+ time_cols = [c for c in self.pair_columns if c.startswith('t0_') or c.startswith('t_')]
1417
+ cols_to_remove += time_cols
1418
+ neigh_cols = [c for c in self.pair_columns if c.startswith('neighborhood_')]
1419
+ cols_to_remove += neigh_cols
1420
+
1421
+ for col in cols_to_remove:
1422
+ try:
1423
+ self.pair_columns.remove(col)
1424
+ except:
1425
+ pass
1426
+
1427
+ x = self.df_relative[self.pair_columns].values
1428
+ self.MinMaxScaler_pairs.fit(x)
1429
+
1430
+
1431
+ def set_reference_and_neighbor_populations(self):
1432
+
1433
+ neigh = self.neighborhood_choice_cb.currentText()
1434
+ self.current_neighborhood = neigh.replace('target_ref_','').replace('effector_ref_','')
1435
+ self.reference_population = ['targets' if 'target' in neigh else 'effectors'][0]
1436
+ self.neighbor_population = self.df_relative.loc[(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population), 'neighbor_population'].values[0]
1437
+
1438
+ print(f'Current neighborhood: {self.current_neighborhood}')
1439
+ print(f'New reference population: {self.reference_population}')
1440
+ print(f'New neighbor population: {self.neighbor_population}')
1441
+
1442
+ idx = self.relative_class_choice_cb.findText('class_'+self.current_neighborhood)
1443
+ if idx is not None:
1444
+ self.relative_class_choice_cb.setCurrentIndex(idx)
1445
+
1446
+ def make_reference_status_column(self):
1447
+
1448
+ df_reference = self.dataframes[self.reference_population]
1449
+ print('remaking the status column')
1450
+
1451
+ for tid, group in df_reference.groupby('TRACK_ID'):
1452
+
1453
+ indices = group.index
1454
+ t0 = group[self.reference_time_name].to_numpy()[0]
1455
+ cclass = group[self.reference_class_name].to_numpy()[0]
1456
+ timeline = group['FRAME'].to_numpy()
1457
+ status = np.zeros_like(timeline)
1458
+ if t0 > 0:
1459
+ status[timeline>=t0] = 1.
1460
+ if cclass==2:
1461
+ status[:] = 2
1462
+ if cclass>2:
1463
+ status[:] = 42
1464
+ status_color = [color_from_status(s) for s in status]
1465
+ class_color = [color_from_class(cclass) for i in range(len(status))]
1466
+
1467
+ df_reference.loc[indices, self.reference_status_name] = status
1468
+ df_reference.loc[indices, 'status_color'] = status_color
1469
+ df_reference.loc[indices, 'class_color'] = class_color
1470
+
1471
+ def make_relative_status_column(self):
1472
+
1473
+ pair_filter = self.df_relative.loc[~(self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population), :]
1474
+
1475
+ for tid, group in pair_filter.groupby(['REFERENCE_ID','NEIGHBOR_ID','reference_population','neighbor_population']):
1476
+
1477
+ indices = group.index
1478
+ t0 = group[self.pair_time_name].to_numpy()[0]
1479
+ cclass = group[self.pair_class_name].to_numpy()[0]
1480
+ timeline = group['FRAME'].to_numpy()
1481
+ status = np.zeros_like(timeline)
1482
+ if t0 > 0:
1483
+ status[timeline>=t0] = 1.
1484
+ if cclass==2:
1485
+ status[:] = 2
1486
+ if cclass>2:
1487
+ status[:] = 42
1488
+ print(t0, status)
1489
+ status_color = [color_from_status(s) for s in status]
1490
+ class_color = [color_from_class(cclass) for i in range(len(status))]
1491
+
1492
+ self.df_relative.loc[indices, self.pair_status_name] = status
1493
+ self.df_relative.loc[indices, 'status_color'] = status_color
1494
+ self.df_relative.loc[indices, 'class_color'] = class_color
1495
+
1496
+ def make_neighbor_status_column(self):
1497
+
1498
+ df_neighbors = self.dataframes[self.neighbor_population]
1499
+ print('remaking the status column')
1500
+
1501
+ for tid, group in df_neighbors.groupby('TRACK_ID'):
1502
+
1503
+ indices = group.index
1504
+ t0 = group[self.neighbor_time_name].to_numpy()[0]
1505
+ cclass = group[self.neighbor_class_name].to_numpy()[0]
1506
+ timeline = group['FRAME'].to_numpy()
1507
+ status = np.zeros_like(timeline)
1508
+ if t0 > 0:
1509
+ status[timeline>=t0] = 1.
1510
+ if cclass==2:
1511
+ status[:] = 2
1512
+ if cclass>2:
1513
+ status[:] = 42
1514
+ status_color = [color_from_status(s) for s in status]
1515
+ class_color = [color_from_class(cclass) for i in range(len(status))]
1516
+
1517
+ df_neighbors.loc[indices, self.neighbor_status_name] = status
1518
+ df_neighbors.loc[indices, 'status_color'] = status_color
1519
+ df_neighbors.loc[indices, 'class_color'] = class_color
1520
+
1521
+ def fill_signal_choices(self):
1522
+
1523
+
1524
+ self.reference_signals = list(self.dataframes[self.reference_population].columns)
1525
+ self.neighbor_signals = list(self.dataframes[self.neighbor_population].columns)
1526
+ self.relative_signals = list(self.relative_cols)
1527
+
1528
+ self.cols_to_remove.extend([c for c in self.reference_signals if c.startswith('neighborhood')])
1529
+ self.cols_to_remove.extend([c for c in self.neighbor_signals if c.startswith('neighborhood')])
1530
+
1531
+ for c in self.cols_to_remove:
1532
+ if c in self.reference_signals:
1533
+ self.reference_signals.remove(c)
1534
+ if c in self.neighbor_signals:
1535
+ self.neighbor_signals.remove(c)
1536
+ if c in self.relative_signals:
1537
+ self.relative_signals.remove(c)
1538
+
1539
+ self.update_signal_choices(0)
1540
+ self.update_signal_choices(1)
1541
+ self.update_signal_choices(2)
1542
+
1543
+
1544
+ def update_signal_choices(self, index):
1545
+
1546
+ self.signal_choices[index].disconnect()
1547
+
1548
+ current_idx = self.signal_choices[index].currentIndex()
1549
+ if current_idx==-1:
1550
+ current_idx = 0
1551
+
1552
+ self.signal_choices[index].clear()
1553
+ if self.reference_pop_option_buttons[index].isChecked():
1554
+ self.signal_choices[index].addItems(['--'] + self.reference_signals)
1555
+ self.signal_choices[index].setCurrentIndex(current_idx)
1556
+ if self.neighbor_pop_option_buttons[index].isChecked():
1557
+ self.signal_choices[index].addItems(['--'] + self.neighbor_signals)
1558
+ self.signal_choices[index].setCurrentIndex(current_idx)
1559
+ if self.relative_pop_option_buttons[index].isChecked():
1560
+ self.signal_choices[index].addItems(['--'] + self.relative_signals)
1561
+ self.signal_choices[index].setCurrentIndex(current_idx)
1562
+
1563
+ self.signal_choices[index].currentIndexChanged.connect(self.plot_signals)
1564
+
1565
+ self.plot_signals()
1566
+
1567
+ def generate_signal_choices(self):
1568
+
1569
+ self.signal_choices = []
1570
+ self.signal_labels = []
1571
+ self.n_signals = 3
1572
+
1573
+ self.signal_choices = [QSearchableComboBox() for i in range(self.n_signals)]
1574
+ self.signal_pop_button_groups = [QButtonGroup() for i in range(self.n_signals)]
1575
+ self.reference_pop_option_buttons = [QRadioButton() for i in range(self.n_signals)]
1576
+ self.neighbor_pop_option_buttons = [QRadioButton() for i in range(self.n_signals)]
1577
+ self.relative_pop_option_buttons = [QRadioButton() for i in range(self.n_signals)]
1578
+
1579
+ for i in range(self.n_signals):
1580
+
1581
+ self.signal_pop_button_groups[i].addButton(self.reference_pop_option_buttons[i], 0)
1582
+ self.signal_pop_button_groups[i].addButton(self.neighbor_pop_option_buttons[i], 1)
1583
+ self.signal_pop_button_groups[i].addButton(self.relative_pop_option_buttons[i], 2)
1584
+
1585
+ self.signal_choices[i].currentIndexChanged.connect(self.plot_signals)
1586
+ self.reference_pop_option_buttons[i].toggled.connect(partial(self.update_signal_choices,i))
1587
+ self.neighbor_pop_option_buttons[i].toggled.connect(partial(self.update_signal_choices,i))
1588
+ self.relative_pop_option_buttons[i].toggled.connect(partial(self.update_signal_choices,i))
1589
+
1590
+ self.reference_pop_option_buttons[i].setEnabled(False)
1591
+ self.neighbor_pop_option_buttons[i].setEnabled(False)
1592
+ self.relative_pop_option_buttons[i].setEnabled(False)
1593
+
1594
+ def plot_signals(self):
1595
+
1596
+ range_values = []
1597
+
1598
+ if self.reference_track_of_interest is None and self.neighbor_track_of_interest is None:
1599
+ # No cell selected, plot nothing
1600
+ for t in self.cell_ax.texts:
1601
+ t.remove()
1602
+ self.cell_ax.text(0.5, 0.5, "No data available", horizontalalignment='center', verticalalignment='center', transform=self.cell_ax.transAxes)
1603
+ for i in range(self.n_signals):
1604
+ self.lines[i].set_xdata([])
1605
+ self.lines[i].set_ydata([])
1606
+ self.lines[i].set_label('')
1607
+ self.line_dt.set_xdata([])
1608
+ self.line_dt.set_ydata([])
1609
+ self.cell_fcanvas.canvas.draw()
1610
+ return None
1611
+ else:
1612
+ for t in self.cell_ax.texts:
1613
+ t.remove()
1614
+
1615
+ # Plot signals
1616
+ for i in range(self.n_signals):
1617
+
1618
+ signal = []; timeline = [];
1619
+ signal_txt = self.signal_choices[i].currentText()
1620
+ option = self.signal_pop_button_groups[i].checkedId()
1621
+
1622
+ if option==0 and self.reference_track_of_interest is not None and signal_txt!='--' and signal_txt!='':
1623
+
1624
+ df_reference = self.dataframes[self.reference_population]
1625
+ self.lines[i].set_label(f'reference ({self.reference_population}) '+ signal_txt)
1626
+
1627
+ signal = df_reference.loc[df_reference['TRACK_ID']==self.reference_track_of_interest, signal_txt].to_numpy()
1628
+ timeline = df_reference.loc[df_reference['TRACK_ID']==self.reference_track_of_interest, 'FRAME'].to_numpy()
1629
+ range_values.extend(df_reference.loc[:,signal_txt].values)
1630
+
1631
+ elif option==1 and self.neighbor_track_of_interest is not None and signal_txt!='--' and signal_txt!='':
1632
+
1633
+ df_neighbor = self.dataframes[self.neighbor_population]
1634
+ self.lines[i].set_label(f'neighbor ({self.neighbor_population}) '+ signal_txt)
1635
+
1636
+ signal = df_neighbor.loc[df_neighbor['TRACK_ID']==self.neighbor_track_of_interest, signal_txt].to_numpy()
1637
+ timeline = df_neighbor.loc[df_neighbor['TRACK_ID']==self.neighbor_track_of_interest, 'FRAME'].to_numpy()
1638
+ range_values.extend(df_neighbor.loc[:,signal_txt].values)
1639
+
1640
+ elif option==2 and self.reference_track_of_interest is not None and self.neighbor_track_of_interest is not None and signal_txt!='--' and signal_txt!='':
1641
+
1642
+ self.lines[i].set_label(f'pair '+signal_txt)
1643
+ signal = self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest)&(self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population), signal_txt].to_numpy()
1644
+ timeline = self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest)&(self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population), 'FRAME'].to_numpy()
1645
+ range_values.extend(self.df_relative.loc[(self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population), signal_txt].values)
1646
+ else:
1647
+ self.lines[i].set_label('')
1648
+
1649
+
1650
+ self.lines[i].set_xdata(timeline)
1651
+ self.lines[i].set_ydata(signal)
1652
+ self.lines[i].set_color(tab10(i / float(self.n_signals)))
1653
+
1654
+ #self.configure_ylims()
1655
+ if len(range_values)>0:
1656
+ range_values = np.array(range_values)
1657
+ if len(range_values[range_values==range_values])>0:
1658
+ if len(range_values[range_values>0])>0:
1659
+ self.value_magnitude = np.nanpercentile(range_values, 1)
1660
+ else:
1661
+ self.value_magnitude = 1
1662
+ self.non_log_ymin = 0.98*np.nanmin(range_values)
1663
+ self.non_log_ymax = np.nanmax(range_values)*1.02
1664
+ if self.cell_ax.get_yscale()=='linear':
1665
+ self.cell_ax.set_ylim(self.non_log_ymin, self.non_log_ymax)
1666
+ else:
1667
+ self.cell_ax.set_ylim(self.value_magnitude, self.non_log_ymax)
1668
+
1669
+ if self.reference_track_of_interest is not None and self.neighbor_track_of_interest is not None:
1670
+ t0 = self.df_relative.loc[(self.df_relative['REFERENCE_ID'] == self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID'] == self.neighbor_track_of_interest)&(self.df_relative['reference_population'] == self.reference_population)&(self.df_relative['neighbor_population'] == self.neighbor_population), self.pair_time_name].dropna().to_numpy()
1671
+ if t0!=[]:
1672
+ t0=t0[0]
1673
+ ymin,ymax = self.cell_ax.get_ylim()
1674
+ self.line_dt.set_xdata([t0, t0])
1675
+ self.line_dt.set_ydata([ymin,ymax])
1676
+
1677
+ self.cell_ax.legend()
1678
+ self.cell_fcanvas.canvas.draw()
1679
+
1680
+
1681
+ def extract_scatter_from_lines(self):
1682
+
1683
+ self.lines_list = []
1684
+ self.lines_tracks=[]
1685
+ self.lines_colors_status = []
1686
+ self.initial_lines_colors_status=[]
1687
+ self.lines_colors_class = []
1688
+ self.initial_lines_colors_class=[]
1689
+
1690
+ for t in np.arange(self.len_movie):
1691
+
1692
+ # Append frame_positions to self.line_positions
1693
+ self.lines_tracks.append(self.df_relative.loc[(self.df_relative['FRAME'] == t)&(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population), ['REFERENCE_ID', 'NEIGHBOR_ID']].to_numpy())
1694
+ self.initial_lines_colors_status.append(self.df_relative.loc[(self.df_relative['FRAME'] == t)&(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population), ['REFERENCE_ID', 'NEIGHBOR_ID','status_color']].to_numpy())
1695
+ self.lines_colors_status.append(self.df_relative.loc[(self.df_relative['FRAME'] == t)&(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population), ['REFERENCE_ID', 'NEIGHBOR_ID','status_color']].to_numpy())
1696
+ self.initial_lines_colors_class.append(self.df_relative.loc[(self.df_relative['FRAME'] == t)&(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population), ['REFERENCE_ID', 'NEIGHBOR_ID','class_color']].to_numpy())
1697
+ self.lines_colors_class.append(self.df_relative.loc[(self.df_relative['FRAME'] == t)&(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population), ['REFERENCE_ID', 'NEIGHBOR_ID','class_color']].to_numpy())
1698
+
1699
+ def extract_scatter_from_target_trajectories(self):
1700
+
1701
+ print('extracting scatter from target trajectories...')
1702
+
1703
+ self.target_positions = []
1704
+ self.target_colors = []
1705
+ self.target_tracks = []
1706
+ self.initial_target_colors = []
1707
+
1708
+ for t in np.arange(self.len_movie):
1709
+
1710
+ if self.df_targets is not None:
1711
+ self.target_positions.append(self.df_targets.loc[self.df_targets['FRAME']==t,['x_anim', 'y_anim']].to_numpy())
1712
+ self.target_colors.append(self.df_targets.loc[self.df_targets['FRAME']==t,['class_color', 'status_color']].to_numpy())
1713
+ self.initial_target_colors.append(
1714
+ self.df_targets.loc[self.df_targets['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
1715
+ try:
1716
+ self.target_tracks.append(self.df_targets.loc[self.df_targets['FRAME']==t, 'TRACK_ID'].to_numpy())
1717
+ except:
1718
+ self.target_tracks.append(
1719
+ self.df_targets.loc[self.df_targets['FRAME'] == t, 'ID'].to_numpy())
1720
+
1721
+
1722
+ def extract_scatter_from_effector_trajectories(self):
1723
+
1724
+ self.effector_positions = []
1725
+ self.effector_colors = []
1726
+ self.initial_effector_colors=[]
1727
+ self.effector_tracks = []
1728
+
1729
+ for t in np.arange(self.len_movie):
1730
+
1731
+ if self.df_effectors is not None:
1732
+
1733
+ self.effector_positions.append(self.df_effectors.loc[self.df_effectors['FRAME']==t,['x_anim', 'y_anim']].to_numpy())
1734
+ self.effector_colors.append(self.df_effectors.loc[self.df_effectors['FRAME']==t,['class_color', 'status_color']].to_numpy())
1735
+ self.initial_effector_colors.append(self.df_effectors.loc[self.df_effectors['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
1736
+ try:
1737
+ self.effector_tracks.append(self.df_effectors.loc[self.df_effectors['FRAME']==t, 'TRACK_ID'].to_numpy())
1738
+ except:
1739
+ self.effector_tracks.append(
1740
+ self.df_effectors.loc[self.df_effectors['FRAME'] == t, 'ID'].to_numpy())
1741
+
1742
+ def load_annotator_config(self):
1743
+
1744
+ """
1745
+ Load settings from config or set default values.
1746
+ """
1747
+
1748
+ print('Reading instructions..')
1749
+ if os.path.exists(self.instructions_path):
1750
+ with open(self.instructions_path, 'r') as f:
1751
+
1752
+ instructions = json.load(f)
1753
+ print(f'Reading instructions: {instructions}')
1754
+
1755
+ if 'rgb_mode' in instructions:
1756
+ self.rgb_mode = instructions['rgb_mode']
1757
+ else:
1758
+ self.rgb_mode = False
1759
+
1760
+ if 'percentile_mode' in instructions:
1761
+ self.percentile_mode = instructions['percentile_mode']
1762
+ else:
1763
+ self.percentile_mode = True
1764
+
1765
+ if 'channels' in instructions:
1766
+ self.target_channels = instructions['channels']
1767
+ else:
1768
+ self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
1769
+
1770
+ if 'fraction' in instructions:
1771
+ self.fraction = float(instructions['fraction'])
1772
+ else:
1773
+ self.fraction = 0.25
1774
+
1775
+ if 'interval' in instructions:
1776
+ self.anim_interval = int(instructions['interval'])
1777
+ else:
1778
+ self.anim_interval = 1
1779
+
1780
+ if 'log' in instructions:
1781
+ self.log_option = instructions['log']
1782
+ else:
1783
+ self.log_option = False
1784
+ else:
1785
+ self.rgb_mode = False
1786
+ self.log_option = False
1787
+ self.percentile_mode = True
1788
+ self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
1789
+ self.fraction = 0.25
1790
+ self.anim_interval = 1
1791
+
1792
+ def prepare_stack(self):
1793
+
1794
+ self.img_num_channels = _get_img_num_per_channel(self.channels, self.len_movie, self.nbr_channels)
1795
+ self.stack = []
1796
+ for ch in tqdm(self.target_channels, desc="channel"):
1797
+ target_ch_name = ch[0]
1798
+ if self.percentile_mode:
1799
+ normalize_kwargs = {"percentiles": (ch[1], ch[2]), "values": None}
1800
+ else:
1801
+ normalize_kwargs = {"values": (ch[1], ch[2]), "percentiles": None}
1802
+
1803
+ if self.rgb_mode:
1804
+ normalize_kwargs.update({'amplification': 255., 'clip': True})
1805
+
1806
+ chan = []
1807
+ indices = self.img_num_channels[self.channels[np.where(self.channel_names==target_ch_name)][0]]
1808
+ for t in tqdm(range(len(indices)),desc='FRAME'):
1809
+ if self.rgb_mode:
1810
+ f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=True, normalize_kwargs=normalize_kwargs)
1811
+ f = f.astype(np.uint8)
1812
+ else:
1813
+ f = load_frames(indices[t], self.stack_path, scale=self.fraction, normalize_input=False)
1814
+ chan.append(f[:,:,0])
1815
+
1816
+ self.stack.append(chan)
1817
+
1818
+ self.stack = np.array(self.stack)
1819
+ if self.rgb_mode:
1820
+ self.stack = np.moveaxis(self.stack, 0, -1)
1821
+ else:
1822
+ self.stack = self.stack[0]
1823
+ if self.log_option:
1824
+ self.stack[np.where(self.stack>0.)] = np.log(self.stack[np.where(self.stack>0.)])
1825
+
1826
+ print(f'Load stack of shape: {self.stack.shape}.')
1827
+
1828
+ def neighborhood_changed(self):
1829
+
1830
+ self.cancel_selection()
1831
+ self.set_reference_and_neighbor_populations()
1832
+ # Update reference classes and neighbor classes
1833
+ self.fill_class_cbs()
1834
+
1835
+ self.update_cell_events()
1836
+ self.extract_scatter_from_lines()
1837
+ # self.draw_frame(self.framedata)
1838
+ self.plot_signals()
1839
+
1840
+
1841
+ def closeEvent(self, event):
1842
+
1843
+ self.stop()
1844
+ # result = QMessageBox.question(self,
1845
+ # "Confirm Exit...",
1846
+ # "Are you sure you want to exit ?",
1847
+ # QMessageBox.Yes| QMessageBox.No,
1848
+ # )
1849
+ del self.stack
1850
+ gc.collect()
1851
+
1852
+ def looped_animation(self):
1853
+
1854
+ """
1855
+ Load an image.
1856
+
1857
+ """
1858
+
1859
+ self.framedata = 0
1860
+
1861
+ self.fig, self.ax = plt.subplots(tight_layout=True)
1862
+ self.fcanvas = FigureCanvas(self.fig, interactive=True)
1863
+ self.ax.clear()
1864
+
1865
+ if not hasattr(self, 'lines'):
1866
+ self.lines_data = {}
1867
+
1868
+ self.im = self.ax.imshow(self.stack[0], cmap='gray', vmin=np.nanpercentile(self.stack, 1), vmax=np.nanpercentile(self.stack, 99.99))
1869
+
1870
+
1871
+ if self.df_targets is not None:
1872
+ self.target_status_scatter = self.ax.scatter(self.target_positions[0][:,0], self.target_positions[0][:,1], marker="x", c=self.target_colors[0][:,1], s=50, picker=True, pickradius=10)
1873
+ self.target_class_scatter = self.ax.scatter(self.target_positions[0][:,0], self.target_positions[0][:,1], marker='o', facecolors='none',edgecolors=self.target_colors[0][:,0], s=200)
1874
+ else:
1875
+ self.target_status_scatter = self.ax.scatter([],[], marker="x", s=50, picker=True, pickradius=10)
1876
+ self.target_class_scatter = self.ax.scatter([],[], marker='o', facecolors='none', s=200)
1877
+
1878
+ if self.df_effectors is not None:
1879
+ self.effector_status_scatter = self.ax.scatter(self.effector_positions[0][:,0], self.effector_positions[0][:,1], marker="x", c=self.effector_colors[0][:,1], s=50, picker=True, pickradius=10)
1880
+ self.effector_class_scatter = self.ax.scatter(self.effector_positions[0][:,0], self.effector_positions[0][:,1], marker='^', facecolors='none',edgecolors=self.effector_colors[0][:,0], s=200)
1881
+ else:
1882
+ self.effector_status_scatter = self.ax.scatter([], [], marker="x", s=50, picker=True, pickradius=10)
1883
+ self.effector_class_scatter = self.ax.scatter([],[], marker='^', facecolors='none', s=200)
1884
+
1885
+ self.points=self.ax.scatter([], [], marker="$\Join$", s=100, picker=True, pickradius=10, zorder=10) #picker=True, pickradius=10
1886
+
1887
+ self.ax.set_xticks([])
1888
+ self.ax.set_yticks([])
1889
+ self.ax.set_aspect('equal')
1890
+
1891
+ self.fig.set_facecolor('none') # or 'None'
1892
+ self.fig.canvas.setStyleSheet("background-color: black;")
1893
+
1894
+ self.anim = FuncAnimation(
1895
+ self.fig,
1896
+ self.draw_frame,
1897
+ frames = self.len_movie, # better would be to cast np.arange(len(movie)) in case frame column is incomplete
1898
+ interval = self.anim_interval, # in ms
1899
+ blit=True,
1900
+ )
1901
+
1902
+ self.fig.canvas.mpl_connect('pick_event', self.on_scatter_pick)
1903
+ self.fcanvas.canvas.draw()
1904
+
1905
+
1906
+ def create_cell_signal_canvas(self):
1907
+
1908
+ self.cell_fig, self.cell_ax = plt.subplots()
1909
+ self.cell_fcanvas = FigureCanvas(self.cell_fig, interactive=True)
1910
+ self.cell_ax.clear()
1911
+
1912
+ spacing = 0.5
1913
+ minorLocator = MultipleLocator(1)
1914
+ self.cell_ax.xaxis.set_minor_locator(minorLocator)
1915
+ self.cell_ax.xaxis.set_major_locator(MultipleLocator(5))
1916
+ self.cell_ax.grid(which = 'major')
1917
+ self.cell_ax.set_xlabel("time [frame]")
1918
+ self.cell_ax.set_ylabel("signal")
1919
+
1920
+ self.cell_fig.set_facecolor('none') # or 'None'
1921
+ self.cell_fig.canvas.setStyleSheet("background-color: transparent;")
1922
+
1923
+ self.lines = [self.cell_ax.plot([np.linspace(0,self.len_movie-1,self.len_movie)],[np.zeros((self.len_movie))])[0] for i in range(len(self.signal_choices))]
1924
+ for i in range(len(self.lines)):
1925
+ self.lines[i].set_label(f'signal {i}')
1926
+
1927
+ min_val,max_val = self.cell_ax.get_ylim()
1928
+ self.line_dt, = self.cell_ax.plot([-1,-1],[min_val,max_val],c="k",linestyle="--")
1929
+
1930
+ self.cell_ax.set_xlim(0,self.len_movie)
1931
+ self.cell_ax.legend()
1932
+ self.cell_fcanvas.canvas.draw()
1933
+
1934
+ #self.plot_signals()
1935
+
1936
+
1937
+ def on_scatter_pick(self, event):
1938
+
1939
+ self.identify_closest_marker(event)
1940
+ print(self.pair_selected, self.reference_selection)
1941
+
1942
+ _, tracks, _, _ = self.get_reference_sets()
1943
+
1944
+ if self.selected_population == self.reference_population:
1945
+
1946
+ if self.index is not None:
1947
+ toi = tracks[self.framedata][self.index]
1948
+
1949
+ if len(self.reference_selection)==0:
1950
+
1951
+ self.reference_track_of_interest = toi
1952
+ self.reference_selection.append(self.reference_track_of_interest)
1953
+
1954
+ self.get_neighbors_of_selected_cell(self.reference_track_of_interest)
1955
+ print(f'You selected track {self.reference_track_of_interest} with {len(self.neighbors)} neighbors...')
1956
+
1957
+ self.give_reference_cell_information()
1958
+ self.give_neighbor_cell_information()
1959
+ self.give_pair_information()
1960
+
1961
+ self.recolor_selection()
1962
+ self.trace_neighbors()
1963
+
1964
+ for i in range(self.n_signals):
1965
+ self.reference_pop_option_buttons[i].setEnabled(True)
1966
+
1967
+ self.plot_signals()
1968
+
1969
+ elif len(self.reference_selection) > 0 and toi in self.reference_selection and not self.pair_selected:
1970
+
1971
+ self.cancel_btn.click()
1972
+ self.cancel_selection()
1973
+
1974
+ elif len(self.reference_selection) > 0 and toi in self.neighbors and self.neighbor_population==self.reference_population and not self.pair_selected:
1975
+ if len(self.pair_selection)==0:
1976
+ self.neighbor_track_of_interest = toi
1977
+ self.highlight_the_pair()
1978
+ else:
1979
+ self.cancel_pair_selection()
1980
+ else:
1981
+ print('one cell already selected... skip... ')
1982
+ pass
1983
+ elif len(self.reference_selection) > 0 and not self.pair_selected:
1984
+
1985
+ print('You are picking a cell from the neighbor population...')
1986
+ _, tracks, _, _ = self.get_neighbor_sets()
1987
+ if self.index is not None:
1988
+ toi = tracks[self.framedata][self.index]
1989
+
1990
+ if toi in self.neighbors and len(self.reference_selection) > 0:
1991
+ if len(self.pair_selection)==0:
1992
+ self.neighbor_track_of_interest = toi
1993
+ print('highlight pair!')
1994
+ self.highlight_the_pair()
1995
+ else:
1996
+ print('cancel pair!')
1997
+ self.cancel_pair_selection()
1998
+ else:
1999
+ self.cancel_pair_selection()
2000
+
2001
+ if self.pair_selected and len(self.reference_selection)>0:
2002
+
2003
+ print('You selected a pair...')
2004
+ artist = event.artist
2005
+ print(self.index)
2006
+
2007
+ if self.index is not None and len(self.pair_selection)==0:
2008
+
2009
+ selected_point = artist.get_offsets()[self.index]
2010
+
2011
+ if len(self.pair_selection) == 0 and ((selected_point[0],selected_point[1]) in self.connections.keys()):
2012
+
2013
+ connect = self.connections[(selected_point[0], selected_point[1])]
2014
+ self.neighbor_track_of_interest = connect[0][1]
2015
+ self.highlight_the_pair()
2016
+
2017
+ elif len(self.pair_selection)==1:
2018
+ print('Length of pair selection is larger than one, trying to cancel the pair selection...')
2019
+ self.cancel_pair_selection()
2020
+ else:
2021
+ print('something else')
2022
+ self.cancel_pair_selection()
2023
+ else:
2024
+ print('else #1')
2025
+ print(f"{len(self.pair_selection)=} {self.index=}")
2026
+ self.cancel_pair_selection()
2027
+ else:
2028
+ print('else #2')
2029
+ pass
2030
+
2031
+ print(f"{self.pair_selection=}")
2032
+
2033
+
2034
+ def highlight_the_pair(self):
2035
+
2036
+ # 1) recolor the neighbor marker
2037
+ print(f'Reference cell: {self.reference_track_of_interest}, neighbor cell: {self.neighbor_track_of_interest}')
2038
+
2039
+ _, tracks, colors, _ = self.get_neighbor_sets()
2040
+ self.neigh_cell_loc_idx = []
2041
+ self.neigh_cell_loc_t = []
2042
+ self.neigh_previous_color = []
2043
+
2044
+ for t in range(len(tracks)):
2045
+ indices_picked = np.where(tracks[t]==self.neighbor_track_of_interest)[0]
2046
+ if len(indices_picked)>0:
2047
+ self.neigh_cell_loc_t.append(t)
2048
+ self.neigh_cell_loc_idx.append(indices_picked[0])
2049
+
2050
+ for t,idx in zip(self.neigh_cell_loc_t,self.neigh_cell_loc_idx):
2051
+ self.neigh_previous_color.append(colors[t][idx].copy())
2052
+ colors[t][idx] = 'lime'
2053
+
2054
+ # 2) identify the pair line and recolor it
2055
+ for t in range(self.len_movie):
2056
+
2057
+ self.lines_colors_status[t][:, :2] = self.lines_colors_status[t][:, :2].astype(float)
2058
+ indices1 = np.where((self.lines_colors_status[t][:, 0] == self.reference_track_of_interest)&(self.lines_colors_status[t][:, 1] == self.neighbor_track_of_interest))[0]
2059
+
2060
+ self.lines_colors_class[t][:, :2] = self.lines_colors_class[t][:, :2].astype(float)
2061
+ indices2 = np.where((self.lines_colors_class[t][:, 0] == self.reference_track_of_interest)&(self.lines_colors_class[t][:, 1] == self.neighbor_track_of_interest))[0]
2062
+
2063
+ self.lines_colors_status[t][indices1, 2] = 'lime'
2064
+ self.lines_colors_class[t][indices2, 2] = 'lime'
2065
+ # Maybe do the symmetrical neighborhood when same populations?
2066
+
2067
+ self.pair_selection.append(tuple([self.reference_track_of_interest, self.neighbor_track_of_interest]))
2068
+ self.neighbor_selection.append(self.neighbor_track_of_interest)
2069
+ self.give_neighbor_cell_information()
2070
+ self.give_pair_information()
2071
+
2072
+ # Allow pair signal options
2073
+ for i in range(self.n_signals):
2074
+ self.neighbor_pop_option_buttons[i].setEnabled(True)
2075
+ self.relative_pop_option_buttons[i].setEnabled(True)
2076
+
2077
+ self.plot_signals()
2078
+
2079
+ # Allow pair annotation
2080
+ self.correct_btn.setEnabled(True)
2081
+ self.cancel_btn.setEnabled(True)
2082
+
2083
+
2084
+ def get_neighbor_sets(self):
2085
+
2086
+ if self.reference_population != self.neighbor_population:
2087
+ if self.reference_population=='effectors':
2088
+ return self.target_positions, self.target_tracks, self.target_colors, self.initial_target_colors
2089
+ elif self.reference_population=='targets':
2090
+ return self.effector_positions, self.effector_tracks, self.effector_colors, self.initial_effector_colors
2091
+ else:
2092
+ if self.reference_population=='effectors':
2093
+ return self.effector_positions, self.effector_tracks, self.effector_colors, self.initial_effector_colors
2094
+ elif self.reference_population=='targets':
2095
+ return self.target_positions, self.target_tracks, self.target_colors, self.initial_target_colors
2096
+
2097
+ def get_reference_sets(self):
2098
+
2099
+ if self.reference_population == 'effectors':
2100
+ return self.effector_positions, self.effector_tracks, self.effector_colors, self.initial_effector_colors
2101
+ elif self.reference_population == 'targets':
2102
+ return self.target_positions, self.target_tracks, self.target_colors, self.initial_target_colors
2103
+
2104
+ def trace_neighbors(self):
2105
+
2106
+ self.lines_data = {}
2107
+ self.points_data={}
2108
+ self.connections={}
2109
+ self.line_connections={}
2110
+
2111
+ positions, tracks, colors, _ = self.get_neighbor_sets()
2112
+
2113
+ # Look for neighbors
2114
+ for neigh in self.neighbors:
2115
+
2116
+ self.neighbor_loc_t = []
2117
+ self.neighbor_loc_idx = []
2118
+
2119
+ for t in range(len(tracks)):
2120
+ indices = np.where(tracks[t]==neigh)[0]
2121
+ if len(indices)>0:
2122
+ self.neighbor_loc_t.append(t)
2123
+ self.neighbor_loc_idx.append(indices[0])
2124
+
2125
+ self.neighbor_previous_color = []
2126
+ for t, idx in zip(self.neighbor_loc_t, self.neighbor_loc_idx):
2127
+
2128
+ try:
2129
+
2130
+ neigh_x = positions[t][idx, 0]
2131
+ neigh_y = positions[t][idx, 1]
2132
+ x_m_point = (self.reference_x[t] + neigh_x) / 2
2133
+ y_m_point = (self.reference_y[t] + neigh_y) / 2
2134
+
2135
+ if t not in self.lines_data.keys():
2136
+ self.lines_data[t]=[([self.reference_x[t], neigh_x], [self.reference_y[t], neigh_y])]
2137
+ self.points_data[t]=[(x_m_point, y_m_point)]
2138
+ else:
2139
+ self.lines_data[t].append(([self.reference_x[t], neigh_x], [self.reference_y[t], neigh_y]))
2140
+ self.points_data[t].append((x_m_point, y_m_point))
2141
+
2142
+ self.connections[(x_m_point, y_m_point)] = [(self.reference_track_of_interest, neigh)]
2143
+ self.line_connections[(self.reference_x[t], neigh_x, self.reference_y[t], neigh_y)]=[(self.reference_track_of_interest, neigh)]
2144
+
2145
+ self.neighbor_previous_color.append(colors[t][idx].copy())
2146
+ except Exception as e:
2147
+ print(e)
2148
+ pass
2149
+ #colors[t][idx] = 'salmon'
2150
+
2151
+ # for t in range(len(colors)):
2152
+ # for idx in range(len(colors[t])):
2153
+ # if colors[t][idx].any() != 'salmon':
2154
+ # if colors[t][idx].any() != 'magenta':
2155
+ # #init_color[t][idx] = colors[t][idx].copy()
2156
+ # colors[t][idx] = 'black'
2157
+
2158
+ def recolor_selection(self):
2159
+
2160
+ positions, tracks, colors, init_colors = self.get_reference_sets()
2161
+
2162
+ self.reference_loc_t = []
2163
+ self.reference_loc_idx = []
2164
+ self.reference_loc_t_not_picked = []
2165
+ self.reference_loc_idx_not_picked=[]
2166
+
2167
+ for t in range(len(tracks)):
2168
+
2169
+ indices_picked = np.where(tracks[t]==self.reference_track_of_interest)[0]
2170
+ indices_not_picked = np.where(tracks[t]!=self.reference_track_of_interest)[0]
2171
+ self.reference_loc_t_not_picked.append(t)
2172
+ self.reference_loc_idx_not_picked.append(indices_not_picked)
2173
+ if len(indices_picked)>0:
2174
+ self.reference_loc_t.append(t)
2175
+ self.reference_loc_idx.append(indices_picked[0])
2176
+
2177
+ self.reference_previous_color = []
2178
+ self.reference_not_picked_initial_colors=[]
2179
+ self.reference_x = []
2180
+ self.reference_y = []
2181
+
2182
+ # Recolor selected cell
2183
+ for t,idx in zip(self.reference_loc_t,self.reference_loc_idx):
2184
+ self.reference_x.append(positions[t][idx, 0])
2185
+ self.reference_y.append(positions[t][idx, 1])
2186
+ self.reference_previous_color.append(colors[t][idx].copy())
2187
+ colors[t][idx] = 'lime'
2188
+
2189
+ # Recolor all other cells in black
2190
+ for t, idx in zip(self.reference_loc_t_not_picked, self.reference_loc_idx_not_picked):
2191
+ self.reference_not_picked_initial_colors.append(colors[t][idx].copy())
2192
+ init_colors[t][idx] = colors[t][idx].copy()
2193
+ colors[t][idx] = 'black'
2194
+
2195
+
2196
+ def get_neighbors_of_selected_cell(self, selected_cell):
2197
+
2198
+ self.neighbors = self.df_relative.loc[(self.df_relative['REFERENCE_ID'] == selected_cell)&(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population),'NEIGHBOR_ID']
2199
+ self.neighbors = np.unique(self.neighbors)
2200
+ # if len(self.neighbors)>0:
2201
+ # first_neighbor = np.min(self.neighbors)
2202
+ # self.neighbor_track_of_interest = first_neighbor
2203
+ # else:
2204
+ self.neighbor_track_of_interest = None
2205
+
2206
+ def identify_closest_marker(self, event):
2207
+
2208
+ ind = event.ind
2209
+ label = event.artist.get_label()
2210
+ print(f'{label=}')
2211
+
2212
+ # Identify the nature of the selected object (target/effector/pair)
2213
+ self.pair_selected = False
2214
+ if label == '_child1':
2215
+ self.selected_population = 'targets'
2216
+ elif label == '_child3':
2217
+ self.selected_population = 'effectors'
2218
+ else:
2219
+ number = int(label.split('_child')[1])
2220
+ if number>4:
2221
+ print('A pair is selected...')
2222
+ self.pair_selected = True
2223
+
2224
+ if self.selected_population=='effectors':
2225
+ positions = self.effector_positions
2226
+ elif self.selected_population=='targets':
2227
+ positions = self.target_positions
2228
+
2229
+ if len(ind)==1:
2230
+ self.index = ind[0]
2231
+ elif len(ind)>1:
2232
+ # More than one point in vicinity
2233
+ datax,datay = [positions[self.framedata][i,0] for i in ind],[positions[self.framedata][i,1] for i in ind]
2234
+ msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
2235
+ dist = np.sqrt((np.array(datax)-msx)**2+(np.array(datay)-msy)**2)
2236
+ self.index = ind[np.argmin(dist)]
2237
+ else:
2238
+ self.index = None
2239
+
2240
+
2241
+ def show_annotation_buttons(self):
2242
+
2243
+ for a in self.annotation_btns_to_hide:
2244
+ a.show()
2245
+
2246
+ cclass = self.df_relative.loc[(self.df_relative['REFERENCE_ID'] == self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest)&
2247
+ (self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population)&(~self.df_relative['status_'+self.current_neighborhood].isnull()), self.pair_class_name].to_numpy()[0]
2248
+ t0 = self.df_relative.loc[(self.df_relative['REFERENCE_ID'] == self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest)&
2249
+ (self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population)&(~self.df_relative['status_'+self.current_neighborhood].isnull()), self.pair_time_name].to_numpy()[0]
2250
+
2251
+ if cclass == 0:
2252
+ self.event_btn.setChecked(True)
2253
+ self.time_of_interest_le.setText(str(t0))
2254
+ elif cclass == 1:
2255
+ self.no_event_btn.setChecked(True)
2256
+ elif cclass == 2:
2257
+ self.else_btn.setChecked(True)
2258
+ elif cclass > 2:
2259
+ self.suppr_btn.setChecked(True)
2260
+
2261
+ self.enable_time_of_interest()
2262
+ self.correct_btn.setText('submit')
2263
+
2264
+ self.correct_btn.disconnect()
2265
+ self.correct_btn.clicked.connect(self.apply_modification)
2266
+
2267
+ def shortcut_suppr(self):
2268
+ self.correct_btn.click()
2269
+ self.suppr_btn.click()
2270
+ self.correct_btn.click()
2271
+
2272
+ def shortcut_no_event(self):
2273
+ self.correct_btn.click()
2274
+ self.no_event_btn.click()
2275
+ self.correct_btn.click()
2276
+
2277
+ def configure_ylims(self):
2278
+
2279
+ try:
2280
+ min_values = []
2281
+ max_values = []
2282
+ for i in range(len(self.signal_choices)):
2283
+ signal = self.signal_choices[i].currentText()
2284
+ if signal=='--':
2285
+ continue
2286
+ else:
2287
+ if i==0:
2288
+ if self.reference_button1.isChecked():
2289
+ df_ref=self.dataframes[self.reference_population]
2290
+ maxx_target = np.nanpercentile(df_ref.loc[:,signal].to_numpy().flatten(),99)
2291
+ minn_target = np.nanpercentile(df_ref.loc[:,signal].to_numpy().flatten(),1)
2292
+ min_values.append(minn_target)
2293
+ max_values.append(maxx_target)
2294
+ if self.neighbor_button1.isChecked():
2295
+ df_neigh=self.dataframes[self.neighbor_population]
2296
+ maxx_target = np.nanpercentile(df_neigh.loc[:, signal].to_numpy().flatten(), 99)
2297
+ minn_target = np.nanpercentile(df_neigh.loc[:, signal].to_numpy().flatten(), 1)
2298
+ min_values.append(minn_target)
2299
+ max_values.append(maxx_target)
2300
+ if self.relative_button1.isChecked():
2301
+ maxx_relative = np.nanpercentile(self.df_relative.loc[:, signal].to_numpy().flatten(), 99)
2302
+ minn_relative = np.nanpercentile(self.df_relative.loc[:, signal].to_numpy().flatten(), 1)
2303
+ min_values.append(minn_relative)
2304
+ max_values.append(maxx_relative)
2305
+ elif i==1:
2306
+ if self.reference_button2.isChecked():
2307
+ df_ref=self.dataframes[self.reference_population]
2308
+ maxx_effector = np.nanpercentile(df_ref.loc[:,signal].to_numpy().flatten(),99)
2309
+ minn_effector = np.nanpercentile(df_ref.loc[:,signal].to_numpy().flatten(),1)
2310
+ min_values.append(minn_effector)
2311
+ max_values.append(maxx_effector)
2312
+ if self.neighbor_button2.isChecked():
2313
+ df_neigh=self.dataframes[self.neighbor_population]
2314
+ maxx_effector = np.nanpercentile(df_neigh.loc[:, signal].to_numpy().flatten(), 99)
2315
+ minn_effector = np.nanpercentile(df_neigh.loc[:, signal].to_numpy().flatten(), 1)
2316
+ min_values.append(minn_effector)
2317
+ max_values.append(maxx_effector)
2318
+ if self.relative_button2.isChecked():
2319
+ maxx_relative = np.nanpercentile(self.df_relative.loc[:, signal].to_numpy().flatten(), 99)
2320
+ minn_relative = np.nanpercentile(self.df_relative.loc[:, signal].to_numpy().flatten(), 1)
2321
+ min_values.append(minn_relative)
2322
+ max_values.append(maxx_relative)
2323
+ else:
2324
+ if self.reference_button3.isChecked():
2325
+ df_ref=self.dataframes[self.reference_population]
2326
+ maxx_relative = np.nanpercentile(df_ref.loc[:,signal].to_numpy().flatten(),99)
2327
+ minn_relative = np.nanpercentile(df_ref.loc[:,signal].to_numpy().flatten(),1)
2328
+ min_values.append(minn_relative)
2329
+ max_values.append(maxx_relative)
2330
+ if self.neighbor_button3.isChecked():
2331
+ df_neigh=self.dataframes[self.neighbor_population]
2332
+
2333
+ maxx_relative = np.nanpercentile(df_neigh.loc[:, signal].to_numpy().flatten(), 99)
2334
+ minn_relative = np.nanpercentile(df_neigh.loc[:, signal].to_numpy().flatten(), 1)
2335
+ min_values.append(minn_relative)
2336
+ max_values.append(maxx_relative)
2337
+ if self.relative_button3.isChecked():
2338
+ maxx_relative = np.nanpercentile(self.df_relative.loc[:, signal].to_numpy().flatten(), 99)
2339
+ minn_relative = np.nanpercentile(self.df_relative.loc[:, signal].to_numpy().flatten(), 1)
2340
+ min_values.append(minn_relative)
2341
+ max_values.append(maxx_relative)
2342
+
2343
+ if len(min_values)>0:
2344
+ self.cell_ax.set_ylim(np.amin(min_values), np.amax(max_values))
2345
+ except Exception as e:
2346
+ print(e)
2347
+
2348
+ def draw_frame(self, framedata):
2349
+
2350
+ """
2351
+ Update plot elements at each timestep of the loop.
2352
+ """
2353
+
2354
+ self.framedata = framedata
2355
+ self.frame_lbl.setText(f'frame: {self.framedata}')
2356
+ self.im.set_array(self.stack[self.framedata])
2357
+ #if self.reference_population=='targets':
2358
+
2359
+ if self.df_effectors is not None:
2360
+
2361
+ self.effector_status_scatter.set_visible(True)
2362
+ self.effector_status_scatter.set_picker(True)
2363
+ self.effector_class_scatter.set_visible(True)
2364
+ self.effector_status_scatter.set_offsets(self.effector_positions[self.framedata])
2365
+ self.effector_status_scatter.set_color(self.effector_colors[self.framedata][:, 1])
2366
+ self.effector_class_scatter.set_offsets(self.effector_positions[self.framedata])
2367
+ self.effector_class_scatter.set_edgecolor(self.effector_colors[self.framedata][:, 0])
2368
+
2369
+ if self.df_targets is not None:
2370
+ self.target_status_scatter.set_visible(True)
2371
+ self.target_status_scatter.set_picker(True)
2372
+ self.target_class_scatter.set_visible(True)
2373
+ self.target_status_scatter.set_offsets(self.target_positions[self.framedata])
2374
+ self.target_status_scatter.set_color(self.target_colors[self.framedata][:, 1])
2375
+ self.target_class_scatter.set_offsets(self.target_positions[self.framedata])
2376
+ self.target_class_scatter.set_edgecolor(self.target_colors[self.framedata][:, 0])
2377
+
2378
+ self.lines_list=[]
2379
+
2380
+ for key in self.lines_data:
2381
+ if key==self.framedata:
2382
+ for line in self.lines_data[key]:
2383
+ x_coords, y_coords = line
2384
+ pair=self.line_connections[x_coords[0],x_coords[1],y_coords[0],y_coords[1]]
2385
+
2386
+ this_frame=self.lines_colors_class[self.framedata]
2387
+
2388
+ try:
2389
+ this_pair=this_frame[(this_frame[:, 0] == pair[0][0]) & (this_frame[:, 1] == pair[0][1])]
2390
+ self.lines_plot=self.ax.plot(x_coords, y_coords, alpha=1, linewidth=2,color=this_pair[0][2])
2391
+ self.lines_list.append(self.lines_plot[0])
2392
+ except Exception as e:
2393
+ print(e)
2394
+ pass
2395
+ # Plot points
2396
+ try:
2397
+ self.points.set_offsets(self.points_data[key])
2398
+ colors_at_this_frame = self.lines_colors_status[self.framedata]
2399
+ colors = [colors_at_this_frame[(colors_at_this_frame[:, 0] == self.connections[point[0],point[1]][0][0]) & (colors_at_this_frame[:, 1] == self.connections[point[0],point[1]][0][1])][0][2] for point in self.points_data[key]]
2400
+ self.points.set_color(colors)
2401
+ except Exception as e:
2402
+ print(e)
2403
+
2404
+ if self.lines_list!=[]:
2405
+ return [self.im,self.target_status_scatter,self.target_class_scatter,self.effector_status_scatter,self.effector_class_scatter] +self.lines_list + [self.points]
2406
+ else:
2407
+ return [self.im, self.target_status_scatter, self.target_class_scatter, self.effector_status_scatter,
2408
+ self.effector_class_scatter,]
2409
+
2410
+ def stop(self):
2411
+ # # On stop we disconnect all of our events.
2412
+ self.stop_btn.hide()
2413
+ self.start_btn.show()
2414
+ self.anim.pause()
2415
+ self.stop_btn.clicked.connect(self.start)
2416
+
2417
+
2418
+ def start(self):
2419
+ '''
2420
+ Starts interactive animation. Adds the draw frame command to the GUI
2421
+ handler, calls show to start the event loop.
2422
+ '''
2423
+ self.start_btn.setShortcut(QKeySequence(""))
2424
+
2425
+ self.last_frame_btn.setEnabled(True)
2426
+ self.last_frame_btn.clicked.connect(self.set_last_frame)
2427
+
2428
+ self.first_frame_btn.setEnabled(True)
2429
+ self.first_frame_btn.clicked.connect(self.set_first_frame)
2430
+
2431
+
2432
+ self.start_btn.hide()
2433
+ self.stop_btn.show()
2434
+
2435
+ self.anim.event_source.start()
2436
+ self.stop_btn.clicked.connect(self.stop)
2437
+
2438
+ def give_reference_cell_information(self):
2439
+
2440
+ df_reference = self.dataframes[self.reference_population]
2441
+ if self.reference_track_of_interest is not None:
2442
+ reference_cell_selected = f"reference cell: {self.reference_track_of_interest}\n"
2443
+ reference_cell_population = f"population: {self.reference_population}\n"
2444
+ #reference_cell_class = f"class: {df_reference[df_reference['TRACK_ID']==self.reference_track_of_interest, self.reference_event_choice_cb.currentText()].values[0]}\n"
2445
+ #reference_cell_time = f"time of interest: {df_reference[df_reference['TRACK_ID']==self.reference_track_of_interest, ''].values[0]}\n"
2446
+ self.reference_cell_info.setText(reference_cell_selected+reference_cell_population)
2447
+ else:
2448
+ reference_cell_selected = f"reference cell: None\n"
2449
+ reference_cell_population = f"population: {self.reference_population}\n"
2450
+ self.reference_cell_info.setText(reference_cell_selected+reference_cell_population)
2451
+
2452
+ def give_neighbor_cell_information(self):
2453
+
2454
+ if self.neighbor_track_of_interest is not None:
2455
+ neighbor_cell_selected = f"neighbor cell: {self.neighbor_track_of_interest}\n"
2456
+ neighbor_cell_population = f"population: {self.neighbor_population}\n"
2457
+ #neighbor_cell_time = f"time of interest: {self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest), self.pair_time_name].to_numpy()[0]}\n"
2458
+ #neighbor_cell_class = f"class: {self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest), self.pair_class_name].to_numpy()[0]}\n"
2459
+ self.neighbor_cell_info.setText(neighbor_cell_selected+neighbor_cell_population) #neighbor_cell_class+neighbor_cell_time
2460
+ else:
2461
+ neighbor_cell_selected = f"neighbor cell: None\n"
2462
+ neighbor_cell_population = f"population: {self.neighbor_population}\n"
2463
+ self.neighbor_cell_info.setText(neighbor_cell_selected+neighbor_cell_population)
2464
+
2465
+ def give_pair_information(self):
2466
+
2467
+ if self.neighbor_track_of_interest is not None and self.reference_track_of_interest is not None:
2468
+ pair_selected = f"(reference/neighbor) pair: ({self.reference_track_of_interest},{self.neighbor_track_of_interest})\n"
2469
+ pair_populations = f"populations: ({self.reference_population}, {self.neighbor_population})\n"
2470
+ current_class = self.relative_class_choice_cb.currentText()
2471
+ pair_class = f"interaction event class: {self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest)&(self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population)&(~self.df_relative['status_'+self.current_neighborhood].isnull()), current_class].values[0]}\n"
2472
+ pair_time = f"time of interest: {self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.reference_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.neighbor_track_of_interest)&(self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population)&(~self.df_relative['status_'+self.current_neighborhood].isnull()), self.pair_time_name].values[0]}\n"
2473
+ self.pair_info.setText(pair_selected+pair_populations+pair_class+pair_time)
2474
+ else:
2475
+ pair_selected = f"(reference/neighbor) pair: None\n"
2476
+ pair_populations = f"populations: ({self.reference_population}, {self.neighbor_population})\n"
2477
+ self.pair_info.setText(pair_selected+pair_populations)
2478
+
2479
+
2480
+ #def hide_neighbor_cell_info(self):
2481
+ #neighbor_cell_selected.hide()
2482
+ #neighbor_cell_population.hide()
2483
+
2484
+ def hide_target_cell_info(self):
2485
+
2486
+ self.target_cell_info.setText('')
2487
+
2488
+ # def give_effector_cell_information(self):
2489
+ # self.effector_cell_info.setSpacing(0)
2490
+ # self.effector_cell_info.setContentsMargins(0, 20, 0, 30)
2491
+ # self.neigh_eff_combo=QComboBox()
2492
+ # #self.neighb_eff_combo.addItems(self.df_relative.loc[(self.df_relative['target']==self.target_track_of_interest),'effecor'])
2493
+ # neighs=self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.target_track_of_interest),'NEIGHBOR_ID'].to_numpy()
2494
+ # neighs=np.unique(neighs)
2495
+ # for effector in neighs:
2496
+ # self.neigh_eff_combo.addItem(str(effector))
2497
+ # if self.effector_track_of_interest not in neighs:
2498
+ # self.neigh_eff_combo.addItem(str(self.effector_track_of_interest))
2499
+ # self.neigh_eff_combo.setCurrentText(str(self.effector_track_of_interest))
2500
+ # self.eff_cell_sel=QHBoxLayout()
2501
+ # #effector_cell_selected = f"effector cell: {self.effector_track_of_interest}"
2502
+ # self.effector_cell_selected = f"effector cell: "
2503
+ # self.eff_cell = QLabel(self.effector_cell_selected)
2504
+ # # self.eff_cell_sel.removeWidget(self.eff_cell)
2505
+ # # self.eff_cell_sel.removeWidget(self.neigh_eff_combo)
2506
+ # self.eff_cell_sel.addWidget(self.eff_cell)
2507
+ # self.eff_cell_sel.addWidget(self.neigh_eff_combo, alignment=Qt.AlignLeft)
2508
+ # try:
2509
+ # self.effector_cell_class = f"class: {self.df_effectors.loc[self.df_effectors['TRACK_ID']==self.effector_track_of_interest, self.effector_class_name].to_numpy()[0]}"
2510
+ # except:
2511
+ # self.effector_cell_class = f"class: {self.df_effectors.loc[self.df_effectors['ID'] == self.effector_track_of_interest, self.effector_class_name].to_numpy()[0]}"
2512
+
2513
+ # self.eff_cls = QLabel(self.effector_cell_class)
2514
+ # try:
2515
+ # self.effector_cell_time = f"time of interest: {self.df_effectors.loc[self.df_effectors['TRACK_ID']==self.effector_track_of_interest, self.effector_time_name].to_numpy()[0]}"
2516
+ # except:
2517
+ # self.effector_cell_time = f"time of interest: {self.df_effectors.loc[self.df_effectors['ID']==self.effector_track_of_interest, self.effector_time_name].to_numpy()[0]}"
2518
+
2519
+ # self.eff_tm=QLabel(self.effector_cell_time)
2520
+ # # try:
2521
+ # # self.effector_probabilty = f"probability: {self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.target_track_of_interest)&(self.df_relative['NEIGHBOR_ID']==self.effector_track_of_interest),'probability'].to_numpy()[0]}"
2522
+ # # except:
2523
+ # # self.effector_probabilty=f"probability: 0"
2524
+ # # self.eff_prb=QLabel(self.effector_probabilty)
2525
+ # #self.effector_cell_info.setText(effector_cell_selected+effector_cell_class+effector_cell_time+effector_probabilty)
2526
+ # # self.effector_cell_info.removeWidget(self.eff_cls)
2527
+ # # self.effector_cell_info.removeWidget(self.eff_tm)
2528
+ # # self.effector_cell_info.removeWidget(self.eff_prb)
2529
+ # self.effector_cell_info.addLayout(self.eff_cell_sel)
2530
+ # self.effector_cell_info.addWidget(self.eff_cls)
2531
+ # self.effector_cell_info.addWidget(self.eff_tm)
2532
+ # #self.effector_cell_info.addWidget(self.eff_prb)
2533
+ # self.neigh_eff_combo.currentIndexChanged.connect(self.update_effector_info)
2534
+ # self.eff_info_to_hide=[self.eff_cell,self.neigh_eff_combo,self.eff_cls,self.eff_tm]#self.eff_prb
2535
+
2536
+
2537
+ # def hide_effector_cell_info(self):
2538
+ # self.eff_cls.clear()
2539
+ # self.eff_tm.clear()
2540
+ # #self.eff_prb.clear()
2541
+
2542
+ # for info in self.eff_info_to_hide:
2543
+ # info.hide()
2544
+
2545
+
2546
+ def save_trajectories(self):
2547
+
2548
+ if self.normalized_signals:
2549
+ self.normalize_features_btn.click()
2550
+ self.cancel_selection()
2551
+
2552
+ self.relative_class_name = self.relative_class_choice_cb.currentText()
2553
+ self.df_relative = self.df_relative.drop(self.df_relative[self.df_relative[self.relative_class_name]>2].index)
2554
+ self.df_relative.to_csv(self.relative_trajectories_path, index=False)
2555
+ print('relative table saved.')
2556
+
2557
+
2558
+ def set_last_frame(self):
2559
+
2560
+ self.last_frame_btn.setEnabled(False)
2561
+ self.last_frame_btn.disconnect()
2562
+
2563
+ self.last_key = len(self.stack) - 1
2564
+ while len(np.where(self.stack[self.last_key].flatten()==0)[0]) > 0.99*len(self.stack[self.last_key].flatten()):
2565
+ self.last_key -= 1
2566
+ print(f'Last frame is {len(self.stack) - 1}; last not black is {self.last_key}')
2567
+ self.anim._drawn_artists = self.draw_frame(self.last_key)
2568
+ self.anim._drawn_artists = sorted(self.anim._drawn_artists, key=lambda x: x.get_zorder())
2569
+ for a in self.anim._drawn_artists:
2570
+ a.set_visible(True)
2571
+
2572
+ self.fig.canvas.draw()
2573
+ self.anim.event_source.stop()
2574
+
2575
+ #self.cell_plot.draw()
2576
+ self.stop_btn.hide()
2577
+ self.start_btn.show()
2578
+ self.stop_btn.clicked.connect(self.start)
2579
+ self.start_btn.setShortcut(QKeySequence("l"))
2580
+
2581
+ def set_first_frame(self):
2582
+
2583
+ self.first_frame_btn.setEnabled(False)
2584
+ self.first_frame_btn.disconnect()
2585
+
2586
+ self.first_key = 0
2587
+ print(f'First frame is {0}')
2588
+ self.anim._drawn_artists = self.draw_frame(0)
2589
+ self.anim._drawn_artists = sorted(self.anim._drawn_artists, key=lambda x: x.get_zorder())
2590
+ for a in self.anim._drawn_artists:
2591
+ a.set_visible(True)
2592
+
2593
+ self.fig.canvas.draw()
2594
+ self.anim.event_source.stop()
2595
+
2596
+ #self.cell_plot.draw()
2597
+ self.stop_btn.hide()
2598
+ self.start_btn.show()
2599
+ self.stop_btn.clicked.connect(self.start)
2600
+ self.start_btn.setShortcut(QKeySequence("f"))
2601
+
2602
+ def export_signals(self):
2603
+
2604
+ auto_dataset_name = self.pos.split(os.sep)[-4] + '_' + self.pos.split(os.sep)[-2] + '.npy'
2605
+
2606
+ if self.normalized_signals:
2607
+ self.normalize_features_btn.click()
2608
+
2609
+ training_set = []
2610
+
2611
+ pair_filter = (self.df_relative['reference_population']==self.reference_population)&(self.df_relative['neighbor_population']==self.neighbor_population)&(~self.df_relative['status_'+self.current_neighborhood].isnull())
2612
+
2613
+ for pair, group in self.df_relative.loc[pair_filter, :].groupby(['REFERENCE_ID', 'NEIGHBOR_ID']):
2614
+
2615
+ signals = {}
2616
+
2617
+ time_of_interest = group[self.pair_time_name].values[0]
2618
+ cclass = group[self.pair_class_name].values[0]
2619
+ signals.update({"time_of_interest": time_of_interest, "class": cclass, "neighborhood_of_interest": self.current_neighborhood, 'reference_population': self.reference_population, 'neighbor_population': self.neighbor_population})
2620
+
2621
+ # Pair signals
2622
+ reference_cell = pair[0]; neighbor_cell = pair[1]
2623
+ for col in list(group.columns):
2624
+ if is_numeric_dtype(group[col]):
2625
+ signals.update({'pair_'+col: group[col].to_numpy()})
2626
+
2627
+ # Reference signals
2628
+ df_reference = self.dataframes[self.reference_population]
2629
+ reference_filter = df_reference['TRACK_ID']==reference_cell
2630
+ for col in list(df_reference.columns):
2631
+ if not col.startswith('neighborhood') and is_numeric_dtype(df_reference.loc[reference_filter, col]):
2632
+ signals.update({'reference_'+col: df_reference.loc[reference_filter, col].to_numpy()})
2633
+
2634
+ # Reference signals
2635
+ df_neighbor = self.dataframes[self.neighbor_population]
2636
+ neighbor_filter = df_neighbor['TRACK_ID']==neighbor_cell
2637
+ for col in list(df_neighbor.columns):
2638
+ if not col.startswith('neighborhood') and is_numeric_dtype(df_neighbor.loc[neighbor_filter, col]):
2639
+ signals.update({'neighbor_'+col: df_neighbor.loc[neighbor_filter, col].to_numpy()})
2640
+
2641
+ training_set.append(signals)
2642
+
2643
+ pathsave = QFileDialog.getSaveFileName(self, "Select file name", self.exp_dir + auto_dataset_name, ".npy")[0]
2644
+ if pathsave != '':
2645
+ if not pathsave.endswith(".npy"):
2646
+ pathsave += ".npy"
2647
+ try:
2648
+ np.save(pathsave, training_set)
2649
+ print(f'File successfully written in {pathsave}.')
2650
+ except Exception as e:
2651
+ print(f"Error {e}...")
2652
+
2653
+ def normalize_features(self):
2654
+
2655
+ if self.df_effectors is not None:
2656
+ x_effectors = self.df_effectors[self.effector_columns].values
2657
+ if self.df_targets is not None:
2658
+ x_targets = self.df_targets[self.target_columns].values
2659
+ if self.df_relative is not None:
2660
+ x_pairs = self.df_relative[self.pair_columns].values
2661
+
2662
+ if not self.normalized_signals:
2663
+
2664
+ if self.df_effectors is not None:
2665
+ self.df_effectors[self.effector_columns] = self.MinMaxScaler_effectors.transform(x_effectors)
2666
+ if self.df_targets is not None:
2667
+ self.df_targets[self.target_columns] = self.MinMaxScaler_targets.transform(x_targets)
2668
+ if self.df_relative is not None:
2669
+ self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.transform(x_pairs)
2670
+
2671
+ self.plot_signals()
2672
+ self.normalized_signals = True
2673
+ self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="#1565c0"))
2674
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
2675
+ else:
2676
+
2677
+ if self.df_effectors is not None:
2678
+ self.df_effectors[self.effector_columns] = self.MinMaxScaler_effectors.inverse_transform(x_effectors)
2679
+ if self.df_targets is not None:
2680
+ self.df_targets[self.target_columns] = self.MinMaxScaler_targets.inverse_transform(x_targets)
2681
+ if self.df_relative is not None:
2682
+ self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.inverse_transform(x_pairs)
2683
+
2684
+ self.plot_signals()
2685
+ self.normalized_signals = False
2686
+ self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
2687
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
2688
+
2689
+ def switch_to_log(self):
2690
+
2691
+ """
2692
+ Better would be to create a log(quantity) and plot it...
2693
+ """
2694
+
2695
+ try:
2696
+ if self.cell_ax.get_yscale()=='linear':
2697
+ ymin,ymax = self.cell_ax.get_ylim()
2698
+ self.cell_ax.set_yscale('log')
2699
+ self.log_btn.setIcon(icon(MDI6.math_log,color="#1565c0"))
2700
+ self.cell_ax.set_ylim(self.value_magnitude, ymax)
2701
+ else:
2702
+ self.cell_ax.set_yscale('linear')
2703
+ self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
2704
+ except Exception as e:
2705
+ print(e)
2706
+
2707
+ #self.cell_ax.autoscale()
2708
+ self.cell_fcanvas.canvas.draw_idle()