celldetective 1.3.4.post1__py3-none-any.whl → 1.3.6.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 (35) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/events.py +10 -5
  3. celldetective/filters.py +11 -0
  4. celldetective/gui/btrack_options.py +151 -1
  5. celldetective/gui/classifier_widget.py +44 -15
  6. celldetective/gui/configure_new_exp.py +13 -0
  7. celldetective/gui/control_panel.py +4 -2
  8. celldetective/gui/generic_signal_plot.py +2 -6
  9. celldetective/gui/gui_utils.py +170 -12
  10. celldetective/gui/measurement_options.py +85 -54
  11. celldetective/gui/neighborhood_options.py +1 -1
  12. celldetective/gui/plot_signals_ui.py +3 -4
  13. celldetective/gui/process_block.py +8 -6
  14. celldetective/gui/signal_annotator.py +10 -3
  15. celldetective/gui/signal_annotator2.py +146 -193
  16. celldetective/gui/survival_ui.py +121 -34
  17. celldetective/gui/tableUI.py +26 -12
  18. celldetective/gui/thresholds_gui.py +9 -52
  19. celldetective/gui/viewers.py +58 -21
  20. celldetective/io.py +1087 -161
  21. celldetective/measure.py +175 -102
  22. celldetective/preprocessing.py +2 -2
  23. celldetective/relative_measurements.py +6 -9
  24. celldetective/scripts/measure_cells.py +13 -3
  25. celldetective/scripts/segment_cells.py +0 -1
  26. celldetective/scripts/track_cells.py +25 -1
  27. celldetective/signals.py +9 -7
  28. celldetective/tracking.py +130 -81
  29. celldetective/utils.py +28 -7
  30. {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/METADATA +3 -2
  31. {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/RECORD +35 -35
  32. {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/LICENSE +0 -0
  33. {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/WHEEL +0 -0
  34. {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/entry_points.txt +0 -0
  35. {celldetective-1.3.4.post1.dist-info → celldetective-1.3.6.post1.dist-info}/top_level.txt +0 -0
@@ -4,17 +4,19 @@ from PyQt5.QtGui import QDoubleValidator
4
4
  from celldetective.gui.gui_utils import center_window
5
5
  from superqt import QColormapComboBox
6
6
  from celldetective.gui.generic_signal_plot import SurvivalPlotWidget
7
- from celldetective.utils import get_software_location, _extract_labels_from_config
7
+ from celldetective.utils import get_software_location, _extract_labels_from_config, extract_cols_from_table_list
8
8
  from celldetective.io import load_experiment_tables
9
9
  import numpy as np
10
10
  import os
11
11
  import matplotlib.pyplot as plt
12
12
  plt.rcParams['svg.fonttype'] = 'none'
13
13
  from glob import glob
14
- import pandas as pd
15
14
  from celldetective.gui import Styles
16
15
  from matplotlib import colormaps
17
16
  from celldetective.events import compute_survival
17
+ from celldetective.relative_measurements import expand_pair_table
18
+ import matplotlib.cm
19
+ from celldetective.neighborhood import extract_neighborhood_in_pair_table
18
20
 
19
21
  class ConfigSurvival(QWidget, Styles):
20
22
 
@@ -88,8 +90,51 @@ class ConfigSurvival(QWidget, Styles):
88
90
  main_layout.addWidget(panel_title, alignment=Qt.AlignCenter)
89
91
 
90
92
 
91
- labels = [QLabel('population: '), QLabel('time of\nreference: '), QLabel('time of\ninterest: '), QLabel('cmap: ')] #QLabel('class: '),
92
- self.cb_options = [['targets','effectors'], ['0'], [], []] #['class'],
93
+ pops = []
94
+ for population in ['effectors','targets','pairs']:
95
+ tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{population}.csv']))
96
+ if len(tables)>0:
97
+ pops.append(population)
98
+
99
+ tables_targets = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_targets.csv']))
100
+ self.cols_targets = extract_cols_from_table_list(tables_targets)
101
+ tables_effectors = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_effectors.csv']))
102
+ self.cols_effectors = extract_cols_from_table_list(tables_effectors)
103
+
104
+ # Smart reading of existing neighborhoods (without loading tables in memory)
105
+ if 'pairs' in pops and not 'targets' in pops:
106
+ # must be effector-effector
107
+ effector_neighs = [c[16:] for c in self.cols_effectors if c.startswith('inclusive_count_neighborhood')]
108
+ if len(effector_neighs)>0:
109
+ pops.pop(pops.index('pairs'))
110
+ pops.append('effectors-effectors')
111
+ elif 'pairs' in pops and not 'effectors' in pops:
112
+ # must be target-target
113
+ target_neighs = [c for c in self.cols_targets if c.startswith('inclusive_count_neighborhood')]
114
+ if len(target_neighs)>0:
115
+ pops.pop(pops.index('pairs'))
116
+ pops.append('targets-targets')
117
+ elif 'pairs' in pops:
118
+ # either effector-target or target-effector
119
+ target_neighs_cross = [c for c in self.cols_targets if c.startswith('inclusive_count_neighborhood') and '_2_' in c]
120
+ if len(target_neighs_cross)>0:
121
+ pops.append('targets-effectors')
122
+ effector_neighs_cross = [c for c in self.cols_effectors if c.startswith('inclusive_count_neighborhood') and '_2_' in c]
123
+ if len(effector_neighs_cross)>0:
124
+ pops.append('effectors-targets')
125
+ target_neighs = [c for c in self.cols_targets if c.startswith('inclusive_count_neighborhood') and 'self' in c]
126
+ if len(target_neighs)>0:
127
+ pops.append('targets-targets')
128
+ effector_neighs = [c for c in self.cols_effectors if c.startswith('inclusive_count_neighborhood') and 'self' in c]
129
+ if len(effector_neighs)>0:
130
+ pops.append('effectors-effectors')
131
+ pops.pop(pops.index('pairs'))
132
+ else:
133
+ pass
134
+
135
+
136
+ labels = [QLabel('population: '), QLabel('time of\nreference: '), QLabel('time of\ninterest: '), QLabel('cmap: ')] #QLabel('class: '),
137
+ self.cb_options = [pops, ['0'], [], []] #['class'],
93
138
  self.cbs = [QComboBox() for i in range(len(labels))]
94
139
 
95
140
  self.cbs[-1] = QColormapComboBox()
@@ -107,10 +152,12 @@ class ConfigSurvival(QWidget, Styles):
107
152
 
108
153
  all_cms = list(colormaps)
109
154
  for cm in all_cms:
110
- try:
111
- self.cbs[-1].addColormap(cm)
112
- except:
113
- pass
155
+ if hasattr(matplotlib.cm, str(cm).lower()):
156
+ self.cbs[-1].addColormap(cm.lower())
157
+ #try:
158
+ # self.cbs[-1].addColormap(cm)
159
+ # except:
160
+ # pass
114
161
 
115
162
  main_layout.addLayout(choice_layout)
116
163
 
@@ -155,24 +202,60 @@ class ConfigSurvival(QWidget, Styles):
155
202
  def set_classes_and_times(self):
156
203
 
157
204
  # Look for all classes and times
158
- tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_*.csv']))
159
- self.all_columns = []
160
- for tab in tables:
161
- cols = pd.read_csv(tab, nrows=1,encoding_errors='ignore').columns.tolist()
162
- self.all_columns.extend(cols)
205
+ self.neighborhood_keys = None
206
+ self.population = self.cbs[0].currentText()
207
+ pop_split = self.population.split('-')
163
208
 
164
- self.all_columns = np.unique(self.all_columns)
165
- #class_idx = np.array([s.startswith('class_') for s in self.all_columns])
166
- time_idx = np.array([s.startswith('t_') for s in self.all_columns])
209
+ if len(pop_split)==2:
167
210
 
168
- try:
169
- time_columns = list(self.all_columns[time_idx])
170
- except:
171
- print('no column starts with t')
172
- self.auto_close = True
173
- return None
174
- if 't0' in self.all_columns:
175
- time_columns.append('t0')
211
+ self.population = 'pairs'
212
+ tables_pairs = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_pairs.csv']))
213
+ self.cols_pairs = extract_cols_from_table_list(tables_pairs)
214
+
215
+ self.population_reference = pop_split[0]
216
+ self.population_neigh = pop_split[1]
217
+ if self.population_reference=='targets':
218
+ cols_ref = self.cols_targets
219
+ else:
220
+ cols_ref = self.cols_effectors
221
+ if self.population_neigh=='targets':
222
+ cols_neigh = self.cols_targets
223
+ else:
224
+ cols_neigh = self.cols_effectors
225
+
226
+ time_cols_ref = np.array([s.startswith('t_') or s=='t0' for s in cols_ref])
227
+ if len(time_cols_ref)>0:
228
+ time_cols_ref = list(cols_ref[time_cols_ref])
229
+ time_cols_ref = ['reference_'+t for t in time_cols_ref]
230
+
231
+ time_cols_neigh = np.array([s.startswith('t_') or s=='t0' for s in cols_neigh])
232
+ if len(time_cols_neigh)>0:
233
+ time_cols_neigh = list(cols_neigh[time_cols_neigh])
234
+ time_cols_neigh = ['neighbor_'+t for t in time_cols_neigh]
235
+
236
+ if self.population_reference!=self.population_neigh:
237
+ self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and '_2_' in c]
238
+ else:
239
+ self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and 'self' in c]
240
+
241
+ time_idx = np.array([s.startswith('t_') or s.startswith('t0') for s in self.cols_pairs])
242
+ time_cols_pairs = list(self.cols_pairs[time_idx])
243
+
244
+ time_columns = time_cols_ref + time_cols_neigh + time_cols_pairs
245
+
246
+ else:
247
+ if self.population=='targets':
248
+ self.all_columns = self.cols_targets
249
+ else:
250
+ self.all_columns = self.cols_effectors
251
+ time_idx = np.array([s.startswith('t_') or s=='t0' for s in self.all_columns])
252
+
253
+ try:
254
+ time_columns = list(self.all_columns[time_idx])
255
+ except:
256
+ print('no column starts with t')
257
+ self.auto_close = True
258
+ return None
176
259
 
177
260
  self.cbs[1].clear()
178
261
  self.cbs[1].addItems(np.unique(self.cb_options[1]+time_columns))
@@ -184,19 +267,16 @@ class ConfigSurvival(QWidget, Styles):
184
267
 
185
268
  def process_survival(self):
186
269
 
187
- print('you clicked!!')
188
270
  self.FrameToMin = float(self.time_calibration_le.text().replace(',','.'))
189
- print(self.FrameToMin, 'set')
190
-
191
271
  self.time_of_interest = self.cbs[2].currentText()
192
272
  if self.time_of_interest=="t0":
193
273
  self.class_of_interest = "class"
194
274
  else:
195
275
  self.class_of_interest = self.time_of_interest.replace('t_','class_')
196
276
 
277
+
197
278
  # read instructions from combobox options
198
279
  self.load_available_tables_local()
199
-
200
280
  if self.df is not None:
201
281
 
202
282
  try:
@@ -241,7 +321,8 @@ class ConfigSurvival(QWidget, Styles):
241
321
  self.well_option = self.parent_window.parent_window.well_list.getSelectedIndices()
242
322
  self.position_option = self.parent_window.parent_window.position_list.getSelectedIndices()
243
323
 
244
- self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population=self.cbs[0].currentText(), return_pos_info=True)
324
+ self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population=self.population, return_pos_info=True)
325
+
245
326
  if self.df is None:
246
327
  msgBox = QMessageBox()
247
328
  msgBox.setIcon(QMessageBox.Warning)
@@ -255,6 +336,11 @@ class ConfigSurvival(QWidget, Styles):
255
336
  self.df_well_info = self.df_pos_info.loc[:,['well_path', 'well_index', 'well_name', 'well_number', 'well_alias']].drop_duplicates()
256
337
  #print(f"{self.df_well_info=}")
257
338
 
339
+ if self.population=='pairs':
340
+ self.df = expand_pair_table(self.df)
341
+ self.df = extract_neighborhood_in_pair_table(self.df, reference_population=self.population_reference, neighbor_population=self.population_neigh, neighborhood_key=self.neighborhood_keys[0], contact_only=True)
342
+
343
+
258
344
  def compute_survival_functions(self):
259
345
 
260
346
  cut_observation_time = None
@@ -265,19 +351,20 @@ class ConfigSurvival(QWidget, Styles):
265
351
  cut_observation_time = None
266
352
  except Exception as e:
267
353
  pass
268
- print(f"{cut_observation_time=}")
354
+
355
+ pairs = False
356
+ if self.neighborhood_keys is not None:
357
+ pairs = True
269
358
 
270
359
  # Per position survival
271
360
  for block,movie_group in self.df.groupby(['well','position']):
272
-
273
- ks = compute_survival(movie_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time)
361
+ ks = compute_survival(movie_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time, pairs=pairs)
274
362
  if ks is not None:
275
363
  self.df_pos_info.loc[self.df_pos_info['pos_path']==block[1],'survival_fit'] = ks
276
364
 
277
365
  # Per well survival
278
366
  for well,well_group in self.df.groupby('well'):
279
-
280
- ks = compute_survival(well_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time)
367
+ ks = compute_survival(well_group, self.class_of_interest, self.cbs[2].currentText(), t_reference=self.cbs[1].currentText(), FrameToMin=self.FrameToMin, cut_observation_time=cut_observation_time, pairs=pairs)
281
368
  if ks is not None:
282
369
  self.df_well_info.loc[self.df_well_info['well_path']==well,'survival_fit'] = ks
283
370
 
@@ -19,6 +19,7 @@ from fonticon_mdi6 import MDI6
19
19
  from math import floor
20
20
 
21
21
  from matplotlib import colormaps
22
+ import matplotlib.cm
22
23
 
23
24
 
24
25
  class QueryWidget(QWidget):
@@ -473,6 +474,14 @@ class TableUI(QMainWindow, Styles):
473
474
  self.table_view.resizeColumnsToContents()
474
475
  self.setAttribute(Qt.WA_DeleteOnClose)
475
476
 
477
+ def resizeEvent(self, event):
478
+
479
+ super().resizeEvent(event)
480
+
481
+ try:
482
+ self.fig.tight_layout()
483
+ except:
484
+ pass
476
485
 
477
486
  def _createActions(self):
478
487
 
@@ -718,8 +727,11 @@ class TableUI(QMainWindow, Styles):
718
727
  def save_as_csv_inplace_per_pos(self):
719
728
 
720
729
  print("Saving each table in its respective position folder...")
721
- for pos,pos_group in self.data.groupby('position'):
722
- pos_group.to_csv(pos+os.sep.join(['output', 'tables', f'trajectories_{self.population}.csv']), index=False)
730
+ for pos,pos_group in self.data.groupby(['position']):
731
+ invalid_cols = [c for c in list(pos_group.columns) if c.startswith('Unnamed')]
732
+ if len(invalid_cols)>0:
733
+ pos_group = pos_group.drop(invalid_cols, axis=1)
734
+ pos_group.to_csv(pos[0]+os.sep.join(['output', 'tables', f'trajectories_{self.population}.csv']), index=False)
723
735
  print("Done...")
724
736
 
725
737
  def differenciate_selected_feature(self):
@@ -812,7 +824,7 @@ class TableUI(QMainWindow, Styles):
812
824
 
813
825
  num_df = self.data.select_dtypes(include=self.numerics)
814
826
 
815
- timeseries = num_df.groupby("FRAME").sum().copy()
827
+ timeseries = num_df.groupby(["FRAME"]).sum().copy()
816
828
  timeseries["timeline"] = timeseries.index
817
829
  self.subtable = TableUI(timeseries,"Group by frames", plot_mode="plot_timeseries")
818
830
  self.subtable.show()
@@ -1029,11 +1041,10 @@ class TableUI(QMainWindow, Styles):
1029
1041
  layout.addLayout(hbox)
1030
1042
 
1031
1043
  self.cmap_cb = QColormapComboBox()
1032
- for cm in list(colormaps):
1033
- try:
1034
- self.cmap_cb.addColormap(cm)
1035
- except:
1036
- pass
1044
+ all_cms = list(colormaps)
1045
+ for cm in all_cms:
1046
+ if hasattr(matplotlib.cm, str(cm).lower()):
1047
+ self.cmap_cb.addColormap(cm.lower())
1037
1048
 
1038
1049
  hbox = QHBoxLayout()
1039
1050
  hbox.addWidget(QLabel('colormap: '), 33)
@@ -1324,6 +1335,9 @@ class TableUI(QMainWindow, Styles):
1324
1335
  if file_name:
1325
1336
  if not file_name.endswith(".csv"):
1326
1337
  file_name += ".csv"
1338
+ invalid_cols = [c for c in list(self.data.columns) if c.startswith('Unnamed')]
1339
+ if len(invalid_cols)>0:
1340
+ self.data = self.data.drop(invalid_cols, axis=1)
1327
1341
  self.data.to_csv(file_name, index=False)
1328
1342
 
1329
1343
  def test_bool(self, array):
@@ -1417,8 +1431,8 @@ class TableUI(QMainWindow, Styles):
1417
1431
  row_idx_i = row_idx[np.where(col_idx == unique_cols[k])[0]]
1418
1432
  y = self.data.iloc[row_idx_i, unique_cols[k]]
1419
1433
  print(unique_cols[k])
1420
- for w,well_group in self.data.groupby('well_name'):
1421
- for pos,pos_group in well_group.groupby('pos_name'):
1434
+ for w,well_group in self.data.groupby(['well_name']):
1435
+ for pos,pos_group in well_group.groupby(['pos_name']):
1422
1436
  for tid,group_track in pos_group.groupby(self.groupby_cols[1:]):
1423
1437
  ax.plot(group_track["FRAME"], group_track[column_names[unique_cols[k]]],label=column_names[unique_cols[k]])
1424
1438
  #ax.plot(self.data["FRAME"][row_idx_i], y, label=column_names[unique_cols[k]])
@@ -1454,8 +1468,8 @@ class TableUI(QMainWindow, Styles):
1454
1468
  # else:
1455
1469
  # ref_time_col = 'FRAME'
1456
1470
 
1457
- for w,well_group in self.data.groupby('well_name'):
1458
- for pos,pos_group in well_group.groupby('pos_name'):
1471
+ for w,well_group in self.data.groupby(['well_name']):
1472
+ for pos,pos_group in well_group.groupby(['pos_name']):
1459
1473
  for tid,group_track in pos_group.groupby(self.groupby_cols[1:]):
1460
1474
  self.ax.plot(group_track["FRAME"], group_track[column_names[unique_cols[0]]],c="k", alpha = 0.1)
1461
1475
  self.ax.set_xlabel(r"$t$ [frame]")
@@ -3,7 +3,8 @@ from PyQt5.QtWidgets import QAction, QMenu, QMainWindow, QMessageBox, QLabel, QW
3
3
  from PyQt5.QtGui import QDoubleValidator, QIntValidator
4
4
 
5
5
  from celldetective.filters import std_filter, gauss_filter
6
- from celldetective.gui.gui_utils import center_window, FigureCanvas, ListWidget, FilterChoice, color_from_class, help_generic
6
+ from celldetective.gui.gui_utils import center_window, FigureCanvas, color_from_class, help_generic
7
+ from celldetective.gui.gui_utils import PreprocessingLayout
7
8
  from celldetective.utils import get_software_location, extract_experiment_channels, rename_intensity_column, estimate_unreliable_edge
8
9
  from celldetective.io import auto_load_number_of_frames, load_frames
9
10
  from celldetective.segmentation import threshold_image, identify_markers_from_binary, apply_watershed
@@ -23,6 +24,7 @@ import os
23
24
 
24
25
  from celldetective.gui import Styles
25
26
 
27
+
26
28
  class ThresholdConfigWizard(QMainWindow, Styles):
27
29
  """
28
30
  UI to create a threshold pipeline for segmentation.
@@ -140,53 +142,8 @@ class ThresholdConfigWizard(QMainWindow, Styles):
140
142
 
141
143
  def populate_left_panel(self):
142
144
 
143
- self.filters_qlist = ListWidget(FilterChoice, [])
144
-
145
- grid_preprocess = QGridLayout()
146
- grid_preprocess.setContentsMargins(20, 20, 20, 20)
147
-
148
- filter_list_option_grid = QHBoxLayout()
149
- section_preprocess = QLabel("Preprocessing")
150
- section_preprocess.setStyleSheet("font-weight: bold;")
151
- filter_list_option_grid.addWidget(section_preprocess, 90, alignment=Qt.AlignLeft)
152
-
153
- self.delete_filter = QPushButton("")
154
- self.delete_filter.setStyleSheet(self.button_select_all)
155
- self.delete_filter.setIcon(icon(MDI6.trash_can, color="black"))
156
- self.delete_filter.setToolTip("Remove filter")
157
- self.delete_filter.setIconSize(QSize(20, 20))
158
- self.delete_filter.clicked.connect(self.filters_qlist.removeSel)
159
-
160
- self.add_filter = QPushButton("")
161
- self.add_filter.setStyleSheet(self.button_select_all)
162
- self.add_filter.setIcon(icon(MDI6.filter_plus, color="black"))
163
- self.add_filter.setToolTip("Add filter")
164
- self.add_filter.setIconSize(QSize(20, 20))
165
- self.add_filter.clicked.connect(self.filters_qlist.addItem)
166
-
167
- self.help_prefilter_btn = QPushButton()
168
- self.help_prefilter_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
169
- self.help_prefilter_btn.setIconSize(QSize(20, 20))
170
- self.help_prefilter_btn.clicked.connect(self.help_prefilter)
171
- self.help_prefilter_btn.setStyleSheet(self.button_select_all)
172
- self.help_prefilter_btn.setToolTip("Help.")
173
-
174
- # filter_list_option_grid.addWidget(QLabel(""),90)
175
- filter_list_option_grid.addWidget(self.delete_filter, 5)
176
- filter_list_option_grid.addWidget(self.add_filter, 5)
177
- filter_list_option_grid.addWidget(self.help_prefilter_btn, 5)
178
-
179
- grid_preprocess.addLayout(filter_list_option_grid, 0, 0, 1, 3)
180
- grid_preprocess.addWidget(self.filters_qlist, 1, 0, 1, 3)
181
-
182
- self.apply_filters_btn = QPushButton("Apply")
183
- self.apply_filters_btn.setIcon(icon(MDI6.filter_cog_outline, color="white"))
184
- self.apply_filters_btn.setIconSize(QSize(20, 20))
185
- self.apply_filters_btn.setStyleSheet(self.button_style_sheet)
186
- self.apply_filters_btn.clicked.connect(self.preprocess_image)
187
- grid_preprocess.addWidget(self.apply_filters_btn, 2, 0, 1, 3)
188
-
189
- self.left_panel.addLayout(grid_preprocess)
145
+ self.preprocessing = PreprocessingLayout(self)
146
+ self.left_panel.addLayout(self.preprocessing)
190
147
 
191
148
  ###################
192
149
  # THRESHOLD SECTION
@@ -484,7 +441,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
484
441
  self.fcanvas = FigureCanvas(self.fig, interactive=True)
485
442
  self.ax.clear()
486
443
 
487
- self.im = self.ax.imshow(self.img, cmap='gray')
444
+ self.im = self.ax.imshow(self.img, cmap='gray', interpolation='none')
488
445
 
489
446
  self.binary = threshold_image(self.img, self.threshold_slider.value()[0], self.threshold_slider.value()[1],
490
447
  foreground_value=1., fill_holes=True, edge_exclusion=None)
@@ -640,7 +597,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
640
597
  """
641
598
 
642
599
  self.reload_frame()
643
- filters = self.filters_qlist.items
600
+ filters = self.preprocessing.list.items
644
601
  self.edge = estimate_unreliable_edge(filters)
645
602
  self.img = filter_image(self.img, filters)
646
603
  self.refresh_imshow()
@@ -853,7 +810,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
853
810
  instructions = {
854
811
  "target_channel": self.channels_cb.currentText(), # for now index but would be more universal to use name
855
812
  "thresholds": self.threshold_slider.value(),
856
- "filters": self.filters_qlist.items,
813
+ "filters": self.preprocessing.list.items,
857
814
  "marker_min_distance": self.min_dist,
858
815
  "marker_footprint_size": self.footprint,
859
816
  "feature_queries": [self.property_query_le.text()],
@@ -906,7 +863,7 @@ class ThresholdConfigWizard(QMainWindow, Styles):
906
863
  items_to_add = [f[0] + '_filter' for f in filters]
907
864
  self.filters_qlist.list_widget.clear()
908
865
  self.filters_qlist.list_widget.addItems(items_to_add)
909
- self.filters_qlist.items = filters
866
+ self.preprocessing.list.items = filters
910
867
 
911
868
  self.apply_filters_btn.click()
912
869
 
@@ -14,7 +14,7 @@ import os
14
14
  from PyQt5.QtWidgets import QWidget, QHBoxLayout, QPushButton, QLabel, QComboBox, QLineEdit, QListWidget, QShortcut
15
15
  from PyQt5.QtCore import Qt, QSize
16
16
  from PyQt5.QtGui import QKeySequence, QDoubleValidator
17
- from celldetective.gui.gui_utils import FigureCanvas, center_window, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit
17
+ from celldetective.gui.gui_utils import FigureCanvas, center_window, QuickSliderLayout, QHSeperationLine, ThresholdLineEdit, PreprocessingLayout2
18
18
  from celldetective.gui import Styles
19
19
  from superqt import QLabeledDoubleSlider, QLabeledSlider, QLabeledDoubleRangeSlider
20
20
  from superqt.fonticon import icon
@@ -626,21 +626,26 @@ class CellEdgeVisualizer(StackVisualizer):
626
626
 
627
627
  class SpotDetectionVisualizer(StackVisualizer):
628
628
 
629
- def __init__(self, parent_channel_cb=None, parent_diameter_le=None, parent_threshold_le=None, cell_type='targets', labels=None, *args, **kwargs):
629
+ def __init__(self, parent_channel_cb=None, parent_diameter_le=None, parent_threshold_le=None, parent_preprocessing_list=None, cell_type='targets', labels=None, *args, **kwargs):
630
630
 
631
631
  super().__init__(*args, **kwargs)
632
632
 
633
633
  self.cell_type = cell_type
634
634
  self.labels = labels
635
635
  self.detection_channel = self.target_channel
636
+
636
637
  self.parent_channel_cb = parent_channel_cb
637
638
  self.parent_diameter_le = parent_diameter_le
638
639
  self.parent_threshold_le = parent_threshold_le
639
- self.spot_sizes = []
640
+ self.parent_preprocessing_list = parent_preprocessing_list
640
641
 
642
+ self.spot_sizes = []
641
643
  self.floatValidator = QDoubleValidator()
642
644
  self.init_scatter()
643
- self.generate_detection_channel()
645
+
646
+ self.generate_detection_channel()
647
+ self.detection_channel = self.detection_channel_cb.currentIndex()
648
+
644
649
  self.generate_spot_detection_params()
645
650
  self.generate_add_measurement_btn()
646
651
  self.load_labels()
@@ -663,10 +668,10 @@ class SpotDetectionVisualizer(StackVisualizer):
663
668
 
664
669
  # Data-to-pixel scale
665
670
  ax_width_in_pixels = self.ax.bbox.width
666
- ax_height_in_pixels = self.ax.bbox.height
671
+ ax_height_in_pixels =self.ax.bbox.height
667
672
 
668
- x_scale = (xlim[1] - xlim[0]) / ax_width_in_pixels
669
- y_scale = (ylim[1] - ylim[0]) / ax_height_in_pixels
673
+ x_scale = (float(xlim[1]) - float(xlim[0])) / ax_width_in_pixels
674
+ y_scale = (float(ylim[1]) - float(ylim[0])) / ax_height_in_pixels
670
675
 
671
676
  # Choose the smaller scale for square pixels
672
677
  scale = min(x_scale, y_scale)
@@ -674,7 +679,7 @@ class SpotDetectionVisualizer(StackVisualizer):
674
679
  # Convert radius_px to data units
675
680
  if len(self.spot_sizes)>0:
676
681
 
677
- radius_data_units = self.spot_sizes / scale
682
+ radius_data_units = self.spot_sizes / float(scale)
678
683
 
679
684
  # Convert to scatter `s` size (points squared)
680
685
  radius_pts = radius_data_units * (72. / self.fig.dpi )
@@ -697,8 +702,7 @@ class SpotDetectionVisualizer(StackVisualizer):
697
702
  if self.mode=='virtual':
698
703
  self.init_label = imread(self.mask_paths[value])
699
704
  self.target_img = load_frames(self.img_num_per_channel[self.detection_channel, value],
700
- self.stack_path,
701
- normalize_input=False).astype(float)[:,:,0]
705
+ self.stack_path,normalize_input=False).astype(float)[:,:,0]
702
706
  elif self.mode=='direct':
703
707
  self.init_label = self.labels[value,:,:]
704
708
  self.target_img = self.stack[value,:,:,self.detection_channel].copy()
@@ -707,18 +711,28 @@ class SpotDetectionVisualizer(StackVisualizer):
707
711
 
708
712
  self.reset_detection()
709
713
  self.control_valid_parameters() # set current diam and threshold
710
- blobs_filtered = extract_blobs_in_image(self.target_img, self.init_label,threshold=self.thresh, diameter=self.diameter)
714
+ #self.change_frame(self.frame_slider.value())
715
+ #self.set_detection_channel_index(self.detection_channel_cb.currentIndex())
716
+
717
+ image_preprocessing = self.preprocessing.list.items
718
+ if image_preprocessing==[]:
719
+ image_preprocessing = None
720
+
721
+ blobs_filtered = extract_blobs_in_image(self.target_img, self.init_label,threshold=self.thresh, diameter=self.diameter, image_preprocessing=image_preprocessing)
711
722
  if blobs_filtered is not None:
712
723
  self.spot_positions = np.array([[x,y] for y,x,_ in blobs_filtered])
713
-
714
- self.spot_sizes = np.sqrt(2)*np.array([sig for _,_,sig in blobs_filtered])
715
- print(f"{self.spot_sizes=}")
724
+ if len(self.spot_positions)>0:
725
+ self.spot_sizes = np.sqrt(2)*np.array([sig for _,_,sig in blobs_filtered])
716
726
  #radius_pts = self.spot_sizes * (self.fig.dpi / 72.0)
717
727
  #sizes = np.pi*(radius_pts**2)
718
-
719
- self.spot_scat.set_offsets(self.spot_positions)
728
+ if len(self.spot_positions)>0:
729
+ self.spot_scat.set_offsets(self.spot_positions)
730
+ else:
731
+ empty_offset = np.ma.masked_array([0, 0], mask=True)
732
+ self.spot_scat.set_offsets(empty_offset)
720
733
  #self.spot_scat.set_sizes(sizes)
721
- self.update_marker_sizes()
734
+ if len(self.spot_positions)>0:
735
+ self.update_marker_sizes()
722
736
  self.canvas.canvas.draw()
723
737
 
724
738
  def reset_detection(self):
@@ -778,17 +792,34 @@ class SpotDetectionVisualizer(StackVisualizer):
778
792
  self.detection_channel_cb.addItems(self.channel_names)
779
793
  self.detection_channel_cb.currentIndexChanged.connect(self.set_detection_channel_index)
780
794
  channel_layout.addWidget(self.detection_channel_cb, 75)
795
+
796
+ # self.invert_check = QCheckBox('invert')
797
+ # if self.invert:
798
+ # self.invert_check.setChecked(True)
799
+ # self.invert_check.toggled.connect(self.set_invert)
800
+ # channel_layout.addWidget(self.invert_check, 10)
801
+
781
802
  self.canvas.layout.addLayout(channel_layout)
803
+
804
+ self.preprocessing = PreprocessingLayout2(fraction=25, parent_window=self)
805
+ self.preprocessing.setContentsMargins(15,0,15,0)
806
+ self.canvas.layout.addLayout(self.preprocessing)
807
+
808
+
809
+ # def set_invert(self):
810
+ # if self.invert_check.isChecked():
811
+ # self.invert = True
812
+ # else:
813
+ # self.invert = False
782
814
 
783
815
  def set_detection_channel_index(self, value):
784
816
 
785
817
  self.detection_channel = value
786
818
  if self.mode == 'direct':
787
- self.last_frame = self.stack[-1,:,:,self.target_channel]
819
+ self.target_img = self.stack[-1,:,:,self.detection_channel]
788
820
  elif self.mode == 'virtual':
789
- self.target_img = load_frames(self.img_num_per_channel[self.detection_channel, self.stack_length-1],
790
- self.stack_path,
791
- normalize_input=False).astype(float)[:,:,0]
821
+ self.target_img = load_frames(self.img_num_per_channel[self.detection_channel, self.frame_slider.value()],
822
+ self.stack_path,normalize_input=False).astype(float)[:,:,0]
792
823
 
793
824
  def generate_spot_detection_params(self):
794
825
 
@@ -865,6 +896,12 @@ class SpotDetectionVisualizer(StackVisualizer):
865
896
  self.parent_diameter_le.setText(self.spot_diam_le.text())
866
897
  if self.parent_threshold_le is not None:
867
898
  self.parent_threshold_le.setText(self.spot_thresh_le.text())
899
+ if self.parent_preprocessing_list is not None:
900
+ self.parent_preprocessing_list.clear()
901
+ items = self.preprocessing.list.getItems()
902
+ for item in items:
903
+ self.parent_preprocessing_list.addItemToList(item)
904
+ self.parent_preprocessing_list.items = self.preprocessing.list.items
868
905
  self.close()
869
906
 
870
907
  class CellSizeViewer(StackVisualizer):