celldetective 1.3.9.post5__py3-none-any.whl → 1.4.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. celldetective/__init__.py +0 -3
  2. celldetective/_version.py +1 -1
  3. celldetective/events.py +2 -4
  4. celldetective/exceptions.py +11 -0
  5. celldetective/extra_properties.py +132 -0
  6. celldetective/filters.py +7 -1
  7. celldetective/gui/InitWindow.py +37 -46
  8. celldetective/gui/__init__.py +3 -9
  9. celldetective/gui/about.py +19 -15
  10. celldetective/gui/analyze_block.py +34 -19
  11. celldetective/gui/base_annotator.py +786 -0
  12. celldetective/gui/base_components.py +23 -0
  13. celldetective/gui/classifier_widget.py +86 -94
  14. celldetective/gui/configure_new_exp.py +163 -46
  15. celldetective/gui/control_panel.py +76 -146
  16. celldetective/gui/{signal_annotator.py → event_annotator.py} +533 -1438
  17. celldetective/gui/generic_signal_plot.py +11 -13
  18. celldetective/gui/gui_utils.py +54 -23
  19. celldetective/gui/help/neighborhood.json +2 -2
  20. celldetective/gui/json_readers.py +5 -4
  21. celldetective/gui/layouts.py +265 -31
  22. celldetective/gui/{signal_annotator2.py → pair_event_annotator.py} +433 -635
  23. celldetective/gui/plot_measurements.py +21 -17
  24. celldetective/gui/plot_signals_ui.py +125 -72
  25. celldetective/gui/process_block.py +283 -188
  26. celldetective/gui/processes/compute_neighborhood.py +594 -0
  27. celldetective/gui/processes/downloader.py +37 -34
  28. celldetective/gui/processes/measure_cells.py +19 -8
  29. celldetective/gui/processes/segment_cells.py +47 -11
  30. celldetective/gui/processes/track_cells.py +18 -13
  31. celldetective/gui/seg_model_loader.py +21 -62
  32. celldetective/gui/settings/__init__.py +7 -0
  33. celldetective/gui/settings/_settings_base.py +70 -0
  34. celldetective/gui/{retrain_signal_model_options.py → settings/_settings_event_model_training.py} +54 -109
  35. celldetective/gui/{measurement_options.py → settings/_settings_measurements.py} +54 -92
  36. celldetective/gui/{neighborhood_options.py → settings/_settings_neighborhood.py} +10 -13
  37. celldetective/gui/settings/_settings_segmentation.py +49 -0
  38. celldetective/gui/{retrain_segmentation_model_options.py → settings/_settings_segmentation_model_training.py} +38 -92
  39. celldetective/gui/{signal_annotator_options.py → settings/_settings_signal_annotator.py} +78 -103
  40. celldetective/gui/{btrack_options.py → settings/_settings_tracking.py} +85 -116
  41. celldetective/gui/styles.py +2 -1
  42. celldetective/gui/survival_ui.py +49 -95
  43. celldetective/gui/tableUI.py +53 -25
  44. celldetective/gui/table_ops/__init__.py +0 -0
  45. celldetective/gui/table_ops/merge_groups.py +118 -0
  46. celldetective/gui/thresholds_gui.py +617 -1221
  47. celldetective/gui/viewers.py +107 -42
  48. celldetective/gui/workers.py +8 -4
  49. celldetective/io.py +137 -57
  50. celldetective/links/zenodo.json +145 -144
  51. celldetective/measure.py +94 -53
  52. celldetective/neighborhood.py +342 -268
  53. celldetective/preprocessing.py +56 -35
  54. celldetective/regionprops/_regionprops.py +16 -5
  55. celldetective/relative_measurements.py +50 -29
  56. celldetective/scripts/analyze_signals.py +4 -1
  57. celldetective/scripts/measure_cells.py +5 -5
  58. celldetective/scripts/measure_relative.py +20 -12
  59. celldetective/scripts/segment_cells.py +4 -10
  60. celldetective/scripts/segment_cells_thresholds.py +3 -3
  61. celldetective/scripts/track_cells.py +10 -8
  62. celldetective/scripts/train_segmentation_model.py +18 -6
  63. celldetective/signals.py +29 -14
  64. celldetective/tracking.py +14 -3
  65. celldetective/utils.py +91 -62
  66. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/METADATA +24 -16
  67. celldetective-1.4.1.dist-info/RECORD +123 -0
  68. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/WHEEL +1 -1
  69. tests/gui/__init__.py +0 -0
  70. tests/gui/test_new_project.py +228 -0
  71. tests/gui/test_project.py +99 -0
  72. tests/test_preprocessing.py +2 -2
  73. celldetective/models/segmentation_effectors/ricm_bf_all_last/config_input.json +0 -79
  74. celldetective/models/segmentation_effectors/ricm_bf_all_last/ricm_bf_all_last +0 -0
  75. celldetective/models/segmentation_effectors/ricm_bf_all_last/training_instructions.json +0 -37
  76. celldetective/models/segmentation_effectors/test-transfer/config_input.json +0 -39
  77. celldetective/models/segmentation_effectors/test-transfer/test-transfer +0 -0
  78. celldetective/models/signal_detection/NucCond/classification_loss.png +0 -0
  79. celldetective/models/signal_detection/NucCond/classifier.h5 +0 -0
  80. celldetective/models/signal_detection/NucCond/config_input.json +0 -1
  81. celldetective/models/signal_detection/NucCond/log_classifier.csv +0 -126
  82. celldetective/models/signal_detection/NucCond/log_regressor.csv +0 -282
  83. celldetective/models/signal_detection/NucCond/regression_loss.png +0 -0
  84. celldetective/models/signal_detection/NucCond/regressor.h5 +0 -0
  85. celldetective/models/signal_detection/NucCond/scores.npy +0 -0
  86. celldetective/models/signal_detection/NucCond/test_confusion_matrix.png +0 -0
  87. celldetective/models/signal_detection/NucCond/test_regression.png +0 -0
  88. celldetective/models/signal_detection/NucCond/validation_confusion_matrix.png +0 -0
  89. celldetective/models/signal_detection/NucCond/validation_regression.png +0 -0
  90. celldetective-1.3.9.post5.dist-info/RECORD +0 -129
  91. tests/test_qt.py +0 -103
  92. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info}/entry_points.txt +0 -0
  93. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.dist-info/licenses}/LICENSE +0 -0
  94. {celldetective-1.3.9.post5.dist-info → celldetective-1.4.1.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 PairEventAnnotator(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
 
@@ -175,8 +167,10 @@ class SignalAnnotator2(QMainWindow,Styles):
175
167
  self.left_panel.addLayout(neigh_hbox)
176
168
 
177
169
  self.reference_cell_info = QLabel('')
170
+ self.reference_cell_info.setStyleSheet("color: #1f77b4;")
178
171
  self.pair_info = QLabel('')
179
172
  self.neighbor_cell_info= QLabel('')
173
+ self.neighbor_cell_info.setStyleSheet("color: #d62728;")
180
174
 
181
175
  class_hbox = QHBoxLayout()
182
176
  class_hbox.addWidget(QLabel('interaction event: '), 25)
@@ -237,11 +231,11 @@ class SignalAnnotator2(QMainWindow,Styles):
237
231
  reference_layout.addLayout(self.cell_events_hbox)
238
232
 
239
233
  neighbor_layout = QVBoxLayout()
240
- neighbor_layout.addWidget(self.neighbor_cell_info)
234
+ neighbor_layout.addWidget(self.neighbor_cell_info, alignment=Qt.AlignRight)
241
235
  neighbor_layout.addLayout(self.neigh_cell_events_hbox)
242
236
 
243
237
  self.cell_info_hbox.addLayout(reference_layout, 33)
244
- self.cell_info_hbox.addWidget(self.pair_info, 33)
238
+ self.cell_info_hbox.addWidget(self.pair_info, 33, alignment=Qt.AlignCenter)
245
239
  self.cell_info_hbox.addLayout(neighbor_layout, 33)
246
240
 
247
241
  self.left_panel.addLayout(self.cell_info_hbox)
@@ -419,8 +413,6 @@ class SignalAnnotator2(QMainWindow,Styles):
419
413
 
420
414
 
421
415
  self.right_panel.addLayout(animation_buttons_box, 5)
422
-
423
-
424
416
  self.right_panel.addWidget(self.fcanvas, 90)
425
417
 
426
418
  if not self.rgb_mode:
@@ -429,7 +421,7 @@ class SignalAnnotator2(QMainWindow,Styles):
429
421
  self.contrast_slider = QLabeledDoubleRangeSlider()
430
422
  # self.contrast_slider.setSingleStep(0.001)
431
423
  # self.contrast_slider.setTickInterval(0.001)
432
- self.contrast_slider.setOrientation(1)
424
+ self.contrast_slider.setOrientation(Qt.Horizontal)
433
425
  print('range: ', [np.nanpercentile(self.stack.flatten(), 0.001), np.nanpercentile(self.stack.flatten(), 99.999)])
434
426
  self.contrast_slider.setRange(
435
427
  *[np.nanpercentile(self.stack, 0.001), np.nanpercentile(self.stack, 99.999)])
@@ -444,6 +436,7 @@ class SignalAnnotator2(QMainWindow,Styles):
444
436
  main_layout.addLayout(self.right_panel, 65)
445
437
  self.button_widget.adjustSize()
446
438
  self.compute_status_and_colors_reference()
439
+ self.compute_status_and_colors_neighbor()
447
440
 
448
441
  self.extract_relevant_events()
449
442
 
@@ -486,53 +479,6 @@ class SignalAnnotator2(QMainWindow,Styles):
486
479
  self.neighbor_event_choice_cb.addItems(neighbor_class_cols)
487
480
  self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_neighbor)
488
481
 
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
482
  def del_relative_event_class(self):
537
483
 
538
484
  msgBox = QMessageBox()
@@ -557,55 +503,30 @@ class SignalAnnotator2(QMainWindow,Styles):
557
503
  self.relative_class_choice_cb.removeItem(item_idx)
558
504
 
559
505
  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
506
 
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)
507
+ try:
508
+ self.neighbor_event_choice_cb.show()
509
+ self.neigh_lab.show()
510
+ except:
511
+ pass
587
512
 
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)
513
+ self.reference_event_choice_cb.disconnect()
514
+ self.reference_event_choice_cb.clear()
515
+ self.reference_event_choice_cb.addItems(self.class_cols_per_pop[self.reference_population])
516
+ self.reference_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
591
517
 
