celldetective 1.1.1__tar.gz → 1.1.1.post1__tar.gz

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 (90) hide show
  1. {celldetective-1.1.1 → celldetective-1.1.1.post1}/PKG-INFO +1 -1
  2. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/extra_properties.py +1 -1
  3. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/filters.py +39 -11
  4. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/classifier_widget.py +56 -7
  5. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/layouts.py +2 -2
  6. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/tableUI.py +15 -1
  7. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/thresholds_gui.py +44 -5
  8. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/io.py +29 -14
  9. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/scripts/segment_cells.py +12 -4
  10. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/segmentation.py +16 -12
  11. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/utils.py +3 -0
  12. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective.egg-info/PKG-INFO +1 -1
  13. {celldetective-1.1.1 → celldetective-1.1.1.post1}/setup.py +1 -1
  14. {celldetective-1.1.1 → celldetective-1.1.1.post1}/LICENSE +0 -0
  15. {celldetective-1.1.1 → celldetective-1.1.1.post1}/README.md +0 -0
  16. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/__init__.py +0 -0
  17. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/__main__.py +0 -0
  18. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/datasets/segmentation_annotations/blank +0 -0
  19. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/datasets/signal_annotations/blank +0 -0
  20. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/events.py +0 -0
  21. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/__init__.py +0 -0
  22. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/about.py +0 -0
  23. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/analyze_block.py +0 -0
  24. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/btrack_options.py +0 -0
  25. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/configure_new_exp.py +0 -0
  26. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/control_panel.py +0 -0
  27. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/gui_utils.py +0 -0
  28. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/json_readers.py +0 -0
  29. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/measurement_options.py +0 -0
  30. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/neighborhood_options.py +0 -0
  31. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/plot_measurements.py +0 -0
  32. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/plot_signals_ui.py +0 -0
  33. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/process_block.py +0 -0
  34. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/retrain_segmentation_model_options.py +0 -0
  35. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/retrain_signal_model_options.py +0 -0
  36. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/seg_model_loader.py +0 -0
  37. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/signal_annotator.py +0 -0
  38. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/signal_annotator_options.py +0 -0
  39. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/styles.py +0 -0
  40. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/survival_ui.py +0 -0
  41. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/gui/viewers.py +0 -0
  42. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/logo-large.png +0 -0
  43. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/logo.png +0 -0
  44. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/signals_icon.png +0 -0
  45. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/splash-test.png +0 -0
  46. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/splash.png +0 -0
  47. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/splash0.png +0 -0
  48. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/survival2.png +0 -0
  49. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/vignette_signals2.png +0 -0
  50. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/icons/vignette_signals2.svg +0 -0
  51. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/links/zenodo.json +0 -0
  52. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/measure.py +0 -0
  53. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/segmentation_effectors/blank +0 -0
  54. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/segmentation_effectors/primNK_cfse/config_input.json +0 -0
  55. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/segmentation_effectors/primNK_cfse/cp-cfse-transfer +0 -0
  56. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/segmentation_effectors/primNK_cfse/training_instructions.json +0 -0
  57. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/segmentation_generic/blank +0 -0
  58. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/segmentation_targets/blank +0 -0
  59. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/signal_detection/blank +0 -0
  60. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/tracking_configs/mcf7.json +0 -0
  61. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/tracking_configs/ricm.json +0 -0
  62. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/models/tracking_configs/ricm2.json +0 -0
  63. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/neighborhood.py +0 -0
  64. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/preprocessing.py +0 -0
  65. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/scripts/analyze_signals.py +0 -0
  66. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/scripts/measure_cells.py +0 -0
  67. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/scripts/segment_cells_thresholds.py +0 -0
  68. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/scripts/track_cells.py +0 -0
  69. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/scripts/train_segmentation_model.py +0 -0
  70. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/scripts/train_signal_model.py +0 -0
  71. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/signals.py +0 -0
  72. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective/tracking.py +0 -0
  73. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective.egg-info/SOURCES.txt +0 -0
  74. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective.egg-info/dependency_links.txt +0 -0
  75. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective.egg-info/entry_points.txt +0 -0
  76. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective.egg-info/not-zip-safe +0 -0
  77. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective.egg-info/requires.txt +0 -0
  78. {celldetective-1.1.1 → celldetective-1.1.1.post1}/celldetective.egg-info/top_level.txt +0 -0
  79. {celldetective-1.1.1 → celldetective-1.1.1.post1}/setup.cfg +0 -0
  80. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/__init__.py +0 -0
  81. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_events.py +0 -0
  82. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_filters.py +0 -0
  83. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_io.py +0 -0
  84. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_measure.py +0 -0
  85. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_neighborhood.py +0 -0
  86. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_preprocessing.py +0 -0
  87. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_segmentation.py +0 -0
  88. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_signals.py +0 -0
  89. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_tracking.py +0 -0
  90. {celldetective-1.1.1 → celldetective-1.1.1.post1}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: celldetective
