celldetective 1.3.9.post5__py3-none-any.whl → 1.4.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 (57) hide show
  1. celldetective/__init__.py +0 -3
  2. celldetective/_version.py +1 -1
  3. celldetective/events.py +2 -4
  4. celldetective/extra_properties.py +132 -0
  5. celldetective/gui/InitWindow.py +33 -45
  6. celldetective/gui/__init__.py +1 -0
  7. celldetective/gui/about.py +19 -15
  8. celldetective/gui/analyze_block.py +34 -19
  9. celldetective/gui/base_components.py +23 -0
  10. celldetective/gui/btrack_options.py +26 -34
  11. celldetective/gui/classifier_widget.py +68 -81
  12. celldetective/gui/configure_new_exp.py +113 -17
  13. celldetective/gui/control_panel.py +68 -141
  14. celldetective/gui/generic_signal_plot.py +9 -12
  15. celldetective/gui/gui_utils.py +49 -21
  16. celldetective/gui/json_readers.py +5 -4
  17. celldetective/gui/layouts.py +246 -22
  18. celldetective/gui/measurement_options.py +32 -17
  19. celldetective/gui/neighborhood_options.py +10 -13
  20. celldetective/gui/plot_measurements.py +21 -17
  21. celldetective/gui/plot_signals_ui.py +125 -72
  22. celldetective/gui/process_block.py +180 -123
  23. celldetective/gui/processes/compute_neighborhood.py +594 -0
  24. celldetective/gui/processes/measure_cells.py +5 -0
  25. celldetective/gui/processes/segment_cells.py +27 -6
  26. celldetective/gui/processes/track_cells.py +6 -0
  27. celldetective/gui/retrain_segmentation_model_options.py +12 -20
  28. celldetective/gui/retrain_signal_model_options.py +57 -56
  29. celldetective/gui/seg_model_loader.py +21 -62
  30. celldetective/gui/signal_annotator.py +129 -70
  31. celldetective/gui/signal_annotator2.py +431 -635
  32. celldetective/gui/signal_annotator_options.py +8 -11
  33. celldetective/gui/survival_ui.py +49 -95
  34. celldetective/gui/tableUI.py +28 -25
  35. celldetective/gui/thresholds_gui.py +617 -1221
  36. celldetective/gui/viewers.py +106 -39
  37. celldetective/gui/workers.py +9 -3
  38. celldetective/io.py +57 -20
  39. celldetective/measure.py +63 -27
  40. celldetective/neighborhood.py +342 -268
  41. celldetective/preprocessing.py +25 -17
  42. celldetective/relative_measurements.py +50 -29
  43. celldetective/scripts/analyze_signals.py +4 -1
  44. celldetective/scripts/measure_relative.py +4 -1
  45. celldetective/scripts/segment_cells.py +0 -6
  46. celldetective/scripts/track_cells.py +3 -1
  47. celldetective/scripts/train_segmentation_model.py +7 -4
  48. celldetective/signals.py +29 -14
  49. celldetective/tracking.py +7 -2
  50. celldetective/utils.py +36 -8
  51. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.0.dist-info}/METADATA +24 -16
  52. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.0.dist-info}/RECORD +57 -55
  53. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.0.dist-info}/WHEEL +1 -1
  54. tests/test_qt.py +21 -21
  55. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.0.dist-info}/entry_points.txt +0 -0
  56. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.0.dist-info/licenses}/LICENSE +0 -0
  57. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.0.dist-info}/top_level.txt +0 -0
@@ -1,13 +1,13 @@
1
- from PyQt5.QtWidgets import QMainWindow, QComboBox, QLabel, QRadioButton, QLineEdit, QFileDialog, QApplication, \
2
- QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QShortcut, QLineEdit, \
1
+ from PyQt5.QtWidgets import QComboBox, QLabel, QRadioButton, QLineEdit, QFileDialog, QApplication, \
2
+ QPushButton, QVBoxLayout, QHBoxLayout, QMessageBox, QShortcut, QLineEdit, \
3
3
  QButtonGroup
4
4
  from PyQt5.QtCore import Qt, QSize
5
5
  from PyQt5.QtGui import QKeySequence
6
- from celldetective.gui import Styles
6
+ from celldetective.gui import Styles, CelldetectiveMainWindow, CelldetectiveWidget
7
7
  from celldetective.gui.gui_utils import center_window
8
8
  from superqt import QLabeledDoubleRangeSlider, QSearchableComboBox
9
9
  from celldetective.utils import extract_experiment_channels, get_software_location, _get_img_num_per_channel
10
- from celldetective.io import auto_load_number_of_frames, load_frames, get_experiment_metadata
10
+ from celldetective.io import auto_load_number_of_frames, load_frames, get_experiment_metadata, get_experiment_labels
11
11
  from celldetective.gui.gui_utils import FigureCanvas, color_from_status, color_from_class
12
12
  import json
13
13
  import numpy as np
@@ -26,7 +26,7 @@ from sklearn.preprocessing import MinMaxScaler
26
26
  from functools import partial
27
27
  from pandas.api.types import is_numeric_dtype
28
28
 
29
- class SignalAnnotator2(QMainWindow,Styles):
29
+ class SignalAnnotator2(CelldetectiveMainWindow):
30
30
 
31
31
  """
32
32
  UI to set tracking parameters for bTrack.
@@ -41,14 +41,12 @@ class SignalAnnotator2(QMainWindow,Styles):
41
41
 
42
42
  self.pos = self.parent_window.parent_window.pos
43
43
  self.exp_dir = self.parent_window.exp_dir
44
- print(f'{self.pos=} {self.exp_dir=}')
44
+ self.populations = self.parent_window.parent_window.populations
45
45
 
46
46
  self.soft_path = get_software_location()
47
47
  self.recently_modified = False
48
48
  self.n_signals = 3
49
- self.target_selection = []
50
- self.effector_selection = []
51
-
49
+
52
50
  self.reference_selection = []
53
51
  self.neighbor_selection = []
54
52
  self.pair_selection = []
@@ -61,25 +59,27 @@ class SignalAnnotator2(QMainWindow,Styles):
61
59
  self.neighbor_track_of_interest = None
62
60
  self.value_magnitude = 1
63
61
 
64
- self.cols_to_remove = ['REFERENCE_ID', 'NEIGHBOR_ID', 'FRAME', 't0_arrival', 'TRACK_ID', 'class_color', 'status_color',
65
- 'FRAME', 'x_anim', 'y_anim', 't', 'state', 'generation', 'root', 'parent', 'class_id', 'class',
66
- 't0', 'POSITION_X', 'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name',
67
- 'index', 'relxy', 'tc', 'nk', 'concentration', 'antibody', 'cell_type', 'pharmaceutical_agent',
68
- 'reference_population', 'neighbor_population']
62
+ self.cols_to_remove = ['group', 'group_color', 'status', 'status_color', 'class_color', 'TRACK_ID', 'FRAME',
63
+ 'x_anim', 'y_anim', 't','dummy','group_color',
64
+ 'state', 'generation', 'root', 'parent', 'class_id', 'class', 't0', 'POSITION_X',
65
+ 'POSITION_Y', 'position', 'well', 'well_index', 'well_name', 'pos_name', 'index',
66
+ 'concentration', 'cell_type', 'antibody', 'pharmaceutical_agent', 'ID', "REFERENCE_ID", "NEIGHBOR_ID", "reference_population", "neighbor_population"]
67
+
69
68
  meta = get_experiment_metadata(self.exp_dir)
70
69
  if meta is not None:
71
70
  keys = list(meta.keys())
72
- self.cols_to_remove.extend(keys)
71
+ self.cols_to_remove.extend(keys)
72
+
73
+ labels = get_experiment_labels(self.exp_dir)
74
+ if labels is not None:
75
+ keys = list(labels.keys())
76
+ self.cols_to_remove.extend(labels)
77
+
73
78
 
74
79
  # Read instructions from target block for now...
75
80
  self.mode = "neighborhood"
76
81
  self.instructions_path = self.exp_dir + os.sep.join(['configs', 'signal_annotator_config_neighborhood.json'])
77
82
 
78
- # default params
79
- self.target_class_name = 'class'
80
- self.target_time_name = 't0'
81
- self.target_status_name = 'status'
82
-
83
83
  center_window(self)
84
84
 
85
85
  # Locate stack
@@ -87,21 +87,15 @@ class SignalAnnotator2(QMainWindow,Styles):
87
87
  self.load_annotator_config()
88
88
 
89
89
  # Locate tracks
90
- self.locate_target_tracks()
91
- self.locate_effector_tracks()
92
-
93
- self.dataframes = {
94
- 'targets': self.df_targets,
95
- 'effectors': self.df_effectors,
96
- }
90
+ self.locate_all_tracks()
91
+ self.extract_scatter_from_trajectories()
97
92
 
98
93
  self.neighborhood_cols = []
99
- if self.df_targets is not None:
100
- self.neighborhood_cols.extend(['target_ref_'+c for c in list(self.df_targets.columns) if c.startswith('neighborhood')])
101
- if self.df_effectors is not None:
102
- print(self.df_effectors.columns)
103
- self.neighborhood_cols.extend(['effector_ref_'+c for c in list(self.df_effectors.columns) if c.startswith('neighborhood')])
104
- print(f"The following neighborhoods were detected: {self.neighborhood_cols=}")
94
+ for pop in self.dataframes.keys():
95
+ if self.dataframes[pop] is not None:
96
+ self.neighborhood_cols.extend([f'{pop}_ref_'+c for c in list(self.dataframes[pop].columns) if c.startswith('neighborhood')])
97
+ print(f"The following neighborhoods were detected: {self.neighborhood_cols=}...")
98
+
105
99
  self.locate_relative_tracks()
106
100
 
107
101
  # Prepare stack
@@ -132,8 +126,6 @@ class SignalAnnotator2(QMainWindow,Styles):
132
126
 
133
127
  #self.cell_fcanvas.setMinimumHeight(int(0.3*self.screen_height))
134
128
 
135
- self.setAttribute(Qt.WA_DeleteOnClose)
136
-
137
129
  def resizeEvent(self, event):
138
130
 
139
131
  super().resizeEvent(event)
@@ -149,7 +141,7 @@ class SignalAnnotator2(QMainWindow,Styles):
149
141
 
150
142
  """
