celldetective 1.1.0__py3-none-any.whl → 1.1.1.post1__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 (34) hide show
  1. celldetective/__main__.py +5 -19
  2. celldetective/extra_properties.py +63 -53
  3. celldetective/filters.py +39 -11
  4. celldetective/gui/classifier_widget.py +56 -7
  5. celldetective/gui/control_panel.py +5 -0
  6. celldetective/gui/layouts.py +3 -2
  7. celldetective/gui/measurement_options.py +13 -109
  8. celldetective/gui/plot_signals_ui.py +1 -0
  9. celldetective/gui/process_block.py +1 -1
  10. celldetective/gui/survival_ui.py +7 -1
  11. celldetective/gui/tableUI.py +294 -28
  12. celldetective/gui/thresholds_gui.py +51 -10
  13. celldetective/gui/viewers.py +169 -22
  14. celldetective/io.py +41 -17
  15. celldetective/measure.py +13 -238
  16. celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +29 -0
  17. celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
  18. celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +37 -0
  19. celldetective/neighborhood.py +4 -1
  20. celldetective/preprocessing.py +483 -143
  21. celldetective/scripts/segment_cells.py +26 -7
  22. celldetective/scripts/train_segmentation_model.py +35 -34
  23. celldetective/segmentation.py +29 -20
  24. celldetective/signals.py +13 -231
  25. celldetective/tracking.py +2 -1
  26. celldetective/utils.py +440 -26
  27. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/METADATA +1 -1
  28. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/RECORD +34 -30
  29. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/WHEEL +1 -1
  30. tests/test_preprocessing.py +37 -0
  31. tests/test_utils.py +48 -1
  32. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/LICENSE +0 -0
  33. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/entry_points.txt +0 -0
  34. {celldetective-1.1.0.dist-info → celldetective-1.1.1.post1.dist-info}/top_level.txt +0 -0
@@ -1,16 +1,20 @@
1
- from PyQt5.QtWidgets import QMainWindow, QTableView, QAction, QMenu,QFileDialog, QLineEdit, QHBoxLayout, QWidget, QPushButton, QVBoxLayout, QComboBox, QLabel, QCheckBox, QMessageBox
1
+ from PyQt5.QtWidgets import QRadioButton, QButtonGroup, QMainWindow, QTableView, QAction, QMenu,QFileDialog, QLineEdit, QHBoxLayout, QWidget, QPushButton, QVBoxLayout, QComboBox, QLabel, QCheckBox, QMessageBox
2
2
  from PyQt5.QtCore import Qt, QAbstractTableModel
3
3
  import pandas as pd
4
4
  import matplotlib.pyplot as plt
5
5
  from matplotlib.cm import viridis
6
6
  plt.rcParams['svg.fonttype'] = 'none'
7
7
  from celldetective.gui.gui_utils import FigureCanvas, center_window
8
+ from celldetective.utils import differentiate_per_track
8
9
  import numpy as np
9
10
  import seaborn as sns
10
11
  import matplotlib.cm as mcm
11
12
  import os
12
13
  from celldetective.gui import Styles
13
- from superqt import QColormapComboBox
14
+ from superqt import QColormapComboBox, QLabeledSlider, QSearchableComboBox
15
+ from math import floor
16
+
17
+ from matplotlib import colormaps
14
18
 
15
19
  class PandasModel(QAbstractTableModel):
16
20
 
@@ -55,6 +59,7 @@ class QueryWidget(QWidget):
55
59
  self.submit_btn = QPushButton('submit')
56
60
  self.submit_btn.clicked.connect(self.filter_table)
57
61
  layout.addWidget(self.submit_btn, 30)
62
+ self.setAttribute(Qt.WA_DeleteOnClose)
58
63
 
59
64
  def filter_table(self):
60
65
  try:
@@ -67,6 +72,84 @@ class QueryWidget(QWidget):
67
72
  print(e)
68
73
  return None
69
74
 
