celldetective 1.1.1.post3__py3-none-any.whl → 1.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. celldetective/__init__.py +2 -1
  2. celldetective/__main__.py +17 -0
  3. celldetective/extra_properties.py +62 -34
  4. celldetective/gui/__init__.py +1 -0
  5. celldetective/gui/analyze_block.py +2 -1
  6. celldetective/gui/classifier_widget.py +18 -10
  7. celldetective/gui/control_panel.py +57 -6
  8. celldetective/gui/layouts.py +14 -11
  9. celldetective/gui/neighborhood_options.py +21 -13
  10. celldetective/gui/plot_signals_ui.py +39 -11
  11. celldetective/gui/process_block.py +413 -95
  12. celldetective/gui/retrain_segmentation_model_options.py +17 -4
  13. celldetective/gui/retrain_signal_model_options.py +106 -6
  14. celldetective/gui/signal_annotator.py +110 -30
  15. celldetective/gui/signal_annotator2.py +2708 -0
  16. celldetective/gui/signal_annotator_options.py +3 -1
  17. celldetective/gui/survival_ui.py +15 -6
  18. celldetective/gui/tableUI.py +248 -43
  19. celldetective/io.py +598 -416
  20. celldetective/measure.py +919 -969
  21. celldetective/models/pair_signal_detection/blank +0 -0
  22. celldetective/neighborhood.py +482 -340
  23. celldetective/preprocessing.py +81 -61
  24. celldetective/relative_measurements.py +648 -0
  25. celldetective/scripts/analyze_signals.py +1 -1
  26. celldetective/scripts/measure_cells.py +28 -8
  27. celldetective/scripts/measure_relative.py +103 -0
  28. celldetective/scripts/segment_cells.py +5 -5
  29. celldetective/scripts/track_cells.py +4 -1
  30. celldetective/scripts/train_segmentation_model.py +23 -18
  31. celldetective/scripts/train_signal_model.py +33 -0
  32. celldetective/segmentation.py +67 -29
  33. celldetective/signals.py +402 -8
  34. celldetective/tracking.py +8 -2
  35. celldetective/utils.py +144 -12
  36. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/METADATA +8 -8
  37. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/RECORD +42 -38
  38. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/WHEEL +1 -1
  39. tests/test_segmentation.py +1 -1
  40. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/LICENSE +0 -0
  41. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/entry_points.txt +0 -0
  42. {celldetective-1.1.1.post3.dist-info → celldetective-1.2.0.dist-info}/top_level.txt +0 -0
@@ -34,7 +34,9 @@ class ConfigSignalAnnotator(QMainWindow, Styles):
34
34
  self.instructions_path = self.parent_window.exp_dir + "configs/signal_annotator_config_targets.json"
35
35
  elif self.mode=="effectors":
36
36
  self.instructions_path = self.parent_window.exp_dir + "configs/signal_annotator_config_effectors.json"
37
-
37
+ elif self.mode == "neighborhood":
38
+ self.instructions_path = self.parent_window.exp_dir + "configs/signal_annotator_config_neighborhood.json"
39
+
38
40
  exp_config = self.exp_dir +"config.ini"
39
41
  #self.config_path = self.exp_dir + self.config_name
40
42
  self.channel_names, self.channels = extract_experiment_channels(exp_config)
@@ -109,9 +109,10 @@ class ConfigSurvival(QWidget, Styles):
109
109
  main_layout.addWidget(panel_title, alignment=Qt.AlignCenter)
110
110
 
111
111
 
112
- labels = [QLabel('population: '), QLabel('time of\nreference: '), QLabel('time of\ninterest: '), QLabel('exclude\nclass: '), QLabel('cmap: ')] #QLabel('class: '),
113
- self.cb_options = [['targets','effectors'], ['0','t_firstdetection'], ['t0'], ['--'], list(plt.colormaps())] #['class'],
112
+ 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'],
114
114
  self.cbs = [QComboBox() for i in range(len(labels))]
115
+
115
116
  self.cbs[-1] = QColormapComboBox()
116
117
  self.cbs[0].currentIndexChanged.connect(self.set_classes_and_times)