3
- Version: 1.1.1
3
+ Version: 1.1.1.post1
4
4
  Summary: description
5
5
  Home-page: http://github.com/remyeltorro/celldetective
6
6
  Author: Rémy Torro
@@ -7,7 +7,7 @@ If intensity is in function name, it will be replaced by the name of the channel
7
7
  import warnings
8
8
 
9
9
  import numpy as np
10
- from scipy.ndimage import distance_transform_edt
10
+ from scipy.ndimage import distance_transform_edt, center_of_mass
11
11
  from scipy.spatial.distance import euclidean
12
12
 
13
13
 
@@ -1,21 +1,36 @@
1
1
  from skimage.filters import difference_of_gaussians, threshold_otsu, threshold_local, threshold_niblack, threshold_sauvola
2
+ from celldetective.utils import interpolate_nan
2
3
  import scipy.ndimage as snd
3
4
  import numpy as np
4
5
 
5
- def gauss_filter(img, sigma, *kwargs):
6
+ def gauss_filter(img, sigma, interpolate=True, *kwargs):
7
+ if interpolate:
8
+ img = interpolate_nan(img.astype(float))
6
9
  return snd.gaussian_filter(img.astype(float), sigma, *kwargs)
7
10
 
8
- def median_filter(img, size, *kwargs):
11
+ def median_filter(img, size, interpolate=True, *kwargs):
12
+ if interpolate:
13
+ img = interpolate_nan(img.astype(float))
14
+
9
15
  size = int(size)
10
16
  return snd.median_filter(img, size, *kwargs)
11
17
 
12
- def maximum_filter(img, size, *kwargs):
18
+ def maximum_filter(img, size, interpolate=True, *kwargs):
19
+ if interpolate:
20
+ img = interpolate_nan(img.astype(float))
21
+
13
22
  return snd.maximum_filter(img.astype(float), size, *kwargs)
14
23
 
15
- def minimum_filter(img, size, *kwargs):
24
+ def minimum_filter(img, size, interpolate=True, *kwargs):
25
+ if interpolate:
26
+ img = interpolate_nan(img.astype(float))
27
+
16
28
  return snd.minimum_filter(img.astype(float), size, *kwargs)
17
29
 
18
- def percentile_filter(img, percentile, size, *kwargs):
30
+ def percentile_filter(img, percentile, size, interpolate=True, *kwargs):
31
+ if interpolate:
32
+ img = interpolate_nan(img.astype(float))
33
+
19
34
  return snd.percentile_filter(img.astype(float), percentile, size, *kwargs)
20
35
 
21
36
  def subtract_filter(img, value, *kwargs):
@@ -24,14 +39,19 @@ def subtract_filter(img, value, *kwargs):
24
39
  def abs_filter(img, *kwargs):
25
40
  return np.abs(img)
26
41
 
27
- def ln_filter(img, *kwargs):
42
+ def ln_filter(img, interpolate=True, *kwargs):
43
+ if interpolate:
44
+ img = interpolate_nan(img.astype(float))
28
45
 
29
46
  img[np.where(img>0.)] = np.log(img[np.where(img>0.)])
30
47
  img[np.where(img<=0.)] = 0.
31
48
 
32
49
  return img
33
50
 
34
- def variance_filter(img, size):
51
+ def variance_filter(img, size, interpolate=True):
52
+
53
+ if interpolate:
54
+ img = interpolate_nan(img.astype(float))
35
55
 
36
56
  size = int(size)
37
57
  img = img.astype(float)
@@ -41,8 +61,10 @@ def variance_filter(img, size):
41
61
 
42
62
  return img
43
63
 
44
- def std_filter(img, size):
64
+ def std_filter(img, size, interpolate=True):
45
65
 
