celldetective 1.4.0__py3-none-any.whl → 1.4.1__py3-none-any.whl

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