75
+ class DifferentiateColWidget(QWidget, Styles):
76
+
77
+ def __init__(self, parent_window, column=None):
78
+
79
+ super().__init__()
80
+ self.parent_window = parent_window
81
+ self.column = column
82
+
83
+ self.setWindowTitle("d/dt")
84
+ # Create the QComboBox and add some items
85
+ center_window(self)
86
+
87
+ layout = QVBoxLayout(self)
88
+ layout.setContentsMargins(30,30,30,30)
89
+
90
+ self.measurements_cb = QComboBox()
91
+ self.measurements_cb.addItems(list(self.parent_window.data.columns))
92
+ if self.column is not None:
93
+ idx = self.measurements_cb.findText(self.column)
94
+ self.measurements_cb.setCurrentIndex(idx)
95
+
96
+ measurement_layout = QHBoxLayout()
97
+ measurement_layout.addWidget(QLabel('measurements: '), 25)
98
+ measurement_layout.addWidget(self.measurements_cb, 75)
99
+ layout.addLayout(measurement_layout)
100
+
101
+ self.window_size_slider = QLabeledSlider()
102
+ self.window_size_slider.setRange(1,np.nanmax(self.parent_window.data.FRAME.to_numpy()))
103
+ self.window_size_slider.setValue(3)
104
+ window_layout = QHBoxLayout()
105
+ window_layout.addWidget(QLabel('window size: '), 25)
106
+ window_layout.addWidget(self.window_size_slider, 75)
107
+ layout.addLayout(window_layout)
108
+
109
+ self.backward_btn = QRadioButton('backward')
110
+ self.bi_btn = QRadioButton('bi')
111
+ self.bi_btn.click()
112
+ self.forward_btn = QRadioButton('forward')
113
+ self.mode_btn_group = QButtonGroup()
114
+ self.mode_btn_group.addButton(self.backward_btn)
115
+ self.mode_btn_group.addButton(self.bi_btn)
116
+ self.mode_btn_group.addButton(self.forward_btn)
117
+
118
+ mode_layout = QHBoxLayout()
119
+ mode_layout.addWidget(QLabel('mode: '),25)
120
+ mode_sublayout = QHBoxLayout()
121
+ mode_sublayout.addWidget(self.backward_btn, 33, alignment=Qt.AlignCenter)
122
+ mode_sublayout.addWidget(self.bi_btn, 33, alignment=Qt.AlignCenter)
123
+ mode_sublayout.addWidget(self.forward_btn, 33, alignment=Qt.AlignCenter)
124
+ mode_layout.addLayout(mode_sublayout, 75)
125
+ layout.addLayout(mode_layout)
126
+
127
+ self.submit_btn = QPushButton('Compute')
128
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
129
+ self.submit_btn.clicked.connect(self.compute_derivative_and_add_new_column)
130
+ layout.addWidget(self.submit_btn, 30)
131
+
132
+ self.setAttribute(Qt.WA_DeleteOnClose)
133
+
134
+
135
+ def compute_derivative_and_add_new_column(self):
136
+
137
+ if self.bi_btn.isChecked():
138
+ mode = 'bi'
139
+ elif self.forward_btn.isChecked():
140
+ mode = 'forward'
141
+ elif self.backward_btn.isChecked():
142
+ mode = 'backward'
143
+ self.parent_window.data = differentiate_per_track(self.parent_window.data,
144
+ self.measurements_cb.currentText(),
145
+ window_size=self.window_size_slider.value(),
146
+ mode=mode)
147
+ self.parent_window.model = PandasModel(self.parent_window.data)
148
+ self.parent_window.table_view.setModel(self.parent_window.model)
149
+ self.close()
150
+
151
+
152
+
70
153
  class RenameColWidget(QWidget):
71
154
 
72
155
  def __init__(self, parent_window, column=None):
@@ -90,6 +173,7 @@ class RenameColWidget(QWidget):
90
173
  self.submit_btn = QPushButton('rename')
91
174
  self.submit_btn.clicked.connect(self.rename_col)
92
175
  layout.addWidget(self.submit_btn, 30)
176
+ self.setAttribute(Qt.WA_DeleteOnClose)
93
177
 
94
178
  def rename_col(self):
95
179
 
@@ -128,6 +212,8 @@ class TableUI(QMainWindow, Styles):
128
212
  self.model = PandasModel(data)
129
213
  self.table_view.setModel(self.model)
130
214
  self.table_view.resizeColumnsToContents()
215
+ self.setAttribute(Qt.WA_DeleteOnClose)
216
+
131
217
 