518
+ if not 'self' in self.current_neighborhood:
592
519
  self.neighbor_event_choice_cb.disconnect()
593
520
  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)
521
+ self.neighbor_event_choice_cb.addItems(self.class_cols_per_pop[self.neighbor_population])
522
+ self.neighbor_event_choice_cb.currentIndexChanged.connect(self.compute_status_and_colors_reference)
602
523
 
603
524
 
604
525
 
605
526
  def create_new_relative_event_class(self):
606
527
 
607
528
  # display qwidget to name the event
608
- self.newClassWidget = QWidget()
529
+ self.newClassWidget = CelldetectiveWidget()
609
530
  self.newClassWidget.setWindowTitle('Create new event class')
610
531
 
611
532
  layout = QVBoxLayout()
@@ -723,12 +644,18 @@ class SignalAnnotator2(QMainWindow,Styles):
723
644
  df_reference['status_color'] = [color_from_status(i) for i in df_reference[self.reference_status_name].to_numpy()]
724
645
  df_reference['class_color'] = [color_from_class(i) for i in df_reference[self.reference_class_name].to_numpy()]
725
646
 
726
- if self.reference_population=='targets':
727
- self.extract_scatter_from_target_trajectories()
728
- else:
729
- self.extract_scatter_from_effector_trajectories()
730
-
731
-
647
+ self.refresh_scatter_from_trajectories(self.reference_population, clear_selection=False)
648
+
649
+ if self.reference_track_of_interest is not None:
650
+ t_reference = df_reference.loc[
651
+ df_reference['TRACK_ID'] == self.reference_track_of_interest, self.reference_time_name].values
652
+ if len(t_reference) > 0:
653
+ t_reference = t_reference[0]
654
+ ymin, ymax = self.cell_ax.get_ylim()
655
+ self.line_dt_reference.set_xdata([t_reference, t_reference])
656
+ self.line_dt_reference.set_ydata([ymin,ymax])
657
+ self.cell_fcanvas.canvas.draw()
658
+
732
659
  def compute_status_and_colors_neighbor(self):
