celldetective 1.0.2__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 (66) hide show
  1. celldetective/__init__.py +2 -0
  2. celldetective/__main__.py +432 -0
  3. celldetective/datasets/segmentation_annotations/blank +0 -0
  4. celldetective/datasets/signal_annotations/blank +0 -0
  5. celldetective/events.py +149 -0
  6. celldetective/extra_properties.py +100 -0
  7. celldetective/filters.py +89 -0
  8. celldetective/gui/__init__.py +20 -0
  9. celldetective/gui/about.py +44 -0
  10. celldetective/gui/analyze_block.py +563 -0
  11. celldetective/gui/btrack_options.py +898 -0
  12. celldetective/gui/classifier_widget.py +386 -0
  13. celldetective/gui/configure_new_exp.py +532 -0
  14. celldetective/gui/control_panel.py +438 -0
  15. celldetective/gui/gui_utils.py +495 -0
  16. celldetective/gui/json_readers.py +113 -0
  17. celldetective/gui/measurement_options.py +1425 -0
  18. celldetective/gui/neighborhood_options.py +452 -0
  19. celldetective/gui/plot_signals_ui.py +1042 -0
  20. celldetective/gui/process_block.py +1055 -0
  21. celldetective/gui/retrain_segmentation_model_options.py +706 -0
  22. celldetective/gui/retrain_signal_model_options.py +643 -0
  23. celldetective/gui/seg_model_loader.py +460 -0
  24. celldetective/gui/signal_annotator.py +2388 -0
  25. celldetective/gui/signal_annotator_options.py +340 -0
  26. celldetective/gui/styles.py +217 -0
  27. celldetective/gui/survival_ui.py +903 -0
  28. celldetective/gui/tableUI.py +608 -0
  29. celldetective/gui/thresholds_gui.py +1300 -0
  30. celldetective/icons/logo-large.png +0 -0
  31. celldetective/icons/logo.png +0 -0
  32. celldetective/icons/signals_icon.png +0 -0
  33. celldetective/icons/splash-test.png +0 -0
  34. celldetective/icons/splash.png +0 -0
  35. celldetective/icons/splash0.png +0 -0
  36. celldetective/icons/survival2.png +0 -0
  37. celldetective/icons/vignette_signals2.png +0 -0
  38. celldetective/icons/vignette_signals2.svg +114 -0
  39. celldetective/io.py +2050 -0
  40. celldetective/links/zenodo.json +561 -0
  41. celldetective/measure.py +1258 -0
  42. celldetective/models/segmentation_effectors/blank +0 -0
  43. celldetective/models/segmentation_generic/blank +0 -0
  44. celldetective/models/segmentation_targets/blank +0 -0
  45. celldetective/models/signal_detection/blank +0 -0
  46. celldetective/models/tracking_configs/mcf7.json +68 -0
  47. celldetective/models/tracking_configs/ricm.json +203 -0
  48. celldetective/models/tracking_configs/ricm2.json +203 -0
  49. celldetective/neighborhood.py +717 -0
  50. celldetective/scripts/analyze_signals.py +51 -0
  51. celldetective/scripts/measure_cells.py +275 -0
  52. celldetective/scripts/segment_cells.py +212 -0
  53. celldetective/scripts/segment_cells_thresholds.py +140 -0
  54. celldetective/scripts/track_cells.py +206 -0
  55. celldetective/scripts/train_segmentation_model.py +246 -0
  56. celldetective/scripts/train_signal_model.py +49 -0
  57. celldetective/segmentation.py +712 -0
  58. celldetective/signals.py +2826 -0
  59. celldetective/tracking.py +974 -0
  60. celldetective/utils.py +1681 -0
  61. celldetective-1.0.2.dist-info/LICENSE +674 -0
  62. celldetective-1.0.2.dist-info/METADATA +192 -0
  63. celldetective-1.0.2.dist-info/RECORD +66 -0
  64. celldetective-1.0.2.dist-info/WHEEL +5 -0
  65. celldetective-1.0.2.dist-info/entry_points.txt +2 -0
  66. celldetective-1.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,903 @@
