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