733
660
 
734
661
  df_neighbors = self.dataframes[self.neighbor_population]
@@ -767,10 +694,8 @@ class SignalAnnotator2(QMainWindow,Styles):
767
694
  df_neighbors['status_color'] = [color_from_status(i) for i in df_neighbors[self.neighbor_status_name].to_numpy()]
768
695
  df_neighbors['class_color'] = [color_from_class(i) for i in df_neighbors[self.neighbor_class_name].to_numpy()]
769
696
 
770
- if self.neighbor_population=='targets':
771
- self.extract_scatter_from_target_trajectories()
772
- else:
773
- self.extract_scatter_from_effector_trajectories()
697
+ self.refresh_scatter_from_trajectories(self.neighbor_population)
698
+
774
699
 
775
700
  def compute_status_and_colors_pair(self):
776
701
 
@@ -845,13 +770,8 @@ class SignalAnnotator2(QMainWindow,Styles):
845
770
  if len(self.pair_selection) > 0:
846
771
  self.cancel_pair_selection()
847
772
 
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
773
  _, _, neighbor_colors, initial_neighbor_colors = self.get_neighbor_sets()
854
- _, _, reference_colors, initial_reference_colors = self.get_reference_sets()
774
+ _, _, _, reference_colors, initial_reference_colors = self.get_reference_sets()
855
775
 
856
776
  for k, (t,idx) in enumerate(zip(self.neighbor_loc_t, self.neighbor_loc_idx)):
857
777
  neighbor_colors[t][idx,0] = initial_neighbor_colors[k][0]
@@ -936,8 +856,17 @@ class SignalAnnotator2(QMainWindow,Styles):
936
856
  if option!=0:
937
857
  self.lines[i].set_xdata([])
938
858
  self.lines[i].set_ydata([])
859
+
939
860
  self.line_dt.set_xdata([])
940
861
  self.line_dt.set_ydata([])
862
+
863
+ if self.reference_track_of_interest is None:
864
+ self.line_dt_reference.set_xdata([])
865
+ self.line_dt_reference.set_ydata([])
866
+
867
+ self.line_dt_neighbor.set_xdata([])
868
+ self.line_dt_neighbor.set_ydata([])
869
+
941
870
  self.lines[i].set_label('')
942
871
 
943
872
  self.correct_btn.setEnabled(False)
@@ -949,15 +878,15 @@ class SignalAnnotator2(QMainWindow,Styles):
949
878
  # Plot the new time
950
879
  t0 = -1
951
880
  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
881
+ # try:
882
+ cclass = 0
883
+ t0 = float(self.time_of_interest_le.text().replace(',', '.'))
884
+ self.line_dt.set_xdata([t0, t0])
885
+ self.cell_fcanvas.canvas.draw_idle()
886
+ # except Exception as e:
887
+ # print(e)
888
+ # t0 = -1
889
+ # cclass = 2
961
890
 
962
891
  elif self.no_event_btn.isChecked():
963
892
  cclass = 1
@@ -1012,8 +941,8 @@ class SignalAnnotator2(QMainWindow,Styles):
1012
941
  # but keep reference
1013
942
 
1014
943
  #self.make_status_column()
1015
- self.extract_scatter_from_target_trajectories()
1016
- self.extract_scatter_from_effector_trajectories()
944
+ self.refresh_scatter_from_trajectories(self.reference_population)
945
+ self.refresh_scatter_from_trajectories(self.neighbor_population)
1017
946
 
1018
947
  self.recolor_selection()
1019
948
  self.trace_neighbors()
@@ -1048,234 +977,128 @@ class SignalAnnotator2(QMainWindow,Styles):
1048
977
  self.channels = np.array(self.channels)
1049
978
  self.nbr_channels = len(self.channels)
1050
979
 
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)
980
+ def locate_all_tracks(self):
981
+
982
+ self.dataframes = {}
983
+ self.class_cols_per_pop = {}
984
+ self.population_columns = {}
985
+ self.MinMaxScaler_per_pop = {}
986
+
987
+ for population in self.populations:
988
+
989
+ population_trajectories_path = self.pos + os.sep.join(['output','tables', f'trajectories_{population}.pkl'])
990
+ if not os.path.exists(population_trajectories_path):
991
+ population_trajectories_path = population_trajectories_path.replace('.pkl','.csv')
992
+
993
+ if not os.path.exists(population_trajectories_path):
994
+
995
+ msgBox = QMessageBox()
996
+ msgBox.setIcon(QMessageBox.Warning)
997
+ msgBox.setText(f"The trajectories for the {population} population cannot be detected...")
998
+ msgBox.setWindowTitle("Warning")
999
+ msgBox.setStandardButtons(QMessageBox.Ok)
1000
+ returnValue = msgBox.exec()
1001
+ df_population = None
1073
1002
  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
1003
+ # Load and prep tracks
1004
+ if population_trajectories_path.endswith('.pkl'):
1005
+ df_population = np.load(population_trajectories_path, allow_pickle=True)
1006
+ else:
1007
+ df_population = pd.read_csv(population_trajectories_path)
1089
1008
 
1090
- if len(self.target_class_cols)>0:
1009
+ df_population = df_population.sort_values(by=['TRACK_ID', 'FRAME'])
1091
1010
 
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'
1011
+ cols = np.array(df_population.columns)
1012
+ class_cols = [c for c in list(df_population.columns) if c.startswith('class')]
1106
1013
 
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
1014
  try:
1155
- self.target_columns.remove(col)
1015
+ class_cols.remove('class_id')
1016
+ except:
1017
+ pass
1018
+ try:
1019
+ class_cols.remove('class_color')
1156
1020
  except:
1157
1021
  pass
1158
1022
 
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
-
1023
+ if len(class_cols)>0:
1190
1024
 
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
1025
+ pop_class_name = class_cols[0]
1026
+ pop_expected_status = 'status'
1027
+ suffix = pop_class_name.replace('class','').replace('_','')
1028
+ if suffix!='':
1029
+ pop_expected_status+='_'+suffix
1030
+ pop_expected_time = 't_'+suffix
1031
+ else:
1032
+ pop_expected_time = 't0'
1033
+ pop_time_name = pop_expected_time
1034
+ pop_status_name = pop_expected_status
1209
1035
  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'