1
+ from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QScrollArea, QButtonGroup, QComboBox, QFrame, QCheckBox, QFileDialog, QGridLayout, QTextEdit, QLineEdit, QVBoxLayout, QWidget, QLabel, QHBoxLayout, QPushButton, QRadioButton
2
+ from PyQt5.QtCore import Qt, QSize
3
+ from PyQt5.QtGui import QIcon, QDoubleValidator
4
+ from celldetective.gui.gui_utils import center_window, FeatureChoice, ListWidget, QHSeperationLine, FigureCanvas, GeometryChoice, OperationChoice
5
+ from superqt import QLabeledDoubleRangeSlider, QLabeledDoubleSlider,QLabeledSlider
6
+ from superqt.fonticon import icon
7
+ from fonticon_mdi6 import MDI6
8
+ from celldetective.utils import extract_experiment_channels, get_software_location, _extract_labels_from_config
9
+ from celldetective.io import interpret_tracking_configuration, load_frames, auto_load_number_of_frames, load_experiment_tables
10
+ from celldetective.measure import compute_haralick_features, contour_of_instance_segmentation
11
+ import numpy as np
12
+ from tifffile import imread
13
+ import json
14
+ from shutil import copyfile
15
+ import os
16
+ import matplotlib.pyplot as plt
17
+ plt.rcParams['svg.fonttype'] = 'none'
18
+ from mpl_toolkits.axes_grid1 import make_axes_locatable
19
+ from glob import glob
20
+ from natsort import natsorted
21
+ from tifffile import imread
22
+ from pathlib import Path, PurePath
23
+ import gc
24
+ import pandas as pd
25
+ from tqdm import tqdm
26
+ from lifelines import KaplanMeierFitter
27
+ import matplotlib.cm as mcm
28
+ import math
29
+ from celldetective.events import switch_to_events_v2
30
+
31
+ class ConfigSurvival(QWidget):
32
+
33
+ """
34
+ UI to set survival instructions.
35
+
36
+ """
37
+
38
+ def __init__(self, parent=None):
39
+
40
+ super().__init__()
41
+ self.parent = parent
42
+ self.setWindowTitle("Configure survival")
43
+ self.setWindowIcon(QIcon(os.sep.join(['celldetective','icons','mexican-hat.png'])))
44
+
45
+ self.exp_dir = self.parent.exp_dir
46
+ self.soft_path = get_software_location()
47
+ self.exp_config = self.exp_dir +"config.ini"
48
+ self.wells = np.array(self.parent.parent.wells,dtype=str)
49
+ self.well_labels = _extract_labels_from_config(self.exp_config,len(self.wells))
50
+ self.FrameToMin = self.parent.parent.FrameToMin
51
+ self.float_validator = QDoubleValidator()
52
+ self.auto_close = False
53
+
54
+ print('Parent wells: ', self.wells)
55
+
56
+
57
+ self.well_option = self.parent.parent.well_list.currentIndex()
58
+ self.position_option = self.parent.parent.position_list.currentIndex()
59
+ self.interpret_pos_location()
60
+ #self.config_path = self.exp_dir + self.config_name
61
+
62
+ self.screen_height = self.parent.parent.parent.screen_height
63
+ center_window(self)
64
+
65
+ self.setMinimumWidth(350)
66
+ #self.setMinimumHeight(int(0.8*self.screen_height))
67
+ #self.setMaximumHeight(int(0.8*self.screen_height))
68
+ self.populate_widget()
69
+ #self.load_previous_measurement_instructions()
70
+ if self.auto_close:
71
+ self.close()
72
+
73
+ def interpret_pos_location(self):
74
+
75
+ """
76
+ Read the well/position selection from the control panel to decide which data to load
77
+ Set position_indices to None if all positions must be taken
78
+
79
+ """
80
+
81
+ if self.well_option==len(self.wells):
82
+ self.well_indices = np.arange(len(self.wells))
83
+ else:
84
+ self.well_indices = np.array([self.well_option],dtype=int)
85
+
86
+ if self.position_option==0:
87
+ self.position_indices = None
88
+ else:
89
+ self.position_indices = np.array([self.position_option],dtype=int)
90
+
91
+
92
+ def populate_widget(self):
93
+
94
+ """
95
+ Create the multibox design.
96
+
97
+ """
98
+
99
+ # Create button widget and layout
100
+ main_layout = QVBoxLayout()
101
+ self.setLayout(main_layout)
102
+ main_layout.setContentsMargins(30,30,30,30)
103
+
104
+ panel_title = QLabel('Options')
105
+ panel_title.setStyleSheet("""
106
+ font-weight: bold;
107
+ padding: 0px;
108
+ """)
109
+ main_layout.addWidget(panel_title, alignment=Qt.AlignCenter)
110
+
111
+
112
+ labels = [QLabel('population: '), QLabel('time of\ninterest: '), QLabel('time of\nreference: '), QLabel('cmap: ')] #QLabel('class: '),
113
+ self.cb_options = [['targets','effectors'],['t0','first detection'], ['0','first detection', 't0'], list(plt.colormaps())] #['class'],
114
+ self.cbs = [QComboBox() for i in range(len(labels))]
115
+ self.cbs[0].currentIndexChanged.connect(self.set_classes_and_times)
116
+
117
+ choice_layout = QVBoxLayout()
118
+ choice_layout.setContentsMargins(20,20,20,20)
119
+ for i in range(len(labels)):
120
+ hbox = QHBoxLayout()
121
+ hbox.addWidget(labels[i], 33)
122
+ hbox.addWidget(self.cbs[i],66)
123
+ self.cbs[i].addItems(self.cb_options[i])
124
+ choice_layout.addLayout(hbox)
125
+ main_layout.addLayout(choice_layout)
126
+
127
+ self.cbs[0].setCurrentIndex(1)
128
+ self.cbs[0].setCurrentIndex(0)
129
+
130
+ time_calib_layout = QHBoxLayout()
131
+ time_calib_layout.setContentsMargins(20,20,20,20)
132
+ time_calib_layout.addWidget(QLabel('time calibration\n(frame to min)'), 33)
133
+ self.time_calibration_le = QLineEdit(str(self.FrameToMin).replace('.',','))
134
+ self.time_calibration_le.setValidator(self.float_validator)
135
+ time_calib_layout.addWidget(self.time_calibration_le, 66)
136
+ #time_calib_layout.addWidget(QLabel(' min'))
137
+ main_layout.addLayout(time_calib_layout)
138
+
139
+ self.submit_btn = QPushButton('Submit')
140
+ self.submit_btn.setStyleSheet(self.parent.parent.parent.button_style_sheet)
141
+ self.submit_btn.clicked.connect(self.process_survival)
142
+ main_layout.addWidget(self.submit_btn)
143
+
144
+ #self.populate_left_panel()
145
+ #grid.addLayout(self.left_side, 0, 0, 1, 1)
146
+
147
+ # self.setCentralWidget(self.scroll_area)
148
+ # self.show()
149
+
150
+ def set_classes_and_times(self):
151
+
152
+ # Look for all classes and times
153
+ tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_*']))
154
+ self.all_columns = []
155
+ for tab in tables:
156
+ cols = pd.read_csv(tab, nrows=1,encoding_errors='ignore').columns.tolist()
157
+ self.all_columns.extend(cols)
158
+ self.all_columns = np.unique(self.all_columns)
159
+ #class_idx = np.array([s.startswith('class_') for s in self.all_columns])
160
+ time_idx = np.array([s.startswith('t_') for s in self.all_columns])
161
+ # class_columns = list(self.all_columns[class_idx])
162
+ # for c in ['class_id', 'class_color']:
163
+ # if c in class_columns:
164
+ # class_columns.remove(c)
165
+
166
+ try:
167
+ time_columns = list(self.all_columns[time_idx])
168
+ except:
169
+ print('no column starts with t')
170
+ self.auto_close = True
171
+ return None
172
+
173
+ self.cbs[1].clear()
174
+ self.cbs[1].addItems(np.unique(self.cb_options[1]+time_columns))
175
+
176
+ self.cbs[2].clear()
177
+ self.cbs[2].addItems(np.unique(self.cb_options[2]+time_columns))
178
+
179
+ # self.cbs[3].clear()
180
+ # self.cbs[3].addItems(np.unique(self.cb_options[3]+class_columns))
181
+
182
+
183
+ def process_survival(self):
184
+
185
+ print('you clicked!!')
186
+ self.FrameToMin = float(self.time_calibration_le.text().replace(',','.'))
187
+ print(self.FrameToMin, 'set')
188
+
189
+ self.time_of_interest = self.cbs[1].currentText()
190
+ if self.time_of_interest=="t0":
191
+ self.class_of_interest = "class"
192
+ else:
193
+ self.class_of_interest = self.time_of_interest.replace('t_','class_')
194
+
195
+ # read instructions from combobox options
196
+ self.load_available_tables_local()
197
+ if self.df is not None:
198
+ self.compute_survival_functions()
199
+ # prepare survival
200
+
201
+ # plot survival
202
+ self.survivalWidget = QWidget()
203
+ self.survivalWidget.setMinimumHeight(int(0.8*self.screen_height))
204
+ self.survivalWidget.setWindowTitle('survival')
205
+ self.plotvbox = QVBoxLayout(self.survivalWidget)
206
+ self.plotvbox.setContentsMargins(30,30,30,30)
207
+ self.survival_title = QLabel('Survival function')
208
+ self.survival_title.setStyleSheet("""
209
+ font-weight: bold;
210
+ padding: 0px;
211
+ """)
212
+ self.plotvbox.addWidget(self.survival_title, alignment=Qt.AlignCenter)
213
+
214
+ plot_buttons_hbox = QHBoxLayout()
215
+ plot_buttons_hbox.addWidget(QLabel(''),90, alignment=Qt.AlignLeft)
216
+
217
+ self.legend_btn = QPushButton('')
218
+ self.legend_btn.setIcon(icon(MDI6.text_box,color="black"))
219
+ self.legend_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
220
+ self.legend_btn.setToolTip('Show or hide the legend')
221
+ self.legend_visible = True
222
+ self.legend_btn.clicked.connect(self.show_hide_legend)
223
+ plot_buttons_hbox.addWidget(self.legend_btn, 5,alignment=Qt.AlignRight)
224
+
225
+
226
+ self.log_btn = QPushButton('')
227
+ self.log_btn.setIcon(icon(MDI6.math_log,color="black"))
228
+ self.log_btn.setStyleSheet(self.parent.parent.parent.button_select_all)
229
+ self.log_btn.clicked.connect(self.switch_to_log)
230
+ self.log_btn.setToolTip('Enable or disable log scale')
231
+ plot_buttons_hbox.addWidget(self.log_btn, 5, alignment=Qt.AlignRight)
232
+
233
+ self.fig, self.ax = plt.subplots(1,1,figsize=(4,3))
234
+ self.survival_window = FigureCanvas(self.fig, title="Survival")
235
+ self.survival_window.setContentsMargins(0,0,0,0)
236
+ if self.df is not None:
237
+ self.initialize_axis()
238
+ plt.tight_layout()
239
+ self.fig.set_facecolor('none') # or 'None'
240
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
241
+ self.survival_window.canvas.draw()
242
+
243
+ #self.survival_window.layout.addWidget(QLabel('WHAAAAATTT???'))
244
+
245
+ self.plot_options = [QRadioButton() for i in range(3)]
246
+ self.radio_labels = ['well', 'pos', 'both']
247
+ radio_hbox = QHBoxLayout()
248
+ radio_hbox.setContentsMargins(30,30,30,30)
249
+ self.plot_btn_group = QButtonGroup()
250
+ for i in range(3):
251
+ self.plot_options[i].setText(self.radio_labels[i])
252
+ #self.plot_options[i].toggled.connect(self.plot_survivals)
253
+ self.plot_btn_group.addButton(self.plot_options[i])
254
+ radio_hbox.addWidget(self.plot_options[i], 33, alignment=Qt.AlignCenter)
255
+ self.plot_btn_group.buttonClicked[int].connect(self.plot_survivals)
256
+
257
+ if self.position_indices is not None:
258
+ if len(self.well_indices)>1 and len(self.position_indices)==1:
259
+ self.plot_btn_group.buttons()[0].click()
260
+ for i in [1,2]:
261
+ self.plot_options[i].setEnabled(False)
262
+ elif len(self.well_indices)>1:
263
+ self.plot_btn_group.buttons()[0].click()
264
+ elif len(self.well_indices)==1 and len(self.position_indices)==1:
265
+ self.plot_btn_group.buttons()[1].click()
266
+ for i in [0,2]:
267
+ self.plot_options[i].setEnabled(False)
268
+ else:
269
+ if len(self.well_indices)>1:
270
+ self.plot_btn_group.buttons()[0].click()
271
+ elif len(self.well_indices)==1:
272
+ self.plot_btn_group.buttons()[2].click()
273
+
274
+
275
+ # elif len(self.well_indices)>1:
276
+ # self.plot_btn_group.buttons()[0].click()
277
+ # else:
278
+ # self.plot_btn_group.buttons()[1].click()
279
+
280
+ # if self.position_indices is not None:
281
+ # for i in [0,2]:
282
+ # self.plot_options[i].setEnabled(False)
283
+
284
+
285
+ #self.plot_options[0].setChecked(True)
286
+ self.plotvbox.addLayout(radio_hbox)
287
+
288
+ self.plotvbox.addLayout(plot_buttons_hbox)
289
+ self.plotvbox.addWidget(self.survival_window)
290
+
291
+ self.select_pos_label = QLabel('Select positions')
292
+ self.select_pos_label.setStyleSheet("""
293
+ font-weight: bold;
294
+ padding: 0px;
295
+ """)
296
+ self.plotvbox.addWidget(self.select_pos_label, alignment=Qt.AlignCenter)
297
+
298
+ self.select_option = [QRadioButton() for i in range(2)]
299
+ self.select_label = ['name', 'spatial']
300
+ select_hbox = QHBoxLayout()
301
+ select_hbox.setContentsMargins(30,30,30,30)
302
+ self.select_btn_group = QButtonGroup()
303
+ for i in range(2):
304
+ self.select_option[i].setText(self.select_label[i])
305
+ #self.select_option[i].toggled.connect(self.switch_selection_mode)
306
+ self.select_btn_group.addButton(self.select_option[i])
307
+ select_hbox.addWidget(self.select_option[i],33, alignment=Qt.AlignCenter)
308
+ self.select_btn_group.buttonClicked[int].connect(self.switch_selection_mode)
309
+ self.plotvbox.addLayout(select_hbox)
310
+
311
+ self.look_for_metadata()
312
+ if self.metadata_found:
313
+ self.fig_scatter, self.ax_scatter = plt.subplots(1,1,figsize=(4,3))
314
+ self.position_scatter = FigureCanvas(self.fig_scatter)
315
+ self.load_coordinates()
316
+ self.plot_spatial_location()
317
+ #self.plot_positions()
318
+ self.ax_scatter.spines['top'].set_visible(False)
319
+ self.ax_scatter.spines['right'].set_visible(False)
320
+ self.ax_scatter.set_aspect('equal')
321
+ self.ax_scatter.set_xticks([])
322
+ self.ax_scatter.set_yticks([])
323
+ plt.tight_layout()
324
+ self.fig_scatter.set_facecolor('none') # or 'None'
325
+ self.fig_scatter.canvas.setStyleSheet("background-color: transparent;")
326
+ self.plotvbox.addWidget(self.position_scatter)
327
+
328
+ self.generate_pos_selection_widget()
329
+
330
+ # if self.df is not None and len(self.ks_estimators_per_position)>0:
331
+ # self.plot_survivals()
332
+ self.select_btn_group.buttons()[0].click()
333
+ self.survivalWidget.show()
334
+
335
+ def generate_pos_selection_widget(self):
336
+
337
+ self.well_names = self.df['well_name'].unique()
338
+ self.pos_names = self.df_pos_info['pos_name'].unique() #pd.DataFrame(self.ks_estimators_per_position)['position_name'].unique()
339
+ print(f'POSITION NAMES: ',self.pos_names)
340
+ self.usable_well_labels = []
341
+ for name in self.well_names:
342
+ for lbl in self.well_labels:
343
+ if name+':' in lbl:
344
+ self.usable_well_labels.append(lbl)
345
+
346
+ self.line_choice_widget = QWidget()
347
+ self.line_check_vbox = QVBoxLayout()
348
+ self.line_choice_widget.setLayout(self.line_check_vbox)
349
+ if len(self.well_indices)>1:
350
+ self.well_display_options = [QCheckBox(self.usable_well_labels[i]) for i in range(len(self.usable_well_labels))]
351
+ for i in range(len(self.well_names)):
352
+ self.line_check_vbox.addWidget(self.well_display_options[i], alignment=Qt.AlignLeft)
353
+ self.well_display_options[i].setChecked(True)
354
+ self.well_display_options[i].toggled.connect(self.select_survival_lines)
355
+ else:
356
+ self.pos_display_options = [QCheckBox(self.pos_names[i]) for i in range(len(self.pos_names))]
357
+ for i in range(len(self.pos_names)):
358
+ self.line_check_vbox.addWidget(self.pos_display_options[i], alignment=Qt.AlignLeft)
359
+ self.pos_display_options[i].setChecked(True)
360
+ self.pos_display_options[i].toggled.connect(self.select_survival_lines)
361
+
362
+ self.plotvbox.addWidget(self.line_choice_widget, alignment=Qt.AlignCenter)
363
+
364
+
365
+ def load_available_tables_local(self):
366
+
367
+ """
368
+ Load the tables of the selected wells/positions from the control Panel for the population of interest
369
+
370
+ """
371
+
372
+ self.well_option = self.parent.parent.well_list.currentIndex()
373
+ if self.well_option==len(self.wells):
374
+ wo = '*'
375
+ else:
376
+ wo = self.well_option
377
+ self.position_option = self.parent.parent.position_list.currentIndex()
378
+ if self.position_option==0:
379
+ po = '*'
380
+ else:
381
+ po = self.position_option - 1
382
+
383
+ self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=wo, position_option=po, population=self.cbs[0].currentText(), return_pos_info=True)
384
+ if self.df is None:
385
+ msgBox = QMessageBox()
386
+ msgBox.setIcon(QMessageBox.Warning)
387
+ msgBox.setText("No table could be found.. Abort.")
388
+ msgBox.setWindowTitle("Warning")
389
+ msgBox.setStandardButtons(QMessageBox.Ok)
390
+ returnValue = msgBox.exec()
391
+ if returnValue == QMessageBox.Ok:
392
+ self.close()
393
+ return None
394
+ print('no table could be found...')
395
+ else:
396
+ self.df_well_info = self.df_pos_info.loc[:,['well_path', 'well_index', 'well_name', 'well_number', 'well_alias']].drop_duplicates()
397
+ #print(f"{self.df_well_info=}")
398
+
399
+ def compute_survival_functions(self):
400
+
401
+ # Per position survival
402
+ left_censored = False
403
+ for block,movie_group in self.df.groupby(['well','position']):
404
+ try:
405
+ classes = movie_group.groupby('TRACK_ID')[self.class_of_interest].min().values
406
+ times = movie_group.groupby('TRACK_ID')[self.cbs[1].currentText()].min().values
407
+ except Exception as e:
408
+ print(e)
409
+ continue
410
+ max_times = movie_group.groupby('TRACK_ID')['FRAME'].max().values
411
+ first_detections = None
412
+
413
+ if self.cbs[2].currentText()=='first detection':
414
+ left_censored = True
415
+
416
+ first_detections = []
417
+ for tid,track_group in movie_group.groupby('TRACK_ID'):
418
+ if 'area' in self.df.columns:
419
+ area = track_group['area'].values
420
+ timeline = track_group['FRAME'].values
421
+ if np.any(area==area):
422
+ first_det = timeline[area==area][0]
423
+ first_detections.append(first_det)
424
+ else:
425
+ # think about assymmetry with class and times
426
+ continue
427
+ else:
428
+ continue
429
+
430
+ elif self.cbs[2].currentText().startswith('t'):
431
+ left_censored = True
432
+ first_detections = movie_group.groupby('TRACK_ID')[self.cbs[2].currentText()].max().values
433
+ print(first_detections)
434
+
435
+
436
+ if self.cbs[2].currentText()=='first detection' or self.cbs[2].currentText().startswith('t'):
437
+ left_censored = True
438
+ else:
439
+ left_censored = False
440
+ events, survival_times = switch_to_events_v2(classes, times, max_times, first_detections, left_censored=left_censored, FrameToMin=self.FrameToMin)
441
+ ks = KaplanMeierFitter()
442
+ if len(events)>0:
443
+ ks.fit(survival_times, event_observed=events)
444
+ self.df_pos_info.loc[self.df_pos_info['pos_path']==block[1],'survival_fit'] = ks
445
+
446
+ # Per well survival
447
+ left_censored = False
448
+ for well,well_group in self.df.groupby('well'):
449
+
450
+ well_classes = []
451
+ well_times = []
452
+ well_max_times = []
453
+ well_first_detections = []
454
+
455
+ for block,movie_group in well_group.groupby('position'):
456
+ try:
457
+ classes = movie_group.groupby('TRACK_ID')[self.class_of_interest].min().values
458
+ times = movie_group.groupby('TRACK_ID')[self.cbs[1].currentText()].min().values
459
+ except Exception as e:
460
+ print(e)
461
+ continue
462
+ max_times = movie_group.groupby('TRACK_ID')['FRAME'].max().values
463
+ first_detections = None
464
+
465
+ if self.cbs[2].currentText()=='first detection':
466
+
467
+ left_censored = True
468
+ first_detections = []
469
+ for tid,track_group in movie_group.groupby('TRACK_ID'):
470
+ if 'area' in self.df.columns:
471
+ area = track_group['area'].values
472
+ timeline = track_group['FRAME'].values
473
+ if np.any(area==area):
474
+ first_det = timeline[area==area][0]
475
+ first_detections.append(first_det)
476
+ else:
477
+ # think about assymmetry with class and times
478
+ continue
479
+
480
+ elif self.cbs[2].currentText().startswith('t'):
481
+ left_censored = True
482
+ first_detections = movie_group.groupby('TRACK_ID')[self.cbs[2].currentText()].max().values
483
+
484
+ else:
485
+ pass
486
+
487
+ well_classes.extend(classes)
488
+ well_times.extend(times)
489
+ well_max_times.extend(max_times)
490
+ if first_detections is not None:
491
+ well_first_detections.extend(first_detections)
492
+
493
+ if len(well_first_detections)==0:
494
+ well_first_detections = None
495
+
496
+ print(f"{well_classes=}; {well_times=}")
497
+ events, survival_times = switch_to_events_v2(well_classes, well_times, well_max_times, well_first_detections,left_censored=left_censored, FrameToMin=self.FrameToMin)
498
+ print(f"{events=}; {survival_times=}")
499
+ ks = KaplanMeierFitter()
500
+ if len(survival_times)>0:
501
+ ks.fit(survival_times, event_observed=events)
502
+ print(ks.survival_function_)
503
+ else:
504
+ ks = None
505
+ print(f"{ks=}")
506
+ self.df_well_info.loc[self.df_well_info['well_path']==well,'survival_fit'] = ks
507
+
508
+ self.df_pos_info.loc[:,'select'] = True
509
+ self.df_well_info.loc[:,'select'] = True
510
+
511
+ def initialize_axis(self):
512
+
513
+ self.ax.clear()
514
+ self.ax.plot([],[])
515
+ self.ax.spines['top'].set_visible(False)
516
+ self.ax.spines['right'].set_visible(False)
517
+ #self.ax.set_ylim(0.001,1.05)
518
+ self.ax.set_xlim(0,self.df['FRAME'].max()*self.FrameToMin)
519
+ self.ax.set_xlabel('time [min]')
520
+ self.ax.set_ylabel('survival')
521
+
522
+ def plot_survivals(self, id):
523
+
524
+ for i in range(3):
525
+ if self.plot_options[i].isChecked():
526
+ self.plot_mode = self.radio_labels[i]
527
+
528
+ cmap_lbl = self.cbs[-1].currentText()
529
+ self.cmap = getattr(mcm, cmap_lbl)
530
+
531
+ colors = np.array([self.cmap(i / len(self.df_pos_info)) for i in range(len(self.df_pos_info))])
532
+ well_color = [self.cmap(i / len(self.df_well_info)) for i in range(len(self.df_well_info))]
533
+
534
+ if self.plot_mode=='pos':
535
+ self.initialize_axis()
536
+ lines = self.df_pos_info.loc[self.df_pos_info['select'],'survival_fit'].values
537
+ pos_labels = self.df_pos_info.loc[self.df_pos_info['select'],'pos_name'].values
538
+ pos_indices = self.df_pos_info.loc[self.df_pos_info['select'],'pos_index'].values
539
+ well_index = self.df_pos_info.loc[self.df_pos_info['select'],'well_index'].values
540
+ for i in range(len(lines)):
541
+ if (len(self.well_indices)<=1) and lines[i]==lines[i]:
542
+ try:
543
+ lines[i].plot_survival_function(ax=self.ax, legend=None, color=colors[pos_indices[i]],label=pos_labels[i], xlabel='timeline [min]')
544
+ except Exception as e:
545
+ print(f'error {e}')
546
+ pass
547
+ elif lines[i]==lines[i]:
548
+ try:
549
+ lines[i].plot_survival_function(ax=self.ax, legend=None, color=well_color[well_index[i]],label=pos_labels[i], xlabel='timeline [min]')
550
+ except Exception as e:
551
+ print(f'error {e}')
552
+ pass
553
+ else:
554
+ pass
555
+
556
+ elif self.plot_mode=='well':
557
+ self.initialize_axis()
558
+ lines = self.df_well_info.loc[self.df_well_info['select'],'survival_fit'].values
559
+ well_index = self.df_well_info.loc[self.df_well_info['select'],'well_index'].values
560
+ well_labels = self.df_well_info.loc[self.df_well_info['select'],'well_name'].values
561
+ for i in range(len(lines)):
562
+ if len(self.well_indices)<=1 and lines[i]==lines[i]:
563
+
564
+ try:
565
+ lines[i].plot_survival_function(ax=self.ax, label=well_labels[i], color="k", xlabel='timeline [min]')
566
+ except Exception as e:
567
+ print(f'error {e}')
568
+ pass
569
+ elif lines[i]==lines[i]:
570
+ try:
571
+ lines[i].plot_survival_function(ax=self.ax, label=well_labels[i], color=well_color[well_index[i]], xlabel='timeline [min]')
572
+ except Exception as e:
573
+ print(f'error {e}')
574
+ pass
575
+ else:
576
+ pass
577
+
578
+ elif self.plot_mode=='both':
579
+ self.initialize_axis()
580
+ if 'survival_fit' in self.df_pos_info.columns:
581
+ lines_pos = self.df_pos_info.loc[self.df_pos_info['select'],'survival_fit'].values
582
+ else:
583
+ lines_pos = []
584
+ if 'survival_fit' in self.df_well_info.columns:
585
+ lines_well = self.df_well_info.loc[self.df_well_info['select'],'survival_fit'].values
586
+ else:
587
+ lines_well = []
588
+
589
+ pos_indices = self.df_pos_info.loc[self.df_pos_info['select'],'pos_index'].values
590
+ well_index_pos = self.df_pos_info.loc[self.df_pos_info['select'],'well_index'].values
591
+ well_index = self.df_well_info.loc[self.df_well_info['select'],'well_index'].values
592
+ well_labels = self.df_well_info.loc[self.df_well_info['select'],'well_name'].values
593
+ pos_labels = self.df_pos_info.loc[self.df_pos_info['select'],'pos_name'].values
594
+
595
+
596
+ for i in range(len(lines_pos)):
597
+ if len(self.well_indices)<=1 and lines_pos[i]==lines_pos[i]:
598
+
599
+ try:
600
+ lines_pos[i].plot_survival_function(ax=self.ax, label=pos_labels[i], alpha=0.25, color=colors[pos_indices[i]], xlabel='timeline [min]')
601
+ except Exception as e:
602
+ print(f'error {e}')
603
+ pass
604
+ elif lines_pos[i]==lines_pos[i]:
605
+ try:
606
+ lines_pos[i].plot_survival_function(ci_show=False, ax=self.ax, legend=None, alpha=0.25, color=well_color[well_index_pos[i]], xlabel='timeline [min]')
607
+ except Exception as e:
608
+ print(f'error {e}')
609
+ pass
610
+ else:
611
+ pass
612
+
613
+ for i in range(len(lines_well)):
614
+
615
+ if len(self.well_indices)<=1 and lines_well[i]==lines_well[i]:
616
+ try:
617
+ lines_well[i].plot_survival_function(ax=self.ax, label='pool', color="k")
618
+ except Exception as e:
619
+ print(f'error {e}')
620
+ pass
621
+ elif lines_well[i]==lines_well[i]:
622
+ try:
623
+ lines_well[i].plot_survival_function(ax=self.ax, label=well_labels[i], color=well_color[well_index[i]])
624
+ except Exception as e:
625
+ print(f'error {e}')
626
+ pass
627
+ else:
628
+ pass
629
+
630
+
631
+ self.survival_window.canvas.draw()
632
+
633
+ def switch_to_log(self):
634
+
635
+ """
636
+ Switch threshold histogram to log scale. Auto adjust.
637
+ """
638
+
639
+ if self.ax.get_yscale()=='linear':
640
+ self.ax.set_yscale('log')
641
+ #self.ax.set_ylim(0.01,1.05)
642
+ else:
643
+ self.ax.set_yscale('linear')
644
+ #self.ax.set_ylim(0.01,1.05)
645
+
646
+ #self.ax.autoscale()
647
+ self.survival_window.canvas.draw_idle()
648
+
649
+ def show_hide_legend(self):
650
+ if self.legend_visible:
651
+ self.ax.legend().set_visible(False)
652
+ self.legend_visible = False
653
+ self.legend_btn.setIcon(icon(MDI6.text_box_outline,color="black"))
654
+ else:
655
+ self.ax.legend().set_visible(True)
656
+ self.legend_visible = True
657
+ self.legend_btn.setIcon(icon(MDI6.text_box,color="black"))
658
+
659
+ self.survival_window.canvas.draw_idle()
660
+
661
+ def look_for_metadata(self):
662
+
663
+ self.metadata_found = False
664
+ self.metafiles = glob(self.exp_dir+os.sep.join([f'W*','*','movie','*metadata.txt'])) \
665
+ + glob(self.exp_dir+os.sep.join([f'W*','*','*metadata.txt'])) \
666
+ + glob(self.exp_dir+os.sep.join([f'W*','*metadata.txt'])) \
667
+ + glob(self.exp_dir+'*metadata.txt')
668
+ print(f'Found {len(self.metafiles)} metadata files...')
669
+ if len(self.metafiles)>0:
670
+ self.metadata_found = True
671
+
672
+ def switch_selection_mode(self, id):
673
+ print(f'button {id} was clicked')
674
+ for i in range(2):
675
+ if self.select_option[i].isChecked():
676
+ self.selection_mode = self.select_label[i]
677
+ if self.selection_mode=='name':
678
+ if len(self.metafiles)>0:
679
+ self.position_scatter.hide()
680
+ self.line_choice_widget.show()
681
+ else:
682
+ if len(self.metafiles)>0:
683
+ self.position_scatter.show()
684
+ self.line_choice_widget.hide()
685
+
686
+
687
+ def load_coordinates(self):
688
+
689
+ """
690
+ Read metadata and try to extract position coordinates
691
+ """
692
+
693
+ self.no_meta = False
694
+ try:
695
+ with open(self.metafiles[0], 'r') as f:
696
+ data = json.load(f)
697
+ positions = data['Summary']['InitialPositionList']
698
+ except Exception as e:
699
+ print(f'Trouble loading metadata: error {e}...')
700
+ return None
701
+
702
+ for k in range(len(positions)):
703
+ pos_label = positions[k]['Label']
704
+ try:
705
+ coords = positions[k]['DeviceCoordinatesUm']['XYStage']
706
+ except:
707
+ try:
708
+ coords = positions[k]['DeviceCoordinatesUm']['PIXYStage']
709
+ except:
710
+ self.no_meta = True
711
+
712
+ if not self.no_meta:
713
+ self.df_pos_info = self.df_pos_info.dropna(subset=['stack_path'])
714
+ files = self.df_pos_info['stack_path'].values
715
+ print(files)
716
+ pos_loc = [pos_label in f for f in files]
717
+ self.df_pos_info.loc[pos_loc, 'x'] = coords[0]
718
+ self.df_pos_info.loc[pos_loc, 'y'] = coords[1]
719
+ self.df_pos_info.loc[pos_loc, 'metadata_tag'] = pos_label
720
+
721
+
722
+ def update_annot(self, ind):
723
+
724
+ pos = self.sc.get_offsets()[ind["ind"][0]]
725
+ self.annot.xy = pos
726
+ text = self.scat_labels[ind["ind"][0]]
727
+ self.annot.set_text(text)
728
+ self.annot.get_bbox_patch().set_facecolor('k')
729
+ self.annot.get_bbox_patch().set_alpha(0.4)
730
+
731
+ def hover(self, event):
732
+ vis = self.annot.get_visible()
733
+ if event.inaxes == self.ax_scatter:
734
+ cont, ind = self.sc.contains(event)
735
+ if cont:
736
+ self.update_annot(ind)
737
+ self.annot.set_visible(True)
738
+ self.fig_scatter.canvas.draw_idle()
739
+ else:
740
+ if vis:
741
+ self.annot.set_visible(False)
742
+ self.fig_scatter.canvas.draw_idle()
743
+
744
+ def unselect_position(self, event):
745
+
746
+ ind = event.ind # index of selected position
747
+ well_idx = self.df_pos_info.iloc[ind]['well_index'].values[0]
748
+ selectedPos = self.df_pos_info.iloc[ind]['pos_path'].values[0]
749
+ currentSelState = self.df_pos_info.iloc[ind]['select'].values[0]
750
+ if self.plot_options[0].isChecked() or self.plot_options[2].isChecked():
751
+ self.df_pos_info.loc[self.df_pos_info['well_index']==well_idx,'select'] = not currentSelState
752
+ self.df_well_info.loc[self.df_well_info['well_index']==well_idx, 'select'] = not currentSelState
753
+ if len(self.well_indices)>1:
754
+ self.well_display_options[well_idx].setChecked(not currentSelState)
755
+ else:
756
+ for p in self.pos_display_options:
757
+ p.setChecked(not currentSelState)
758
+ else:
759
+ self.df_pos_info.loc[self.df_pos_info['pos_path']==selectedPos,'select'] = not currentSelState
760
+ if len(self.well_indices)<=1:
761
+ self.pos_display_options[ind[0]].setChecked(not currentSelState)
762
+
763
+ self.sc.set_color(self.select_color(self.df_pos_info["select"].values))
764
+ self.position_scatter.canvas.draw_idle()
765
+ self.plot_survivals(0)
766
+
767
+ def select_survival_lines(self):
768
+
769
+ if len(self.well_indices)>1:
770
+ for i in range(len(self.well_display_options)):
771
+ self.df_well_info.loc[self.df_well_info['well_index']==i,'select'] = self.well_display_options[i].isChecked()
772
+ self.df_pos_info.loc[self.df_pos_info['well_index']==i,'select'] = self.well_display_options[i].isChecked()
773
+ else:
774
+ for i in range(len(self.pos_display_options)):
775
+ self.df_pos_info.loc[self.df_pos_info['pos_index']==i,'select'] = self.pos_display_options[i].isChecked()
776
+
777
+ if len(self.metafiles)>0:
778
+ try:
779
+ self.sc.set_color(self.select_color(self.df_pos_info["select"].values))
780
+ self.position_scatter.canvas.draw_idle()
781
+ except:
782
+ pass
783
+ self.plot_survivals(0)
784
+
785
+
786
+ def select_color(self, selection):
787
+ colors = [self.cmap(0) if s else self.cmap(0.1) for s in selection]
788
+ return colors
789
+
790
+ def plot_spatial_location(self):
791
+
792
+ try:
793
+ self.sc = self.ax_scatter.scatter(self.df_pos_info["x"].values, self.df_pos_info["y"].values, picker=True, pickradius=1, color=self.select_color(self.df_pos_info["select"].values))
794
+ self.scat_labels = self.df_pos_info['metadata_tag'].values
795
+ self.ax_scatter.invert_xaxis()
796
+ self.annot = self.ax_scatter.annotate("", xy=(0,0), xytext=(10,10),textcoords="offset points",
797
+ bbox=dict(boxstyle="round", fc="w"),
798
+ arrowprops=dict(arrowstyle="->"))
799
+ self.annot.set_visible(False)
800
+ self.fig_scatter.canvas.mpl_connect("motion_notify_event", self.hover)
801
+ self.fig_scatter.canvas.mpl_connect("pick_event", self.unselect_position)
802
+ except Exception as e:
803
+ pass
804
+
805
+ # def plot_positions(self):
806
+
807
+ # # Load metadata, read X-Y positions
808
+ # coordinates = []
809
+ # line_index = []
810
+ # label_annotations = []
811
+ # coords_wells = []
812
+ # for m in self.metafiles[:1]:
813
+ # with open(m, 'r') as f:
814
+ # data = json.load(f)
815
+ # positions = data['Summary']['InitialPositionList']
816
+ # for k in range(len(positions)):
817
+ # pos_label = positions[k]['Label']
818
+ # coords = positions[k]['DeviceCoordinatesUm']['XYStage']
819
+ # for k,ks in enumerate(self.ks_estimators_per_position):
820
+ # pos = ks['position']
821
+ # pos_blocks = pos.split(os.sep)
822
+ # pos_blocks.remove('')
823
+ # well_id = pos_blocks[-3]
824
+ # movies = glob(pos+f'movie{os.sep}{self.parent.parent.movie_prefix}*.tif')
825
+ # if len(movies)>0:
826
+ # file = movies[0]
827
+ # if pos_label in file:
828
+ # print(f"match for index {k} between position {pos} and coordinates {pos_label}")
829
+ # coordinates.append(coords)
830
+ # line_index.append(k)
831
+ # label_annotations.append(pos_label)
832
+ # coords_wells.append({'x': coords[0], 'y': coords[1], 'well': well_id})
833
+
834
+ # coords_wells = pd.DataFrame(coords_wells)
835
+ # well_coordinates = coords_wells.groupby('well').mean()[['x','y']].to_numpy()
836
+
837
+ # coordinates = np.array(coordinates)
838
+
839
+ # if self.plot_options[0].isChecked():
840
+ # label_annotations_scat = list(coords_wells.groupby('well').mean().index)
841
+ # self.position_colors = [tab10(0) for i in range(len(well_coordinates))]
842
+ # self.sc = self.ax_scatter.scatter(well_coordinates[:,0], well_coordinates[:,1],picker=True, pickradius=1, color=self.position_colors)
843
+
844
+ # elif self.plot_options[1].isChecked():
845
+ # label_annotations_scat = label_annotations
846
+ # self.position_colors = [tab10(0) for i in range(len(coordinates))]
847
+ # self.sc = self.ax_scatter.scatter(coordinates[:,0], coordinates[:,1],picker=True, pickradius=1, color=self.position_colors)
848
+
849
+ # self.ax_scatter.invert_xaxis()
850
+ # annot = self.ax_scatter.annotate("", xy=(0,0), xytext=(10,10),textcoords="offset points",
851
+ # bbox=dict(boxstyle="round", fc="w"),
852
+ # arrowprops=dict(arrowstyle="->"))
853
+ # annot.set_visible(False)
854
+
855
+ # def update_annot(ind):
856
+
857
+ # pos = self.sc.get_offsets()[ind["ind"][0]]
858
+ # annot.xy = pos
859
+ # text = label_annotations_scat[ind["ind"][0]]
860
+ # # text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))),
861
+ # # " ".join([label_annotations[n] for n in ind["ind"]]))
862
+ # annot.set_text(text)
863
+ # annot.get_bbox_patch().set_facecolor('k')
864
+ # annot.get_bbox_patch().set_alpha(0.4)
865
+
866
+ # def hover(event):
867
+ # vis = annot.get_visible()
868
+ # if event.inaxes == self.ax_scatter:
869
+ # cont, ind = self.sc.contains(event)
870
+ # if cont:
871
+ # update_annot(ind)
872
+ # annot.set_visible(True)
873
+ # self.fig_scatter.canvas.draw_idle()
874
+ # else:
875
+ # if vis:
876
+ # annot.set_visible(False)
877
+ # self.fig_scatter.canvas.draw_idle()
878
+
879
+ # def unselect_position(event):
880
+ # print(event)
881
+ # ind = event.ind
882
+ # print(ind)
883
+ # if len(ind)>0:
884
+ # ind = ind[0]
885
+ # if self.position_colors[ind]==tab10(0.1):
886
+ # self.position_colors[ind] = tab10(0)
887
+ # if self.plot_options[1].isChecked():
888
+ # self.line_to_plot[ind] = True # reselect line
889
+ # elif self.plot_options[0].isChecked():
890
+ # self.line_to_plot_well[ind] = True
891
+ # else:
892
+ # self.position_colors[ind] = tab10(0.1)
893
+ # if self.plot_options[1].isChecked():
894
+ # self.line_to_plot[ind] = False # unselect line
895
+ # elif self.plot_options[0].isChecked():
896
+ # self.line_to_plot_well[ind] = False
897
+ # self.sc.set_color(self.position_colors)
898
+ # self.position_scatter.canvas.draw_idle()
899
+ # self.plot_survivals(0)
900
+
901
+ # self.fig_scatter.canvas.mpl_connect("motion_notify_event", hover)
902
+ # self.fig_scatter.canvas.mpl_connect("pick_event", unselect_position)
903
+