117
118
 
@@ -133,6 +134,12 @@ class ConfigSurvival(QWidget, Styles):
133
134
 
134
135
  main_layout.addLayout(choice_layout)
135
136
 
137
+ select_layout = QHBoxLayout()
138
+ select_layout.addWidget(QLabel('select cells\nwith query: '), 33)
139
+ self.query_le = QLineEdit()
140
+ select_layout.addWidget(self.query_le, 66)
141
+ main_layout.addLayout(select_layout)
142
+
136
143
  self.cbs[0].setCurrentIndex(0)
137
144
  self.cbs[1].setCurrentText('t_firstdetection')
138
145
 
@@ -218,10 +225,12 @@ class ConfigSurvival(QWidget, Styles):
218
225
 
219
226
  if self.df is not None:
220
227
 
221
- excluded_class = self.cbs[3].currentText()
222
- if excluded_class!='--':
223
- print(f"Excluding {excluded_class}...")
224
- self.df = self.df.loc[~(self.df[excluded_class].isin([0,2])),:]
228
+ try:
229
+ query_text = self.query_le.text()
230
+ if query_text != '':
231
+ self.df = self.df.query(query_text)
232
+ except Exception as e:
233
+ print(e, ' The query is misunderstood and will not be applied...')
225
234
 
226
235
  self.compute_survival_functions()
227
236
  # prepare survival
@@ -20,6 +20,10 @@ from matplotlib import colormaps
20
20
 
21
21
  class PandasModel(QAbstractTableModel):
22
22
 
23
+ """
24
+ from https://stackoverflow.com/questions/31475965/fastest-way-to-populate-qtableview-from-pandas-data-frame
25
+ """
26
+
23
27
  def __init__(self, data):
24
28
  QAbstractTableModel.__init__(self)
25
29
  self._data = data
@@ -65,9 +69,9 @@ class QueryWidget(QWidget):
65
69
 
66
70
  def filter_table(self):
67
71
  try:
68
- query_text = self.query_le.text().replace('class', '`class`')
72
+ query_text = self.query_le.text() #.replace('class', '`class`')
69
73
  tab = self.parent_window.data.query(query_text)
70
- self.subtable = TableUI(tab, query_text, plot_mode="scatter")
74
+ self.subtable = TableUI(tab, query_text, plot_mode="static", population=self.parent_window.population)
71
75
  self.subtable.show()
72
76
  self.close()
73
77
  except Exception as e:
@@ -236,7 +240,47 @@ class DifferentiateColWidget(QWidget, Styles):
236
240
  self.parent_window.table_view.setModel(self.parent_window.model)
237
241
  self.close()
238
242
 
243
+ class AbsColWidget(QWidget, Styles):
244
+
245
+ def __init__(self, parent_window, column=None):
246
+
247
+ super().__init__()
248
+ self.parent_window = parent_window
249
+ self.column = column
250
+
251
+ self.setWindowTitle("abs(.)")
252
+ # Create the QComboBox and add some items
253
+ center_window(self)
254
+
255
+ layout = QVBoxLayout(self)
256
+ layout.setContentsMargins(30,30,30,30)
257
+
258
+ self.measurements_cb = QComboBox()
259
+ self.measurements_cb.addItems(list(self.parent_window.data.columns))
260
+ if self.column is not None:
261
+ idx = self.measurements_cb.findText(self.column)
262
+ self.measurements_cb.setCurrentIndex(idx)
239
263
 
264
+ measurement_layout = QHBoxLayout()
265
+ measurement_layout.addWidget(QLabel('measurements: '), 25)
266
+ measurement_layout.addWidget(self.measurements_cb, 75)
267
+ layout.addLayout(measurement_layout)
268
+
269
+ self.submit_btn = QPushButton('Compute')
270
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
271
+ self.submit_btn.clicked.connect(self.compute_abs_and_add_new_column)
272
+ layout.addWidget(self.submit_btn, 30)
273
+
274
+ self.setAttribute(Qt.WA_DeleteOnClose)
275
+
276
+
277
+ def compute_abs_and_add_new_column(self):
278
+
279
+
280
+ self.parent_window.data['|'+self.measurements_cb.currentText()+'|'] = self.parent_window.data[self.measurements_cb.currentText()].abs()
281
+ self.parent_window.model = PandasModel(self.parent_window.data)
282
+ self.parent_window.table_view.setModel(self.parent_window.model)
283
+ self.close()
240
284
 