1036
+ pop_class_name = 'class'
1037
+ pop_time_name = 't0'
1038
+ pop_status_name = 'status'
1217
1039
 
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:
1040
+ 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:
1041
+ # only create the status column if it does not exist to not erase static classification results
1042
+ pass
1043
+ #self.make_target_status_column()
1044
+ elif pop_time_name in df_population.columns and pop_class_name in df_population.columns:
1045
+ # all good, do nothing
1273
1046
  pass
1047
+ else:
1048
+ if not pop_status_name in df_population.columns:
1049
+ df_population[pop_status_name] = 0
1050
+ df_population['status_color'] = color_from_status(0)
1051
+ df_population['class_color'] = color_from_class(1)
1052
+
1053
+ if not pop_class_name in df_population.columns:
1054
+ df_population[pop_class_name] = 1
1055
+ if not pop_time_name in df_population.columns:
1056
+ df_population[pop_time_name] = -1
1057
+
1058
+ df_population['status_color'] = color_from_status(2)
1059
+ df_population['class_color'] = color_from_status(2)
1060
+
1061
+ df_population = df_population.dropna(subset=['POSITION_X', 'POSITION_Y'])
1062
+ df_population['x_anim'] = df_population['POSITION_X'] * self.fraction
1063
+ df_population['y_anim'] = df_population['POSITION_Y'] * self.fraction
1064
+ df_population['x_anim'] = df_population['x_anim'].astype(int)
1065
+ df_population['y_anim'] = df_population['y_anim'].astype(int)
1066
+
1067
+ #self.reference_positions, self.reference_tracks, self.reference_colors, self.reference_initial_colors = self.extract_scatter_from_trajectories(self.reference_population)
1068
+ #self.extract_scatter_from_target_trajectories()
1069
+
1070
+ tracks = df_population['TRACK_ID'].unique()
1071
+ self.target_track_of_interest = df_population['TRACK_ID'].min()
1072
+
1073
+ self.loc_t = []
1074
+ self.loc_idx = []
1075
+ for t in range(len(tracks)):
1076
+ indices = np.where(tracks[t]==self.target_track_of_interest)[0]
1077
+ if len(indices)>0:
1078
+ self.loc_t.append(t)
1079
+ self.loc_idx.append(indices[0])
1080
+
1081
+ minmax = MinMaxScaler()
1082
+ pop_cols = list(df_population.columns)
1083
+ cols_to_remove = [c for c in self.cols_to_remove if c in pop_cols] + class_cols
1084
+ time_cols = [c for c in pop_cols if c.startswith('t_')]
1085
+ cols_to_remove += time_cols
1086
+ neigh_cols = [c for c in pop_cols if c.startswith('neighborhood_')]
1087
+ cols_to_remove += neigh_cols
1088
+
1089
+ for col in cols_to_remove:
1090
+ try:
1091
+ pop_cols.remove(col)
1092
+ except:
1093
+ pass
1274
1094
 
1275
- x = self.df_effectors[self.effector_columns].to_numpy()
1276
- print(self.effector_columns, x, x.shape)
1277
- self.MinMaxScaler_effectors.fit(x)
1095
+ x = df_population[pop_cols].values
1096
+ minmax.fit(x)
1278
1097
 
1098
+ self.dataframes.update({population: df_population})
1099
+ self.class_cols_per_pop.update({population: class_cols})
1100
+ self.population_columns.update({population: pop_cols})
1101
+ self.MinMaxScaler_per_pop.update({population: minmax})
1279
1102
 
1280
1103
  def locate_relative_tracks(self):
1281
1104
 
@@ -1294,7 +1117,8 @@ class SignalAnnotator2(QMainWindow,Styles):
1294
1117
  else:
1295
1118
  # Load and prep tracks
1296
1119
  self.df_relative = pd.read_csv(self.relative_trajectories_path)
1297
- print(self.df_relative.columns)
1120
+ self.df_relative = self.df_relative.astype({"reference_population": str, "neighbor_population": str})
1121
+
1298
1122
  self.df_relative= self.df_relative.sort_values(by=['REFERENCE_ID','NEIGHBOR_ID','reference_population','neighbor_population','FRAME'])
1299
1123
  self.relative_cols = np.array(self.df_relative.columns)
1300
1124
 
@@ -1341,10 +1165,21 @@ class SignalAnnotator2(QMainWindow,Styles):
1341
1165
  def set_reference_and_neighbor_populations(self):
1342
1166
 
1343
1167
  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]
1168
+
1169
+ self.current_neighborhood = neigh
1170
+ for pop in self.dataframes.keys():
1171
+ self.current_neighborhood = self.current_neighborhood.replace(f'{pop}_ref_','')
1347
1172
 
1173
+ self.reference_population = self.neighborhood_choice_cb.currentText().split('_')[0]
1174
+
1175
+ if '_(' in self.current_neighborhood and ')_' in self.current_neighborhood:
1176
+ self.neighbor_population = self.current_neighborhood.split('_(')[-1].split(')_')[0].split('-')[-1]
1177
+ self.reference_population = self.current_neighborhood.split('_(')[-1].split(')_')[0].split('-')[0]
1178
+ else:
1179
+ if 'self' in self.current_neighborhood:
1180
+ self.neighbor_population = self.reference_population
1181
+
1182
+
1348
1183
  print(f'Current neighborhood: {self.current_neighborhood}')
1349
1184
  print(f'New reference population: {self.reference_population}')
1350
1185
  print(f'New neighbor population: {self.neighbor_population}')
@@ -1395,7 +1230,6 @@ class SignalAnnotator2(QMainWindow,Styles):
1395
1230
  status[:] = 2
1396
1231
  if cclass>2:
1397
1232
  status[:] = 42
1398
- print(t0, status)
1399
1233
  status_color = [color_from_status(s) for s in status]
1400
1234
  class_color = [color_from_class(cclass) for i in range(len(status))]
1401
1235
 
@@ -1514,8 +1348,16 @@ class SignalAnnotator2(QMainWindow,Styles):
1514
1348
  self.lines[i].set_xdata([])
1515
1349
  self.lines[i].set_ydata([])
1516
1350
  self.lines[i].set_label('')