132
218
  def _createActions(self):
133
219
 
@@ -146,6 +232,11 @@ class TableUI(QMainWindow, Styles):
146
232
  self.plot_action.setShortcut("Ctrl+p")
147
233
  self.fileMenu.addAction(self.plot_action)
148
234
 
235
+ self.plot_inst_action = QAction("&Plot instantaneous...", self)
236
+ self.plot_inst_action.triggered.connect(self.plot_instantaneous)
237
+ self.plot_inst_action.setShortcut("Ctrl+i")
238
+ self.fileMenu.addAction(self.plot_inst_action)
239
+
149
240
  self.groupby_action = QAction("&Group by tracks...", self)
150
241
  self.groupby_action.triggered.connect(self.set_projection_mode_tracks)
151
242
  self.groupby_action.setShortcut("Ctrl+g")
@@ -231,8 +322,17 @@ class TableUI(QMainWindow, Styles):
231
322
  # check only one col selected and assert is numerical
232
323
  # open widget to select window parameters, directionality
233
324
  # create new col
234
- print('you want to differentiate? cool but I"m too tired to code it now...')
235
- pass
325
+
326
+ x = self.table_view.selectedIndexes()
327
+ col_idx = np.unique(np.array([l.column() for l in x]))
328
+ if col_idx!=0:
329
+ cols = np.array(list(self.data.columns))
330
+ selected_col = str(cols[col_idx][0])
331
+ else:
332
+ selected_col = None
333
+
334
+ self.diffWidget = DifferentiateColWidget(self, selected_col)
335
+ self.diffWidget.show()
236
336
 
237
337
 
238
338
  def groupby_time_table(self):
@@ -270,25 +370,102 @@ class TableUI(QMainWindow, Styles):
270
370
  def set_projection_mode_tracks(self):
271
371
 
272
372
  self.projectionWidget = QWidget()
373
+ self.projectionWidget.setMinimumWidth(500)
273
374
  self.projectionWidget.setWindowTitle('Set projection mode')
274
375
 
275
376
  layout = QVBoxLayout()
276
377
  self.projectionWidget.setLayout(layout)
378
+
379
+ self.projection_option = QRadioButton('global operation: ')
380
+ self.projection_option.setToolTip('Collapse the cell track measurements with an operation over each track.')
381
+ self.projection_option.toggled.connect(self.enable_projection_options)
277
382
  self.projection_op_cb = QComboBox()
278
383
  self.projection_op_cb.addItems(['mean','median','min','max', 'prod', 'sum'])
279
- hbox = QHBoxLayout()
280
- hbox.addWidget(QLabel('operation: '), 33)
281
- hbox.addWidget(self.projection_op_cb, 66)
282
- layout.addLayout(hbox)
283
384
 