151
143
 
152
- self.button_widget = QWidget()
144
+ self.button_widget = CelldetectiveWidget()
153
145
  main_layout = QHBoxLayout()
154
146
  main_layout.setSpacing(30)
155
147
 
@@ -237,11 +229,11 @@ class SignalAnnotator2(QMainWindow,Styles):
237
229
  reference_layout.addLayout(self.cell_events_hbox)
238
230
 
239
231
  neighbor_layout = QVBoxLayout()
240
- neighbor_layout.addWidget(self.neighbor_cell_info)
232
+ neighbor_layout.addWidget(self.neighbor_cell_info, alignment=Qt.AlignRight)
241
233
  neighbor_layout.addLayout(self.neigh_cell_events_hbox)
242
234
 
243
235
  self.cell_info_hbox.addLayout(reference_layout, 33)
244
- self.cell_info_hbox.addWidget(self.pair_info, 33)
236
+ self.cell_info_hbox.addWidget(self.pair_info, 33, alignment=Qt.AlignCenter)
245
237
  self.cell_info_hbox.addLayout(neighbor_layout, 33)
246
238
 
247
239
  self.left_panel.addLayout(self.cell_info_hbox)
@@ -419,8 +411,6 @@ class SignalAnnotator2(QMainWindow,Styles):
419
411
 
420
412
 
421
413
  self.right_panel.addLayout(animation_buttons_box, 5)
422
-
423
-
424
414
  self.right_panel.addWidget(self.fcanvas, 90)
425
415
 
426
416
  if not self.rgb_mode:
@@ -429,7 +419,7 @@ class SignalAnnotator2(QMainWindow,Styles):
429
419
  self.contrast_slider = QLabeledDoubleRangeSlider()
430
420
  # self.contrast_slider.setSingleStep(0.001)
431
421
  # self.contrast_slider.setTickInterval(0.001)
432
- self.contrast_slider.setOrientation(1)
422
+ self.contrast_slider.setOrientation(Qt.Horizontal)
433
423
  print('range: ', [np.nanpercentile(self.stack.flatten(), 0.001), np.nanpercentile(self.stack.flatten(), 99.999)])
434
424
  self.contrast_slider.setRange(
435
425
  *[np.nanpercentile(self.stack, 0.001), np.nanpercentile(self.stack, 99.999)])
@@ -444,6 +434,7 @@ class SignalAnnotator2(QMainWindow,Styles):
444
434
  main_layout.addLayout(self.right_panel, 65)
445
435
  self.button_widget.adjustSize()
446
436
  self.compute_status_and_colors_reference()
437
+ self.compute_status_and_colors_neighbor()
447
438
 
448
439
  self.extract_relevant_events()
449
440
 
@@ -486,53 +477,6 @@ class SignalAnnotator2(QMainWindow,Styles):
486
477
  self.neighbor_event_choice_cb.addItems(neighbor_class_cols)
487
478
  self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
488
479
 
489
-
490
- def del_target_event_class(self):
491
-
492
- msgBox = QMessageBox()
493
- msgBox.setIcon(QMessageBox.Warning)
494
- 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?")
495
- msgBox.setWindowTitle("Warning")
496
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
497
- returnValue = msgBox.exec()
498
- if returnValue == QMessageBox.No:
499
- return None
500
- else:
501
- class_to_delete = self.target_class_choice_cb.currentText()
502
- time_to_delete = class_to_delete.replace('class','t')
503
- status_to_delete = class_to_delete.replace('class', 'status')
504
- cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
505
- for c in cols_to_delete:
506
- try:
507
- self.df_targets = self.df_targets.drop([c], axis=1)
508
- except Exception as e:
509
- print(e)
510
- item_idx = self.target_class_choice_cb.findText(class_to_delete)
511
- self.target_class_choice_cb.removeItem(item_idx)
512
-
513
- def del_effector_event_class(self):
514
-
515
- msgBox = QMessageBox()
516
- msgBox.setIcon(QMessageBox.Warning)
517
- 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?")
518
- msgBox.setWindowTitle("Warning")
519
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
520
- returnValue = msgBox.exec()
521
- if returnValue == QMessageBox.No:
522
- return None
523
- else:
524
- class_to_delete = self.effector_class_choice_cb.currentText()
525
- time_to_delete = class_to_delete.replace('class','t')
526
- status_to_delete = class_to_delete.replace('class', 'status')
527
- cols_to_delete = [class_to_delete, time_to_delete, status_to_delete]
528
- for c in cols_to_delete:
529
- try:
530
- self.df_effectors = self.df_effectors.drop([c], axis=1)
531
- except Exception as e:
532
- print(e)
533
- item_idx = self.effector_class_choice_cb.findText(class_to_delete)
534
- self.effector_class_choice_cb.removeItem(item_idx)
535
-
536
480
  def del_relative_event_class(self):
537
481
 
538
482
  msgBox = QMessageBox()
@@ -557,55 +501,30 @@ class SignalAnnotator2(QMainWindow,Styles):
557
501
  self.relative_class_choice_cb.removeItem(item_idx)
558
502
 
559
503
  def update_cell_events(self):
560
- if 'self' in self.current_neighborhood:
561
- try:
562
- self.neighbor_event_choice_cb.hide()
563
- self.neigh_lab.hide()
564
- except:
565
- pass
566
- self.reference_event_choice_cb.disconnect()
567
- self.reference_event_choice_cb.clear()
568
- if self.reference_population=='targets':
569
- self.reference_event_choice_cb.addItems(self.target_class_cols)
570
- self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
571
- else:
572
- self.reference_event_choice_cb.addItems(self.effector_class_cols)
573
- self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
574
-
575
- else:
576
- try:
577
- self.neighbor_event_choice_cb.show()
578
- self.neigh_lab.show()
579
- except:
580
- pass
581
- self.reference_event_choice_cb.disconnect()
582
- self.reference_event_choice_cb.clear()
583
504
 
584
- if self.reference_population=='targets':
585
- self.reference_event_choice_cb.addItems(self.target_class_cols)
586
- self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
505
+ try:
506
+ self.neighbor_event_choice_cb.show()
507
+ self.neigh_lab.show()
508
+ except:
509
+ pass
587
510
 
588
- else:
589
- self.reference_event_choice_cb.addItems(self.effector_class_cols)
590
- self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
511
+ self.reference_event_choice_cb.disconnect()
512
+ self.reference_event_choice_cb.clear()
513
+ self.reference_event_choice_cb.addItems(self.class_cols_per_pop[self.reference_population])
514
+ self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
591
515
 
516
+ if not 'self' in self.current_neighborhood:
592
517
  self.neighbor_event_choice_cb.disconnect()
593
518
  self.neighbor_event_choice_cb.clear()
594
-
595
- if self.neighbor_population=='targets':
596
- self.neighbor_event_choice_cb.addItems(self.target_class_cols)
597
- self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
598
-
599
- else:
600
- self.neighbor_event_choice_cb.addItems(self.effector_class_cols)
601
- self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
519
+ self.neighbor_event_choice_cb.addItems(self.class_cols_per_pop[self.neighbor_population])
520
+ self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
602
521
 
603
522
 
604
523
 
605
524
  def create_new_relative_event_class(self):
606
525
 
607
526
  # display qwidget to name the event
608
- self.newClassWidget = QWidget()
527
+ self.newClassWidget = CelldetectiveWidget()
609
528
  self.newClassWidget.setWindowTitle('Create new event class')
610
529
 
611
530
  layout = QVBoxLayout()
@@ -723,12 +642,18 @@ class SignalAnnotator2(QMainWindow,Styles):
723
642
  df_reference['status_color'] = [color_from_status(i) for i in df_reference[self.reference_status_name].to_numpy()]
724
643
  df_reference['class_color'] = [color_from_class(i) for i in df_reference[self.reference_class_name].to_numpy()]
725
644
 
726
- if self.reference_population=='targets':
727
- self.extract_scatter_from_target_trajectories()
728
- else:
729
- self.extract_scatter_from_effector_trajectories()
730
-
731
-
645
+ self.refresh_scatter_from_trajectories(self.reference_population, clear_selection=False)
646
+
647
+ if self.reference_track_of_interest is not None:
648
+ t_reference = df_reference.loc[
649
+ df_reference['TRACK_ID'] == self.reference_track_of_interest, self.reference_time_name].values
650
+ if len(t_reference) > 0:
651
+ t_reference = t_reference[0]
652
+ ymin, ymax = self.cell_ax.get_ylim()
653
+ self.line_dt_reference.set_xdata([t_reference, t_reference])
654
+ self.line_dt_reference.set_ydata([ymin,ymax])
655
+ self.cell_fcanvas.canvas.draw()
656
+
732
657
  def compute_status_and_colors_neighbor(self):
733
658
 
734
659
  df_neighbors = self.dataframes[self.neighbor_population]
@@ -767,10 +692,8 @@ class SignalAnnotator2(QMainWindow,Styles):
767
692
  df_neighbors['status_color'] = [color_from_status(i) for i in df_neighbors[self.neighbor_status_name].to_numpy()]
768
693
  df_neighbors['class_color'] = [color_from_class(i) for i in df_neighbors[self.neighbor_class_name].to_numpy()]
769
694
 
770
- if self.neighbor_population=='targets':
771
- self.extract_scatter_from_target_trajectories()
772
- else:
773
- self.extract_scatter_from_effector_trajectories()
695
+ self.refresh_scatter_from_trajectories(self.neighbor_population)
696
+
774
697
 
775
698
  def compute_status_and_colors_pair(self):
776
699
 
@@ -845,13 +768,8 @@ class SignalAnnotator2(QMainWindow,Styles):
845
768
  if len(self.pair_selection) > 0:
846
769
  self.cancel_pair_selection()
847
770
 
848
- if self.df_targets is not None:
849
- self.target_selection = []
850
- if self.df_effectors is not None:
851
- self.effector_selection = []
852
-
853
771
  _, _, neighbor_colors, initial_neighbor_colors = self.get_neighbor_sets()