241
285
  class RenameColWidget(QWidget):
242
286
 
@@ -268,7 +312,6 @@ class RenameColWidget(QWidget):
268
312
  old_name = self.column
269
313
  new_name = self.new_col_name.text()
270
314
  self.parent_window.data = self.parent_window.data.rename(columns={old_name: new_name})
271
- print(self.parent.data.columns)
272
315
 
273
316
  self.parent_window.model = PandasModel(self.parent_window.data)
274
317
  self.parent_window.table_view.setModel(self.parent_window.model)
@@ -276,6 +319,7 @@ class RenameColWidget(QWidget):
276
319
 
277
320
 
278
321
  class TableUI(QMainWindow, Styles):
322
+
279
323
  def __init__(self, data, title, population='targets',plot_mode="plot_track_signals", *args, **kwargs):
280
324
 
281
325
  QMainWindow.__init__(self, *args, **kwargs)
@@ -287,6 +331,16 @@ class TableUI(QMainWindow, Styles):
287
331
  self.plot_mode = plot_mode
288
332
  self.population = population
289
333
  self.numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
334
+ self.groupby_cols = ['position', 'TRACK_ID']
335
+ self.tracks = False
336
+
337
+ if self.population=='pairs':
338
+ self.groupby_cols = ['position','reference_population', 'neighbor_population','REFERENCE_ID', 'NEIGHBOR_ID', 'FRAME']
339
+ self.tracks = True # for now
340
+ else:
341
+ if 'TRACK_ID' in data.columns:
342
+ if not np.all(data['TRACK_ID'].isnull()):
343
+ self.tracks = True
290
344
 
291
345
  self._createMenuBar()
292
346
  self._createActions()
@@ -329,6 +383,18 @@ class TableUI(QMainWindow, Styles):
329
383
  self.groupby_action.triggered.connect(self.set_projection_mode_tracks)
330
384
  self.groupby_action.setShortcut("Ctrl+g")
331
385
  self.fileMenu.addAction(self.groupby_action)
386
+ if not self.tracks:
387
+ self.groupby_action.setEnabled(False)
388
+
389
+ if self.population=='pairs':
390
+ self.groupby_neigh_action = QAction("&Group by neighbors...", self)
391
+ self.groupby_neigh_action.triggered.connect(self.set_projection_mode_neigh)
392
+ self.fileMenu.addAction(self.groupby_neigh_action)
393
+
394
+ self.groupby_ref_action = QAction("&Group by reference...", self)
395
+ self.groupby_ref_action.triggered.connect(self.set_projection_mode_ref)
396
+ self.fileMenu.addAction(self.groupby_ref_action)
397
+
332
398
 
333
399
  self.groupby_time_action = QAction("&Group by frames...", self)
334
400
  self.groupby_time_action.triggered.connect(self.groupby_time_table)
@@ -349,16 +415,76 @@ class TableUI(QMainWindow, Styles):
349
415
  #self.rename_col_action.setShortcut(Qt.Key_Delete)
350
416
  self.editMenu.addAction(self.rename_col_action)
351
417
 
418
+ if self.population=='pairs':
419
+ self.merge_action = QAction('&Merge...', self)
420
+ self.merge_action.triggered.connect(self.merge_tables)
421
+ #self.rename_col_action.setShortcut(Qt.Key_Delete)
422
+ self.editMenu.addAction(self.merge_action)
423
+
352
424
  self.derivative_action = QAction('&Differentiate...', self)
353
425
  self.derivative_action.triggered.connect(self.differenciate_selected_feature)
354
426
  self.derivative_action.setShortcut("Ctrl+D")
355
427
  self.mathMenu.addAction(self.derivative_action)
356
428
 