66
+ if interpolate:
67
+ img = interpolate_nan(img.astype(float))
46
68
  size = int(size)
47
69
  img = img.astype(float)
48
70
  win_mean = snd.uniform_filter(img, (size,size), mode='wrap')
@@ -53,10 +75,14 @@ def std_filter(img, size):
53
75
 
54
76
  return img
55
77
 
56
- def laplace_filter(img, output=float, *kwargs):
78
+ def laplace_filter(img, output=float, interpolate=True, *kwargs):
79
+ if interpolate:
80
+ img = interpolate_nan(img.astype(float))
57
81
  return snd.laplace(img.astype(float), *kwargs)
58
82
 
59
- def dog_filter(img, sigma_low, sigma_high, *kwargs):
83
+ def dog_filter(img, sigma_low, sigma_high, interpolate=True, *kwargs):
84
+ if interpolate:
85
+ img = interpolate_nan(img.astype(float))
60
86
  return difference_of_gaussians(img.astype(float), sigma_low, sigma_high, *kwargs)
61
87
 
62
88
  def otsu_filter(img, *kwargs):
@@ -82,7 +108,9 @@ def sauvola_filter(img, *kwargs):
82
108
  def log_filter(img, sigma, *kwargs):
83
109
  return snd.gaussian_laplace(img.astype(float), sigma, *kwargs)
84
110
 
85
- def tophat_filter(img, size, connectivity=4, *kwargs):
111
+ def tophat_filter(img, size, connectivity=4, interpolate=True, *kwargs):
112
+ if interpolate:
113
+ img = interpolate_nan(img.astype(float))
86
114
  structure = snd.generate_binary_structure(rank=2, connectivity=connectivity)
87
115
  img = snd.white_tophat(img.astype(float), structure=structure, size=size, *kwargs)
88
116
  return img
@@ -136,9 +136,10 @@ class ClassifierWidget(QWidget, Styles):
136
136
 
137
137
  self.irreversible_event_btn = QRadioButton('irreversible event')
138
138
  self.unique_state_btn = QRadioButton('unique state')
139
- self.time_corr_options = [self.irreversible_event_btn, self.unique_state_btn]
140
139
  time_corr_btn_group = QButtonGroup()
141
140
  self.unique_state_btn.click()
141
+ self.time_corr_options = [self.irreversible_event_btn, self.unique_state_btn]
142
+
142
143
  for btn in self.time_corr_options:
143
144
  time_corr_btn_group.addButton(btn)
144
145
  btn.setEnabled(False)
@@ -148,8 +149,28 @@ class ClassifierWidget(QWidget, Styles):
148
149
  time_corr_layout.addWidget(self.irreversible_event_btn, 50,alignment=Qt.AlignCenter)
149
150
  layout.addLayout(time_corr_layout)
150
151
 
152
+ self.r2_slider = QLabeledDoubleSlider()
153
+ self.r2_slider.setValue(0.75)
154
+ self.r2_slider.setRange(0,1)
155
+ self.r2_slider.setSingleStep(0.01)
156
+ self.r2_slider.setOrientation(1)
157
+ self.r2_label = QLabel('R2 tolerance:')
158
+ self.r2_label.setToolTip('Minimum R2 between the fit sigmoid and the binary response to the filters to accept the event.')
159
+ r2_threshold_layout = QHBoxLayout()
160
+ r2_threshold_layout.addWidget(QLabel(''), 50)
161
+ r2_threshold_layout.addWidget(self.r2_label, 15)
162
+ r2_threshold_layout.addWidget(self.r2_slider, 35)
163
+ layout.addLayout(r2_threshold_layout)
164
+
165
+ self.irreversible_event_btn.clicked.connect(self.activate_r2)
166
+ self.unique_state_btn.clicked.connect(self.activate_r2)
167
+
168
+ for wg in [self.r2_slider, self.r2_label]:
169
+ wg.setEnabled(False)
170
+
151
171
  layout.addWidget(QLabel())
152
172
 
173
+
153
174
  self.submit_btn = QPushButton('apply')
154
175
  self.submit_btn.setStyleSheet(self.button_style_sheet)
155
176
  self.submit_btn.clicked.connect(self.submit_classification)
@@ -158,14 +179,30 @@ class ClassifierWidget(QWidget, Styles):
158
179
  self.frame_slider.valueChanged.connect(self.set_frame)