1351
+
1517
1352
  self.line_dt.set_xdata([])
1518
1353
  self.line_dt.set_ydata([])
1354
+
1355
+ self.line_dt_reference.set_xdata([])
1356
+ self.line_dt_reference.set_ydata([])
1357
+
1358
+ self.line_dt_neighbor.set_xdata([])
1359
+ self.line_dt_neighbor.set_ydata([])
1360
+
1519
1361
  self.cell_fcanvas.canvas.draw()
1520
1362
  return None
1521
1363
  else:
@@ -1528,11 +1370,18 @@ class SignalAnnotator2(QMainWindow,Styles):
1528
1370
  signal = []; timeline = [];
1529
1371
  signal_txt = self.signal_choices[i].currentText()
1530
1372
  option = self.signal_pop_button_groups[i].checkedId()
1373
+
1374
+ n_cut = 25
1375
+ if len(signal_txt) > n_cut:
1376
+ signal_txt_to_show = signal_txt[:(n_cut - 3)] + '...'
1377
+ else:
1378
+ signal_txt_to_show = signal_txt
1531
1379
 
1532
1380
  if option==0 and self.reference_track_of_interest is not None and signal_txt!='--' and signal_txt!='':
1533
1381
 
1534
1382
  df_reference = self.dataframes[self.reference_population]
1535
- self.lines[i].set_label(f'reference ({self.reference_population}) '+ signal_txt)
1383
+
1384
+ self.lines[i].set_label(f'reference ({self.reference_population}) '+ signal_txt_to_show)
1536
1385
 
1537
1386
  signal = df_reference.loc[df_reference['TRACK_ID']==self.reference_track_of_interest, signal_txt].to_numpy()
1538
1387
  timeline = df_reference.loc[df_reference['TRACK_ID']==self.reference_track_of_interest, 'FRAME'].to_numpy()
@@ -1541,15 +1390,14 @@ class SignalAnnotator2(QMainWindow,Styles):
1541
1390
  elif option==1 and self.neighbor_track_of_interest is not None and signal_txt!='--' and signal_txt!='':
1542
1391
 
1543
1392
  df_neighbor = self.dataframes[self.neighbor_population]
1544
- self.lines[i].set_label(f'neighbor ({self.neighbor_population}) '+ signal_txt)
1393
+ self.lines[i].set_label(f'neighbor ({self.neighbor_population}) '+ signal_txt_to_show)
1545
1394
 
1546
1395
  signal = df_neighbor.loc[df_neighbor['TRACK_ID']==self.neighbor_track_of_interest, signal_txt].to_numpy()
1547
1396
  timeline = df_neighbor.loc[df_neighbor['TRACK_ID']==self.neighbor_track_of_interest, 'FRAME'].to_numpy()
1548
1397
  range_values.extend(df_neighbor.loc[:,signal_txt].values)
1549
1398
 
1550
1399
  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)
1400
+ self.lines[i].set_label(f'pair '+signal_txt_to_show)
1553
1401
  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
1402
  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
1403
  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 +1408,7 @@ class SignalAnnotator2(QMainWindow,Styles):
1560
1408
  self.lines[i].set_xdata(timeline)
1561
1409
  self.lines[i].set_ydata(signal)
1562
1410
  self.lines[i].set_color(tab10(i / float(self.n_signals)))
1563
-
1411
+
1564
1412
  #self.configure_ylims()
1565
1413
  if len(range_values)>0:
1566
1414
  range_values = np.array(range_values)
@@ -1576,18 +1424,36 @@ class SignalAnnotator2(QMainWindow,Styles):
1576
1424
  else:
1577
1425
  self.cell_ax.set_ylim(self.value_magnitude, self.non_log_ymax)
1578
1426
 
1427
+ df_reference = self.dataframes[self.reference_population]
1428
+ t_reference = df_reference.loc[df_reference['TRACK_ID']==self.reference_track_of_interest, self.reference_time_name].values
1429
+
1430
+ if len(t_reference)>0:
1431
+ t_reference=t_reference[0]
1432
+ ymin,ymax = self.cell_ax.get_ylim()
1433
+ self.line_dt_reference.set_xdata([t_reference, t_reference])
1434
+ self.line_dt_reference.set_ydata([ymin,ymax])
1435
+
1436
+
1579
1437
  if self.reference_track_of_interest is not None and self.neighbor_track_of_interest is not None:
1580
1438
  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
1439
 
1590
- self.cell_ax.legend()
1440
+ df_neighbor = self.dataframes[self.neighbor_population]
1441
+ t_neighbor = df_neighbor.loc[
1442
+ df_neighbor['TRACK_ID'] == self.neighbor_track_of_interest, self.neighbor_time_name].values
1443
+
1444
+ if len(t0)>0:
1445
+ t0=t0[0]
1446
+ ymin,ymax = self.cell_ax.get_ylim()
1447
+ self.line_dt.set_xdata([t0, t0])
1448
+ self.line_dt.set_ydata([ymin,ymax])
1449
+
1450
+ if len(t_neighbor)>0:
1451
+ t_neighbor=t_neighbor[0]
1452
+ ymin,ymax = self.cell_ax.get_ylim()
1453
+ self.line_dt_neighbor.set_xdata([t_neighbor, t_neighbor])
1454
+ self.line_dt_neighbor.set_ydata([ymin,ymax])
1455
+
1456
+ self.cell_ax.legend(fontsize=8)
1591
1457
  self.cell_fcanvas.canvas.draw()
1592
1458
 
1593
1459
 
@@ -1603,55 +1469,63 @@ class SignalAnnotator2(QMainWindow,Styles):
1603
1469
  for t in np.arange(self.len_movie):
1604
1470
 
1605
1471
  # 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())
1472
+ 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)
1473
+ data = self.df_relative.loc[neigh_at_time_filter, ['REFERENCE_ID', 'NEIGHBOR_ID','status_color','class_color']].to_numpy()
1474
+ self.lines_tracks.append(data[:,[0,1]])
1475
+
1476
+ self.initial_lines_colors_status.append(data[:,[0,1,2]])
1477
+ self.lines_colors_status.append(data[:,[0,1,2]])
1478
+
1479
+ self.initial_lines_colors_class.append(data[:,[0,1,3]])
1480
+ self.lines_colors_class.append(data[:,[0,1,3]])
1633
1481
 