429
+ self.abs_action = QAction('&Absolute value...', self)
430
+ self.abs_action.triggered.connect(self.take_abs_of_selected_feature)
431
+ #self.derivative_action.setShortcut("Ctrl+D")
432
+ self.mathMenu.addAction(self.abs_action)
433
+
357
434
  self.onehot_action = QAction('&One hot to categorical...', self)
358
435
  self.onehot_action.triggered.connect(self.transform_one_hot_cols_to_categorical)
359
436
  #self.onehot_action.setShortcut("Ctrl+D")
360
437
  self.mathMenu.addAction(self.onehot_action)
361
438
 
439
+ def merge_tables(self):
440
+
441
+ expanded_table = []
442
+
443
+ for neigh, group in self.data.groupby(['reference_population','neighbor_population']):
444
+ print(f'{neigh=}')
445
+ ref_pop = neigh[0]; neigh_pop = neigh[1];
446
+ for pos,pos_group in group.groupby('position'):
447
+ print(f'{pos=}')
448
+
449
+ ref_tab = os.sep.join([pos,'output','tables',f'trajectories_{ref_pop}.csv'])
450
+ neigh_tab = os.sep.join([pos,'output','tables',f'trajectories_{neigh_pop}.csv'])
451
+ if os.path.exists(ref_tab):
452
+ df_ref = pd.read_csv(ref_tab)
453
+ if 'TRACK_ID' in df_ref.columns:
454
+ if not np.all(df_ref['TRACK_ID'].isnull()):
455
+ ref_merge_cols = ['TRACK_ID','FRAME']
456
+ else:
457
+ ref_merge_cols = ['ID','FRAME']
458
+ else:
459
+ ref_merge_cols = ['ID','FRAME']
460
+ if os.path.exists(neigh_tab):
461
+ df_neigh = pd.read_csv(neigh_tab)
462
+ if 'TRACK_ID' in df_neigh.columns:
463
+ if not np.all(df_neigh['TRACK_ID'].isnull()):
464
+ neigh_merge_cols = ['TRACK_ID','FRAME']
465
+ else:
466
+ neigh_merge_cols = ['ID','FRAME']
467
+ else:
468
+ neigh_merge_cols = ['ID','FRAME']
469
+
470
+ df_ref = df_ref.add_prefix('reference_',axis=1)
471
+ df_neigh = df_neigh.add_prefix('neighbor_',axis=1)
472
+ ref_merge_cols = ['reference_'+c for c in ref_merge_cols]
473
+ neigh_merge_cols = ['neighbor_'+c for c in neigh_merge_cols]
474
+
475
+ merge_ref = pos_group.merge(df_ref, how='outer', left_on=['REFERENCE_ID','FRAME'], right_on=ref_merge_cols, suffixes=('', '_reference'))
476
+ print(f'{merge_ref.columns=}')
477
+ merge_neigh = merge_ref.merge(df_neigh, how='outer', left_on=['NEIGHBOR_ID','FRAME'], right_on=neigh_merge_cols, suffixes=('_reference', '_neighbor'))
478
+ print(f'{merge_neigh.columns=}')
479
+ expanded_table.append(merge_neigh)
480
+
481
+ df_expanded = pd.concat(expanded_table, axis=0, ignore_index = True)
482
+ df_expanded = df_expanded.sort_values(by=['position', 'reference_population','neighbor_population','REFERENCE_ID','NEIGHBOR_ID','FRAME'])
483
+ df_expanded = df_expanded.dropna(axis=0, subset=['REFERENCE_ID','NEIGHBOR_ID','reference_population','neighbor_population'])
484
+ self.subtable = TableUI(df_expanded, 'merge', plot_mode = "static", population='pairs')
485
+ self.subtable.show()
486
+
487
+
362
488
  def delete_columns(self):
363
489
 
364
490
  x = self.table_view.selectedIndexes()
@@ -408,8 +534,6 @@ class TableUI(QMainWindow, Styles):
408
534
  pos_group.to_csv(pos+os.sep.join(['output', 'tables', f'trajectories_{self.population}.csv']), index=False)
409
535
  print("Done...")
410
536
 
411
-
412
-
413
537
  def differenciate_selected_feature(self):
414
538
 
415
539
  # check only one col selected and assert is numerical