284
- self.set_projection_btn = QPushButton('set')
385
+ projection_layout = QHBoxLayout()
386
+ projection_layout.addWidget(self.projection_option, 33)
387
+ projection_layout.addWidget(self.projection_op_cb, 66)
388
+ layout.addLayout(projection_layout)
389
+
390
+ self.event_time_option = QRadioButton('@event time: ')
391
+ self.event_time_option.setToolTip('Pick the measurements at a specific event time.')
392
+ self.event_time_option.toggled.connect(self.enable_projection_options)
393
+ self.event_times_cb = QComboBox()
394
+ cols = np.array(self.data.columns)
395
+ time_cols = np.array([c.startswith('t_') for c in cols])
396
+ time_cols = list(cols[time_cols])
397
+ if 't0' in list(self.data.columns):
398
+ time_cols.append('t0')
399
+ self.event_times_cb.addItems(time_cols)
400
+ self.event_times_cb.setEnabled(False)
401
+
402
+ event_time_layout = QHBoxLayout()
403
+ event_time_layout.addWidget(self.event_time_option, 33)
404
+ event_time_layout.addWidget(self.event_times_cb, 66)
405
+ layout.addLayout(event_time_layout)
406
+
407
+
408
+ self.per_status_option = QRadioButton('per status: ')
409
+ self.per_status_option.setToolTip('Collapse the cell track measurements independently for each of the cell state.')
410
+ self.per_status_option.toggled.connect(self.enable_projection_options)
411
+ self.per_status_cb = QComboBox()
412
+ self.status_operation = QComboBox()
413
+ self.status_operation.setEnabled(False)
414
+ self.status_operation.addItems(['mean','median','min','max', 'prod', 'sum'])
415
+
416
+ status_cols = np.array([c.startswith('status_') or c.startswith('group_') for c in cols])
417
+ status_cols = list(cols[status_cols])
418
+ if 'status' in list(self.data.columns):
419
+ status_cols.append('status')
420
+ self.per_status_cb.addItems(status_cols)
421
+ self.per_status_cb.setEnabled(False)
422
+
423
+ per_status_layout = QHBoxLayout()
424
+ per_status_layout.addWidget(self.per_status_option, 33)
425
+ per_status_layout.addWidget(self.per_status_cb, 66)
426
+ layout.addLayout(per_status_layout)
427
+
428
+ status_operation_layout = QHBoxLayout()
429
+ status_operation_layout.addWidget(QLabel('operation: '), 33, alignment=Qt.AlignRight)
430
+ status_operation_layout.addWidget(self.status_operation, 66)
431
+ layout.addLayout(status_operation_layout)
432
+
433
+ self.btn_projection_group = QButtonGroup()
434
+ self.btn_projection_group.addButton(self.projection_option)
435
+ self.btn_projection_group.addButton(self.event_time_option)
436
+ self.btn_projection_group.addButton(self.per_status_option)
437
+
438
+ apply_layout = QHBoxLayout()
439
+
440
+ self.set_projection_btn = QPushButton('Apply')
285
441
  self.set_projection_btn.setStyleSheet(self.button_style_sheet)
286
442
  self.set_projection_btn.clicked.connect(self.set_proj_mode)
287
- layout.addWidget(self.set_projection_btn)
443
+ apply_layout.addWidget(QLabel(''), 33)
444
+ apply_layout.addWidget(self.set_projection_btn,33)
445
+ apply_layout.addWidget(QLabel(''),33)
446
+ layout.addLayout(apply_layout)
288
447
 
289
448
  self.projectionWidget.show()
290
449
  center_window(self.projectionWidget)
291
450
 
451
+ def enable_projection_options(self):
452
+
453
+ if self.projection_option.isChecked():
454
+ self.projection_op_cb.setEnabled(True)
455
+ self.event_times_cb.setEnabled(False)
456
+ self.per_status_cb.setEnabled(False)
457
+ self.status_operation.setEnabled(False)
458
+ elif self.event_time_option.isChecked():
459
+ self.projection_op_cb.setEnabled(False)
460
+ self.event_times_cb.setEnabled(True)
461
+ self.per_status_cb.setEnabled(False)
462
+ self.status_operation.setEnabled(False)
463
+ elif self.per_status_option.isChecked():
464
+ self.projection_op_cb.setEnabled(False)
465
+ self.event_times_cb.setEnabled(False)
466
+ self.per_status_cb.setEnabled(True)
467
+ self.status_operation.setEnabled(True)
468
+
292
469
  def set_1D_plot_params(self):
293
470
 
294
471
  self.plot1Dparams = QWidget()
@@ -316,12 +493,12 @@ class TableUI(QMainWindow, Styles):
316
493
  layout.addWidget(self.box_check)
317
494
  layout.addWidget(self.boxenplot_check)
318
495
 
319
- self.x_cb = QComboBox()
496
+ self.x_cb = QSearchableComboBox()
320
497
  self.x_cb.addItems(['--']+list(self.data.columns))
321
498
 
322
- self.hue_cb = QComboBox()
323
- self.hue_cb.addItems(list(self.data.columns))
324
- idx = self.hue_cb.findText('well_index')
499
+ self.hue_cb = QSearchableComboBox()
500
+ self.hue_cb.addItems(['--']+list(self.data.columns))
501
+ idx = self.hue_cb.findText('--')
325
502
 
326
503
  self.x_cb.findText('--')
327
504
  hbox = QHBoxLayout()
@@ -335,7 +512,12 @@ class TableUI(QMainWindow, Styles):
335
512
  layout.addLayout(hbox)
336
513
 
337
514
  self.cmap_cb = QColormapComboBox()