854
- _, _, reference_colors, initial_reference_colors = self.get_reference_sets()
772
+ _, _, _, reference_colors, initial_reference_colors = self.get_reference_sets()
855
773
 
856
774
  for k, (t,idx) in enumerate(zip(self.neighbor_loc_t, self.neighbor_loc_idx)):
857
775
  neighbor_colors[t][idx,0] = initial_neighbor_colors[k][0]
@@ -936,8 +854,17 @@ class SignalAnnotator2(QMainWindow,Styles):
936
854
  if option!=0:
937
855
  self.lines[i].set_xdata([])
938
856
  self.lines[i].set_ydata([])
857
+
939
858
  self.line_dt.set_xdata([])
940
859
  self.line_dt.set_ydata([])
860
+
861
+ if self.reference_track_of_interest is None:
862
+ self.line_dt_reference.set_xdata([])
863
+ self.line_dt_reference.set_ydata([])
864
+
865
+ self.line_dt_neighbor.set_xdata([])
866
+ self.line_dt_neighbor.set_ydata([])
867
+
941
868
  self.lines[i].set_label('')
942
869
 
943
870
  self.correct_btn.setEnabled(False)
@@ -949,15 +876,15 @@ class SignalAnnotator2(QMainWindow,Styles):
949
876
  # Plot the new time
950
877
  t0 = -1
951
878
  if self.event_btn.isChecked():
952
- try:
953
- cclass = 0
954
- t0 = float(self.time_of_interest_le.text().replace(',', '.'))
955
- self.line_dt.set_xdata([t0, t0])
956
- self.cell_fcanvas.canvas.draw_idle()
957
- except Exception as e:
958
- print(e)
959
- t0 = -1
960
- cclass = 2
879
+ # try:
880
+ cclass = 0
881
+ t0 = float(self.time_of_interest_le.text().replace(',', '.'))
882
+ self.line_dt.set_xdata([t0, t0])
883
+ self.cell_fcanvas.canvas.draw_idle()
884
+ # except Exception as e:
885
+ # print(e)
886
+ # t0 = -1
887
+ # cclass = 2
961
888
 
962
889
  elif self.no_event_btn.isChecked():
963
890
  cclass = 1
@@ -1012,8 +939,8 @@ class SignalAnnotator2(QMainWindow,Styles):
1012
939
  # but keep reference
1013
940
 
1014
941
  #self.make_status_column()
1015
- self.extract_scatter_from_target_trajectories()
1016
- self.extract_scatter_from_effector_trajectories()
942
+ self.refresh_scatter_from_trajectories(self.reference_population)
943
+ self.refresh_scatter_from_trajectories(self.neighbor_population)
1017
944
 
1018
945
  self.recolor_selection()
1019
946
  self.trace_neighbors()
@@ -1048,234 +975,128 @@ class SignalAnnotator2(QMainWindow,Styles):
1048
975
  self.channels = np.array(self.channels)
1049
976
  self.nbr_channels = len(self.channels)
1050
977
 
1051
- def locate_target_tracks(self):
1052
-
1053
- population = 'targets'
1054
- self.target_trajectories_path = self.pos + os.sep.join(['output','tables', f'trajectories_{population}.pkl'])
1055
- if not os.path.exists(self.target_trajectories_path):
1056
- self.target_trajectories_path = self.target_trajectories_path.replace('.pkl','.csv')
1057
-
1058
- if not os.path.exists(self.target_trajectories_path):
1059
-
1060
- msgBox = QMessageBox()
1061
- msgBox.setIcon(QMessageBox.Warning)
1062
- msgBox.setText("The target trajectories cannot be detected...")
1063
- msgBox.setWindowTitle("Warning")
1064
- msgBox.setStandardButtons(QMessageBox.Ok)
1065
- returnValue = msgBox.exec()
1066
- self.df_targets = None
1067
-
1068
- else:
1069
-
1070
- # Load and prep tracks
1071
- if self.target_trajectories_path.endswith('.pkl'):
1072
- self.df_targets = np.load(self.target_trajectories_path, allow_pickle=True)
978
+ def locate_all_tracks(self):
979
+
980
+ self.dataframes = {}
981
+ self.class_cols_per_pop = {}
982
+ self.population_columns = {}
983
+ self.MinMaxScaler_per_pop = {}
984
+
985
+ for population in self.populations:
986
+
987
+ population_trajectories_path = self.pos + os.sep.join(['output','tables', f'trajectories_{population}.pkl'])
988
+ if not os.path.exists(population_trajectories_path):
989
+ population_trajectories_path = population_trajectories_path.replace('.pkl','.csv')
990
+
991
+ if not os.path.exists(population_trajectories_path):
992
+
993
+ msgBox = QMessageBox()
994
+ msgBox.setIcon(QMessageBox.Warning)
995
+ msgBox.setText(f"The trajectories for the {population} population cannot be detected...")
996
+ msgBox.setWindowTitle("Warning")
997
+ msgBox.setStandardButtons(QMessageBox.Ok)
998
+ returnValue = msgBox.exec()
999
+ df_population = None
1073
1000
  else:
1074
- self.df_targets = pd.read_csv(self.target_trajectories_path)
1075
-
1076
- self.df_targets = self.df_targets.sort_values(by=['TRACK_ID', 'FRAME'])
1077
-
1078
- cols = np.array(self.df_targets.columns)
1079
- self.target_class_cols = [c for c in list(self.df_targets.columns) if c.startswith('class')]
1080
-
1081
- try:
1082
- self.target_class_cols.remove('class_id')
1083
- except:
1084
- pass
1085
- try:
1086
- self.target_class_cols.remove('class_color')
1087
- except:
1088
- pass
1001
+ # Load and prep tracks
1002
+ if population_trajectories_path.endswith('.pkl'):
1003
+ df_population = np.load(population_trajectories_path, allow_pickle=True)
1004
+ else:
1005
+ df_population = pd.read_csv(population_trajectories_path)
1089
1006
 
1090
- if len(self.target_class_cols)>0:
1007
+ df_population = df_population.sort_values(by=['TRACK_ID', 'FRAME'])
1091
1008
 
1092
- self.target_class_name = self.target_class_cols[0]
1093
- self.target_expected_status = 'status'
1094
- suffix = self.target_class_name.replace('class','').replace('_','')
1095
- if suffix!='':
1096
- self.target_expected_status+='_'+suffix
1097
- self.target_expected_time = 't_'+suffix
1098
- else:
1099
- self.target_expected_time = 't0'
1100
- self.target_time_name = self.target_expected_time
1101
- self.target_status_name = self.target_expected_status
1102
- else:
1103
- self.target_class_name = 'class'
1104
- self.target_time_name = 't0'
1105
- self.target_status_name = 'status'
1009
+ cols = np.array(df_population.columns)
1010
+ class_cols = [c for c in list(df_population.columns) if c.startswith('class')]
1106
1011
 
1107
- 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:
1108
- # only create the status column if it does not exist to not erase static classification results
1109
- pass
1110
- #self.make_target_status_column()
1111
- elif self.target_time_name in self.df_targets.columns and self.target_class_name in self.df_targets.columns:
1112
- # all good, do nothing
1113
- pass
1114
- else:
1115
- if not self.target_status_name in self.df_targets.columns:
1116
- self.df_targets[self.target_status_name] = 0
1117
- self.df_targets['status_color'] = color_from_status(0)
1118
- self.df_targets['class_color'] = color_from_class(1)
1119
-
1120
- if not self.target_class_name in self.df_targets.columns:
1121
- self.df_targets[self.target_class_name] = 1
1122
- if not self.target_time_name in self.df_targets.columns:
1123
- self.df_targets[self.target_time_name] = -1
1124
-
1125
- 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()]
1126
- 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()]
1127
-
1128
- self.df_targets = self.df_targets.dropna(subset=['POSITION_X', 'POSITION_Y'])
1129
- self.df_targets['x_anim'] = self.df_targets['POSITION_X'] * self.fraction
1130
- self.df_targets['y_anim'] = self.df_targets['POSITION_Y'] * self.fraction
1131
- self.df_targets['x_anim'] = self.df_targets['x_anim'].astype(int)
1132
- self.df_targets['y_anim'] = self.df_targets['y_anim'].astype(int)
1133
-
1134
- self.extract_scatter_from_target_trajectories()
1135
- self.target_track_of_interest = self.df_targets['TRACK_ID'].min()
1136
-
1137
- self.loc_t = []
1138
- self.loc_idx = []
1139
- for t in range(len(self.target_tracks)):
1140
- indices = np.where(self.target_tracks[t]==self.target_track_of_interest)[0]
1141
- if len(indices)>0:
1142
- self.loc_t.append(t)
1143
- self.loc_idx.append(indices[0])
1144
-
1145
- self.MinMaxScaler_targets = MinMaxScaler()
1146
- self.target_columns = list(self.df_targets.columns)
1147
- cols_to_remove = [c for c in self.cols_to_remove if c in self.target_columns] + self.target_class_cols
1148
- time_cols = [c for c in self.target_columns if c.startswith('t_')]
1149
- cols_to_remove += time_cols
1150
- neigh_cols = [c for c in self.target_columns if c.startswith('neighborhood_')]
1151
- cols_to_remove += neigh_cols
1152
-
1153
- for col in cols_to_remove:
1154
1012
  try:
1155
- self.target_columns.remove(col)
1013
+ class_cols.remove('class_id')
1014
+ except:
1015
+ pass
1016
+ try:
1017
+ class_cols.remove('class_color')
1156
1018
  except:
1157
1019
  pass
1158
1020
 