@@ -427,6 +551,24 @@ class TableUI(QMainWindow, Styles):
427
551
  self.diffWidget = DifferentiateColWidget(self, selected_col)
428
552
  self.diffWidget.show()
429
553
 
554
+ def take_abs_of_selected_feature(self):
555
+
556
+ # check only one col selected and assert is numerical
557
+ # open widget to select window parameters, directionality
558
+ # create new col
559
+
560
+ x = self.table_view.selectedIndexes()
561
+ col_idx = np.unique(np.array([l.column() for l in x]))
562
+ if col_idx!=0:
563
+ cols = np.array(list(self.data.columns))
564
+ selected_col = str(cols[col_idx][0])
565
+ else:
566
+ selected_col = None
567
+
568
+ self.absWidget = AbsColWidget(self, selected_col)
569
+ self.absWidget.show()
570
+
571
+
430
572
  def transform_one_hot_cols_to_categorical(self):
431
573
 
432
574
  x = self.table_view.selectedIndexes()
@@ -451,7 +593,7 @@ class TableUI(QMainWindow, Styles):
451
593
 
452
594
  num_df = self.data.select_dtypes(include=self.numerics)
453
595
 
454
- timeseries = num_df.groupby("FRAME").mean().copy()
596
+ timeseries = num_df.groupby("FRAME").sum().copy()
455
597
  timeseries["timeline"] = timeseries.index
456
598
  self.subtable = TableUI(timeseries,"Group by frames", plot_mode="plot_timeseries")
457
599
  self.subtable.show()
@@ -473,6 +615,16 @@ class TableUI(QMainWindow, Styles):
473
615
  # self.subtable = TableUI(timeseries,"Group by frames", plot_mode="plot_timeseries")
474
616
  # self.subtable.show()
475
617
 
618
+ def set_projection_mode_neigh(self):
619
+
620
+ self.groupby_cols = ['position', 'reference_population', 'neighbor_population', 'NEIGHBOR_ID', 'FRAME']
621
+ self.set_projection_mode_tracks()
622
+
623
+ def set_projection_mode_ref(self):
624
+
625
+ self.groupby_cols = ['position', 'reference_population', 'neighbor_population', 'REFERENCE_ID', 'FRAME']
626
+ self.set_projection_mode_tracks()
627
+
476
628
  def set_projection_mode_tracks(self):
477
629
 
478
630
  self.projectionWidget = QWidget()
@@ -484,6 +636,7 @@ class TableUI(QMainWindow, Styles):
484
636
 
485
637
  self.projection_option = QRadioButton('global operation: ')
486
638
  self.projection_option.setToolTip('Collapse the cell track measurements with an operation over each track.')
639
+ self.projection_option.setChecked(True)
487
640
  self.projection_option.toggled.connect(self.enable_projection_options)
488
641
  self.projection_op_cb = QComboBox()
489
642
  self.projection_op_cb.addItems(['mean','median','min','max', 'prod', 'sum'])
@@ -583,18 +736,20 @@ class TableUI(QMainWindow, Styles):
583
736
  layout.addWidget(QLabel('Representations: '))
584
737
  self.hist_check = QCheckBox('histogram')
585
738
  self.kde_check = QCheckBox('KDE plot')
586
- self.count_check = QCheckBox('Countplot')
739
+ self.count_check = QCheckBox('countplot')
587
740
  self.ecdf_check = QCheckBox('ECDF plot')
741
+ self.scat_check = QCheckBox('scatter plot')
588
742
  self.swarm_check = QCheckBox('swarm')
589
743
  self.violin_check = QCheckBox('violin')
590
744
  self.strip_check = QCheckBox('strip')
591
- self.box_check = QCheckBox('Boxplot')
592
- self.boxenplot_check = QCheckBox('Boxenplot')
745
+ self.box_check = QCheckBox('boxplot')
746
+ self.boxenplot_check = QCheckBox('boxenplot')
593
747
 
594
748
  layout.addWidget(self.hist_check)
595
749
  layout.addWidget(self.kde_check)
596
750
  layout.addWidget(self.count_check)
