celldetective 1.3.9.post4__py3-none-any.whl → 1.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. celldetective/__init__.py +0 -3
  2. celldetective/_version.py +1 -1
  3. celldetective/events.py +2 -4
  4. celldetective/extra_properties.py +320 -24
  5. celldetective/gui/InitWindow.py +33 -45
  6. celldetective/gui/__init__.py +1 -0
  7. celldetective/gui/about.py +19 -15
  8. celldetective/gui/analyze_block.py +34 -19
  9. celldetective/gui/base_components.py +23 -0
  10. celldetective/gui/btrack_options.py +26 -34
  11. celldetective/gui/classifier_widget.py +71 -80
  12. celldetective/gui/configure_new_exp.py +113 -17
  13. celldetective/gui/control_panel.py +68 -141
  14. celldetective/gui/generic_signal_plot.py +9 -12
  15. celldetective/gui/gui_utils.py +49 -21
  16. celldetective/gui/json_readers.py +5 -4
  17. celldetective/gui/layouts.py +246 -22
  18. celldetective/gui/measurement_options.py +32 -17
  19. celldetective/gui/neighborhood_options.py +10 -13
  20. celldetective/gui/plot_measurements.py +21 -17
  21. celldetective/gui/plot_signals_ui.py +131 -75
  22. celldetective/gui/process_block.py +180 -123
  23. celldetective/gui/processes/compute_neighborhood.py +594 -0
  24. celldetective/gui/processes/measure_cells.py +5 -0
  25. celldetective/gui/processes/segment_cells.py +27 -6
  26. celldetective/gui/processes/track_cells.py +6 -0
  27. celldetective/gui/retrain_segmentation_model_options.py +12 -20
  28. celldetective/gui/retrain_signal_model_options.py +57 -56
  29. celldetective/gui/seg_model_loader.py +21 -62
  30. celldetective/gui/signal_annotator.py +139 -72
  31. celldetective/gui/signal_annotator2.py +431 -635
  32. celldetective/gui/signal_annotator_options.py +8 -11
  33. celldetective/gui/survival_ui.py +49 -95
  34. celldetective/gui/tableUI.py +28 -25
  35. celldetective/gui/thresholds_gui.py +617 -1221
  36. celldetective/gui/viewers.py +106 -39
  37. celldetective/gui/workers.py +9 -3
  38. celldetective/io.py +73 -27
  39. celldetective/measure.py +63 -27
  40. celldetective/neighborhood.py +342 -268
  41. celldetective/preprocessing.py +25 -17
  42. celldetective/relative_measurements.py +50 -29
  43. celldetective/scripts/analyze_signals.py +4 -1
  44. celldetective/scripts/measure_relative.py +4 -1
  45. celldetective/scripts/segment_cells.py +0 -6
  46. celldetective/scripts/track_cells.py +3 -1
  47. celldetective/scripts/train_segmentation_model.py +7 -4
  48. celldetective/signals.py +29 -14
  49. celldetective/tracking.py +7 -2
  50. celldetective/utils.py +36 -8
  51. {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/METADATA +24 -16
  52. {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/RECORD +57 -55
  53. {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/WHEEL +1 -1
  54. tests/test_qt.py +21 -21
  55. {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/entry_points.txt +0 -0
  56. {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info/licenses}/LICENSE +0 -0
  57. {celldetective-1.3.9.post4.dist-info → celldetective-1.4.0.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,7 @@
1
- from PyQt5.QtWidgets import QWidget, QLineEdit, QMessageBox, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, \
1
+ from PyQt5.QtWidgets import QLineEdit, QMessageBox, QHBoxLayout, QVBoxLayout, QPushButton, QLabel, \
2
2
  QCheckBox, QRadioButton, QButtonGroup, QComboBox
3
3
  from PyQt5.QtCore import Qt, QSize
4
- from superqt import QLabeledSlider,QLabeledDoubleSlider, QSearchableComboBox
4
+ from superqt import QLabeledSlider, QLabeledDoubleSlider, QSearchableComboBox
5
5
  from superqt.fonticon import icon
6
6
  from fonticon_mdi6 import MDI6
7
7
 
@@ -11,11 +11,11 @@ import matplotlib.pyplot as plt
11
11
  import json
12
12
 
13
13
  from celldetective.gui.gui_utils import FigureCanvas, center_window, color_from_status, help_generic
14
- from celldetective.gui import Styles
14
+ from celldetective.gui.base_components import CelldetectiveWidget
15
15
  from celldetective.utils import get_software_location
16
16
  from celldetective.measure import classify_cells_from_query, interpret_track_classification
17
17
 
18
- class ClassifierWidget(QWidget, Styles):
18
+ class ClassifierWidget(CelldetectiveWidget):
19
19
 
20
20
  def __init__(self, parent_window):
21
21
 
@@ -33,7 +33,7 @@ class ClassifierWidget(QWidget, Styles):
33
33
 
34
34
  is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
35
35
  is_number_test = is_number(self.df.dtypes)
36
- self.cols = [col for t,col in zip(is_number_test,self.df.columns) if t]
36
+ self.cols = [col for t, col in zip(is_number_test, self.df.columns) if t]
37
37
 
38
38
  self.class_name = 'custom'
39
39
  self.name_le = QLineEdit(self.class_name)
@@ -44,7 +44,7 @@ class ClassifierWidget(QWidget, Styles):
44
44
 
45
45
 
46
46
  layout = QVBoxLayout(self)
47
- layout.setContentsMargins(30,30,30,30)
47
+ layout.setContentsMargins(30, 30, 30, 30)
48
48
 
49
49
  name_layout = QHBoxLayout()
50
50
  name_layout.addWidget(QLabel('class name: '), 33)
@@ -55,7 +55,7 @@ class ClassifierWidget(QWidget, Styles):
55
55
  fig_btn_hbox.addWidget(QLabel(''), 95)
56
56
  self.project_times_btn = QPushButton('')
57
57
  self.project_times_btn.setStyleSheet(self.parent_window.parent_window.parent_window.button_select_all)
58
- self.project_times_btn.setIcon(icon(MDI6.math_integral,color="black"))
58
+ self.project_times_btn.setIcon(icon(MDI6.math_integral, color="black"))
59
59
  self.project_times_btn.setToolTip("Project measurements at all times.")
60
60
  self.project_times_btn.setIconSize(QSize(20, 20))
61
61
  self.project_times = False
@@ -70,8 +70,8 @@ class ClassifierWidget(QWidget, Styles):
70
70
  # slider
71
71
  self.frame_slider = QLabeledSlider()
72
72
  self.frame_slider.setSingleStep(1)
73
- self.frame_slider.setOrientation(1)
74
- self.frame_slider.setRange(0,int(self.df.FRAME.max()) - 1)
73
+ self.frame_slider.setOrientation(Qt.Horizontal)
74
+ self.frame_slider.setRange(0, int(self.df.FRAME.max()) - 1)
75
75
  self.frame_slider.setValue(0)
76
76
  self.currentFrame = 0
77
77
 
@@ -84,8 +84,8 @@ class ClassifierWidget(QWidget, Styles):
84
84
  # transparency slider
85
85
  self.alpha_slider = QLabeledDoubleSlider()
86
86
  self.alpha_slider.setSingleStep(0.001)
87
- self.alpha_slider.setOrientation(1)
88
- self.alpha_slider.setRange(0,1)
87
+ self.alpha_slider.setOrientation(Qt.Horizontal)
88
+ self.alpha_slider.setRange(0, 1)
89
89
  self.alpha_slider.setValue(1.0)
90
90
  self.alpha_slider.setDecimals(3)
91
91
 
@@ -95,8 +95,8 @@ class ClassifierWidget(QWidget, Styles):
95
95
  layout.addLayout(slider_alpha_hbox)
96
96
 
97
97
 
98
- self.features_cb = [QSearchableComboBox() for i in range(2)]
99
- self.log_btns = [QPushButton() for i in range(2)]
98
+ self.features_cb = [QSearchableComboBox() for _ in range(2)]
99
+ self.log_btns = [QPushButton() for _ in range(2)]
100
100
 
101
101
  for i in range(2):
102
102
  hbox_feat = QHBoxLayout()
@@ -106,7 +106,7 @@ class ClassifierWidget(QWidget, Styles):
106
106
  layout.addLayout(hbox_feat)
107
107
 
108
108
  self.features_cb[i].clear()
109
- self.features_cb[i].addItems(sorted(list(self.cols),key=str.lower))
109
+ self.features_cb[i].addItems(sorted(list(self.cols), key=str.lower))
110
110
  self.features_cb[i].currentTextChanged.connect(self.update_props_scatter)
111
111
  self.features_cb[i].setCurrentIndex(i)
112
112
 
@@ -134,15 +134,15 @@ class ClassifierWidget(QWidget, Styles):
134
134
  self.time_corr.setEnabled(False)
135
135
 
136
136
  time_prop_hbox = QHBoxLayout()
137
- time_prop_hbox.addWidget(self.time_corr,alignment=Qt.AlignCenter)
137
+ time_prop_hbox.addWidget(self.time_corr, alignment=Qt.AlignCenter)
138
138
 
139
139
  self.help_propagate_btn = QPushButton()
140
- self.help_propagate_btn.setIcon(icon(MDI6.help_circle,color=self.help_color))
140
+ self.help_propagate_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
141
141
  self.help_propagate_btn.setIconSize(QSize(20, 20))
142
142
  self.help_propagate_btn.clicked.connect(self.help_propagate)
143
143
  self.help_propagate_btn.setStyleSheet(self.button_select_all)
144
144
  self.help_propagate_btn.setToolTip("Help.")
145
- time_prop_hbox.addWidget(self.help_propagate_btn,5,alignment=Qt.AlignRight)
145
+ time_prop_hbox.addWidget(self.help_propagate_btn, 5, alignment=Qt.AlignRight)
146
146
 
147
147
  layout.addLayout(time_prop_hbox)
148
148
 
@@ -154,23 +154,23 @@ class ClassifierWidget(QWidget, Styles):
154
154
 
155
155
  time_corr_layout = QHBoxLayout()
156
156
  time_corr_layout.addWidget(self.unique_state_btn, 33, alignment=Qt.AlignCenter)
157
- time_corr_layout.addWidget(self.irreversible_event_btn, 33,alignment=Qt.AlignCenter)
158
- time_corr_layout.addWidget(self.transient_event_btn, 33,alignment=Qt.AlignCenter)
157
+ time_corr_layout.addWidget(self.irreversible_event_btn, 33, alignment=Qt.AlignCenter)
158
+ time_corr_layout.addWidget(self.transient_event_btn, 33, alignment=Qt.AlignCenter)
159
159
  layout.addLayout(time_corr_layout)
160
160
 
161
161
  self.prereq_event_check = QCheckBox('prerequisite event:')
162
162
  self.prereq_event_check.toggled.connect(self.activate_prereq_cb)
163
163
  self.prereq_event_cb = QComboBox()
164
- event_cols = ['--'] + [c.replace('t_','') for c in self.cols if c.startswith('t_')]
164
+ event_cols = ['--'] + [c.replace('t_', '') for c in self.cols if c.startswith('t_')]
165
165
  self.prereq_event_cb.addItems(event_cols)
166
166
  self.prereq_event_check.setEnabled(False)
167
167
  self.prereq_event_cb.setEnabled(False)
168
168
 
169
169
  self.r2_slider = QLabeledDoubleSlider()
170
170
  self.r2_slider.setValue(0.75)
171
- self.r2_slider.setRange(0,1)
171
+ self.r2_slider.setRange(0, 1)
172
172
  self.r2_slider.setSingleStep(0.01)
173
- self.r2_slider.setOrientation(1)
173
+ self.r2_slider.setOrientation(Qt.Horizontal)
174
174
  self.r2_label = QLabel('R2 tolerance:')
175
175
  self.r2_label.setToolTip('Minimum R2 between the fit sigmoid and the binary response to the filters to accept the event.')
176
176
 
@@ -196,7 +196,7 @@ class ClassifierWidget(QWidget, Styles):
196
196
  wg.setEnabled(False)
197
197
 
198
198
  prereq_layout = QHBoxLayout()
199
- prereq_layout.setContentsMargins(30,0,0,0)
199
+ prereq_layout.setContentsMargins(30, 0, 0, 0)
200
200
  prereq_layout.addWidget(self.prereq_event_check, 20)
201
201
  prereq_layout.addWidget(self.prereq_event_cb, 80)
202
202
  layout.addLayout(prereq_layout)
@@ -212,7 +212,6 @@ class ClassifierWidget(QWidget, Styles):
212
212
  self.frame_slider.valueChanged.connect(self.set_frame)
213
213
  self.alpha_slider.valueChanged.connect(self.set_transparency)
214
214
 
215
- self.setAttribute(Qt.WA_DeleteOnClose)
216
215
 
217
216
  def activate_prereq_cb(self):
218
217
  if self.prereq_event_check.isChecked():
@@ -270,11 +269,10 @@ class ClassifierWidget(QWidget, Styles):
270
269
  Define properties scatter.
271
270
  """
272
271
 
273
- self.fig_props, self.ax_props = plt.subplots(figsize=(4,4),tight_layout=True)
272
+ self.fig_props, self.ax_props = plt.subplots(figsize=(4, 4), tight_layout=True)
274
273
  self.propscanvas = FigureCanvas(self.fig_props, interactive=True)
275
274
  self.fig_props.set_facecolor('none')
276
- self.fig_props.canvas.setStyleSheet("background-color: transparent;")
277
- self.scat_props = self.ax_props.scatter([],[], color="k", alpha=self.currentAlpha)
275
+ self.scat_props = self.ax_props.scatter([], [], color="k", alpha=self.currentAlpha)
278
276
  self.propscanvas.canvas.draw_idle()
279
277
  self.propscanvas.canvas.setMinimumHeight(self.screen_height//5)
280
278
 
@@ -295,25 +293,24 @@ class ClassifierWidget(QWidget, Styles):
295
293
  self.log_btns[0].setEnabled(True)
296
294
 
297
295
  if np.any(self.df[self.features_cb[1].currentText()].to_numpy() <= 0.):
298
- if self.ax_props.get_xscale()=='log':
296
+ if self.ax_props.get_xscale() == 'log':
299
297
  self.log_btns[1].click()
300
298
  self.log_btns[1].setEnabled(False)
301
299
  else:
302
300
  self.log_btns[1].setEnabled(True)
303
301
  except Exception as e:
304
- #print(e)
305
- pass
302
+ print(e)
306
303
 
307
304
  class_name = self.class_name
308
305
 
309
306
  try:
310
307
 
311
308
  if not self.project_times:
312
- self.scat_props.set_offsets(self.df.loc[self.df['FRAME']==self.currentFrame,[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
313
- colors = [color_from_status(c) for c in self.df.loc[self.df['FRAME']==self.currentFrame,class_name].to_numpy()]
309
+ self.scat_props.set_offsets(self.df.loc[self.df['FRAME'] == self.currentFrame, [self.features_cb[1].currentText(), self.features_cb[0].currentText()]].to_numpy())
310
+ colors = [color_from_status(c) for c in self.df.loc[self.df['FRAME'] == self.currentFrame, class_name].to_numpy()]
314
311
  self.scat_props.set_facecolor(colors)
315
312
  else:
316
- self.scat_props.set_offsets(self.df[[self.features_cb[1].currentText(),self.features_cb[0].currentText()]].to_numpy())
313
+ self.scat_props.set_offsets(self.df[[self.features_cb[1].currentText(), self.features_cb[0].currentText()]].to_numpy())
317
314
  colors = [color_from_status(c) for c in self.df[class_name].to_numpy()]
318
315
  self.scat_props.set_facecolor(colors)
319
316
 
@@ -333,18 +330,18 @@ class ClassifierWidget(QWidget, Styles):
333
330
 
334
331
  x_padding = (max_x - min_x) * 0.05
335
332
  y_padding = (max_y - min_y) * 0.05
336
- if x_padding==0:
333
+ if x_padding == 0:
337
334
  x_padding = 0.05
338
- if y_padding==0:
335
+ if y_padding == 0:
339
336
  y_padding = 0.05
340
337
 
341
- if min_x==min_x and max_x==max_x:
342
- if self.ax_props.get_xscale()=='linear':
338
+ if min_x == min_x and max_x == max_x:
339
+ if self.ax_props.get_xscale() == 'linear':
343
340
  self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
344
341
  else:
345
342
  self.ax_props.set_xlim(min_x, max_x)
346
- if min_y==min_y and max_y==max_y:
347
- if self.ax_props.get_yscale()=='linear':
343
+ if min_y == min_y and max_y == max_y:
344
+ if self.ax_props.get_yscale() == 'linear':
348
345
  self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
349
346
  else:
350
347
  self.ax_props.set_ylim(min_y, max_y)
@@ -379,7 +376,7 @@ class ClassifierWidget(QWidget, Styles):
379
376
  if self.df is None:
380
377
  msgBox = QMessageBox()
381
378
  msgBox.setIcon(QMessageBox.Warning)
382
- msgBox.setText(f"The query could not be understood. No filtering was applied. {e}")
379
+ msgBox.setText(f"The query could not be understood. No filtering was applied.")
383
380
  msgBox.setWindowTitle("Warning")
384
381
  msgBox.setStandardButtons(QMessageBox.Ok)
385
382
  returnValue = msgBox.exec()
@@ -390,22 +387,18 @@ class ClassifierWidget(QWidget, Styles):
390
387
  self.update_props_scatter(feature_changed=False)
391
388
 
392
389
  def set_frame(self, value):
393
- xlim=self.ax_props.get_xlim()
394
- ylim=self.ax_props.get_ylim()
390
+ xlim = self.ax_props.get_xlim()
391
+ ylim = self.ax_props.get_ylim()
395
392
  self.currentFrame = value
396
393
  self.update_props_scatter(feature_changed=False)
397
394
  self.ax_props.set_xlim(xlim)
398
395
  self.ax_props.set_ylim(ylim)
399
396
 
400
-
401
397
  def set_transparency(self, value):
402
- xlim=self.ax_props.get_xlim()
403
- ylim=self.ax_props.get_ylim()
398
+ xlim = self.ax_props.get_xlim()
399
+ ylim = self.ax_props.get_ylim()
404
400
  self.currentAlpha = value
405
- #fc = self.scat_props.get_facecolors()
406
- #fc[:, 3] = value
407
- #self.scat_props.set_facecolors(fc)
408
- #self.propscanvas.canvas.draw_idle()
401
+
409
402
  self.update_props_scatter(feature_changed=False)
410
403
  self.ax_props.set_xlim(xlim)
411
404
  self.ax_props.set_ylim(ylim)
@@ -413,12 +406,12 @@ class ClassifierWidget(QWidget, Styles):
413
406
  def switch_projection(self):
414
407
  if self.project_times:
415
408
  self.project_times = False
416
- self.project_times_btn.setIcon(icon(MDI6.math_integral,color="black"))
409
+ self.project_times_btn.setIcon(icon(MDI6.math_integral, color="black"))
417
410
  self.project_times_btn.setIconSize(QSize(20, 20))
418
411
  self.frame_slider.setEnabled(True)
419
412
  else:
420
413
  self.project_times = True
421
- self.project_times_btn.setIcon(icon(MDI6.math_integral_box,color="black"))
414
+ self.project_times_btn.setIcon(icon(MDI6.math_integral_box, color="black"))
422
415
  self.project_times_btn.setIconSize(QSize(20, 20))
423
416
  self.frame_slider.setEnabled(False)
424
417
  self.update_props_scatter(feature_changed=False)
@@ -432,12 +425,13 @@ class ClassifierWidget(QWidget, Styles):
432
425
 
433
426
  if self.time_corr.isChecked():
434
427
  self.class_name_user = 'class_'+self.name_le.text()
435
- print(f'User defined class name: {self.class_name_user}.')
428
+ print(f'User defined class name: {self.class_name_user}...')
436
429
  if self.class_name_user in self.df.columns:
437
430
 
438
431
  msgBox = QMessageBox()
439
432
  msgBox.setIcon(QMessageBox.Information)
440
- msgBox.setText(f"The class column {self.class_name_user} already exists in the table.\nProceeding will reclassify. Do you want to continue?")
433
+ msgBox.setText(f"The class column {self.class_name_user} already exists in the table.\nProceeding will "
434
+ f"reclassify. Do you want to continue?")
441
435
  msgBox.setWindowTitle("Warning")
442
436
  msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
443
437
  returnValue = msgBox.exec()
@@ -447,7 +441,6 @@ class ClassifierWidget(QWidget, Styles):
447
441
  return None
448
442
 
449
443
  name_map = {self.class_name: self.class_name_user}
450
- print(f"{name_map=}")
451
444
  self.df = self.df.drop(list(set(name_map.values()) & set(self.df.columns)), axis=1).rename(columns=name_map)
452
445
  self.df.reset_index(inplace=True, drop=True)
453
446
 
@@ -455,7 +448,7 @@ class ClassifierWidget(QWidget, Styles):
455
448
  if self.prereq_event_check.isChecked() and "t_"+self.prereq_event_cb.currentText() in self.cols:
456
449
  pre_event = self.prereq_event_cb.currentText()
457
450
 
458
- self.df = interpret_track_classification(self.df, self.class_name_user, irreversible_event=self.irreversible_event_btn.isChecked(), unique_state=self.unique_state_btn.isChecked(), transient_event=self.transient_event_btn.isChecked(),r2_threshold=self.r2_slider.value(), pre_event=pre_event)
451
+ self.df = interpret_track_classification(self.df, self.class_name_user, irreversible_event=self.irreversible_event_btn.isChecked(), unique_state=self.unique_state_btn.isChecked(), transient_event=self.transient_event_btn.isChecked(), r2_threshold=self.r2_slider.value(), pre_event=pre_event)
459
452
 
460
453
  else:
461
454
  self.group_name_user = 'group_' + self.name_le.text()
@@ -465,7 +458,8 @@ class ClassifierWidget(QWidget, Styles):
465
458
  msgBox = QMessageBox()
466
459
  msgBox.setIcon(QMessageBox.Information)
467
460
  msgBox.setText(
468
- f"The group column {self.group_name_user} already exists in the table.\nProceeding will reclassify. Do you want to continue?")
461
+ f"The group column {self.group_name_user} already exists in the table.\nProceeding will "
462
+ f"reclassify. Do you want to continue?")
469
463
  msgBox.setWindowTitle("Warning")
470
464
  msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
471
465
  returnValue = msgBox.exec()
@@ -477,14 +471,18 @@ class ClassifierWidget(QWidget, Styles):
477
471
  name_map = {self.class_name: self.group_name_user}
478
472
  self.df = self.df.drop(list(set(name_map.values()) & set(self.df.columns)), axis=1).rename(columns=name_map)
479
473
  print(self.df.columns)
480
- #self.df[self.group_name_user] = self.df[self.group_name_user].replace({0: 1, 1: 0})
474
+ # self.df[self.group_name_user] = self.df[self.group_name_user].replace({0: 1, 1: 0})
481
475
  self.df.reset_index(inplace=True, drop=True)
482
476
 
483
477
  if 'custom' in list(self.df.columns):
484
- self.df = self.df.drop(['custom'],axis=1)
485
-
486
- for pos,pos_group in self.df.groupby('position'):
487
- pos_group.to_csv(pos+os.sep.join(['output', 'tables', f'trajectories_{self.mode}.csv']), index=False)
478
+ self.df = self.df.drop(['custom'], axis=1)
479
+
480
+ self.fig_props.set_size_inches(4, 3)
481
+ self.fig_props.suptitle(self.property_query_le.text(), fontsize=10)
482
+ self.fig_props.tight_layout()
483
+ for pos, pos_group in self.df.groupby('position'):
484
+ self.fig_props.savefig(str(pos)+os.sep.join(['output', f'{self.class_name}.png']), bbox_inches='tight', dpi=300)
485
+ pos_group.to_csv(str(pos)+os.sep.join(['output', 'tables', f'trajectories_{self.mode}.csv']), index=False)
488
486
 
489
487
  self.parent_window.parent_window.update_position_options()
490
488
  self.close()
@@ -496,7 +494,7 @@ class ClassifierWidget(QWidget, Styles):
496
494
  Helper for segmentation strategy between threshold-based and Deep learning.
497
495
  """
498
496
 
499
- dict_path = os.sep.join([get_software_location(),'celldetective','gui','help','propagate-classification.json'])
497
+ dict_path = os.sep.join([get_software_location(), 'celldetective', 'gui', 'help', 'propagate-classification.json'])
500
498
 
501
499
  with open(dict_path) as f:
502
500
  d = json.load(f)
@@ -520,53 +518,46 @@ class ClassifierWidget(QWidget, Styles):
520
518
  Switch threshold histogram to log scale. Auto adjust.
521
519
  """
522
520
 
523
- if i==1:
521
+ if i == 1:
524
522
  try:
525
523
  feat_x = self.features_cb[1].currentText()
526
524
  min_x = self.df.dropna(subset=feat_x)[feat_x].min()
527
525
  max_x = self.df.dropna(subset=feat_x)[feat_x].max()
528
526
  x_padding = (max_x - min_x) * 0.05
529
- if x_padding==0:
527
+ if x_padding == 0:
530
528
  x_padding = 0.05
531
529
 
532
- if self.ax_props.get_xscale()=='linear':
530
+ if self.ax_props.get_xscale() == 'linear':
533
531
  self.ax_props.set_xlim(min_x, max_x)
534
532
  self.ax_props.set_xscale('log')
535
- self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
533
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="#1565c0"))
536
534
  else:
537
535
  self.ax_props.set_xscale('linear')
538
536
  self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
539
- self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
537
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
540
538
  except Exception as e:
541
539
  print(e)
542
- elif i==0:
540
+ elif i == 0:
543
541
  try:
544
542
  feat_y = self.features_cb[0].currentText()
545
543
  min_y = self.df.dropna(subset=feat_y)[feat_y].min()
546
544
  max_y = self.df.dropna(subset=feat_y)[feat_y].max()
547
545
  y_padding = (max_y - min_y) * 0.05
548
- if y_padding==0:
546
+ if y_padding == 0:
549
547
  y_padding = 0.05
550
548
 
551
- if self.ax_props.get_yscale()=='linear':
549
+ if self.ax_props.get_yscale() == 'linear':
552
550
  self.ax_props.set_ylim(min_y, max_y)
553
551
  self.ax_props.set_yscale('log')
554
- self.log_btns[i].setIcon(icon(MDI6.math_log,color="#1565c0"))
552
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="#1565c0"))
555
553
  else:
556
554
  self.ax_props.set_yscale('linear')
557
555
  self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
558
- self.log_btns[i].setIcon(icon(MDI6.math_log,color="black"))
556
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
559
557
  except Exception as e:
560
558
  print(e)
561
559
 
562
560
  self.ax_props.autoscale()
563
561
  self.propscanvas.canvas.draw_idle()
564
562
 
565
- print('Done.')
566
-
567
-
568
-
569
-
570
-
571
-
572
-
563
+ print('Done.')
@@ -1,7 +1,6 @@
1
- from PyQt5.QtWidgets import QMainWindow, QApplication, QMessageBox, QHBoxLayout, QFileDialog, QVBoxLayout, QScrollArea, QCheckBox, QGridLayout, QLabel, QLineEdit, QPushButton, QWidget
1
+ from PyQt5.QtWidgets import QApplication, QMessageBox, QHBoxLayout, QFileDialog, QVBoxLayout, QScrollArea, QCheckBox, QGridLayout, QLabel, QLineEdit, QPushButton
2
2
  from PyQt5.QtGui import QIntValidator, QDoubleValidator
3
3
  from celldetective.gui.gui_utils import center_window, help_generic
4
- from celldetective.gui.styles import Styles
5
4
  from celldetective.utils import get_software_location
6
5
  import json
7
6
 
@@ -13,9 +12,10 @@ from configparser import ConfigParser
13
12
  import os
14
13
  from functools import partial
15
14
  import numpy as np
16
- from celldetective.gui import Styles
15
+ from celldetective.gui import CelldetectiveMainWindow, CelldetectiveWidget
17
16
 
18
- class ConfigNewExperiment(QMainWindow, Styles):
17
+
18
+ class ConfigNewExperiment(CelldetectiveMainWindow):
19
19
 
20
20
  def __init__(self, parent_window=None):
21
21
 
@@ -34,11 +34,11 @@ class ConfigNewExperiment(QMainWindow, Styles):
34
34
 
35
35
  # Create button widget and layout
36
36
  self.scroll_area = QScrollArea(self)
37
- button_widget = QWidget()
37
+ button_widget = CelldetectiveWidget()
38
38
  self.grid = QGridLayout()
39
39
  button_widget.setLayout(self.grid)
40
40
 
41
- self.grid.setContentsMargins(30,30,30,30)
41
+ self.grid.setContentsMargins(30, 30, 30, 30)
42
42
  self.grid.addWidget(QLabel("Folder:"), 0, 0, 1, 3)
43
43
  self.supFolder = QLineEdit()
44
44
  self.supFolder.setAlignment(Qt.AlignLeft)
@@ -64,17 +64,20 @@ class ConfigNewExperiment(QMainWindow, Styles):
64
64
  self.grid.addWidget(self.expName, 3, 0, 1, 3)
65
65
 
66
66
  self.generate_movie_settings()
67
- self.grid.addLayout(self.ms_grid,29,0,1,3)
67
+ self.grid.addLayout(self.ms_grid, 29, 0, 1, 3)
68
68
 
69
69
  self.generate_channel_params_box()
70
- self.grid.addLayout(self.channel_grid,30,0,1,3)
70
+ self.grid.addLayout(self.channel_grid, 30, 0, 1, 3)
71
+
72
+ self.generate_population_params_box()
73
+ self.grid.addLayout(self.population_grid, 31, 0, 1, 3)
71
74
 
72
75
  self.validate_button = QPushButton("Submit")
73
76
  self.validate_button.clicked.connect(self.create_config)
74
77
  self.validate_button.setStyleSheet(self.button_style_sheet)
75
78
  #self.validate_button.setIcon(QIcon_from_svg(abs_path+f"/icons/process.svg", color='white'))
76
79
 
77
- self.grid.addWidget(self.validate_button, 31, 0, 1, 3, alignment = Qt.AlignBottom)
80
+ self.grid.addWidget(self.validate_button, 32, 0, 1, 3, alignment = Qt.AlignBottom)
78
81
  button_widget.adjustSize()
79
82
 
80
83
  self.scroll_area.setAlignment(Qt.AlignCenter)
@@ -128,10 +131,9 @@ class ConfigNewExperiment(QMainWindow, Styles):
128
131
  self.help_btn.setToolTip("Help.")
129
132
  self.ms_grid.addWidget(self.help_btn, 1, 0, 1, 3, alignment=Qt.AlignRight)
130
133
 
131
-
132
134
  self.SliderWells = QLabeledSlider(Qt.Horizontal, self)
133
135
  self.SliderWells.setMinimum(1)
134
- self.SliderWells.setMaximum(32)
136
+ self.SliderWells.setMaximum(512)
135
137
  self.ms_grid.addWidget(self.SliderWells, 2, 0, 1, 3, alignment=Qt.AlignTop)
136
138
 
137
139
  self.number_of_positions = QLabel("Number of positions per well:")
@@ -139,7 +141,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
139
141
 
140
142
  self.SliderPos = QLabeledSlider(Qt.Horizontal, self)
141
143
  self.SliderPos.setMinimum(1)
142
- self.SliderPos.setMaximum(50)
144
+ self.SliderPos.setMaximum(512)
143
145
 
144
146
  self.ms_grid.addWidget(self.SliderPos, 4, 0, 1, 3)
145
147
 
@@ -168,7 +170,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
168
170
  self.ms_grid.addWidget(self.movie_length,9, 0, 1, 3)
169
171
  self.MovieLengthSlider = QLabeledSlider(Qt.Horizontal, self)
170
172
  self.MovieLengthSlider.setMinimum(2)
171
- self.MovieLengthSlider.setMaximum(128)
173
+ #self.MovieLengthSlider.setMaximum(128)
172
174
  self.ms_grid.addWidget(self.MovieLengthSlider, 10, 0, 1, 3)
173
175
 
174
176
  self.prefix_lbl = QLabel("Prefix for the movies:")
@@ -181,7 +183,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
181
183
  self.movie_prefix_field.setText("")
182
184
  self.ms_grid.addWidget(self.movie_prefix_field, 12, 0, 1, 3)
183
185
 
184
- self.ms_grid.addWidget(QLabel("X shape in pixels:"), 13, 0, 1, 3)
186
+ self.ms_grid.addWidget(QLabel("Image width:"), 13, 0, 1, 3)
185
187
  self.shape_x_field = QLineEdit()
186
188
  self.shape_x_field.setValidator(onlyInt)
187
189
  self.shape_x_field.setAlignment(Qt.AlignLeft)
@@ -190,7 +192,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
190
192
  self.shape_x_field.setText("2048")
191
193
  self.ms_grid.addWidget(self.shape_x_field, 14, 0, 1, 3)
192
194
 
193
- self.ms_grid.addWidget(QLabel("Y shape in pixels:"), 15, 0, 1, 3)
195
+ self.ms_grid.addWidget(QLabel("Image height:"), 15, 0, 1, 3)
194
196
  self.shape_y_field = QLineEdit()
195
197
  self.shape_y_field.setValidator(onlyInt)
196
198
  self.shape_y_field.setAlignment(Qt.AlignLeft)
@@ -271,7 +273,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
271
273
 
272
274
  def add_custom_channel(self):
273
275
 
274
- self.CustomChannelWidget = QWidget()
276
+ self.CustomChannelWidget = CelldetectiveWidget()
275
277
  self.CustomChannelWidget.setWindowTitle("Custom channel")
276
278
  layout = QVBoxLayout()
277
279
  self.CustomChannelWidget.setLayout(layout)
@@ -323,6 +325,83 @@ class ConfigNewExperiment(QMainWindow, Styles):
323
325
  else:
324
326
  self.sliders[index].setEnabled(False)
325
327
 
328
+ def generate_population_params_box(self):
329
+
330
+ """
331
+ Parameters related to the movie channels
332
+ Rewrite all of it
333
+
334
+ """
335
+
336
+ self.population_grid = QGridLayout()
337
+ self.population_grid.setContentsMargins(21,30,20,30)
338
+
339
+ pop_lbl = QLabel("CELL POPULATIONS")
340
+ pop_lbl.setStyleSheet("""
341
+ font-weight: bold;
342
+ """)
343
+ self.population_grid.addWidget(pop_lbl, 0,0,1,3, alignment=Qt.AlignCenter)
344
+
345
+
346
+ self.populations = ['effectors','targets']
347
+ self.population_checkboxes = [QCheckBox() for i in range(len(self.populations))]
348
+
349
+ for i in range(len(self.populations)):
350
+
351
+ self.population_checkboxes[i].setText(self.populations[i])
352
+ self.population_checkboxes[i].setChecked(True)
353
+ self.population_grid.addWidget(self.population_checkboxes[i], i+1, 0, 1, 1)
354
+
355
+ # Add channel button
356
+ self.addPopBtn = QPushButton('Add a cell population')
357
+ self.addPopBtn.setIcon(icon(MDI6.plus,color="white"))
358
+ self.addPopBtn.setIconSize(QSize(25, 25))
359
+ self.addPopBtn.setStyleSheet(self.button_style_sheet)
360
+ self.addPopBtn.clicked.connect(self.add_custom_population)
361
+ self.population_grid.addWidget(self.addPopBtn, 1000, 0, 1, 1)
362
+
363
+
364
+ def add_custom_population(self):
365
+ self.CustomPopWidget = CelldetectiveWidget()
366
+ self.CustomPopWidget.setWindowTitle("Define custom population")
367
+ layout = QVBoxLayout()
368
+ self.CustomPopWidget.setLayout(layout)
369
+
370
+ self.name_le = QLineEdit()
371
+ self.name_le.setPlaceholderText('name')
372
+ self.name_le.textChanged.connect(self.check_population_name)
373
+ hbox = QHBoxLayout()
374
+ hbox.addWidget(QLabel('population name: '), 33)
375
+ hbox.addWidget(self.name_le, 66)
376
+ layout.addLayout(hbox)
377
+
378
+ self.addPopBtn = QPushButton('add')
379
+ self.addPopBtn.setStyleSheet(self.button_style_sheet)
380
+ self.addPopBtn.setEnabled(False)
381
+ self.addPopBtn.clicked.connect(self.write_custom_population)
382
+ layout.addWidget(self.addPopBtn)
383
+ center_window(self.CustomPopWidget)
384
+ self.CustomPopWidget.show()
385
+
386
+ def check_population_name(self, text):
387
+ # define all conditions for valid population name (like no space)
388
+ if len(text)>0 and ' ' not in text:
389
+ self.addPopBtn.setEnabled(True)
390
+ else:
391
+ self.addPopBtn.setEnabled(False)
392
+
393
+ def write_custom_population(self):
394
+
395
+ self.new_population_name = self.name_le.text()
396
+ name_map = self.new_population_name
397
+
398
+ self.populations.append(self.new_population_name)
399
+ self.population_checkboxes.append(QCheckBox())
400
+ self.CustomPopWidget.close()
401
+
402
+ self.population_checkboxes[-1].setText(self.populations[-1])
403
+ self.population_grid.addWidget(self.population_checkboxes[-1], len(self.populations)+1, 0, 1, 1)
404
+
326
405
  def browse_experiment_folder(self):
327
406
 
328
407
  """
@@ -362,6 +441,17 @@ class ConfigNewExperiment(QMainWindow, Styles):
362
441
  if returnValue == QMessageBox.Ok:
363
442
  return None
364
443
 
444
+ populations_checked = [self.population_checkboxes[i].isChecked() for i in range(len(self.population_checkboxes))]
445
+ if not np.any(populations_checked):
446
+ msgBox = QMessageBox()
447
+ msgBox.setIcon(QMessageBox.Warning)
448
+ msgBox.setText("Please set at least one cell population before proceeding...")
449
+ msgBox.setWindowTitle("Warning")
450
+ msgBox.setStandardButtons(QMessageBox.Ok)
451
+ returnValue = msgBox.exec()
452
+ if returnValue == QMessageBox.Ok:
453
+ return None
454
+
365
455
  sorted_list = set(channel_indices)
366
456
  expected_list = set(np.arange(max(sorted_list)+1))
367
457
  print(sorted_list, expected_list, sorted_list==expected_list)
@@ -429,8 +519,13 @@ class ConfigNewExperiment(QMainWindow, Styles):
429
519
  Write all user input parameters to a configuration file associated to an experiment.
430
520
  """
431
521
 
522
+
432
523
  config = ConfigParser(interpolation=None)
433
524
 
525
+ config.add_section('Populations')
526
+ pops = ','.join([self.populations[i].lower() for i in range(len(self.population_checkboxes)) if self.population_checkboxes[i].isChecked()])
527
+ config.set('Populations','populations', pops)
528
+
434
529
  # add a new section and some values
435
530
  config.add_section('MovieSettings')
436
531
  config.set('MovieSettings', 'PxToUm', self.PxToUm_field.text().replace(',','.'))
@@ -456,6 +551,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
456
551
  config.add_section('Metadata')
457
552
  config.set('Metadata', 'concentration_units', self.concentration_units)
458
553
 
554
+
459
555
  # save to a file
460
556
  with open('config.ini', 'w') as configfile:
461
557
  config.write(configfile)
@@ -464,7 +560,7 @@ class ConfigNewExperiment(QMainWindow, Styles):
464
560
  print(f'New experiment successfully configured in folder {self.directory}...')
465
561
  self.close()
466
562
 
467
- class SetupConditionLabels(QWidget, Styles):
563
+ class SetupConditionLabels(CelldetectiveWidget):
468
564
  def __init__(self, parent_window, n_wells):
469
565
  super().__init__()
470
566
  self.parent_window = parent_window