1159
- x = self.df_targets[self.target_columns].values
1160
- self.MinMaxScaler_targets.fit(x)
1161
-
1162
- def locate_effector_tracks(self):
1163
-
1164
- population = 'effectors'
1165
- self.effector_trajectories_path = self.pos + os.sep.join(['output','tables',f'trajectories_{population}.pkl'])
1166
- if not os.path.exists(self.effector_trajectories_path):
1167
- self.effector_trajectories_path = self.effector_trajectories_path.replace('.pkl','.csv')
1168
-
1169
- if not os.path.exists(self.effector_trajectories_path):
1170
-
1171
- msgBox = QMessageBox()
1172
- msgBox.setIcon(QMessageBox.Warning)
1173
- msgBox.setText("The effector trajectories cannot be detected...")
1174
- msgBox.setWindowTitle("Warning")
1175
- msgBox.setStandardButtons(QMessageBox.Ok)
1176
- returnValue = msgBox.exec()
1177
- self.df_effectors = None
1178
- else:
1179
- # Load and prep tracks
1180
- if self.effector_trajectories_path.endswith('.pkl'):
1181
- self.df_effectors = np.load(self.effector_trajectories_path, allow_pickle=True)
1182
- else:
1183
- self.df_effectors = pd.read_csv(self.effector_trajectories_path)
1184
-
1185
- try:
1186
- self.df_effectors = self.df_effectors.sort_values(by=['TRACK_ID', 'FRAME'])
1187
- except:
1188
- self.df_effectors = self.df_effectors.sort_values(by=['ID', 'FRAME'])
1189
-
1021
+ if len(class_cols)>0:
1190
1022
 
1191
- cols = np.array(self.df_effectors.columns)
1192
- self.effector_class_cols = np.array([c.startswith('class') for c in list(self.df_effectors.columns)])
1193
- self.effector_class_cols = list(cols[self.effector_class_cols])
1194
- try:
1195
- self.effector_class_cols.remove('class_id')
1196
- except:
1197
- pass
1198
- try:
1199
- self.effector_class_cols.remove('class_color')
1200
- except:
1201
- pass
1202
- if len(self.effector_class_cols)>0:
1203
- self.effector_class_name = self.effector_class_cols[0]
1204
- self.effector_expected_status = 'status'
1205
- suffix = self.effector_class_name.replace('class','').replace('_','')
1206
- if suffix!='':
1207
- self.effector_expected_status+='_'+suffix
1208
- self.effector_expected_time = 't_'+suffix
1023
+ pop_class_name = class_cols[0]
1024
+ pop_expected_status = 'status'
1025
+ suffix = pop_class_name.replace('class','').replace('_','')
1026
+ if suffix!='':
1027
+ pop_expected_status+='_'+suffix
1028
+ pop_expected_time = 't_'+suffix
1029
+ else:
1030
+ pop_expected_time = 't0'
1031
+ pop_time_name = pop_expected_time
1032
+ pop_status_name = pop_expected_status
1209
1033
  else:
1210
- self.effector_expected_time = 't0'
1211
- self.effector_time_name = self.effector_expected_time
1212
- self.effector_status_name = self.effector_expected_status
1213
- else:
1214
- self.effector_class_name = 'class'
1215
- self.effector_time_name = 't0'
1216
- self.effector_status_name = 'status'
1034
+ pop_class_name = 'class'
1035
+ pop_time_name = 't0'
1036
+ pop_status_name = 'status'
1217
1037
 
1218
- 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:
1219
- # only create the status column if it does not exist to not erase static classification results
1220
- pass
1221
- #self.make_effector_status_column()
1222
- elif self.effector_time_name in self.df_effectors.columns and self.effector_class_name in self.df_effectors.columns:
1223
- # all good, do nothing
1224
- pass
1225
- else:
1226
- if not self.effector_status_name in self.df_effectors.columns:
1227
- self.df_effectors[self.effector_status_name] = 0
1228
- self.df_effectors['status_color'] = color_from_status(0)
1229
- self.df_effectors['class_color'] = color_from_class(1)
1230
-
1231
- if not self.effector_class_name in self.df_effectors.columns:
1232
- self.df_effectors[self.effector_class_name] = 1
1233
- if not self.effector_time_name in self.df_effectors.columns:
1234
- self.df_effectors[self.effector_time_name] = -1
1235
-
1236
- 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()]
1237
- 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()]
1238
-
1239
-
1240
- self.df_effectors = self.df_effectors.dropna(subset=['POSITION_X', 'POSITION_Y'])
1241
- self.df_effectors['x_anim'] = self.df_effectors['POSITION_X'] * self.fraction
1242
- self.df_effectors['y_anim'] = self.df_effectors['POSITION_Y'] * self.fraction
1243
- self.df_effectors['x_anim'] = self.df_effectors['x_anim'].astype(int)
1244
- self.df_effectors['y_anim'] = self.df_effectors['y_anim'].astype(int)
1245
-
1246
- self.extract_scatter_from_effector_trajectories()
1247
- try:
1248
- self.effector_track_of_interest = self.df_effectors['TRACK_ID'].min()
1249
- except:
1250
- self.effector_track_of_interest = self.df_effectors['ID'].min()
1251
-
1252
-
1253
- self.loc_t = []
1254
- self.loc_idx = []
1255
- for t in range(len(self.effector_tracks)):
1256
- indices = np.where(self.effector_tracks[t]==self.effector_track_of_interest)[0]
1257
- if len(indices)>0:
1258
- self.loc_t.append(t)
1259
- self.loc_idx.append(indices[0])
1260
-
1261
- self.MinMaxScaler_effectors = MinMaxScaler()
1262
- self.effector_columns = list(self.df_effectors.columns)
1263
- cols_to_remove = [c for c in self.cols_to_remove if c in self.effector_columns] + self.effector_class_cols
1264
- time_cols = [c for c in self.effector_columns if c.startswith('t_')]
1265
- cols_to_remove += time_cols
1266
- neigh_cols = [c for c in self.effector_columns if c.startswith('neighborhood_')]
1267
- cols_to_remove += neigh_cols
1268
-
1269
- for col in cols_to_remove:
1270
- try:
1271
- self.effector_columns.remove(col)
1272
- except:
1038
+ if pop_time_name in df_population.columns and pop_class_name in df_population.columns and not pop_status_name in df_population.columns:
1039
+ # only create the status column if it does not exist to not erase static classification results
1040
+ pass
1041
+ #self.make_target_status_column()
1042
+ elif pop_time_name in df_population.columns and pop_class_name in df_population.columns:
1043
+ # all good, do nothing
1273
1044
  pass
1045
+ else:
1046
+ if not pop_status_name in df_population.columns:
1047
+ df_population[pop_status_name] = 0
1048
+ df_population['status_color'] = color_from_status(0)
1049
+ df_population['class_color'] = color_from_class(1)
1050
+
1051
+ if not pop_class_name in df_population.columns:
1052
+ df_population[pop_class_name] = 1
1053
+ if not pop_time_name in df_population.columns:
1054
+ df_population[pop_time_name] = -1
1055
+
1056
+ df_population['status_color'] = color_from_status(2)
1057
+ df_population['class_color'] = color_from_status(2)
1058
+
1059
+ df_population = df_population.dropna(subset=['POSITION_X', 'POSITION_Y'])
1060
+ df_population['x_anim'] = df_population['POSITION_X'] * self.fraction
1061
+ df_population['y_anim'] = df_population['POSITION_Y'] * self.fraction
1062
+ df_population['x_anim'] = df_population['x_anim'].astype(int)
1063
+ df_population['y_anim'] = df_population['y_anim'].astype(int)
1064
+
1065
+ #self.reference_positions, self.reference_tracks, self.reference_colors, self.reference_initial_colors = self.extract_scatter_from_trajectories(self.reference_population)
1066
+ #self.extract_scatter_from_target_trajectories()
1067
+
1068
+ tracks = df_population['TRACK_ID'].unique()
1069
+ self.target_track_of_interest = df_population['TRACK_ID'].min()
1070
+
1071
+ self.loc_t = []
1072
+ self.loc_idx = []
1073
+ for t in range(len(tracks)):
1074
+ indices = np.where(tracks[t]==self.target_track_of_interest)[0]
1075
+ if len(indices)>0:
1076
+ self.loc_t.append(t)
1077
+ self.loc_idx.append(indices[0])
1078
+
1079
+ minmax = MinMaxScaler()
1080
+ pop_cols = list(df_population.columns)
1081
+ cols_to_remove = [c for c in self.cols_to_remove if c in pop_cols] + class_cols
1082
+ time_cols = [c for c in pop_cols if c.startswith('t_')]
1083
+ cols_to_remove += time_cols
1084
+ neigh_cols = [c for c in pop_cols if c.startswith('neighborhood_')]
1085
+ cols_to_remove += neigh_cols
1086
+
1087
+ for col in cols_to_remove:
1088
+ try:
1089
+ pop_cols.remove(col)
1090
+ except:
1091
+ pass
1274
1092
 
1275
- x = self.df_effectors[self.effector_columns].to_numpy()
1276
- print(self.effector_columns, x, x.shape)
1277
- self.MinMaxScaler_effectors.fit(x)
1093
+ x = df_population[pop_cols].values
1094
+ minmax.fit(x)
1278
1095
 
1096
+ self.dataframes.update({population: df_population})
1097
+ self.class_cols_per_pop.update({population: class_cols})
1098
+ self.population_columns.update({population: pop_cols})
1099
+ self.MinMaxScaler_per_pop.update({population: minmax})
1279
1100
 
1280
1101
  def locate_relative_tracks(self):
1281
1102
 
@@ -1294,7 +1115,8 @@ class SignalAnnotator2(QMainWindow,Styles):
1294
1115
  else:
1295
1116
  # Load and prep tracks
1296
1117
  self.df_relative = pd.read_csv(self.relative_trajectories_path)
1297
- print(self.df_relative.columns)
1118
+ self.df_relative = self.df_relative.astype({"reference_population": str, "neighbor_population": str})
1119
+
1298
1120
  self.df_relative= self.df_relative.sort_values(by=['REFERENCE_ID','NEIGHBOR_ID','reference_population','neighbor_population','FRAME'])
1299
1121
  self.relative_cols = np.array(self.df_relative.columns)
1300
1122
 
@@ -1341,10 +1163,21 @@ class SignalAnnotator2(QMainWindow,Styles):
1341
1163
  def set_reference_and_neighbor_populations(self):
1342
1164
 
1343
1165
  neigh = self.neighborhood_choice_cb.currentText()