597
751
  layout.addWidget(self.ecdf_check)
752
+ layout.addWidget(self.scat_check)
598
753
  layout.addWidget(self.swarm_check)
599
754
  layout.addWidget(self.violin_check)
600
755
  layout.addWidget(self.strip_check)
@@ -695,18 +850,49 @@ class TableUI(QMainWindow, Styles):
695
850
  self.x = self.x_cb.currentText()
696
851
 
697
852
  legend=True
853
+
698
854
  if self.hist_check.isChecked():
699
- sns.histplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors, kde=True, common_norm=False, stat='density')
700
- legend = False
855
+ if self.x is not None:
856
+ sns.histplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors, kde=True, common_norm=False, stat='density')
857
+ legend = False
858
+ elif self.x is None and self.y is not None:
859
+ sns.histplot(data=self.data, x=self.y, hue=hue_variable, legend=legend, ax=self.ax, palette=colors, kde=True, common_norm=False, stat='density')
860
+ legend = False
861
+ else:
862
+ pass
863
+
701
864
  if self.kde_check.isChecked():
702
- sns.kdeplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors, cut=0)
703
- legend = False
865
+ if self.x is not None:
866
+ sns.kdeplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors, cut=0)
867
+ legend = False
868
+ elif self.x is None and self.y is not None:
869
+ sns.kdeplot(data=self.data, x=self.y, hue=hue_variable, legend=legend, ax=self.ax, palette=colors, cut=0)
870
+ legend = False
871
+ else:
872
+ pass
873
+
704
874
  if self.count_check.isChecked():
705
875
  sns.countplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors)
706
876
  legend = False
877
+
878
+
707
879
  if self.ecdf_check.isChecked():
708
- sns.ecdfplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors)
709
- legend = False
880
+ if self.x is not None:
881
+ sns.ecdfplot(data=self.data, x=self.x, hue=hue_variable, legend=legend, ax=self.ax, palette=colors)
882
+ legend = False
883
+ elif self.x is None and self.y is not None:
884
+ sns.ecdfplot(data=self.data, x=self.y, hue=hue_variable, legend=legend, ax=self.ax, palette=colors)
885
+ legend = False
886
+ else:
887
+ pass
888
+
889
+ if self.scat_check.isChecked():
890
+ if self.x_option:
891
+ sns.scatterplot(data=self.data, x=self.x,y=self.y, hue=hue_variable,legend=legend, ax=self.ax, palette=colors)
892
+ legend = False
893
+ else:
894
+ print('please provide a -x variable...')
895
+ pass
710
896
 
711
897
  if self.swarm_check.isChecked():
712
898
  if self.x_option:
@@ -718,7 +904,7 @@ class TableUI(QMainWindow, Styles):
718
904
 
719
905
  if self.violin_check.isChecked():
720
906
  if self.x_option:
721
- sns.stripplot(data=self.data,x=self.x, y=self.y,dodge=True, ax=self.ax, hue=hue_variable, legend=legend, palette=colors)
907
+ sns.violinplot(data=self.data,x=self.x, y=self.y,dodge=True, ax=self.ax, hue=hue_variable, legend=legend, palette=colors)
722
908
  legend = False
723
909
  else:
724
910
  sns.violinplot(data=self.data, y=self.y,dodge=True, hue=hue_variable,legend=legend, ax=self.ax, palette=colors, cut=0)
@@ -757,32 +943,39 @@ class TableUI(QMainWindow, Styles):
757
943
 
758
944
  def set_proj_mode(self):
759
945
 
760
- self.static_columns = ['well_index', 'well_name', 'pos_name', 'position', 'well', 'status', 't0', 'class','cell_type','concentration', 'antibody', 'pharmaceutical_agent','TRACK_ID','position']
946
+ self.static_columns = ['well_index', 'well_name', 'pos_name', 'position', 'well', 'status', 't0', 'class','cell_type','concentration', 'antibody', 'pharmaceutical_agent','TRACK_ID','position', 'neighbor_population', 'reference_population', 'NEIGHBOR_ID', 'REFERENCE_ID', 'FRAME']
761
947
 