159
180
  self.alpha_slider.valueChanged.connect(self.set_transparency)
160
181
 
182
+ def activate_r2(self):
183
+ if self.irreversible_event_btn.isChecked() and self.time_corr.isChecked():
184
+ for wg in [self.r2_slider, self.r2_label]:
185
+ wg.setEnabled(True)
186
+ else:
187
+ for wg in [self.r2_slider, self.r2_label]:
188
+ wg.setEnabled(False)
189
+
161
190
  def activate_time_corr_options(self):
162
191
 
163
192
  if self.time_corr.isChecked():
164
193
  for btn in self.time_corr_options:
165
194
  btn.setEnabled(True)
195
+ if self.irreversible_event_btn.isChecked():
196
+ for wg in [self.r2_slider, self.r2_label]:
197
+ wg.setEnabled(True)
198
+ else:
199
+ for wg in [self.r2_slider, self.r2_label]:
200
+ wg.setEnabled(False)
166
201
  else:
167
202
  for btn in self.time_corr_options:
168
203
  btn.setEnabled(False)
204
+ for wg in [self.r2_slider, self.r2_label]:
205
+ wg.setEnabled(False)
169
206
 
170
207
  def init_class(self):
171
208
 
@@ -207,8 +244,20 @@ class ClassifierWidget(QWidget, Styles):
207
244
  self.scat_props.set_alpha(self.currentAlpha)
208
245
  self.ax_props.set_xlabel(self.features_cb[1].currentText())
209
246
  self.ax_props.set_ylabel(self.features_cb[0].currentText())
210
- self.ax_props.set_xlim(1*self.df[self.features_cb[1].currentText()].min(),1.0*self.df[self.features_cb[1].currentText()].max())
211
- self.ax_props.set_ylim(1*self.df[self.features_cb[0].currentText()].min(),1.0*self.df[self.features_cb[0].currentText()].max())
247
+
248
+
249
+ feat_x = self.features_cb[1].currentText()
250
+ feat_y = self.features_cb[0].currentText()
251
+ min_x = self.df.dropna(subset=feat_x)[feat_x].min()
252
+ max_x = self.df.dropna(subset=feat_x)[feat_x].max()
253
+ min_y = self.df.dropna(subset=feat_y)[feat_y].min()
254
+ max_y = self.df.dropna(subset=feat_y)[feat_y].max()
255
+
256
+ if min_x==min_x and max_x==max_x:
257
+ self.ax_props.set_xlim(min_x, max_x)
258
+ if min_y==min_y and max_y==max_y:
259
+ self.ax_props.set_ylim(min_y, max_y)
260
+
212
261
  if feature_changed:
213
262
  self.propscanvas.canvas.toolbar.update()
214
263
  self.propscanvas.canvas.draw_idle()
@@ -400,15 +449,16 @@ class ClassifierWidget(QWidget, Styles):
400
449
  timeline = group['FRAME'].values
401
450
 
402
451
  try:
403
- popt, pcov = curve_fit(step_function, timeline, status_signal,p0=[self.df['FRAME'].max()//2, 0.5],maxfev=10000)
404
- r2 = r2_score(status_signal, step_function(timeline, *popt))
452
+ popt, pcov = curve_fit(step_function, timeline.astype(int), status_signal, p0=[self.df['FRAME'].max()//2, 0.8],maxfev=30000)
453
+ values = [step_function(t, *popt) for t in timeline]
454
+ r2 = r2_score(status_signal,values)
405
455
  except Exception as e:
406
456
  print(e)
407
457
  self.df.loc[indices, self.class_name_user] = 2.0
408
458
  self.df.loc[indices, self.class_name_user.replace('class','t')] = -1
409
459
  continue
410
460
 
411
- if r2 > 0.7:
461
+ if r2 > float(self.r2_slider.value()):
412
462
  t0 = popt[0]
413
463
  self.df.loc[indices, self.class_name_user.replace('class','t')] = t0
414
464
  self.df.loc[indices, self.class_name_user] = 0.0
@@ -426,4 +476,3 @@ class ClassifierWidget(QWidget, Styles):
426
476
 
427
477
 
428
478
 
429
-
@@ -702,12 +702,12 @@ class BackgroundModelFreeCorrectionLayout(QGridLayout, Styles):
702
702
  show_progress_per_pos = False,
703
703
  )
704
704
 
705
-
706
705
  self.viewer = StackVisualizer(
707
706
  stack=corrected_stacks[0],
708
707
  window_title='Corrected channel',
709
708
  frame_slider = True,
710
- contrast_slider = True
709
+ contrast_slider = True,
710
+ target_channel=self.channels_cb.currentIndex(),
711
711
  )
712
712
  self.viewer.show()
713
713
 
@@ -232,6 +232,11 @@ class TableUI(QMainWindow, Styles):
232
232
  self.plot_action.setShortcut("Ctrl+p")
233
233
  self.fileMenu.addAction(self.plot_action)
234
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
+
235
240
  self.groupby_action = QAction("&Group by tracks...", self)
236
241
  self.groupby_action.triggered.connect(self.set_projection_mode_tracks)
237
242
  self.groupby_action.setShortcut("Ctrl+g")
@@ -408,7 +413,7 @@ class TableUI(QMainWindow, Styles):
408
413
  self.status_operation.setEnabled(False)
409
414
  self.status_operation.addItems(['mean','median','min','max', 'prod', 'sum'])
410
415
 
411
- status_cols = np.array([c.startswith('status_') for c in cols])
416
+ status_cols = np.array([c.startswith('status_') or c.startswith('group_') for c in cols])
412
417
  status_cols = list(cols[status_cols])
413
418
  if 'status' in list(self.data.columns):
414
419
  status_cols.append('status')
@@ -739,6 +744,15 @@ class TableUI(QMainWindow, Styles):
739
744
  else:
740
745
  return array
741
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
+
742
756
  def plot(self):
743
757
  if self.plot_mode == "static":
744
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
@@ -17,6 +17,7 @@ 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
@@ -263,10 +264,20 @@ class ThresholdConfigWizard(QMainWindow, Styles):
263
264
  marker_box = QVBoxLayout()
264
265
  marker_box.setContentsMargins(30, 30, 30, 30)
265
266
 
266
- marker_lbl = QLabel('Markers')
267
+ marker_lbl = QLabel('Objects')
267
268
  marker_lbl.setStyleSheet("font-weight: bold;")
268
269
  marker_box.addWidget(marker_lbl, alignment=Qt.AlignCenter)
269
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
+
270
281
  hbox_footprint = QHBoxLayout()
271
282
  hbox_footprint.addWidget(QLabel('Footprint: '), 20)
272
283
  self.footprint_slider = QLabeledSlider()
@@ -275,7 +286,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
275
286
  self.footprint_slider.setRange(1, self.binary.shape[0] // 4)
276
287
  self.footprint_slider.setValue(self.footprint)
277
288
  self.footprint_slider.valueChanged.connect(self.set_footprint)
278
- hbox_footprint.addWidget(self.footprint_slider, 80)
289
+ hbox_footprint.addWidget(self.footprint_slider, 30)
290
+ hbox_footprint.addWidget(QLabel(''), 50)
279
291
  marker_box.addLayout(hbox_footprint)
280
292
 
281
293
  hbox_distance = QHBoxLayout()
@@ -286,7 +298,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
286
298
  self.min_dist_slider.setRange(0, self.binary.shape[0] // 4)
287
299
  self.min_dist_slider.setValue(self.min_dist)
288
300
  self.min_dist_slider.valueChanged.connect(self.set_min_dist)
289
- hbox_distance.addWidget(self.min_dist_slider, 80)
301
+ hbox_distance.addWidget(self.min_dist_slider, 30)
302
+ hbox_distance.addWidget(QLabel(''), 50)
290
303
  marker_box.addLayout(hbox_distance)
291
304
 
292
305
  hbox_marker_btns = QHBoxLayout()
@@ -305,8 +318,23 @@ class ThresholdConfigWizard(QMainWindow, Styles):
305
318
  hbox_marker_btns.addWidget(self.watershed_btn)
306
319
  marker_box.addLayout(hbox_marker_btns)
307
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
+
308
325
  self.left_panel.addLayout(marker_box)
309
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
+
310
338
  def generate_props_contents(self):
311
339
 
312
340
  properties_box = QVBoxLayout()
@@ -675,7 +703,10 @@ class ThresholdConfigWizard(QMainWindow, Styles):
675
703
 
676
704
  def apply_watershed_to_selection(self):
677
705
 
678
- 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))
679
710
 
680
711
  self.current_channel = self.channels_cb.currentIndex()
681
712
  t = int(self.frame_slider.value())
@@ -806,6 +837,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
806
837
  "marker_footprint_size": self.footprint,
807
838
  "feature_queries": [self.property_query_le.text()],
808
839
  "equalize_reference": [self.equalize_option, self.frame_slider.value()],
840
+ "do_watershed": self.marker_option.isChecked(),
809
841
  }
810
842
 
811
843
  print('The following instructions will be written: ', instructions)
@@ -873,6 +905,13 @@ class ThresholdConfigWizard(QMainWindow, Styles):
873
905
  self.property_query_le.setText(feature_queries[0])
874
906
  self.submit_query_btn.click()
875
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
+
876
915
 
877
916
  class ThresholdNormalisation(ThresholdConfigWizard):
878
917
  def __init__(self, min_threshold, current_channel, parent_window=None):
@@ -1238,12 +1238,23 @@ def load_napari_data(position, prefix="Aligned", population="target", return_sta
1238
1238
  """
1239
1239
  position = position.replace('\\','/')
1240
1240
  if population.lower()=="target" or population.lower()=="targets":
1241
- napari_data = np.load(position+os.sep.join(['output','tables','napari_target_trajectories.npy']), allow_pickle=True)
1241
+ if os.path.exists(position+os.sep.join(['output','tables','napari_target_trajectories.npy'])):
1242
+ napari_data = np.load(position+os.sep.join(['output','tables','napari_target_trajectories.npy']), allow_pickle=True)
1243
+ else:
1244
+ napari_data = None
1242
1245
  elif population.lower()=="effector" or population.lower()=="effectors":
1243
- napari_data = np.load(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy']), allow_pickle=True)
1244
- data = napari_data.item()['data']
1245
- properties = napari_data.item()['properties']
1246
- graph = napari_data.item()['graph']
1246
+ if os.path.exists(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy'])):
1247
+ napari_data = np.load(position+os.sep.join(['output', 'tables', 'napari_effector_trajectories.npy']), allow_pickle=True)
1248
+ else:
1249
+ napari_data = None
1250
+ if napari_data is not None:
1251
+ data = napari_data.item()['data']
1252
+ properties = napari_data.item()['properties']
1253
+ graph = napari_data.item()['graph']
1254
+ else:
1255
+ data = None
1256
+ properties = None
1257
+ graph = None
1247
1258
  if return_stack:
1248
1259
  stack,labels = locate_stack_and_labels(position, prefix=prefix, population=population)
1249
1260
  else:
@@ -1968,15 +1979,19 @@ def normalize_multichannel(multichannel_frame, percentiles=None,
1968
1979
  v = values[c]
1969
1980
  else:
1970
1981
  v = None
1971
- norm = normalize(mf[:,:,c].copy(),
1972
- percentiles=percentiles[c],
1973
- values=v,
1974
- ignore_gray_value=ignore_gray_value,
1975
- clip=clip,
1976
- amplification=amplification,
1977
- dtype=dtype,
1978
- )
1979
- mf_new.append(norm)
1982
+
1983
+ if np.all(mf[:,:,c]==0.):
1984
+ mf_new.append(mf[:,:,c].copy())
1985
+ else:
1986
+ norm = normalize(mf[:,:,c].copy(),
1987
+ percentiles=percentiles[c],
1988
+ values=v,
1989
+ ignore_gray_value=ignore_gray_value,
1990
+ clip=clip,
1991
+ amplification=amplification,
1992
+ dtype=dtype,
1993
+ )
1994
+ mf_new.append(norm)
1980
1995
 
1981
1996
  return np.moveaxis(mf_new,0,-1)
1982
1997
 
@@ -9,7 +9,7 @@ import json
9
9
  from stardist.models import StarDist2D
10
10
  from cellpose.models import CellposeModel
11
11
  from celldetective.io import locate_segmentation_model, auto_load_number_of_frames, load_frames
12
- from celldetective.utils import _estimate_scale_factor, _extract_channel_indices_from_config, _extract_channel_indices, ConfigSectionMap, _extract_nbr_channels_from_config, _get_img_num_per_channel, normalize_per_channel
12
+ from celldetective.utils import interpolate_nan, _estimate_scale_factor, _extract_channel_indices_from_config, _extract_channel_indices, ConfigSectionMap, _extract_nbr_channels_from_config, _get_img_num_per_channel, normalize_per_channel
13
13
  from pathlib import Path, PurePath
14
14
  from glob import glob
15
15
  from shutil import rmtree
@@ -121,6 +121,7 @@ if model_type=='cellpose':
121
121
  flow_threshold = input_config['flow_threshold']
122
122
 
123
123
  scale = _estimate_scale_factor(spatial_calibration, required_spatial_calibration)
124
+ print(f"Scale = {scale}...")
124
125
 
125
126
  nbr_channels = _extract_nbr_channels_from_config(config)
126
127
  print(f'Number of channels in the input movie: {nbr_channels}')
@@ -140,6 +141,7 @@ with open(pos+f'log_{mode}.json', 'a') as f:
140
141
 
141
142
  # Loop over all frames and segment
142
143
  def segment_index(indices):
144
+ global scale
143
145
 
144
146
  if model_type=='stardist':
145
147
  model = StarDist2D(None, name=modelname, basedir=Path(model_complete_path).parent)
@@ -156,7 +158,11 @@ def segment_index(indices):
156
158
  device = torch.device("cuda")
157
159
 
158
160
  model = CellposeModel(gpu=use_gpu, device=device, pretrained_model=model_complete_path+modelname, model_type=None, nchan=len(required_channels)) #diam_mean=30.0,
159
- model.diam_mean = 30.0
161
+ if scale is None:
162
+ scale_model = model.diam_mean / model.diam_labels
163
+ else:
164
+ scale_model = scale * model.diam_mean / model.diam_labels
165
+ print(f"Diam mean: {model.diam_mean}; Diam labels: {model.diam_labels}; Final rescaling: {scale_model}...")
160
166
  print(f'Cellpose model {modelname} successfully loaded.')
161
167
 
162
168
  for t in tqdm(indices,desc="frame"):
@@ -172,10 +178,12 @@ def segment_index(indices):
172
178
  percentiles.append(None)
173
179
  values.append(normalization_values[k])
174
180
 
175
- f = load_frames(img_num_channels[:,t], file, scale=scale, normalize_input=True, normalize_kwargs={"percentiles": percentiles, 'values': values, 'clip': normalization_clip})
181
+ f = load_frames(img_num_channels[:,t], file, scale=scale_model, normalize_input=True, normalize_kwargs={"percentiles": percentiles, 'values': values, 'clip': normalization_clip})
182
+ f = np.moveaxis([interpolate_nan(f[:,:,c].copy()) for c in range(f.shape[-1])],0,-1)
176
183
 
177
184
  if np.any(img_num_channels[:,t]==-1):
178
185
  f[:,:,np.where(img_num_channels[:,t]==-1)[0]] = 0.
186
+
179
187
 
180
188
  if model_type=="stardist":
181
189
  Y_pred, details = model.predict_instances(f, n_tiles=model._guess_n_tiles(f), show_tile_progress=False, verbose=False)
@@ -188,7 +196,7 @@ def segment_index(indices):
188
196
  Y_pred = Y_pred.astype(np.uint16)
189
197
 
190
198
  if scale is not None:
191
- Y_pred = zoom(Y_pred, [1./scale,1./scale],order=0)
199
+ Y_pred = zoom(Y_pred, [1./scale_model,1./scale_model],order=0)
192
200
 
193
201
  template = load_frames(0,file,scale=1,normalize_input=False)
194
202
  if Y_pred.shape != template.shape[:2]:
@@ -116,6 +116,10 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
116
116
 
117
117
  elif model_type=='cellpose':
118
118
  model = CellposeModel(gpu=use_gpu, pretrained_model=model_path+model_path.split('/')[-2], diam_mean=30.0)
119
+ if scale is None:
120
+ scale_model = model.diam_mean / model.diam_labels
121
+ else:
122
+ scale_model = scale * model.diam_mean / model.diam_labels
119
123
 
120
124
  labels = []
121
125
  if (time_flat_normalization)*normalize:
@@ -133,7 +137,7 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
133
137
  frame = normalize_multichannel(frame, values=normalization_values)
134
138
 
135
139
  if scale is not None:
136
- frame = [ndi.zoom(frame[:,:,c].copy(), [scale,scale], order=3, prefilter=False) for c in range(frame.shape[-1])]
140
+ frame = [ndi.zoom(frame[:,:,c].copy(), [scale_model,scale_model], order=3, prefilter=False) for c in range(frame.shape[-1])]
137
141
 
138
142
  if model_type=="stardist":
139
143
 
@@ -142,16 +146,11 @@ def segment(stack, model_name, channels=None, spatial_calibration=None, view_on_
142
146
 
143
147
  elif model_type=="cellpose":
144
148
 
145
- if stack.ndim==3:
146
- channels_cp = [[0,0]]
147
- else:
148
- channels_cp = [[0,1]]
149
-
150
- Y_pred, _, _ = model.eval([frame], diameter = diameter, flow_threshold=flow_threshold, channels=channels_cp, normalize=normalize)
151
- Y_pred = Y_pred[0].astype(np.uint16)
149
+ Y_pred, _, _ = model.eval(frame, diameter = diameter, cellprob_threshold=cellprob_threshold, flow_threshold=flow_threshold, channels=None, normalize=False)
150
+ Y_pred = Y_pred.astype(np.uint16)
152
151
 
153
152
  if scale is not None:
154
- Y_pred = ndi.zoom(Y_pred, [1./scale,1./scale],order=0)
153
+ Y_pred = ndi.zoom(Y_pred, [1./scale_model,1./scale_model],order=0)
155
154
 
156
155
 
157
156
  if Y_pred.shape != stack[0].shape[:2]:
@@ -229,7 +228,7 @@ def segment_from_thresholds(stack, target_channel=0, thresholds=None, view_on_na
229
228
  return masks
230
229
 
231
230
  def segment_frame_from_thresholds(frame, target_channel=0, thresholds=None, equalize_reference=None,
232
- filters=None, marker_min_distance=30, marker_footprint_size=20, marker_footprint=None, feature_queries=None, channel_names=None):
231
+ filters=None, marker_min_distance=30, marker_footprint_size=20, marker_footprint=None, feature_queries=None, channel_names=None, do_watershed=True):
233
232
 
234
233
  """
235
234
  Segments objects within a single frame based on intensity thresholds and optional image processing steps.
@@ -276,8 +275,13 @@ def segment_frame_from_thresholds(frame, target_channel=0, thresholds=None, equa
276
275
  img = filter_image(img, filters=filters)
277
276
  edge = estimate_unreliable_edge(filters)
278
277
  binary_image = threshold_image(img, thresholds[0], thresholds[1], fill_holes=True, edge_exclusion=edge)
279
- coords,distance = identify_markers_from_binary(binary_image, marker_min_distance, footprint_size=marker_footprint_size, footprint=marker_footprint, return_edt=True)
280
- instance_seg = apply_watershed(binary_image, coords, distance)
278
+
279
+ if do_watershed:
280
+ coords,distance = identify_markers_from_binary(binary_image, marker_min_distance, footprint_size=marker_footprint_size, footprint=marker_footprint, return_edt=True)
281
+ instance_seg = apply_watershed(binary_image, coords, distance)
282
+ else:
283
+ instance_seg, _ = ndi.label(binary_image.astype(int).copy())
284
+
281
285
  instance_seg = filter_on_property(instance_seg, intensity_image=img_mc, queries=feature_queries, channel_names=channel_names)
282
286
 
283
287
  return instance_seg
@@ -2220,6 +2220,9 @@ def interpolate_nan(img, method='nearest'):
2220
2220
  Interpolate NaN on single channel array 2D
2221
2221
  """
2222
2222
 
2223
+ if np.all(img==0):
2224
+ return img
2225
+
2223
2226
  if np.any(img.flatten()!=img.flatten()):
2224
2227
  # then need to interpolate
2225
2228
  x_grid, y_grid = np.meshgrid(np.arange(img.shape[1]),np.arange(img.shape[0]))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: celldetective
3
- Version: 1.1.1
3
+ Version: 1.1.1.post1
4
4
  Summary: description
5
5
  Home-page: http://github.com/remyeltorro/celldetective
6
6
  Author: Rémy Torro
@@ -18,7 +18,7 @@ except:
18
18
  requirements = [str(ir.requirement) for ir in requirements]
19
19
 
20
20
  setup(name='celldetective',
21
- version='1.1.1',
21
+ version='1.1.1.post1',
22
22
  description='description',
23
23
  long_description=(this_directory / "README.md").read_text(),
24
24
  #long_description=open('README.rst',encoding="utf8").read(),