338
- self.cmap_cb.addColormaps(list(plt.colormaps()))
515
+ for cm in list(colormaps):
516
+ try:
517
+ self.cmap_cb.addColormap(cm)
518
+ except:
519
+ pass
520
+
339
521
  hbox = QHBoxLayout()
340
522
  hbox.addWidget(QLabel('colormap: '), 33)
341
523
  hbox.addWidget(self.cmap_cb, 66)
@@ -370,9 +552,16 @@ class TableUI(QMainWindow, Styles):
370
552
  y = self.data.iloc[row_idx_i, unique_cols]
371
553
 
372
554
  cmap = getattr(mcm, self.cmap_cb.currentText())
373
- hue_variable = self.hue_cb.currentText()
374
555
 
375
- colors = [cmap(i / len(self.data[hue_variable].unique())) for i in range(len(self.data[hue_variable].unique()))]
556
+ try:
557
+ hue_variable = self.hue_cb.currentText()
558
+ colors = [cmap(i / len(self.data[hue_variable].unique())) for i in range(len(self.data[hue_variable].unique()))]
559
+ except:
560
+ colors = None
561
+
562
+ if self.hue_cb.currentText()=='--':
563
+ hue_variable = None
564
+
376
565
  #for w,well_group in self.data.groupby('well_index'):
377
566
 
378
567
  legend=True
@@ -435,18 +624,86 @@ class TableUI(QMainWindow, Styles):
435
624
 
436
625
 
437
626
  def set_proj_mode(self):
438
- self.projection_mode = self.projection_op_cb.currentText()
439
- #eval(self.projection_mode)
440
- op = getattr(self.data.groupby(['position', 'TRACK_ID']), self.projection_mode)
441
- group_table = op(self.data.groupby(['position', 'TRACK_ID']))
627
+
628
+ self.static_columns = ['well_index', 'well_name', 'pos_name', 'position', 'well', 'status', 't0', 'class','cell_type','concentration', 'antibody', 'pharmaceutical_agent','TRACK_ID','position']
629
+
630
+ if self.projection_option.isChecked():
631
+
632
+ self.projection_mode = self.projection_op_cb.currentText()
633
+ op = getattr(self.data.groupby(['position', 'TRACK_ID']), self.projection_mode)
634
+ group_table = op(self.data.groupby(['position', 'TRACK_ID']))
635
+
636
+ for c in self.static_columns:
637
+ try:
638
+ group_table[c] = self.data.groupby(['position','TRACK_ID'])[c].apply(lambda x: x.unique()[0])
639
+ except Exception as e:
640
+ print(e)
641
+ pass
642
+
643
+ for col in ['TRACK_ID']:
644
+ first_column = group_table.pop(col)
645
+ group_table.insert(0, col, first_column)
646
+ group_table.pop('FRAME')
647
+
648
+
649
+ elif self.event_time_option.isChecked():
650
+ time_of_interest = self.event_times_cb.currentText()
651
+ self.projection_mode = f"measurements at {time_of_interest}"
652
+ new_table = []
653
+ for tid,group in self.data.groupby(['position','TRACK_ID']):
654
+ time = group[time_of_interest].values[0]
655
+ if time==time:
656
+ time = floor(time) # floor for onset
657
+ else:
658
+ continue
659
+ frames = group['FRAME'].values
660
+ values = group.loc[group['FRAME']==time,:].to_numpy()
661
+ if len(values)>0:
662
+ values = dict(zip(list(self.data.columns), values[0]))
663
+ values.update({'TRACK_ID': tid[1]})
664
+ values.update({'position': tid[0]})
665
+ new_table.append(values)
666
+
667
+ group_table = pd.DataFrame(new_table)
668
+ for col in ['TRACK_ID']:
669
+ first_column = group_table.pop(col)
670
+ group_table.insert(0, col, first_column)
671
+
672
+ group_table = group_table.sort_values(by=['position','TRACK_ID','FRAME'],ignore_index=True)
673
+ group_table = group_table.reset_index(drop=True)
674
+
675
+
676
+ elif self.per_status_option.isChecked():
677
+
678
+ status_of_interest = self.per_status_cb.currentText()
679
+ self.projection_mode = f'{self.status_operation.currentText()} per {status_of_interest}'
680
+ self.data = self.data.dropna(subset=status_of_interest,ignore_index=True)
681
+ unique_statuses = np.unique(self.data[status_of_interest].to_numpy())
682
+
683
+ df_sections = []
684
+ for s in unique_statuses:
685
+ subtab = self.data.loc[self.data[status_of_interest]==s,:]
686
+ op = getattr(subtab.groupby(['position', 'TRACK_ID']), self.status_operation.currentText())
687
+ subtab_projected = op(subtab.groupby(['position', 'TRACK_ID']))
688
+ frame_duration = subtab.groupby(['position','TRACK_ID']).size().to_numpy()
689
+ for c in self.static_columns:
690
+ try:
691
+ subtab_projected[c] = subtab.groupby(['position', 'TRACK_ID'])[c].apply(lambda x: x.unique()[0])
692
+ except Exception as e:
693
+ print(e)
694
+ pass
695
+ subtab_projected['duration_in_state'] = frame_duration
696
+ df_sections.append(subtab_projected)
697
+
698
+ group_table = pd.concat(df_sections,axis=0,ignore_index=True)
699
+ for col in ['duration_in_state',status_of_interest,'TRACK_ID']:
700
+ first_column = group_table.pop(col)
701
+ group_table.insert(0, col, first_column)
702
+ group_table.pop('FRAME')
703
+ group_table = group_table.sort_values(by=['position','TRACK_ID',status_of_interest],ignore_index=True)
704
+ group_table = group_table.reset_index(drop=True)
705
+
442
706
 