1482
+ def extract_scatter_from_trajectories(self):
1634
1483
 
1635
- def extract_scatter_from_effector_trajectories(self):
1484
+ self.tracks = {}
1485
+ self.positions = {}
1486
+ self.colors = {}
1487
+ self.initial_colors = {}
1488
+ self.timeline = {}
1489
+
1490
+ for population in self.dataframes.keys():
1491
+ self.refresh_scatter_from_trajectories(population)
1636
1492
 
1637
- self.effector_positions = []
1638
- self.effector_colors = []
1639
- self.initial_effector_colors=[]
1640
- self.effector_tracks = []
1493
+ def refresh_scatter_from_trajectories(self, population, clear_selection=True):
1494
+
1495
+ df = self.dataframes[population]
1496
+ positions = []
1497
+ tracks = []
1498
+ timeline = []
1499
+ colors = []
1500
+ initial_colors = []
1641
1501
 
1642
- for t in np.arange(self.len_movie):
1502
+ if df is not None:
1643
1503
 
1644
- if self.df_effectors is not None:
1504
+ for t in np.arange(self.len_movie):
1645
1505
 
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())
1506
+ positions.append(df.loc[df['FRAME']==t,['x_anim', 'y_anim']].to_numpy())
1507
+ colors.append(df.loc[df['FRAME']==t,['class_color', 'status_color']].to_numpy())
1508
+ initial_colors.append(
1509
+ df.loc[df['FRAME'] == t, ['class_color', 'status_color']].to_numpy())
1649
1510
  try:
1650
- self.effector_tracks.append(self.df_effectors.loc[self.df_effectors['FRAME']==t, 'TRACK_ID'].to_numpy())
1511
+ tracks.append(df.loc[df['FRAME']==t, 'TRACK_ID'].to_numpy())
1512
+ timeline.append(t)
1651
1513
  except:
1652
- self.effector_tracks.append(
1653
- self.df_effectors.loc[self.df_effectors['FRAME'] == t, 'ID'].to_numpy())
1654
-
1514
+ tracks.append(
1515
+ df.loc[df['FRAME'] == t, 'ID'].to_numpy())
1516
+ timeline.append(t)
1517
+
1518
+ self.tracks.update({population: tracks})
1519
+ self.timeline.update({population: timeline})
1520
+ self.positions.update({population: positions})
1521
+ self.colors.update({population: colors})
1522
+ self.initial_colors.update({population: initial_colors})
1523
+
1524
+ if self.reference_track_of_interest is not None:
1525
+ #self.recolor_selection()
1526
+ #self.trace_neighbors()
1527
+ self.highlight_the_pair()
1528
+
1655
1529
  def load_annotator_config(self):
1656
1530
 