1344
- self.current_neighborhood = neigh.replace('target_ref_','').replace('effector_ref_','')
1345
- self.reference_population = ['targets' if 'target' in neigh else 'effectors'][0]
1346
- 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]
1166
+
1167
+ self.current_neighborhood = neigh
1168
+ for pop in self.dataframes.keys():
1169
+ self.current_neighborhood = self.current_neighborhood.replace(f'{pop}_ref_','')
1347
1170
 
1171
+ self.reference_population = self.neighborhood_choice_cb.currentText().split('_')[0]
1172
+
1173
+ if '_(' in self.current_neighborhood and ')_' in self.current_neighborhood:
1174
+ self.neighbor_population = self.current_neighborhood.split('_(')[-1].split(')_')[0].split('-')[-1]
1175
+ self.reference_population = self.current_neighborhood.split('_(')[-1].split(')_')[0].split('-')[0]
1176
+ else:
1177
+ if 'self' in self.current_neighborhood:
1178
+ self.neighbor_population = self.reference_population
1179
+
1180
+
1348
1181
  print(f'Current neighborhood: {self.current_neighborhood}')
1349
1182
  print(f'New reference population: {self.reference_population}')
1350
1183
  print(f'New neighbor population: {self.neighbor_population}')
@@ -1395,7 +1228,6 @@ class SignalAnnotator2(QMainWindow,Styles):
1395
1228
  status[:] = 2
1396
1229
  if cclass>2:
1397
1230
  status[:] = 42
1398
- print(t0, status)
1399
1231
  status_color = [color_from_status(s) for s in status]
1400
1232
  class_color = [color_from_class(cclass) for i in range(len(status))]
1401
1233
 
@@ -1514,8 +1346,16 @@ class SignalAnnotator2(QMainWindow,Styles):
1514
1346
  self.lines[i].set_xdata([])
1515
1347
  self.lines[i].set_ydata([])
1516
1348
  self.lines[i].set_label('')
1349
+
1517
1350
  self.line_dt.set_xdata([])
1518
1351
  self.line_dt.set_ydata([])
1352
+
1353
+ self.line_dt_reference.set_xdata([])
1354
+ self.line_dt_reference.set_ydata([])
1355
+
1356
+ self.line_dt_neighbor.set_xdata([])
1357
+ self.line_dt_neighbor.set_ydata([])
1358
+
1519
1359
  self.cell_fcanvas.canvas.draw()
1520
1360
  return None
1521
1361
  else:
@@ -1528,11 +1368,18 @@ class SignalAnnotator2(QMainWindow,Styles):
1528
1368
  signal = []; timeline = [];
1529
1369
  signal_txt = self.signal_choices[i].currentText()
1530
1370
  option = self.signal_pop_button_groups[i].checkedId()
1371
+
1372
+ n_cut = 25
1373
+ if len(signal_txt) > n_cut:
1374
+ signal_txt_to_show = signal_txt[:(n_cut - 3)] + '...'
1375
+ else:
1376
+ signal_txt_to_show = signal_txt
1531
1377
 
1532
1378
  if option==0 and self.reference_track_of_interest is not None and signal_txt!='--' and signal_txt!='':
1533
1379
 
1534
1380
  df_reference = self.dataframes[self.reference_population]
1535
- self.lines[i].set_label(f'reference ({self.reference_population}) '+ signal_txt)
1381
+
1382
+ self.lines[i].set_label(f'reference ({self.reference_population}) '+ signal_txt_to_show)
1536
1383
 
1537
1384
  signal = df_reference.loc[df_reference['TRACK_ID']==self.reference_track_of_interest, signal_txt].to_numpy()
1538
1385
  timeline = df_reference.loc[df_reference['TRACK_ID']==self.reference_track_of_interest, 'FRAME'].to_numpy()
@@ -1541,15 +1388,14 @@ class SignalAnnotator2(QMainWindow,Styles):
1541
1388
  elif option==1 and self.neighbor_track_of_interest is not None and signal_txt!='--' and signal_txt!='':
1542
1389
 
1543
1390
  df_neighbor = self.dataframes[self.neighbor_population]
1544
- self.lines[i].set_label(f'neighbor ({self.neighbor_population}) '+ signal_txt)
1391
+ self.lines[i].set_label(f'neighbor ({self.neighbor_population}) '+ signal_txt_to_show)
1545
1392
 
1546
1393
  signal = df_neighbor.loc[df_neighbor['TRACK_ID']==self.neighbor_track_of_interest, signal_txt].to_numpy()
1547
1394
  timeline = df_neighbor.loc[df_neighbor['TRACK_ID']==self.neighbor_track_of_interest, 'FRAME'].to_numpy()
1548
1395
  range_values.extend(df_neighbor.loc[:,signal_txt].values)
1549
1396
 
1550
1397
  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!='':
1551
-
1552
- self.lines[i].set_label(f'pair '+signal_txt)
1398
+ self.lines[i].set_label(f'pair '+signal_txt_to_show)
1553
1399
  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()
1554
1400
  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()
1555
1401
  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)
@@ -1560,7 +1406,7 @@ class SignalAnnotator2(QMainWindow,Styles):
1560
1406
  self.lines[i].set_xdata(timeline)
1561
1407
  self.lines[i].set_ydata(signal)
1562
1408
  self.lines[i].set_color(tab10(i / float(self.n_signals)))
1563
-
1409
+
1564
1410
  #self.configure_ylims()
1565
1411
  if len(range_values)>0:
1566
1412
  range_values = np.array(range_values)
@@ -1576,18 +1422,36 @@ class SignalAnnotator2(QMainWindow,Styles):
1576
1422
  else:
1577
1423
  self.cell_ax.set_ylim(self.value_magnitude, self.non_log_ymax)
1578
1424
 
1425
+ df_reference = self.dataframes[self.reference_population]
1426
+ t_reference = df_reference.loc[df_reference['TRACK_ID']==self.reference_track_of_interest, self.reference_time_name].values
1427
+
1428
+ if len(t_reference)>0:
1429
+ t_reference=t_reference[0]
1430
+ ymin,ymax = self.cell_ax.get_ylim()
1431
+ self.line_dt_reference.set_xdata([t_reference, t_reference])
1432
+ self.line_dt_reference.set_ydata([ymin,ymax])
1433
+
1434
+
1579
1435
  if self.reference_track_of_interest is not None and self.neighbor_track_of_interest is not None:
1580
1436
  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().values
1581
- try:
1582
- if t0!=[]:
1583
- t0=t0[0]
1584
- ymin,ymax = self.cell_ax.get_ylim()
1585
- self.line_dt.set_xdata([t0, t0])
1586
- self.line_dt.set_ydata([ymin,ymax])
1587
- except Exception as e:
1588
- print(e)
1589
1437
 
1590
- self.cell_ax.legend()
1438
+ df_neighbor = self.dataframes[self.neighbor_population]
1439
+ t_neighbor = df_neighbor.loc[
1440
+ df_neighbor['TRACK_ID'] == self.neighbor_track_of_interest, self.neighbor_time_name].values
1441
+
1442
+ if len(t0)>0:
1443
+ t0=t0[0]
1444
+ ymin,ymax = self.cell_ax.get_ylim()
1445
+ self.line_dt.set_xdata([t0, t0])
1446
+ self.line_dt.set_ydata([ymin,ymax])
1447
+
1448
+ if len(t_neighbor)>0:
1449
+ t_neighbor=t_neighbor[0]
1450
+ ymin,ymax = self.cell_ax.get_ylim()
1451
+ self.line_dt_neighbor.set_xdata([t_neighbor, t_neighbor])
1452
+ self.line_dt_neighbor.set_ydata([ymin,ymax])
1453
+
1454
+ self.cell_ax.legend(fontsize=8)
1591
1455
  self.cell_fcanvas.canvas.draw()
1592
1456
 
1593
1457
 
@@ -1603,55 +1467,63 @@ class SignalAnnotator2(QMainWindow,Styles):
1603
1467
  for t in np.arange(self.len_movie):
1604
1468
 
1605
1469
  # Append frame_positions to self.line_positions