443
- self.static_columns = ['well_index', 'well_name', 'pos_name', 'position', 'well', 'status', 't0', 'class', 'concentration', 'antibody', 'pharmaceutical_agent']
444
- for c in self.static_columns:
445
- try:
446
- group_table[c] = self.data.groupby(['position','TRACK_ID'])[c].apply(lambda x: x.unique()[0])
447
- except Exception as e:
448
- print(e)
449
- pass
450
707
  self.subtable = TableUI(group_table,f"Group by tracks: {self.projection_mode}", plot_mode="static")
451
708
  self.subtable.show()
452
709
 
@@ -487,6 +744,15 @@ class TableUI(QMainWindow, Styles):
487
744
  else:
488
745
  return array
489
746
 
747
+ def plot_instantaneous(self):
748
+
749
+ if self.plot_mode=='plot_track_signals':
750
+ self.plot_mode = 'static'
751
+ self.plot()
752
+ self.plot_mode = 'plot_track_signals'
753
+ elif self.plot_mode=="static":
754
+ self.plot()
755
+
490
756
  def plot(self):
491
757
  if self.plot_mode == "static":
492
758
 
@@ -2,7 +2,7 @@ import math
2
2
 
3
3
  import skimage
4
4
  from PyQt5.QtWidgets import QAction, QMenu, QMainWindow, QMessageBox, QLabel, QWidget, QFileDialog, QHBoxLayout, \
5
- QGridLayout, QLineEdit, QScrollArea, QVBoxLayout, QComboBox, QPushButton, QApplication, QPushButton
5
+ QGridLayout, QLineEdit, QScrollArea, QVBoxLayout, QComboBox, QPushButton, QApplication, QPushButton, QRadioButton, QButtonGroup
6
6
  from PyQt5.QtGui import QDoubleValidator, QIntValidator
7
7
  from matplotlib.backends.backend_qt import NavigationToolbar2QT
8
8
  from matplotlib.patches import Circle
@@ -12,11 +12,12 @@ from skimage.morphology import disk
12
12
 
13
13
  from celldetective.filters import std_filter, gauss_filter
14
14
  from celldetective.gui.gui_utils import center_window, FigureCanvas, ListWidget, FilterChoice, color_from_class
15
- from celldetective.utils import get_software_location, extract_experiment_channels, rename_intensity_column
15
+ from celldetective.utils import get_software_location, extract_experiment_channels, rename_intensity_column, estimate_unreliable_edge
16
16
  from celldetective.io import auto_load_number_of_frames, load_frames
17
17
  from celldetective.segmentation import threshold_image, identify_markers_from_binary, apply_watershed, \
18
18
  segment_frame_from_thresholds
19
19
  from scipy.ndimage import binary_fill_holes
20
+ import scipy.ndimage as ndi
20
21
  from PyQt5.QtCore import Qt, QSize