762
948
  if self.projection_option.isChecked():
763
949
 
764
950
  self.projection_mode = self.projection_op_cb.currentText()
765
- op = getattr(self.data.groupby(['position', 'TRACK_ID']), self.projection_mode)
766
- group_table = op(self.data.groupby(['position', 'TRACK_ID']))
951
+ op = getattr(self.data.groupby(self.groupby_cols), self.projection_mode)
952
+ group_table = op(self.data.groupby(self.groupby_cols))
767
953
 
768
954
  for c in self.static_columns:
769
955
  try:
770
- group_table[c] = self.data.groupby(['position','TRACK_ID'])[c].apply(lambda x: x.unique()[0])
956
+ group_table[c] = self.data.groupby(self.groupby_cols)[c].apply(lambda x: x.unique()[0])
771
957
  except Exception as e:
772
958
  print(e)
773
959
  pass
774
960
 
775
- for col in ['TRACK_ID']:
776
- first_column = group_table.pop(col)
777
- group_table.insert(0, col, first_column)
778
- group_table.pop('FRAME')
961
+ if self.population=='pairs':
962
+ for col in self.groupby_cols[1:]: #['neighbor_population', 'reference_population', 'NEIGHBOR_ID', 'REFERENCE_ID']
963
+ if col in group_table:
964
+ first_column = group_table.pop(col)
965
+ group_table.insert(0, col, first_column)
966
+ else:
967
+ for col in ['TRACK_ID']:
968
+ first_column = group_table.pop(col)
969
+ group_table.insert(0, col, first_column)
970
+ group_table.pop('FRAME')
779
971
 
780
972
 
781
973
  elif self.event_time_option.isChecked():
974
+
782
975
  time_of_interest = self.event_times_cb.currentText()
783
976
  self.projection_mode = f"measurements at {time_of_interest}"
784
977
  new_table = []
785
- for tid,group in self.data.groupby(['position','TRACK_ID']):
978
+ for tid,group in self.data.groupby(self.groupby_cols):
786
979
  time = group[time_of_interest].values[0]
787
980
  if time==time:
788
981
  time = floor(time) # floor for onset
@@ -792,16 +985,21 @@ class TableUI(QMainWindow, Styles):
792
985
  values = group.loc[group['FRAME']==time,:].to_numpy()
793
986
  if len(values)>0:
794
987
  values = dict(zip(list(self.data.columns), values[0]))
795
- values.update({'TRACK_ID': tid[1]})
796
- values.update({'position': tid[0]})
988
+ for k,c in enumerate(self.groupby_cols):
989
+ values.update({c: tid[k]})
797
990
  new_table.append(values)
798
991
 
799
992
  group_table = pd.DataFrame(new_table)
800
- for col in ['TRACK_ID']:
801
- first_column = group_table.pop(col)
802
- group_table.insert(0, col, first_column)
803
-
804
- group_table = group_table.sort_values(by=['position','TRACK_ID','FRAME'],ignore_index=True)
993
+ if self.population=='pairs':
994
+ for col in self.groupby_cols[1:]:
995
+ first_column = group_table.pop(col)
996
+ group_table.insert(0, col, first_column)
997
+ else:
998
+ for col in ['TRACK_ID']:
999
+ first_column = group_table.pop(col)
1000
+ group_table.insert(0, col, first_column)
1001
+
1002
+ group_table = group_table.sort_values(by=self.groupby_cols+['FRAME'],ignore_index=True)
805
1003
  group_table = group_table.reset_index(drop=True)
806
1004
 
807
1005
 
@@ -815,12 +1013,12 @@ class TableUI(QMainWindow, Styles):
815
1013
  df_sections = []
816
1014
  for s in unique_statuses:
817
1015
  subtab = self.data.loc[self.data[status_of_interest]==s,:]
818
- op = getattr(subtab.groupby(['position', 'TRACK_ID']), self.status_operation.currentText())
819
- subtab_projected = op(subtab.groupby(['position', 'TRACK_ID']))
820
- frame_duration = subtab.groupby(['position','TRACK_ID']).size().to_numpy()
1016
+ op = getattr(subtab.groupby(self.groupby_cols), self.status_operation.currentText())
1017
+ subtab_projected = op(subtab.groupby(self.groupby_cols))
1018
+ frame_duration = subtab.groupby(self.groupby_cols).size().to_numpy()
821
1019
  for c in self.static_columns:
822
1020
  try:
823
- subtab_projected[c] = subtab.groupby(['position', 'TRACK_ID'])[c].apply(lambda x: x.unique()[0])
1021
+ subtab_projected[c] = subtab.groupby(self.groupby_cols)[c].apply(lambda x: x.unique()[0])
824
1022
  except Exception as e:
825
1023
  print(e)
826
1024
  pass
@@ -828,11 +1026,18 @@ class TableUI(QMainWindow, Styles):
828
1026
  df_sections.append(subtab_projected)
829
1027
 
830
1028
  group_table = pd.concat(df_sections,axis=0,ignore_index=True)
831
- for col in ['duration_in_state',status_of_interest,'TRACK_ID']:
832
- first_column = group_table.pop(col)
833
- group_table.insert(0, col, first_column)
1029
+
1030
+ if self.population=='pairs':
1031
+ for col in ['duration_in_state',status_of_interest, 'neighbor_population', 'reference_population', 'NEIGHBOR_ID', 'REFERENCE_ID']:
1032
+ first_column = group_table.pop(col)
1033
+ group_table.insert(0, col, first_column)
1034
+ else:
1035
+ for col in ['duration_in_state',status_of_interest,'TRACK_ID']:
1036
+ first_column = group_table.pop(col)
1037
+ group_table.insert(0, col, first_column)
1038
+
834
1039
  group_table.pop('FRAME')
835
- group_table = group_table.sort_values(by=['position','TRACK_ID',status_of_interest],ignore_index=True)
1040
+ group_table = group_table.sort_values(by=self.groupby_cols + [status_of_interest],ignore_index=True)
836
1041
  group_table = group_table.reset_index(drop=True)
837
1042
 
838
1043
 
@@ -963,7 +1168,7 @@ class TableUI(QMainWindow, Styles):
963
1168
  print(unique_cols[k])
964
1169
  for w,well_group in self.data.groupby('well_name'):
965
1170
  for pos,pos_group in well_group.groupby('pos_name'):
966
- for tid,group_track in pos_group.groupby('TRACK_ID'):
1171
+ for tid,group_track in pos_group.groupby(self.groupby_cols[1:]):
967
1172
  ax.plot(group_track["FRAME"], group_track[column_names[unique_cols[k]]],label=column_names[unique_cols[k]])
968
1173
  #ax.plot(self.data["FRAME"][row_idx_i], y, label=column_names[unique_cols[k]])
969
1174
  ax.legend()
@@ -977,7 +1182,7 @@ class TableUI(QMainWindow, Styles):
977
1182
  self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
978
1183
  self.scatter_wdw = FigureCanvas(self.fig, title="scatter")
979
1184
  self.ax.clear()
980
- for tid,group in self.data.groupby('TRACK_ID'):
1185
+ for tid,group in self.data.groupby(self.groupby_cols[1:]):
981
1186
  self.ax.plot(group[column_names[unique_cols[0]]], group[column_names[unique_cols[1]]], marker="o")
982
1187
  self.ax.set_xlabel(column_names[unique_cols[0]])
983
1188
  self.ax.set_ylabel(column_names[unique_cols[1]])
@@ -1000,7 +1205,7 @@ class TableUI(QMainWindow, Styles):
1000
1205
 
1001
1206
  for w,well_group in self.data.groupby('well_name'):
1002
1207
  for pos,pos_group in well_group.groupby('pos_name'):
1003
- for tid,group_track in pos_group.groupby('TRACK_ID'):
1208
+ for tid,group_track in pos_group.groupby(self.groupby_cols[1:]):
1004
1209
  self.ax.plot(group_track["FRAME"], group_track[column_names[unique_cols[0]]],c="k", alpha = 0.1)
1005
1210
  self.ax.set_xlabel(r"$t$ [frame]")
1006
1211
  self.ax.set_ylabel(column_names[unique_cols[0]])