1606
- 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())
1607
- 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())
1608
- 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())
1609
- 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())
1610
- 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())
1611
-
1612
- def extract_scatter_from_target_trajectories(self):
1613
-
1614
- print('extracting scatter from target trajectories...')
1615
-
1616
- self.target_positions = []
1617
- self.target_colors = []
1618
- self.target_tracks = []
1619
- self.initial_target_colors = []
1620
-
1621
- for t in np.arange(self.len_movie):
1622
-
1623
- if self.df_targets is not None:
1624
- self.target_positions.append(self.df_targets.loc[self.df_targets['FRAME']==t,['x_anim', 'y_anim']].to_numpy())
1625
- self.target_colors.append(self.df_targets.loc[self.df_targets['FRAME']==t,['class_color', 'status_color']].to_numpy())
1626
- self.initial_target_colors.append(
1627
- self.df_targets.loc[self.df_targets['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
1628
- try:
1629
- self.target_tracks.append(self.df_targets.loc[self.df_targets['FRAME']==t, 'TRACK_ID'].to_numpy())
1630
- except:
1631
- self.target_tracks.append(
1632
- self.df_targets.loc[self.df_targets['FRAME'] == t, 'ID'].to_numpy())
1470
+ neigh_at_time_filter = (self.df_relative['FRAME'] == t)&(~self.df_relative['status_'+self.current_neighborhood].isnull())&(self.df_relative['reference_population']==self.reference_population)
1471
+ data = self.df_relative.loc[neigh_at_time_filter, ['REFERENCE_ID', 'NEIGHBOR_ID','status_color','class_color']].to_numpy()
1472
+ self.lines_tracks.append(data[:,[0,1]])
1473
+
1474
+ self.initial_lines_colors_status.append(data[:,[0,1,2]])
1475
+ self.lines_colors_status.append(data[:,[0,1,2]])
1476
+
1477
+ self.initial_lines_colors_class.append(data[:,[0,1,3]])
1478
+ self.lines_colors_class.append(data[:,[0,1,3]])
1633
1479
 
1480
+ def extract_scatter_from_trajectories(self):
1634
1481
 
1635
- def extract_scatter_from_effector_trajectories(self):
1482
+ self.tracks = {}
1483
+ self.positions = {}
1484
+ self.colors = {}
1485
+ self.initial_colors = {}
1486
+ self.timeline = {}
1487
+
1488
+ for population in self.dataframes.keys():
1489
+ self.refresh_scatter_from_trajectories(population)
1636
1490
 
1637
- self.effector_positions = []
1638
- self.effector_colors = []
1639
- self.initial_effector_colors=[]
1640
- self.effector_tracks = []
1491
+ def refresh_scatter_from_trajectories(self, population, clear_selection=True):
1492
+
1493
+ df = self.dataframes[population]
1494
+ positions = []
1495
+ tracks = []
1496
+ timeline = []
1497
+ colors = []
1498
+ initial_colors = []
1641
1499
 
1642
- for t in np.arange(self.len_movie):
1500
+ if df is not None:
1643
1501
 
1644
- if self.df_effectors is not None:
1502
+ for t in np.arange(self.len_movie):
1645
1503
 
1646
- self.effector_positions.append(self.df_effectors.loc[self.df_effectors['FRAME']==t,['x_anim', 'y_anim']].to_numpy())
1647
- self.effector_colors.append(self.df_effectors.loc[self.df_effectors['FRAME']==t,['class_color', 'status_color']].to_numpy())
1648
- self.initial_effector_colors.append(self.df_effectors.loc[self.df_effectors['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
1504
+ positions.append(df.loc[df['FRAME']==t,['x_anim', 'y_anim']].to_numpy())
1505
+ colors.append(df.loc[df['FRAME']==t,['class_color', 'status_color']].to_numpy())
1506
+ initial_colors.append(
1507
+ df.loc[df['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
1649
1508
  try:
1650
- self.effector_tracks.append(self.df_effectors.loc[self.df_effectors['FRAME']==t, 'TRACK_ID'].to_numpy())
1509
+ tracks.append(df.loc[df['FRAME']==t, 'TRACK_ID'].to_numpy())
1510
+ timeline.append(t)
1651
1511
  except:
1652
- self.effector_tracks.append(
1653
- self.df_effectors.loc[self.df_effectors['FRAME'] == t, 'ID'].to_numpy())
1654
-
1512
+ tracks.append(
1513
+ df.loc[df['FRAME'] == t, 'ID'].to_numpy())
1514
+ timeline.append(t)
1515
+
1516
+ self.tracks.update({population: tracks})
1517
+ self.timeline.update({population: timeline})
1518
+ self.positions.update({population: positions})
1519
+ self.colors.update({population: colors})
1520
+ self.initial_colors.update({population: initial_colors})
1521
+
1522
+ if self.reference_track_of_interest is not None:
1523
+ #self.recolor_selection()
1524
+ #self.trace_neighbors()
1525
+ self.highlight_the_pair()
1526
+
1655
1527
  def load_annotator_config(self):
1656
1528
 
1657
1529
  """
@@ -1793,7 +1665,11 @@ class SignalAnnotator2(QMainWindow,Styles):
1793
1665
  # "Are you sure you want to exit ?",
1794
1666
  # QMessageBox.Yes| QMessageBox.No,
1795
1667
  # )
1796
- del self.stack
1668
+ try:
1669
+ del self.stack
1670
+ except:
1671
+ pass
1672
+
1797
1673
  gc.collect()
1798
1674
 
1799
1675
  def looped_animation(self):
@@ -1815,21 +1691,26 @@ class SignalAnnotator2(QMainWindow,Styles):
1815
1691
  self.im = self.ax.imshow(self.stack[0], cmap='gray', vmin=np.nanpercentile(self.stack, 1), vmax=np.nanpercentile(self.stack, 99.99))
1816
1692
 
1817
1693
 
1818
- if self.df_targets is not None:
1819
- 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, zorder=10)
1820
- 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, zorder=10)
1821
- else:
1822
- self.target_status_scatter = self.ax.scatter([],[], marker="x", s=50, picker=True, pickradius=10)
1823
- self.target_class_scatter = self.ax.scatter([],[], marker='o', facecolors='none', s=200)
1824
-
1825
- if self.df_effectors is not None:
1826
- 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, zorder=10)
1827
- 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, zorder=10)
1828
- else:
1829
- self.effector_status_scatter = self.ax.scatter([], [], marker="x", s=50, picker=True, pickradius=10)
1830
- self.effector_class_scatter = self.ax.scatter([],[], marker='^', facecolors='none', s=200)
1694
+ self.status_scatter = {}
1695
+ self.class_scatter = {}
1696
+
1697
+ n_pops = len(list(self.dataframes.keys()))
1698
+ markers = ['x','+','*','.','X']
1699
+ while len(markers) < n_pops:
1700
+ markers = markers*2
1701
+
1702
+ for i,pop in enumerate(self.dataframes.keys()):
1703
+ df = self.dataframes[pop]
1704
+ if df is not None:
1705
+ status_scatter = self.ax.scatter(self.positions[pop][0][:,0], self.positions[pop][0][:,1], c=self.colors[pop][0][:,1], s=50, picker=True, pickradius=150, zorder=10, marker=markers[i]) #marker="x",
1706
+ class_scatter = self.ax.scatter(self.positions[pop][0][:,0], self.positions[pop][0][:,1], marker='o', facecolors='none',edgecolors=self.colors[pop][0][:,0], s=200, zorder=10)
1707
+ else:
1708
+ status_scatter = self.ax.scatter([], [], s=50, picker=True, pickradius=10, marker=markers[i]) #marker="x",
1709
+ class_scatter = self.ax.scatter([], [], marker='o', facecolors='none', s=200)
1710
+ self.status_scatter.update({pop: status_scatter})
1711
+ self.class_scatter.update({pop: class_scatter})
1831
1712
 
1832
- self.points=self.ax.scatter([], [], marker="$\Join$", s=100, picker=True, pickradius=10, zorder=10) #picker=True, pickradius=10
1713
+ self.points=self.ax.scatter([], [], marker="$\\Join$", s=100, picker=True, pickradius=10, zorder=10) #picker=True, pickradius=10
1833
1714
 
1834
1715
  self.ax.set_xticks([])
1835
1716
  self.ax.set_yticks([])
@@ -1869,13 +1750,17 @@ class SignalAnnotator2(QMainWindow,Styles):
1869
1750
 
1870
1751
  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))]
1871
1752
  for i in range(len(self.lines)):
1753
+
1872
1754
  self.lines[i].set_label(f'signal {i}')
1873
1755
 
1874
1756
  min_val,max_val = self.cell_ax.get_ylim()
1875
1757
  self.line_dt, = self.cell_ax.plot([-1,-1],[min_val,max_val],c="k",linestyle="--")
1758
+ self.line_dt_reference, = self.cell_ax.plot([-1,-1],[min_val,max_val],c="tab:blue",linestyle="--")
1759
+ self.line_dt_neighbor, = self.cell_ax.plot([-1,-1],[min_val,max_val],c="tab:red",linestyle="--")
1760
+
1876
1761
 
1877
1762
  self.cell_ax.set_xlim(0,self.len_movie)
1878
- self.cell_ax.legend()
1763
+ self.cell_ax.legend(fontsize=8)
1879
1764
  self.cell_fcanvas.canvas.draw()
1880
1765
 
1881
1766
  #self.plot_signals()
@@ -1884,9 +1769,8 @@ class SignalAnnotator2(QMainWindow,Styles):
1884
1769
  def on_scatter_pick(self, event):
1885
1770
 
1886
1771
  self.identify_closest_marker(event)
1887
- print(self.pair_selected, self.reference_selection)
1888
1772
 
1889
- _, tracks, _, _ = self.get_reference_sets()
1773
+ _, tracks, _, _, _ = self.get_reference_sets()
1890
1774
 
1891
1775
  if self.selected_population == self.reference_population:
1892
1776
 
@@ -1949,12 +1833,15 @@ class SignalAnnotator2(QMainWindow,Styles):
1949
1833
 
1950
1834
  print('You selected a pair...')
1951
1835
  artist = event.artist
1952
- print(self.index)
1953
1836
 
1954
1837
  if self.index is not None and len(self.pair_selection)==0:
1955
1838
 
1956
- selected_point = artist.get_offsets()[self.index]
1957
-
1839
+ try:
1840
+ selected_point = artist.get_offsets()[self.index]
1841
+ except Exception as e:
1842
+ print(f"L1788 {e}")
1843
+ return
1844
+
1958
1845
  if len(self.pair_selection) == 0 and ((selected_point[0],selected_point[1]) in self.connections.keys()):
1959
1846
 
1960
1847
  connect = self.connections[(selected_point[0], selected_point[1])]
@@ -2029,24 +1916,10 @@ class SignalAnnotator2(QMainWindow,Styles):
2029
1916
 
2030
1917
 
2031
1918
  def get_neighbor_sets(self):
2032
-
2033
- if self.reference_population != self.neighbor_population:
2034
- if self.reference_population=='effectors':
2035
- return self.target_positions, self.target_tracks, self.target_colors, self.initial_target_colors
2036
- elif self.reference_population=='targets':
2037
- return self.effector_positions, self.effector_tracks, self.effector_colors, self.initial_effector_colors
2038
- else:
2039
- if self.reference_population=='effectors':
2040
- return self.effector_positions, self.effector_tracks, self.effector_colors, self.initial_effector_colors
2041
- elif self.reference_population=='targets':
2042
- return self.target_positions, self.target_tracks, self.target_colors, self.initial_target_colors
1919
+ return self.positions[self.neighbor_population], self.tracks[self.neighbor_population], self.colors[self.neighbor_population], self.initial_colors[self.neighbor_population]
2043
1920
 
2044
1921
  def get_reference_sets(self):
2045
-
2046
- if self.reference_population == 'effectors':
2047
- return self.effector_positions, self.effector_tracks, self.effector_colors, self.initial_effector_colors
2048
- elif self.reference_population == 'targets':
2049
- return self.target_positions, self.target_tracks, self.target_colors, self.initial_target_colors
1922
+ return self.positions[self.reference_population], self.tracks[self.reference_population], self.timeline[self.reference_population], self.colors[self.reference_population], self.initial_colors[self.reference_population]
2050
1923
 
2051
1924
  def trace_neighbors(self):
2052
1925
 
@@ -2056,7 +1929,8 @@ class SignalAnnotator2(QMainWindow,Styles):
2056
1929
  self.line_connections={}
2057
1930
 
2058
1931
  positions, tracks, colors, _ = self.get_neighbor_sets()
2059
-
1932
+ _, tracks_reference, _, _, _ = self.get_reference_sets()
1933
+
2060
1934
  # Look for neighbors
2061
1935
  for neigh in self.neighbors:
2062
1936
 
@@ -2065,46 +1939,39 @@ class SignalAnnotator2(QMainWindow,Styles):
2065
1939
 
2066
1940
  for t in range(len(tracks)):
2067
1941
  indices = np.where(tracks[t]==neigh)[0]
2068
- if len(indices)>0:
1942
+ indices_reference = np.where(tracks_reference[t]==self.reference_track_of_interest)[0]
1943
+ if len(indices)>0 and len(indices_reference)>0:
2069
1944
  self.neighbor_loc_t.append(t)
2070
1945
  self.neighbor_loc_idx.append(indices[0])
2071
-
1946
+
2072
1947
  self.neighbor_previous_color = []
2073
1948
  for t, idx in zip(self.neighbor_loc_t, self.neighbor_loc_idx):
2074
-
2075
- try:
1949
+
1950
+ t_ref_idx = np.where(self.reference_timeline == t)[0]
1951
+ if t_ref_idx:
1952
+ t_ref_idx = t_ref_idx[0]
2076
1953
 
2077
1954
  neigh_x = positions[t][idx, 0]
2078
1955
  neigh_y = positions[t][idx, 1]
2079
- x_m_point = (self.reference_x[t] + neigh_x) / 2
2080
- y_m_point = (self.reference_y[t] + neigh_y) / 2
2081
-
1956
+ x_m_point = (self.reference_x[t_ref_idx] + neigh_x) / 2
1957
+ y_m_point = (self.reference_y[t_ref_idx] + neigh_y) / 2
1958
+
2082
1959
  if t not in self.lines_data.keys():
2083
- self.lines_data[t]=[([self.reference_x[t], neigh_x], [self.reference_y[t], neigh_y])]
1960
+ self.lines_data[t]=[([self.reference_x[t_ref_idx], neigh_x], [self.reference_y[t_ref_idx], neigh_y])]
2084
1961
  self.points_data[t]=[(x_m_point, y_m_point)]
2085
1962
  else:
2086
- self.lines_data[t].append(([self.reference_x[t], neigh_x], [self.reference_y[t], neigh_y]))
1963
+ self.lines_data[t].append(([self.reference_x[t_ref_idx], neigh_x], [self.reference_y[t_ref_idx], neigh_y]))
2087
1964
  self.points_data[t].append((x_m_point, y_m_point))
2088
-
1965
+
2089
1966
  self.connections[(x_m_point, y_m_point)] = [(self.reference_track_of_interest, neigh)]
2090
- self.line_connections[(self.reference_x[t], neigh_x, self.reference_y[t], neigh_y)]=[(self.reference_track_of_interest, neigh)]
2091
-
1967
+ self.line_connections[(self.reference_x[t_ref_idx], neigh_x, self.reference_y[t_ref_idx], neigh_y)]=[(self.reference_track_of_interest, neigh)]
1968
+
2092
1969
  self.neighbor_previous_color.append(colors[t][idx].copy())
2093
- except Exception as e:
2094
- print(e)
2095
- pass
2096
- #colors[t][idx] = 'salmon'
2097
1970
 
2098
- # for t in range(len(colors)):
2099
- # for idx in range(len(colors[t])):
2100
- # if colors[t][idx].any() != 'salmon':
2101
- # if colors[t][idx].any() != 'magenta':
2102
- # #init_color[t][idx] = colors[t][idx].copy()
2103
- # colors[t][idx] = 'black'
2104
1971
 
2105
1972
  def recolor_selection(self):
2106
1973
 
2107
- positions, tracks, colors, init_colors = self.get_reference_sets()
1974
+ positions, tracks, timelines, colors, init_colors = self.get_reference_sets()
2108
1975
 
2109
1976
  self.reference_loc_t = []
2110
1977
  self.reference_loc_idx = []
@@ -2125,13 +1992,16 @@ class SignalAnnotator2(QMainWindow,Styles):
2125
1992
  self.reference_not_picked_initial_colors=[]
2126
1993
  self.reference_x = []
2127
1994
  self.reference_y = []
1995
+ self.reference_timeline = []
2128
1996
 
2129
1997
  # Recolor selected cell
2130
1998
  for t,idx in zip(self.reference_loc_t,self.reference_loc_idx):
2131
1999
  self.reference_x.append(positions[t][idx, 0])
2132
2000
  self.reference_y.append(positions[t][idx, 1])
2001
+ self.reference_timeline.append(timelines[t])
2133
2002
  self.reference_previous_color.append(colors[t][idx].copy())
2134
2003
  colors[t][idx] = 'lime'
2004
+ self.reference_timeline = np.array(self.reference_timeline)
2135
2005
 
2136
2006
  # Recolor all other cells in black
2137
2007
  for t, idx in zip(self.reference_loc_t_not_picked, self.reference_loc_idx_not_picked):
@@ -2144,6 +2014,7 @@ class SignalAnnotator2(QMainWindow,Styles):
2144
2014
 
2145
2015
  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']
2146
2016
  self.neighbors = np.unique(self.neighbors)
2017
+
2147
2018
  # if len(self.neighbors)>0:
2148
2019
  # first_neighbor = np.min(self.neighbors)
2149
2020
  # self.neighbor_track_of_interest = first_neighbor
@@ -2154,38 +2025,37 @@ class SignalAnnotator2(QMainWindow,Styles):
2154
2025
 
2155
2026
  ind = event.ind
2156
2027
  label = event.artist.get_label()
2157
- print(f'{label=}')
2158
2028
 
2159
2029
  # Identify the nature of the selected object (target/effector/pair)
2160
2030
  self.pair_selected = False
2161
- if label == '_child1':
2162
- self.selected_population = 'targets'
2163
- elif label == '_child3':
2164
- self.selected_population = 'effectors'
2165
- else:
2166
- number = int(label.split('_child')[1])
2167
- if number>4:
2168
- print('A pair is selected...')
2169
- self.pair_selected = True
2170
-
2171
- if self.selected_population=='effectors':
2172
- positions = self.effector_positions
2173
- elif self.selected_population=='targets':
2174
- positions = self.target_positions
2175
-
2176
- if len(ind)==1:
2177
- self.index = ind[0]
2178
- elif len(ind)>1:
2179
- # More than one point in vicinity
2180
- try:
2181
- datax,datay = [positions[self.framedata][i,0] for i in ind],[positions[self.framedata][i,1] for i in ind]
2182
- msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
2183
- dist = np.sqrt((np.array(datax)-msx)**2+(np.array(datay)-msy)**2)
2184
- self.index = ind[np.argmin(dist)]
2185
- except Exception as e:
2186
- print(f"{e=}")
2031
+
2032
+ populations = list(self.dataframes.keys())
2033
+
2034
+ number = int(label.split('_child')[1])
2035
+
2036
+ if number>len(populations)*2:
2037
+ print('A pair is selected...')
2038
+ self.pair_selected = True
2039
+ self.selected_population = None
2187
2040
  else:
2188
- self.index = None
2041
+ self.selected_population = populations[(number-1)//2]
2042
+
2043
+ if self.selected_population is not None:
2044
+ positions = self.positions[self.selected_population]
2045
+
2046
+ if len(ind)==1:
2047
+ self.index = ind[0]
2048
+ elif len(ind)>1:
2049
+ # More than one point in vicinity
2050
+ try:
2051
+ datax,datay = [positions[self.framedata][i,0] for i in ind],[positions[self.framedata][i,1] for i in ind]
2052
+ msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
2053
+ dist = np.sqrt((np.array(datax)-msx)**2+(np.array(datay)-msy)**2)
2054
+ self.index = ind[np.argmin(dist)]
2055
+ except Exception as e:
2056
+ print(f"Exception L2090 to find closest marker: {e=}")
2057
+ else:
2058
+ self.index = None
2189
2059
 
2190
2060
 
2191
2061
  def show_annotation_buttons(self):
@@ -2304,58 +2174,46 @@ class SignalAnnotator2(QMainWindow,Styles):
2304
2174
  self.framedata = framedata
2305
2175
  self.frame_lbl.setText(f'frame: {self.framedata}')
2306
2176
  self.im.set_array(self.stack[self.framedata])
2307
- #if self.reference_population=='targets':
2308
-
2309
- if self.df_effectors is not None:
2310
-
2311
- self.effector_status_scatter.set_visible(True)
2312
- self.effector_status_scatter.set_picker(True)
2313
- self.effector_class_scatter.set_visible(True)
2314
- self.effector_status_scatter.set_offsets(self.effector_positions[self.framedata])
2315
- self.effector_status_scatter.set_color(self.effector_colors[self.framedata][:, 1])
2316
- self.effector_class_scatter.set_offsets(self.effector_positions[self.framedata])
2317
- self.effector_class_scatter.set_edgecolor(self.effector_colors[self.framedata][:, 0])
2318
-
2319
- if self.df_targets is not None:
2320
- self.target_status_scatter.set_visible(True)
2321
- self.target_status_scatter.set_picker(True)
2322
- self.target_class_scatter.set_visible(True)
2323
- self.target_status_scatter.set_offsets(self.target_positions[self.framedata])
2324
- self.target_status_scatter.set_color(self.target_colors[self.framedata][:, 1])
2325
- self.target_class_scatter.set_offsets(self.target_positions[self.framedata])
2326
- self.target_class_scatter.set_edgecolor(self.target_colors[self.framedata][:, 0])
2177
+
2178
+ for pop in self.dataframes.keys():
2179
+ df = self.dataframes[pop]
2180
+ if df is not None:
2181
+ self.status_scatter[pop].set_visible(True)
2182
+ self.status_scatter[pop].set_picker(True)
2183
+ self.status_scatter[pop].set_offsets(self.positions[pop][self.framedata])
2184
+ self.status_scatter[pop].set_color(self.colors[pop][self.framedata][:,1])
2185
+
2186
+ self.class_scatter[pop].set_visible(True)
2187
+ self.class_scatter[pop].set_offsets(self.positions[pop][self.framedata])
2188
+ self.class_scatter[pop].set_edgecolor(self.colors[pop][self.framedata][:,0])
2327
2189
 
2328
2190
  self.lines_list=[]
2329
2191
 
2330
2192
  for key in self.lines_data:
2193
+
2331
2194
  if key==self.framedata:
2195
+
2332
2196
  for line in self.lines_data[key]:
2333
- x_coords, y_coords = line
2334
- pair=self.line_connections[x_coords[0],x_coords[1],y_coords[0],y_coords[1]]
2335
2197
 
2336
- this_frame=self.lines_colors_class[self.framedata]
2198
+ x_coords, y_coords = line
2199
+ pair = self.line_connections[x_coords[0],x_coords[1],y_coords[0],y_coords[1]]
2200
+ this_frame = self.lines_colors_class[self.framedata]
2337
2201
 
2338
- try:
2339
- this_pair=this_frame[(this_frame[:, 0] == pair[0][0]) & (this_frame[:, 1] == pair[0][1])]
2340
- self.lines_plot=self.ax.plot(x_coords, y_coords, alpha=1, linewidth=2,color=this_pair[0][2])
2202
+ this_pair = this_frame[(this_frame[:, 0] == pair[0][0]) & (this_frame[:, 1] == pair[0][1])]
2203
+ if len(this_pair)>0:
2204
+ c = this_pair[0][2]
2205
+ self.lines_plot = self.ax.plot(x_coords, y_coords, alpha=1, linewidth=2, color=c)
2341
2206
  self.lines_list.append(self.lines_plot[0])
2342
- except Exception as e:
2343
- print(e)
2344
- pass
2345
- # Plot points
2346
- try:
2347
- self.points.set_offsets(self.points_data[key])
2348
- colors_at_this_frame = self.lines_colors_status[self.framedata]
2349
- 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]]
2350
- self.points.set_color(colors)
2351
- except Exception as e:
2352
- print(e)
2207
+
2208
+ self.points.set_offsets(self.points_data[key])
2209
+ colors_at_this_frame = self.lines_colors_status[self.framedata]
2210
+ 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]]
2211
+ self.points.set_color(colors)
2353
2212
 
2354
2213
  if self.lines_list!=[]:
2355
- return [self.im,self.target_status_scatter,self.target_class_scatter,self.effector_status_scatter,self.effector_class_scatter] +self.lines_list + [self.points]
2214
+ return [self.im] + [self.status_scatter[p] for p in self.status_scatter.keys()] + [self.class_scatter[p] for p in self.status_scatter.keys()] + self.lines_list + [self.points]
2356
2215
  else:
2357
- return [self.im, self.target_status_scatter, self.target_class_scatter, self.effector_status_scatter,
2358
- self.effector_class_scatter,]
2216
+ return [self.im,] + [self.status_scatter[p] for p in self.status_scatter.keys()] + [self.class_scatter[p] for p in self.status_scatter.keys()]
2359
2217
 
2360
2218
  def stop(self):
2361
2219
  # # On stop we disconnect all of our events.
@@ -2435,63 +2293,6 @@ class SignalAnnotator2(QMainWindow,Styles):
2435
2293
 
2436
2294
  self.target_cell_info.setText('')
2437
2295
 
2438
- # def give_effector_cell_information(self):
2439
- # self.effector_cell_info.setSpacing(0)
2440
- # self.effector_cell_info.setContentsMargins(0, 20, 0, 30)
2441
- # self.neigh_eff_combo=QComboBox()
2442
- # #self.neighb_eff_combo.addItems(self.df_relative.loc[(self.df_relative['target']==self.target_track_of_interest),'effecor'])
2443
- # neighs=self.df_relative.loc[(self.df_relative['REFERENCE_ID']==self.target_track_of_interest),'NEIGHBOR_ID'].to_numpy()
2444
- # neighs=np.unique(neighs)
2445
- # for effector in neighs:
2446
- # self.neigh_eff_combo.addItem(str(effector))
2447
- # if self.effector_track_of_interest not in neighs:
2448
- # self.neigh_eff_combo.addItem(str(self.effector_track_of_interest))
2449
- # self.neigh_eff_combo.setCurrentText(str(self.effector_track_of_interest))
2450
- # self.eff_cell_sel=QHBoxLayout()
2451
- # #effector_cell_selected = f"effector cell: {self.effector_track_of_interest}"
2452
- # self.effector_cell_selected = f"effector cell: "
2453
- # self.eff_cell = QLabel(self.effector_cell_selected)
2454
- # # self.eff_cell_sel.removeWidget(self.eff_cell)
2455
- # # self.eff_cell_sel.removeWidget(self.neigh_eff_combo)
2456
- # self.eff_cell_sel.addWidget(self.eff_cell)
2457
- # self.eff_cell_sel.addWidget(self.neigh_eff_combo, alignment=Qt.AlignLeft)
2458
- # try:
2459
- # 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]}"
2460
- # except:
2461
- # 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]}"
2462
-
2463
- # self.eff_cls = QLabel(self.effector_cell_class)
2464
- # try:
2465
- # 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]}"
2466
- # except:
2467
- # 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]}"
2468
-
2469
- # self.eff_tm=QLabel(self.effector_cell_time)
2470
- # # try:
2471
- # # 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]}"
2472
- # # except:
2473
- # # self.effector_probabilty=f"probability: 0"
2474
- # # self.eff_prb=QLabel(self.effector_probabilty)
2475
- # #self.effector_cell_info.setText(effector_cell_selected+effector_cell_class+effector_cell_time+effector_probabilty)
2476
- # # self.effector_cell_info.removeWidget(self.eff_cls)
2477
- # # self.effector_cell_info.removeWidget(self.eff_tm)
2478
- # # self.effector_cell_info.removeWidget(self.eff_prb)
2479
- # self.effector_cell_info.addLayout(self.eff_cell_sel)
2480
- # self.effector_cell_info.addWidget(self.eff_cls)
2481
- # self.effector_cell_info.addWidget(self.eff_tm)
2482
- # #self.effector_cell_info.addWidget(self.eff_prb)
2483
- # self.neigh_eff_combo.currentIndexChanged.connect(self.update_effector_info)
2484
- # self.eff_info_to_hide=[self.eff_cell,self.neigh_eff_combo,self.eff_cls,self.eff_tm]#self.eff_prb
2485
-
2486
-
2487
- # def hide_effector_cell_info(self):
2488
- # self.eff_cls.clear()
2489
- # self.eff_tm.clear()
2490
- # #self.eff_prb.clear()
2491
-
2492
- # for info in self.eff_info_to_hide:
2493
- # info.hide()
2494
-
2495
2296
 
2496
2297
  def save_trajectories(self):
2497
2298
 
@@ -2602,39 +2403,34 @@ class SignalAnnotator2(QMainWindow,Styles):
2602
2403
 
2603
2404
  def normalize_features(self):
2604
2405
 
2605
- if self.df_effectors is not None:
2606
- x_effectors = self.df_effectors[self.effector_columns].values
2607
- if self.df_targets is not None:
2608
- x_targets = self.df_targets[self.target_columns].values
2609
- if self.df_relative is not None:
2610
- x_pairs = self.df_relative[self.pair_columns].values
2611
-
2612
2406
  if not self.normalized_signals:
2407
+ for pop in self.dataframes.keys():
2408
+ df_pop = self.dataframes[pop]
2409
+ cols = self.population_columns[pop]
2410
+ if df_pop is not None:
2411
+ df_pop[cols] = self.MinMaxScaler_per_pop[pop].transform(df_pop[cols].values)
2412
+ self.dataframes.update({pop: df_pop})
2613
2413
 
2614
- if self.df_effectors is not None:
2615
- self.df_effectors[self.effector_columns] = self.MinMaxScaler_effectors.transform(x_effectors)
2616
- if self.df_targets is not None:
2617
- self.df_targets[self.target_columns] = self.MinMaxScaler_targets.transform(x_targets)
2618
2414
  if self.df_relative is not None:
2619
- self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.transform(x_pairs)
2415
+ self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.transform(self.df_relative[self.pair_columns].values)
2620
2416
 
2621
- self.plot_signals()
2622
2417
  self.normalized_signals = True
2623
2418
  self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="#1565c0"))
2624
- self.normalize_features_btn.setIconSize(QSize(25, 25))
2625
2419
  else:
2626
-
2627
- if self.df_effectors is not None:
2628
- self.df_effectors[self.effector_columns] = self.MinMaxScaler_effectors.inverse_transform(x_effectors)
2629
- if self.df_targets is not None:
2630
- self.df_targets[self.target_columns] = self.MinMaxScaler_targets.inverse_transform(x_targets)
2420
+ for pop in self.dataframes.keys():
2421
+ df_pop = self.dataframes[pop]
2422
+ cols = self.population_columns[pop]
2423
+ if df_pop is not None:
2424
+ df_pop[cols] = self.MinMaxScaler_per_pop[pop].inverse_transform(df_pop[cols].values)
2425
+ self.dataframes.update({pop: df_pop})
2631
2426
  if self.df_relative is not None:
2632
- self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.inverse_transform(x_pairs)
2427
+ self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.inverse_transform(self.df_relative[self.pair_columns].values)
2633
2428
 
2634
- self.plot_signals()
2635
2429
  self.normalized_signals = False
2636
2430
  self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
2637
- self.normalize_features_btn.setIconSize(QSize(25, 25))
2431
+
2432
+ self.plot_signals()
2433
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
2638
2434
 
2639
2435
  def switch_to_log(self):
2640
2436