21
22
  from glob import glob
22
23
  from superqt.fonticon import icon
@@ -61,6 +62,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
61
62
  self.onlyFloat = QDoubleValidator()
62
63
  self.onlyInt = QIntValidator()
63
64
  self.cell_properties = ['centroid', 'area', 'perimeter', 'eccentricity', 'intensity_mean', 'solidity']
65
+ self.edge = None
64
66
 
65
67
  if self.mode == "targets":
66
68
  self.config_out_name = "threshold_targets.json"
@@ -262,10 +264,20 @@ class ThresholdConfigWizard(QMainWindow, Styles):
262
264
  marker_box = QVBoxLayout()
263
265
  marker_box.setContentsMargins(30, 30, 30, 30)
264
266
 
265
- marker_lbl = QLabel('Markers')
267
+ marker_lbl = QLabel('Objects')
266
268
  marker_lbl.setStyleSheet("font-weight: bold;")
267
269
  marker_box.addWidget(marker_lbl, alignment=Qt.AlignCenter)
268
270
 
271
+ object_option_hbox = QHBoxLayout()
272
+ self.marker_option = QRadioButton('markers')
273
+ self.all_objects_option = QRadioButton('all non-contiguous objects')
274
+ self.marker_option_group = QButtonGroup()
275
+ self.marker_option_group.addButton(self.marker_option)
276
+ self.marker_option_group.addButton(self.all_objects_option)
277
+ object_option_hbox.addWidget(self.marker_option, 50, alignment=Qt.AlignCenter)
278
+ object_option_hbox.addWidget(self.all_objects_option, 50, alignment=Qt.AlignCenter)
279
+ marker_box.addLayout(object_option_hbox)
280
+
269
281
  hbox_footprint = QHBoxLayout()
270
282
  hbox_footprint.addWidget(QLabel('Footprint: '), 20)
271
283
  self.footprint_slider = QLabeledSlider()