1657
1531
  """
@@ -1793,7 +1667,11 @@ class SignalAnnotator2(QMainWindow,Styles):
1793
1667
  # "Are you sure you want to exit ?",
1794
1668
  # QMessageBox.Yes| QMessageBox.No,
1795
1669
  # )
1796
- del self.stack
1670
+ try:
1671
+ del self.stack
1672
+ except:
1673
+ pass
1674
+
1797
1675
  gc.collect()
1798
1676
 
1799
1677
  def looped_animation(self):
@@ -1815,21 +1693,26 @@ class SignalAnnotator2(QMainWindow,Styles):
1815
1693
  self.im = self.ax.imshow(self.stack[0], cmap='gray', vmin=np.nanpercentile(self.stack, 1), vmax=np.nanpercentile(self.stack, 99.99))
1816
1694
 
1817
1695
 
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)
1696
+ self.status_scatter = {}
1697
+ self.class_scatter = {}
1698
+
1699
+ n_pops = len(list(self.dataframes.keys()))
1700
+ markers = ['x','+','*','.','X']
1701
+ while len(markers) < n_pops:
1702
+ markers = markers*2
1703
+
1704
+ for i,pop in enumerate(self.dataframes.keys()):
1705
+ df = self.dataframes[pop]
1706
+ if df is not None:
1707
+ 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",
1708
+ 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)
1709
+ else:
1710
+ status_scatter = self.ax.scatter([], [], s=50, picker=True, pickradius=10, marker=markers[i]) #marker="x",
1711
+ class_scatter = self.ax.scatter([], [], marker='o', facecolors='none', s=200)
1712
+ self.status_scatter.update({pop: status_scatter})
1713
+ self.class_scatter.update({pop: class_scatter})
1831
1714
 
1832
- self.points=self.ax.scatter([], [], marker="$\Join$", s=100, picker=True, pickradius=10, zorder=10) #picker=True, pickradius=10
1715
+ self.points=self.ax.scatter([], [], marker="$\\Join$", s=100, picker=True, pickradius=10, zorder=10) #picker=True, pickradius=10
1833
1716
 
1834
1717
  self.ax.set_xticks([])
1835
1718
  self.ax.set_yticks([])
@@ -1869,13 +1752,17 @@ class SignalAnnotator2(QMainWindow,Styles):
1869
1752
 
1870
1753
  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
1754
  for i in range(len(self.lines)):
1755
+
1872
1756
  self.lines[i].set_label(f'signal {i}')
1873
1757
 
1874
1758
  min_val,max_val = self.cell_ax.get_ylim()
1875
1759
  self.line_dt, = self.cell_ax.plot([-1,-1],[min_val,max_val],c="k",linestyle="--")
1760
+ self.line_dt_reference, = self.cell_ax.plot([-1,-1],[min_val,max_val],c="tab:blue",linestyle="--")
1761
+ self.line_dt_neighbor, = self.cell_ax.plot([-1,-1],[min_val,max_val],c="tab:red",linestyle="--")
1762
+
1876
1763
 
1877
1764
  self.cell_ax.set_xlim(0,self.len_movie)
1878
- self.cell_ax.legend()
1765
+ self.cell_ax.legend(fontsize=8)
1879
1766
  self.cell_fcanvas.canvas.draw()
1880
1767
 
1881
1768
  #self.plot_signals()
@@ -1884,9 +1771,8 @@ class SignalAnnotator2(QMainWindow,Styles):
1884
1771
  def on_scatter_pick(self, event):
1885
1772
 
1886
1773
  self.identify_closest_marker(event)
1887
- print(self.pair_selected, self.reference_selection)
1888
1774
 
1889
- _, tracks, _, _ = self.get_reference_sets()
1775
+ _, tracks, _, _, _ = self.get_reference_sets()
1890
1776
 
1891
1777
  if self.selected_population == self.reference_population:
1892
1778
 
@@ -1949,12 +1835,15 @@ class SignalAnnotator2(QMainWindow,Styles):
1949
1835
 
1950
1836
  print('You selected a pair...')
1951
1837
  artist = event.artist
1952
- print(self.index)
1953
1838
 
1954
1839
  if self.index is not None and len(self.pair_selection)==0:
1955
1840
 
1956
- selected_point = artist.get_offsets()[self.index]
1957
-
1841
+ try:
1842
+ selected_point = artist.get_offsets()[self.index]
1843
+ except Exception as e:
1844
+ print(f"L1788 {e}")
1845
+ return
1846
+
1958
1847
  if len(self.pair_selection) == 0 and ((selected_point[0],selected_point[1]) in self.connections.keys()):
1959
1848
 
1960
1849
  connect = self.connections[(selected_point[0], selected_point[1])]
@@ -2029,24 +1918,10 @@ class SignalAnnotator2(QMainWindow,Styles):
2029
1918
 
2030
1919
 
2031
1920
  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
1921
+ return self.positions[self.neighbor_population], self.tracks[self.neighbor_population], self.colors[self.neighbor_population], self.initial_colors[self.neighbor_population]
2043
1922
 
2044
1923
  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
1924
+ 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
1925
 
2051
1926
  def trace_neighbors(self):
2052
1927
 
@@ -2056,7 +1931,8 @@ class SignalAnnotator2(QMainWindow,Styles):
2056
1931
  self.line_connections={}
2057
1932
 
2058
1933
  positions, tracks, colors, _ = self.get_neighbor_sets()
2059
-
1934
+ _, tracks_reference, _, _, _ = self.get_reference_sets()
1935
+
2060
1936
  # Look for neighbors
2061
1937
  for neigh in self.neighbors:
2062
1938
 
@@ -2065,46 +1941,39 @@ class SignalAnnotator2(QMainWindow,Styles):
2065
1941
 
2066
1942
  for t in range(len(tracks)):
2067
1943
  indices = np.where(tracks[t]==neigh)[0]
2068
- if len(indices)>0:
1944
+ indices_reference = np.where(tracks_reference[t]==self.reference_track_of_interest)[0]
1945
+ if len(indices)>0 and len(indices_reference)>0:
2069
1946
  self.neighbor_loc_t.append(t)
2070
1947
  self.neighbor_loc_idx.append(indices[0])
2071
-
1948
+
2072
1949
  self.neighbor_previous_color = []
2073
1950
  for t, idx in zip(self.neighbor_loc_t, self.neighbor_loc_idx):
2074
-
2075
- try:
1951
+
1952
+ t_ref_idx = np.where(self.reference_timeline == t)[0]
1953
+ if t_ref_idx:
1954
+ t_ref_idx = t_ref_idx[0]
2076
1955
 
2077
1956
  neigh_x = positions[t][idx, 0]
2078
1957
  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
-
1958
+ x_m_point = (self.reference_x[t_ref_idx] + neigh_x) / 2
1959
+ y_m_point = (self.reference_y[t_ref_idx] + neigh_y) / 2
1960
+
2082
1961
  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])]
1962
+ self.lines_data[t]=[([self.reference_x[t_ref_idx], neigh_x], [self.reference_y[t_ref_idx], neigh_y])]
2084
1963
  self.points_data[t]=[(x_m_point, y_m_point)]
2085
1964
  else:
2086
- self.lines_data[t].append(([self.reference_x[t], neigh_x], [self.reference_y[t], neigh_y]))
1965
+ self.lines_data[t].append(([self.reference_x[t_ref_idx], neigh_x], [self.reference_y[t_ref_idx], neigh_y]))
2087
1966
  self.points_data[t].append((x_m_point, y_m_point))
2088
-
1967
+
2089
1968
  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
-
1969
+ 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)]
1970
+
2092
1971
  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
1972
 
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
1973
 
2105
1974
  def recolor_selection(self):
2106
1975
 
2107
- positions, tracks, colors, init_colors = self.get_reference_sets()
1976
+ positions, tracks, timelines, colors, init_colors = self.get_reference_sets()
2108
1977
 
2109
1978
  self.reference_loc_t = []
2110
1979
  self.reference_loc_idx = []
@@ -2125,13 +1994,16 @@ class SignalAnnotator2(QMainWindow,Styles):
2125
1994
  self.reference_not_picked_initial_colors=[]
2126
1995
  self.reference_x = []
2127
1996
  self.reference_y = []
1997
+ self.reference_timeline = []
2128
1998
 
2129
1999
  # Recolor selected cell
2130
2000
  for t,idx in zip(self.reference_loc_t,self.reference_loc_idx):
2131
2001
  self.reference_x.append(positions[t][idx, 0])
2132
2002
  self.reference_y.append(positions[t][idx, 1])
2003
+ self.reference_timeline.append(timelines[t])
2133
2004
  self.reference_previous_color.append(colors[t][idx].copy())
2134
2005
  colors[t][idx] = 'lime'
2006
+ self.reference_timeline = np.array(self.reference_timeline)
2135
2007
 
2136
2008
  # Recolor all other cells in black
2137
2009
  for t, idx in zip(self.reference_loc_t_not_picked, self.reference_loc_idx_not_picked):
@@ -2144,6 +2016,7 @@ class SignalAnnotator2(QMainWindow,Styles):
2144
2016
 
2145
2017
  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
2018
  self.neighbors = np.unique(self.neighbors)
2019
+
2147
2020
  # if len(self.neighbors)>0:
2148
2021
  # first_neighbor = np.min(self.neighbors)
2149
2022
  # self.neighbor_track_of_interest = first_neighbor
@@ -2154,38 +2027,37 @@ class SignalAnnotator2(QMainWindow,Styles):
2154
2027
 
2155
2028
  ind = event.ind
2156
2029
  label = event.artist.get_label()
2157
- print(f'{label=}')
2158
2030
 
2159
2031
  # Identify the nature of the selected object (target/effector/pair)
2160
2032
  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=}")
2033
+
2034
+ populations = list(self.dataframes.keys())
2035
+
2036
+ number = int(label.split('_child')[1])
2037
+
2038
+ if number>len(populations)*2:
2039
+ print('A pair is selected...')
2040
+ self.pair_selected = True
2041
+ self.selected_population = None
2187
2042
  else:
2188
- self.index = None
2043
+ self.selected_population = populations[(number-1)//2]
2044
+
2045
+ if self.selected_population is not None:
2046
+ positions = self.positions[self.selected_population]
2047
+
2048
+ if len(ind)==1:
2049
+ self.index = ind[0]
2050
+ elif len(ind)>1:
2051
+ # More than one point in vicinity
2052
+ try:
2053
+ datax,datay = [positions[self.framedata][i,0] for i in ind],[positions[self.framedata][i,1] for i in ind]
2054
+ msx, msy = event.mouseevent.xdata, event.mouseevent.ydata
2055
+ dist = np.sqrt((np.array(datax)-msx)**2+(np.array(datay)-msy)**2)
2056
+ self.index = ind[np.argmin(dist)]
2057
+ except Exception as e:
2058
+ print(f"Exception L2090 to find closest marker: {e=}")
2059
+ else:
2060
+ self.index = None
2189
2061
 
2190
2062
 
2191
2063
  def show_annotation_buttons(self):
@@ -2304,58 +2176,46 @@ class SignalAnnotator2(QMainWindow,Styles):
2304
2176
  self.framedata = framedata
2305
2177
  self.frame_lbl.setText(f'frame: {self.framedata}')
2306
2178
  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])
2179
+
2180
+ for pop in self.dataframes.keys():
2181
+ df = self.dataframes[pop]
2182
+ if df is not None:
2183
+ self.status_scatter[pop].set_visible(True)
2184
+ self.status_scatter[pop].set_picker(True)
2185
+ self.status_scatter[pop].set_offsets(self.positions[pop][self.framedata])
2186
+ self.status_scatter[pop].set_color(self.colors[pop][self.framedata][:,1])
2187
+
2188
+ self.class_scatter[pop].set_visible(True)
2189
+ self.class_scatter[pop].set_offsets(self.positions[pop][self.framedata])
2190
+ self.class_scatter[pop].set_edgecolor(self.colors[pop][self.framedata][:,0])
2327
2191
 
2328
2192
  self.lines_list=[]
2329
2193
 
2330
2194
  for key in self.lines_data:
2195
+
2331
2196
  if key==self.framedata:
2197
+
2332
2198
  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
2199
 
2336
- this_frame=self.lines_colors_class[self.framedata]
2200
+ x_coords, y_coords = line
2201
+ pair = self.line_connections[x_coords[0],x_coords[1],y_coords[0],y_coords[1]]
2202
+ this_frame = self.lines_colors_class[self.framedata]
2337
2203
 
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])
2204
+ this_pair = this_frame[(this_frame[:, 0] == pair[0][0]) & (this_frame[:, 1] == pair[0][1])]
2205
+ if len(this_pair)>0:
2206
+ c = this_pair[0][2]
2207
+ self.lines_plot = self.ax.plot(x_coords, y_coords, alpha=1, linewidth=2, color=c)
2341
2208
  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)
2209
+
2210
+ self.points.set_offsets(self.points_data[key])
2211
+ colors_at_this_frame = self.lines_colors_status[self.framedata]
2212
+ 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]]
2213
+ self.points.set_color(colors)
2353
2214
 
2354
2215
  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]
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()] + self.lines_list + [self.points]
2356
2217
  else:
2357
- return [self.im, self.target_status_scatter, self.target_class_scatter, self.effector_status_scatter,
2358
- self.effector_class_scatter,]
2218
+ 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
2219
 
2360
2220
  def stop(self):
2361
2221
  # # On stop we disconnect all of our events.
@@ -2435,63 +2295,6 @@ class SignalAnnotator2(QMainWindow,Styles):
2435
2295
 
2436
2296
  self.target_cell_info.setText('')
2437
2297
 
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
2298
 
2496
2299
  def save_trajectories(self):
2497
2300
 
@@ -2602,39 +2405,34 @@ class SignalAnnotator2(QMainWindow,Styles):
2602
2405
 
2603
2406
  def normalize_features(self):
2604
2407
 
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
2408
  if not self.normalized_signals:
2409
+ for pop in self.dataframes.keys():
2410
+ df_pop = self.dataframes[pop]
2411
+ cols = self.population_columns[pop]
2412
+ if df_pop is not None:
2413
+ df_pop[cols] = self.MinMaxScaler_per_pop[pop].transform(df_pop[cols].values)
2414
+ self.dataframes.update({pop: df_pop})
2613
2415
 
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
2416
  if self.df_relative is not None:
2619
- self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.transform(x_pairs)
2417
+ self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.transform(self.df_relative[self.pair_columns].values)
2620
2418
 
2621
- self.plot_signals()
2622
2419
  self.normalized_signals = True
2623
2420
  self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="#1565c0"))
2624
- self.normalize_features_btn.setIconSize(QSize(25, 25))
2625
2421
  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)
2422
+ for pop in self.dataframes.keys():
2423
+ df_pop = self.dataframes[pop]
2424
+ cols = self.population_columns[pop]
2425
+ if df_pop is not None:
2426
+ df_pop[cols] = self.MinMaxScaler_per_pop[pop].inverse_transform(df_pop[cols].values)
2427
+ self.dataframes.update({pop: df_pop})
2631
2428
  if self.df_relative is not None:
2632
- self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.inverse_transform(x_pairs)
2429
+ self.df_relative[self.pair_columns] = self.MinMaxScaler_pairs.inverse_transform(self.df_relative[self.pair_columns].values)
2633
2430
 
2634
- self.plot_signals()
2635
2431
  self.normalized_signals = False
2636
2432
  self.normalize_features_btn.setIcon(icon(MDI6.arrow_collapse_vertical, color="black"))
2637
- self.normalize_features_btn.setIconSize(QSize(25, 25))
2433
+
2434
+ self.plot_signals()
2435
+ self.normalize_features_btn.setIconSize(QSize(25, 25))
2638
2436
 
2639
2437
  def switch_to_log(self):
2640
2438