@@ -274,7 +286,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
274
286
  self.footprint_slider.setRange(1, self.binary.shape[0] // 4)
275
287
  self.footprint_slider.setValue(self.footprint)
276
288
  self.footprint_slider.valueChanged.connect(self.set_footprint)
277
- hbox_footprint.addWidget(self.footprint_slider, 80)
289
+ hbox_footprint.addWidget(self.footprint_slider, 30)
290
+ hbox_footprint.addWidget(QLabel(''), 50)
278
291
  marker_box.addLayout(hbox_footprint)
279
292
 
280
293
  hbox_distance = QHBoxLayout()
@@ -285,7 +298,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
285
298
  self.min_dist_slider.setRange(0, self.binary.shape[0] // 4)
286
299
  self.min_dist_slider.setValue(self.min_dist)
287
300
  self.min_dist_slider.valueChanged.connect(self.set_min_dist)
288
- hbox_distance.addWidget(self.min_dist_slider, 80)
301
+ hbox_distance.addWidget(self.min_dist_slider, 30)
302
+ hbox_distance.addWidget(QLabel(''), 50)
289
303
  marker_box.addLayout(hbox_distance)
290
304
 
291
305
  hbox_marker_btns = QHBoxLayout()
@@ -304,8 +318,23 @@ class ThresholdConfigWizard(QMainWindow, Styles):
304
318
  hbox_marker_btns.addWidget(self.watershed_btn)
305
319
  marker_box.addLayout(hbox_marker_btns)
306
320
 
321
+ self.marker_option.clicked.connect(self.enable_marker_options)
322
+ self.all_objects_option.clicked.connect(self.enable_marker_options)
323
+ self.marker_option.click()
324
+
307
325
  self.left_panel.addLayout(marker_box)
308
326
 
327
+ def enable_marker_options(self):
328
+ if self.marker_option.isChecked():
329
+ self.footprint_slider.setEnabled(True)
330
+ self.min_dist_slider.setEnabled(True)
331
+ self.markers_btn.setEnabled(True)
332
+ else:
333
+ self.footprint_slider.setEnabled(False)
334
+ self.min_dist_slider.setEnabled(False)
335
+ self.markers_btn.setEnabled(False)
336
+ self.watershed_btn.setEnabled(True)
337
+
309
338
  def generate_props_contents(self):
310
339
 
311
340
  properties_box = QVBoxLayout()
@@ -437,7 +466,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
437
466
  self.im = self.ax.imshow(self.img, cmap='gray')
438
467
 
439
468
  self.binary = threshold_image(self.img, self.threshold_slider.value()[0], self.threshold_slider.value()[1],
440
- foreground_value=255., fill_holes=False)
469
+ foreground_value=1., fill_holes=True, edge_exclusion=None)
441
470
  self.thresholded_image = np.ma.masked_where(self.binary == 0., self.binary)
442
471
  self.image_thresholded = self.ax.imshow(self.thresholded_image, cmap="viridis", alpha=0.5, interpolation='none')
443
472
 
@@ -591,6 +620,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
591
620
 
592
621
  self.reload_frame()
593
622
  filters = self.filters_qlist.items
623
+ self.edge = estimate_unreliable_edge(filters)
594
624
  self.img = filter_image(self.img, filters)
595
625
  self.refresh_imshow()
596
626
  self.update_histogram()
@@ -637,7 +667,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
637
667
  """
638
668
 
639
669
  self.binary = threshold_image(self.img, self.threshold_slider.value()[0], self.threshold_slider.value()[1],
640
- foreground_value=255., fill_holes=False)
670
+ foreground_value=1., fill_holes=True, edge_exclusion=self.edge)
641
671
  self.thresholded_image = np.ma.masked_where(self.binary == 0., self.binary)
642
672
  self.image_thresholded.set_data(self.thresholded_image)
643
673
  self.fcanvas.canvas.draw_idle()
@@ -658,7 +688,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
658
688
 
659
689
  if self.binary.ndim == 3:
660
690
  self.binary = np.squeeze(self.binary)
661
- self.binary = binary_fill_holes(self.binary)
691
+ #self.binary = binary_fill_holes(self.binary)
662
692
  self.coords, self.edt_map = identify_markers_from_binary(self.binary, self.min_dist,
663
693
  footprint_size=self.footprint, footprint=None,
664
694
  return_edt=True)
@@ -673,7 +703,10 @@ class ThresholdConfigWizard(QMainWindow, Styles):
673
703
 
674
704
  def apply_watershed_to_selection(self):
675
705
 
676
- self.labels = apply_watershed(self.binary, self.coords, self.edt_map)
706
+ if self.marker_option.isChecked():
707
+ self.labels = apply_watershed(self.binary, self.coords, self.edt_map)
708
+ else:
709
+ self.labels,_ = ndi.label(self.binary.astype(int))
677
710
 
678
711
  self.current_channel = self.channels_cb.currentIndex()
679
712
  t = int(self.frame_slider.value())
@@ -785,7 +818,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
785
818
  self.property_query_le.setText('')
786
819
 
787
820
  self.binary = threshold_image(self.img, self.threshold_slider.value()[0], self.threshold_slider.value()[1],
788
- foreground_value=255., fill_holes=False)
821
+ foreground_value=1., fill_holes=True, edge_exclusion=self.edge)
789
822
  self.thresholded_image = np.ma.masked_where(self.binary == 0., self.binary)
790
823
 
791
824
  self.scat_markers.set_color('tab:red')
@@ -804,6 +837,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
804
837
  "marker_footprint_size": self.footprint,
805
838
  "feature_queries": [self.property_query_le.text()],
806
839
  "equalize_reference": [self.equalize_option, self.frame_slider.value()],
840
+ "do_watershed": self.marker_option.isChecked(),
807
841
  }
808
842
 
809
843
  print('The following instructions will be written: ', instructions)
@@ -871,6 +905,13 @@ class ThresholdConfigWizard(QMainWindow, Styles):
871
905
  self.property_query_le.setText(feature_queries[0])
872
906
  self.submit_query_btn.click()
873
907
 
908
+ if 'do_watershed' in threshold_instructions:
909
+ do_watershed = threshold_instructions['do_watershed']
910
+ if do_watershed:
911
+ self.marker_option.click()
912
+ else:
913
+ self.all_objects_option.click()
914
+
874
915
 
875
916
  class ThresholdNormalisation(ThresholdConfigWizard):
876
917
  def __init__(self, min_threshold, current_channel, parent_window=None):