celldetective 1.5.0b7__py3-none-any.whl → 1.5.0b9__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 (33) hide show
  1. celldetective/_version.py +1 -1
  2. celldetective/event_detection_models.py +2463 -0
  3. celldetective/gui/base/channel_norm_generator.py +19 -3
  4. celldetective/gui/base/figure_canvas.py +1 -1
  5. celldetective/gui/base/list_widget.py +1 -1
  6. celldetective/gui/base_annotator.py +2 -5
  7. celldetective/gui/event_annotator.py +248 -138
  8. celldetective/gui/generic_signal_plot.py +14 -14
  9. celldetective/gui/gui_utils.py +27 -6
  10. celldetective/gui/pair_event_annotator.py +146 -20
  11. celldetective/gui/plot_signals_ui.py +32 -15
  12. celldetective/gui/process_block.py +2 -2
  13. celldetective/gui/seg_model_loader.py +4 -4
  14. celldetective/gui/settings/_settings_event_model_training.py +32 -14
  15. celldetective/gui/settings/_settings_segmentation_model_training.py +5 -5
  16. celldetective/gui/settings/_settings_signal_annotator.py +0 -19
  17. celldetective/gui/survival_ui.py +39 -11
  18. celldetective/gui/tableUI.py +69 -148
  19. celldetective/gui/thresholds_gui.py +45 -5
  20. celldetective/gui/viewers/base_viewer.py +17 -20
  21. celldetective/gui/viewers/spot_detection_viewer.py +136 -27
  22. celldetective/processes/train_signal_model.py +1 -1
  23. celldetective/scripts/train_signal_model.py +1 -1
  24. celldetective/signals.py +4 -2426
  25. celldetective/utils/event_detection/__init__.py +1 -1
  26. {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/METADATA +1 -1
  27. {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/RECORD +33 -31
  28. tests/gui/test_spot_detection_viewer.py +187 -0
  29. tests/test_signals.py +135 -116
  30. {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/WHEEL +0 -0
  31. {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/entry_points.txt +0 -0
  32. {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/licenses/LICENSE +0 -0
  33. {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/top_level.txt +0 -0
@@ -29,6 +29,7 @@ import matplotlib.pyplot as plt
29
29
 
30
30
  plt.rcParams["svg.fonttype"] = "none"
31
31
  from glob import glob
32
+ from celldetective import get_logger
32
33
  from matplotlib.cm import tab10
33
34
  from celldetective.gui.base.components import CelldetectiveWidget
34
35
  import matplotlib.cm as mcm
@@ -36,6 +37,8 @@ import pandas as pd
36
37
 
37
38
  from lifelines.utils import qth_survival_times
38
39
 
40
+ logger = get_logger(__name__)
41
+
39
42
 
40
43
  class GenericSignalPlotWidget(CelldetectiveWidget):
41
44
 
@@ -317,7 +320,7 @@ class GenericSignalPlotWidget(CelldetectiveWidget):
317
320
  alpha = value
318
321
  try:
319
322
  alpha = float(alpha)
320
- except:
323
+ except (ValueError, TypeError):
321
324
  return None
322
325
  if alpha > 1.0:
323
326
  alpha = 1.0
@@ -440,7 +443,7 @@ class GenericSignalPlotWidget(CelldetectiveWidget):
440
443
  + glob(self.parent_window.exp_dir + os.sep.join([f"W*", "*metadata.txt"]))
441
444
  + glob(self.parent_window.exp_dir + "*metadata.txt")
442
445
  )
443
- print(f"Found {len(self.metafiles)} metadata files...")
446
+ logger.debug(f"Found {len(self.metafiles)} metadata files.")
444
447
  if len(self.metafiles) > 0:
445
448
  self.metadata_found = True
446
449
 
@@ -455,17 +458,17 @@ class GenericSignalPlotWidget(CelldetectiveWidget):
455
458
  data = json.load(f)
456
459
  positions = data["Summary"]["InitialPositionList"]
457
460
  except Exception as e:
458
- print(f"Trouble loading metadata: error {e}...")
461
+ logger.debug(f"Trouble loading metadata: {e}")
459
462
  return None
460
463
 
461
464
  for k in range(len(positions)):
462
465
  pos_label = positions[k]["Label"]
463
466
  try:
464
467
  coords = positions[k]["DeviceCoordinatesUm"]["XYStage"]
465
- except:
468
+ except KeyError:
466
469
  try:
467
470
  coords = positions[k]["DeviceCoordinatesUm"]["PIXYStage"]
468
- except:
471
+ except KeyError:
469
472
  self.no_meta = True
470
473
 
471
474
  if not self.no_meta:
@@ -515,8 +518,8 @@ class GenericSignalPlotWidget(CelldetectiveWidget):
515
518
  self.fig_scatter.tight_layout()
516
519
  self.fig_scatter.canvas.mpl_connect("motion_notify_event", self.hover)
517
520
  self.fig_scatter.canvas.mpl_connect("pick_event", self.unselect_position)
518
- except Exception as e:
519
- pass
521
+ except (KeyError, TypeError) as e:
522
+ logger.debug(f"Could not plot spatial location: {e}")
520
523
 
521
524
  def update_annot(self, ind):
522
525
 
@@ -616,19 +619,16 @@ class GenericSignalPlotWidget(CelldetectiveWidget):
616
619
  leg.set_visible(True)
617
620
 
618
621
  def show_hide_legend(self):
619
-
622
+ """Toggle legend visibility and replot to sync state."""
620
623
  if self.legend_visible:
621
- leg = self.ax.get_legend()
622
- leg.set_visible(False)
623
624
  self.legend_visible = False
624
625
  self.legend_btn.setIcon(icon(MDI6.text_box, color="black"))
625
626
  else:
626
- leg = self.ax.get_legend()
627
- leg.set_visible(True)
628
627
  self.legend_visible = True
629
628
  self.legend_btn.setIcon(icon(MDI6.text_box, color=self.help_color))
630
629
 
631
- self.plot_widget.canvas.draw_idle()
630
+ # Replot to sync legend state
631
+ self.plot_signals(0)
632
632
 
633
633
  def switch_to_log(self):
634
634
  """
@@ -886,7 +886,7 @@ class GenericSignalPlotWidget(CelldetectiveWidget):
886
886
  try:
887
887
  self.plot_signals(0)
888
888
  except Exception as e:
889
- print(f"{e=}")
889
+ logger.debug(f"Error plotting signals: {e}")
890
890
 
891
891
  def switch_cell_lines(self):
892
892
 
@@ -35,11 +35,19 @@ class PreprocessingLayout(QVBoxLayout, Styles):
35
35
  A widget that allows user to choose preprocessing filters for an image
36
36
  """
37
37
 
38
- def __init__(self, parent_window=None, apply_btn_option=True, *args, **kwargs):
38
+ def __init__(
39
+ self,
40
+ parent_window=None,
41
+ apply_btn_option=True,
42
+ extra_widget=None,
43
+ *args,
44
+ **kwargs,
45
+ ):
39
46
  super().__init__(*args, **kwargs)
40
47
 
41
48
  self.parent_window = parent_window
42
49
  self.apply_btn_option = apply_btn_option
50
+ self.extra_widget = extra_widget
43
51
  self.generate_components()
44
52
  self.add_to_layout()
45
53
 
@@ -48,7 +56,12 @@ class PreprocessingLayout(QVBoxLayout, Styles):
48
56
  self.setContentsMargins(20, 20, 20, 20)
49
57
 
50
58
  button_layout = QHBoxLayout()
51
- button_layout.addWidget(self.preprocess_lbl, 85, alignment=Qt.AlignLeft)
59
+ v_layout = QVBoxLayout()
60
+ v_layout.addWidget(self.preprocess_lbl, alignment=Qt.AlignLeft)
61
+ if self.extra_widget is not None:
62
+ v_layout.addWidget(self.extra_widget, alignment=Qt.AlignLeft)
63
+
64
+ button_layout.addLayout(v_layout, 85)
52
65
  button_layout.addWidget(self.delete_filter_btn, 5)
53
66
  button_layout.addWidget(self.add_filter_btn, 5)
54
67
  button_layout.addWidget(self.help_prefilter_btn, 5)
@@ -129,10 +142,12 @@ class PreprocessingLayout(QVBoxLayout, Styles):
129
142
 
130
143
  class PreprocessingLayout2(PreprocessingLayout):
131
144
 
132
- def __init__(self, fraction=75, *args, **kwargs):
145
+ def __init__(self, fraction=75, extra_widget=None, *args, **kwargs):
133
146
 
134
147
  self.fraction = fraction
135
- super().__init__(apply_btn_option=False, *args, **kwargs)
148
+ super().__init__(
149
+ apply_btn_option=False, extra_widget=extra_widget, *args, **kwargs
150
+ )
136
151
  self.preprocess_lbl.setText("Preprocessing: ")
137
152
  self.preprocess_lbl.setStyleSheet("")
138
153
  self.setContentsMargins(0, 0, 0, 0)
@@ -142,12 +157,18 @@ class PreprocessingLayout2(PreprocessingLayout):
142
157
  main_layout = QHBoxLayout()
143
158
  main_layout.setContentsMargins(0, 0, 0, 0)
144
159
  main_layout.setSpacing(5)
160
+ main_layout.setSpacing(5)
161
+
145
162
  main_layout.addWidget(self.preprocess_lbl, self.fraction, alignment=Qt.AlignTop)
146
163
 
147
164
  list_grid = QGridLayout()
148
165
  list_grid.addWidget(self.list, 0, 0, 2, 2)
149
166
  list_grid.addWidget(self.add_filter_btn, 0, 2, 1, 1)
150
167
  list_grid.addWidget(self.delete_filter_btn, 1, 2, 1, 1)
168
+
169
+ if self.extra_widget is not None:
170
+ list_grid.addWidget(self.extra_widget, 2, 0, 1, 3, alignment=Qt.AlignRight)
171
+
151
172
  main_layout.addLayout(list_grid, 100 - self.fraction)
152
173
  self.add_filter_btn.setFixedWidth(35) # Ensure the button width is fixed
153
174
  self.delete_filter_btn.setFixedWidth(35)
@@ -449,8 +470,6 @@ class FilterChoice(CelldetectiveWidget):
449
470
  def add_current_feature(self):
450
471
 
451
472
  filtername = self.combo_box.currentText()
452
- self.parent_window.list_widget.addItems([filtername])
453
-
454
473
  filter_instructions = [filtername.split("_")[0]]
455
474
  for a in self.arguments_le:
456
475
 
@@ -467,7 +486,9 @@ class FilterChoice(CelldetectiveWidget):
467
486
 
468
487
  print(f"You added filter {filter_instructions}.")
469
488
 
489
+ # Update items BEFORE adding to widget to ensure signals pick up the new state
470
490
  self.parent_window.items.append(filter_instructions)
491
+ self.parent_window.list_widget.addItems([filtername])
471
492
  self.close()
472
493
 
473
494
  def update_arguments(self):
@@ -21,7 +21,7 @@ from celldetective.gui.base.components import (
21
21
  CelldetectiveWidget,
22
22
  )
23
23
  from celldetective.gui.base.utils import center_window
24
- from superqt import QLabeledDoubleRangeSlider, QSearchableComboBox
24
+ from superqt import QLabeledDoubleRangeSlider, QSearchableComboBox, QLabeledSlider
25
25
  from celldetective import (
26
26
  get_software_location,
27
27
  )
@@ -466,11 +466,26 @@ class PairEventAnnotator(CelldetectiveMainWindow):
466
466
  # Animation
467
467
  animation_buttons_box = QHBoxLayout()
468
468
 
469
+ self.speed_slider = QLabeledSlider(Qt.Horizontal)
470
+ self.speed_slider.setRange(1, 60)
471
+ # Convert initial interval (ms) to FPS
472
+ initial_fps = int(1000 / max(1, self.anim_interval))
473
+ self.speed_slider.setValue(initial_fps)
474
+ self.speed_slider.valueChanged.connect(self.update_speed)
475
+ self.speed_slider.setFixedWidth(200)
476
+ self.speed_slider.setToolTip("Adjust animation framerate (FPS)")
477
+
478
+ speed_layout = QHBoxLayout()
479
+ speed_layout.addWidget(QLabel("Framerate: "))
480
+ speed_layout.addWidget(self.speed_slider)
481
+
469
482
  animation_buttons_box.addWidget(self.frame_lbl, 20, alignment=Qt.AlignLeft)
483
+ animation_buttons_box.addLayout(speed_layout, 20)
470
484
 
471
485
  self.first_frame_btn = QPushButton()
472
486
  self.first_frame_btn.clicked.connect(self.set_first_frame)
473
- self.first_frame_btn.setShortcut(QKeySequence("f"))
487
+ self.first_short = QShortcut(QKeySequence("f"), self)
488
+ self.first_short.activated.connect(self.set_first_frame)
474
489
  self.first_frame_btn.setIcon(icon(MDI6.page_first, color="black"))
475
490
  self.first_frame_btn.setStyleSheet(self.button_select_all)
476
491
  self.first_frame_btn.setFixedSize(QSize(60, 60))
@@ -478,7 +493,8 @@ class PairEventAnnotator(CelldetectiveMainWindow):
478
493
 
479
494
  self.last_frame_btn = QPushButton()
480
495
  self.last_frame_btn.clicked.connect(self.set_last_frame)
481
- self.last_frame_btn.setShortcut(QKeySequence("l"))
496
+ self.last_short = QShortcut(QKeySequence("l"), self)
497
+ self.last_short.activated.connect(self.set_last_frame)
482
498
  self.last_frame_btn.setIcon(icon(MDI6.page_last, color="black"))
483
499
  self.last_frame_btn.setStyleSheet(self.button_select_all)
484
500
  self.last_frame_btn.setFixedSize(QSize(60, 60))
@@ -499,11 +515,32 @@ class PairEventAnnotator(CelldetectiveMainWindow):
499
515
  self.start_btn.setIconSize(QSize(30, 30))
500
516
  self.start_btn.hide()
501
517
 
518
+ self.toggle_short = QShortcut(Qt.Key_Space, self)
519
+ self.toggle_short.activated.connect(self.toggle_animation)
520
+
521
+ self.prev_frame_btn = QPushButton()
522
+ self.prev_frame_btn.clicked.connect(self.prev_frame)
523
+ self.prev_frame_btn.setIcon(icon(MDI6.chevron_left, color="black"))
524
+ self.prev_frame_btn.setStyleSheet(self.button_select_all)
525
+ self.prev_frame_btn.setFixedSize(QSize(60, 60))
526
+ self.prev_frame_btn.setIconSize(QSize(30, 30))
527
+ self.prev_frame_btn.setEnabled(False)
528
+
529
+ self.next_frame_btn = QPushButton()
530
+ self.next_frame_btn.clicked.connect(self.next_frame)
531
+ self.next_frame_btn.setIcon(icon(MDI6.chevron_right, color="black"))
532
+ self.next_frame_btn.setStyleSheet(self.button_select_all)
533
+ self.next_frame_btn.setFixedSize(QSize(60, 60))
534
+ self.next_frame_btn.setIconSize(QSize(30, 30))
535
+ self.next_frame_btn.setEnabled(False)
536
+
502
537
  animation_buttons_box.addWidget(
503
538
  self.first_frame_btn, 5, alignment=Qt.AlignRight
504
539
  )
540
+ animation_buttons_box.addWidget(self.prev_frame_btn, 5, alignment=Qt.AlignRight)
505
541
  animation_buttons_box.addWidget(self.stop_btn, 5, alignment=Qt.AlignRight)
506
542
  animation_buttons_box.addWidget(self.start_btn, 5, alignment=Qt.AlignRight)
543
+ animation_buttons_box.addWidget(self.next_frame_btn, 5, alignment=Qt.AlignRight)
507
544
  animation_buttons_box.addWidget(self.last_frame_btn, 5, alignment=Qt.AlignRight)
508
545
 
509
546
  self.right_panel.addLayout(animation_buttons_box, 5)
@@ -1968,10 +2005,7 @@ class PairEventAnnotator(CelldetectiveMainWindow):
1968
2005
  else:
1969
2006
  self.fraction = 0.25
1970
2007
 
1971
- if "interval" in instructions:
1972
- self.anim_interval = int(instructions["interval"])
1973
- else:
1974
- self.anim_interval = 1
2008
+ self.anim_interval = 33
1975
2009
 
1976
2010
  if "log" in instructions:
1977
2011
  self.log_option = instructions["log"]
@@ -1983,7 +2017,7 @@ class PairEventAnnotator(CelldetectiveMainWindow):
1983
2017
  self.percentile_mode = True
1984
2018
  self.target_channels = [[self.channel_names[0], 0.01, 99.99]]
1985
2019
  self.fraction = 0.25
1986
- self.anim_interval = 1
2020
+ self.anim_interval = 33
1987
2021
 
1988
2022
  def prepare_stack(self):
1989
2023
 
@@ -2104,6 +2138,49 @@ class PairEventAnnotator(CelldetectiveMainWindow):
2104
2138
  idx = self.relative_class_choice_cb.findText(self.relative_class)
2105
2139
  self.relative_class_choice_cb.setCurrentIndex(idx)
2106
2140
 
2141
+ def update_speed(self):
2142
+ fps = self.speed_slider.value()
2143
+ # Convert FPS to interval in ms
2144
+ # FPS = 1000 / interval_ms => interval_ms = 1000 / FPS
2145
+ val = int(1000 / max(1, fps))
2146
+
2147
+ self.anim_interval = val
2148
+ print(
2149
+ f"DEBUG: Speed slider moved. FPS: {fps} -> Interval: {val} ms. Recreating animation object."
2150
+ )
2151
+
2152
+ # Check if animation is allowed to run (Pause button is visible means we are Playing)
2153
+ should_play = self.stop_btn.isVisible()
2154
+
2155
+ if hasattr(self, "anim") and self.anim:
2156
+ try:
2157
+ self.anim.event_source.stop()
2158
+ except Exception as e:
2159
+ print(f"DEBUG: Error stopping animation: {e}")
2160
+
2161
+ # Recreate animation with new interval
2162
+ try:
2163
+ # We must disconnect the old pick event to avoid accumulating connections
2164
+ # although mpl_connect returns a cid, we didn't store it properly before.
2165
+ # However, the canvas clears usually handle this if we cleared axes, but we aren't clearing axes here.
2166
+ # Ideally we should clean up, but for now let's focus on the animation object replacement.
2167
+
2168
+ self.anim = FuncAnimation(
2169
+ self.fig,
2170
+ self.draw_frame,
2171
+ frames=self.animation_generator,
2172
+ interval=self.anim_interval,
2173
+ blit=True,
2174
+ cache_frame_data=False,
2175
+ )
2176
+
2177
+ # If we were NOT playing (i.e. Paused), pause the new animation immediately
2178
+ if not should_play:
2179
+ self.anim.event_source.stop()
2180
+
2181
+ except Exception as e:
2182
+ print(f"DEBUG: Error recreating animation: {e}")
2183
+
2107
2184
  def closeEvent(self, event):
2108
2185
 
2109
2186
  self.stop()
@@ -2119,6 +2196,18 @@ class PairEventAnnotator(CelldetectiveMainWindow):
2119
2196
 
2120
2197
  gc.collect()
2121
2198
 
2199
+ def animation_generator(self):
2200
+ """
2201
+ Generator yielding frame indices for the animation,
2202
+ starting from the current self.framedata.
2203
+ """
2204
+ i = self.framedata
2205
+ while True:
2206
+ yield i
2207
+ i += 1
2208
+ if i >= self.len_movie:
2209
+ i = 0
2210
+
2122
2211
  def looped_animation(self):
2123
2212
  """
2124
2213
  Load an image.
@@ -2195,9 +2284,10 @@ class PairEventAnnotator(CelldetectiveMainWindow):
2195
2284
  self.anim = FuncAnimation(
2196
2285
  self.fig,
2197
2286
  self.draw_frame,
2198
- frames=self.len_movie, # better would be to cast np.arange(len(movie)) in case frame column is incomplete
2287
+ frames=self.animation_generator,
2199
2288
  interval=self.anim_interval, # in ms
2200
2289
  blit=True,
2290
+ cache_frame_data=False,
2201
2291
  )
2202
2292
 
2203
2293
  self.fig.canvas.mpl_connect("pick_event", self.on_scatter_pick)
@@ -2855,27 +2945,63 @@ class PairEventAnnotator(CelldetectiveMainWindow):
2855
2945
  self.stop_btn.hide()
2856
2946
  self.start_btn.show()
2857
2947
  self.anim.pause()
2948
+ self.prev_frame_btn.setEnabled(True)
2949
+ self.next_frame_btn.setEnabled(True)
2858
2950
  self.stop_btn.clicked.connect(self.start)
2859
2951
 
2860
2952
  def start(self):
2861
2953
  """
2862
- Starts interactive animation. Adds the draw frame command to the GUI
2863
- handler, calls show to start the event loop.
2954
+ Starts interactive animation.
2864
2955
  """
2865
- self.start_btn.setShortcut(QKeySequence(""))
2866
-
2867
- self.last_frame_btn.setEnabled(True)
2868
- self.last_frame_btn.clicked.connect(self.set_last_frame)
2869
-
2870
- self.first_frame_btn.setEnabled(True)
2871
- self.first_frame_btn.clicked.connect(self.set_first_frame)
2872
-
2873
2956
  self.start_btn.hide()
2874
2957
  self.stop_btn.show()
2875
2958
 
2876
- self.anim.event_source.start()
2959
+ self.prev_frame_btn.setEnabled(False)
2960
+ self.next_frame_btn.setEnabled(False)
2961
+
2962
+ self.anim.resume()
2877
2963
  self.stop_btn.clicked.connect(self.stop)
2878
2964
 
2965
+ def toggle_animation(self):
2966
+ if self.stop_btn.isVisible():
2967
+ self.stop()
2968
+ else:
2969
+ self.start()
2970
+
2971
+ def next_frame(self):
2972
+ self.framedata += 1
2973
+ if self.framedata >= self.len_movie:
2974
+ self.framedata = 0
2975
+ self.draw_frame(self.framedata)
2976
+ self.fcanvas.canvas.draw()
2977
+
2978
+ def prev_frame(self):
2979
+ self.framedata -= 1
2980
+ if self.framedata < 0:
2981
+ self.framedata = self.len_movie - 1
2982
+ self.draw_frame(self.framedata)
2983
+ self.fcanvas.canvas.draw()
2984
+
2985
+ def set_first_frame(self):
2986
+ self.stop()
2987
+ self.framedata = 0
2988
+ self.draw_frame(self.framedata)
2989
+ self.fcanvas.canvas.draw()
2990
+
2991
+ def set_last_frame(self):
2992
+ self.stop()
2993
+ self.framedata = len(self.stack) - 1
2994
+ while len(np.where(self.stack[self.framedata].flatten() == 0)[0]) > 0.99 * len(
2995
+ self.stack[self.framedata].flatten()
2996
+ ):
2997
+ self.framedata -= 1
2998
+ if self.framedata < 0:
2999
+ self.framedata = 0
3000
+ break
3001
+
3002
+ self.draw_frame(self.framedata)
3003
+ self.fcanvas.canvas.draw()
3004
+
2879
3005
  def give_reference_cell_information(self):
2880
3006
 
2881
3007
  df_reference = self.dataframes[self.reference_population]
@@ -28,6 +28,7 @@ import matplotlib.pyplot as plt
28
28
 
29
29
  plt.rcParams["svg.fonttype"] = "none"
30
30
  from glob import glob
31
+ from celldetective import get_logger
31
32
  from natsort import natsorted
32
33
  import math
33
34
  from celldetective.gui.base.components import CelldetectiveWidget
@@ -36,6 +37,8 @@ import matplotlib.cm
36
37
  from celldetective.relative_measurements import expand_pair_table
37
38
  from celldetective.neighborhood import extract_neighborhood_in_pair_table
38
39
 
40
+ logger = get_logger(__name__)
41
+
39
42
 
40
43
  class ConfigSignalPlot(CelldetectiveWidget):
41
44
  """
@@ -173,7 +176,7 @@ class ConfigSignalPlot(CelldetectiveWidget):
173
176
  if hasattr(matplotlib.cm, str(cm).lower()):
174
177
  try:
175
178
  self.cbs[-1].addColormap(cm.lower())
176
- except:
179
+ except Exception:
177
180
  pass
178
181
 
179
182
  self.cbs[0].setCurrentIndex(1)
@@ -257,7 +260,9 @@ class ConfigSignalPlot(CelldetectiveWidget):
257
260
  )
258
261
  )
259
262
  if not tables_pairs:
260
- print("No pair table found... please compute the pair measurements...")
263
+ logger.warning(
264
+ "No pair table found. Please compute the pair measurements."
265
+ )
261
266
  return None
262
267
  self.cols_pairs = extract_cols_from_table_list(tables_pairs)
263
268
 
@@ -333,7 +338,9 @@ class ConfigSignalPlot(CelldetectiveWidget):
333
338
  time_idx = np.array(
334
339
  [s.startswith("t_") or s.startswith("t0_") for s in self.all_columns]
335
340
  )
336
- print(f"{class_idx=} {time_idx=} {self.all_columns=}")
341
+ logger.debug(
342
+ f"class_idx={class_idx}, time_idx={time_idx}, columns={self.all_columns}"
343
+ )
337
344
 
338
345
  try:
339
346
  if len(class_idx) > 0:
@@ -345,7 +352,7 @@ class ConfigSignalPlot(CelldetectiveWidget):
345
352
  else:
346
353
  time_columns = []
347
354
  except Exception as e:
348
- print(f"L266 columns not found {e}")
355
+ logger.warning(f"Columns not found: {e}")
349
356
  self.auto_close = True
350
357
  return None
351
358
 
@@ -435,8 +442,20 @@ class ConfigSignalPlot(CelldetectiveWidget):
435
442
  query_text = self.query_le.text()
436
443
  if query_text != "":
437
444
  self.df = self.df.query(query_text)
445
+ except pd.errors.UndefinedVariableError as e:
446
+ logger.warning(f"Query failed - undefined variable: {e}")
447
+ generic_message(
448
+ f"Query error: column not found.\n{e}\n\nPlease check your column names."
449
+ )
450
+ return None
451
+ except SyntaxError as e:
452
+ logger.warning(f"Query failed - syntax error: {e}")
453
+ generic_message(f"Query syntax error: {e}\n\nCheck your query format.")
454
+ return None
438
455
  except Exception as e:
439
- print(e, " The query is misunderstood and will not be applied...")
456
+ logger.warning(f"Query not applied: {e}")
457
+ generic_message(f"Query could not be applied: {e}")
458
+ return None
440
459
 
441
460
  self.feature_selected = self.feature_cb.currentText()
442
461
  self.feature_choice_widget.close()
@@ -454,17 +473,17 @@ class ConfigSignalPlot(CelldetectiveWidget):
454
473
  )
455
474
  self.plot_window.show()
456
475
  except Exception as e:
457
- print(f"{e=}")
476
+ logger.debug(f"Error creating plot widget: {e}")
458
477
 
459
478
  def process_signal(self):
460
479
 
461
480
  self.FrameToMin = float(self.time_calibration_le.text().replace(",", "."))
462
- print(f"Time calibration set to 1 frame = {self.FrameToMin} min...")
481
+ logger.info(f"Time calibration set to 1 frame = {self.FrameToMin} min")
463
482
 
464
483
  # read instructions from combobox options
465
484
  self.load_available_tables()
466
485
  class_col = self.class_columns[self.cbs[1].currentIndex()]
467
- print(f"{class_col=}")
486
+ logger.debug(f"Selected class column: {class_col}")
468
487
 
469
488
  if self.df is not None:
470
489
 
@@ -512,8 +531,8 @@ class ConfigSignalPlot(CelldetectiveWidget):
512
531
  )
513
532
 
514
533
  if self.df is None:
515
- print("No table could be found...")
516
- generic_message("No table could be found to compute survival...")
534
+ logger.warning("No table could be found.")
535
+ generic_message("No table could be found to compute signals.")
517
536
  self.close()
518
537
  return None
519
538
  else:
@@ -533,9 +552,7 @@ class ConfigSignalPlot(CelldetectiveWidget):
533
552
  # Per position signal
534
553
  self.df = self.df.dropna(subset=["FRAME"])
535
554
  if len(self.df) == 0:
536
- print(
537
- "Warning... The dataset is empty. Please check your filters. Abort..."
538
- )
555
+ logger.warning("The dataset is empty. Please check your filters.")
539
556
  return None
540
557
 
541
558
  pairs = False
@@ -714,7 +731,7 @@ class ConfigSignalPlot(CelldetectiveWidget):
714
731
  ):
715
732
 
716
733
  if "area" in list(track_group.columns):
717
- print("area in list")
734
+ logger.debug("Using 'area' column for first detection")
718
735
  feat = track_group["area"].values
719
736
  else:
720
737
  feat = feature
@@ -731,7 +748,7 @@ class ConfigSignalPlot(CelldetectiveWidget):
731
748
  matrix[cid, loc_t + 1] = second_feature
732
749
 
733
750
  cid += 1
734
- except:
751
+ except (KeyError, IndexError, ValueError):
735
752
  pass
736
753
  return matrix
737
754
 
@@ -1361,12 +1361,12 @@ class ProcessPanel(QFrame, Styles):
1361
1361
  msgBox = QMessageBox()
1362
1362
  msgBox.setIcon(QMessageBox.Question)
1363
1363
  msgBox.setText(
1364
- "A measurement table already exists. Previously annotated data for\nthis position will be lost. Do you want to proceed?"
1364
+ "A measurement table already exists. Previously annotated data for this position will be lost. Do you want to proceed?"
1365
1365
  )
1366
1366
  msgBox.setWindowTitle("Info")
1367
1367
  msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
1368
1368
  if msgBox.exec() == QMessageBox.No:
1369
- run_tracking = False
1369
+ return None
1370
1370
 
1371
1371
  if run_tracking:
1372
1372
  track_args = {"mode": self.mode, "n_threads": self.n_threads}
@@ -129,14 +129,14 @@ class SegmentationModelLoader(CelldetectiveWidget):
129
129
  def unlock_upload(self):
130
130
  if self.stardist_button.isChecked():
131
131
  if np.any(
132
- [c.currentText() != "--" for c in self.channel_layout.channel_cbs]
132
+ [c.currentData() != "--" for c in self.channel_layout.channel_cbs]
133
133
  ):
134
134
  self.upload_button.setEnabled(True)
135
135
  else:
136
136
  self.upload_button.setEnabled(False)
137
137
  elif self.cellpose_button.isChecked():
138
138
  if np.any(
139
- [c.currentText() != "--" for c in self.channel_layout.channel_cbs]
139
+ [c.currentData() != "--" for c in self.channel_layout.channel_cbs]
140
140
  ):
141
141
  self.upload_button.setEnabled(True)
142
142
  else:
@@ -373,7 +373,7 @@ class SegmentationModelLoader(CelldetectiveWidget):
373
373
 
374
374
  channels = []
375
375
  for i in range(len(self.channel_layout.channel_cbs)):
376
- channels.append(self.channel_layout.channel_cbs[i].currentText())
376
+ channels.append(self.channel_layout.channel_cbs[i].currentData())
377
377
 
378
378
  if self.file_label.text() == "No file chosen":
379
379
  generic_message("Please select a model first...")
@@ -450,7 +450,7 @@ class SegmentationModelLoader(CelldetectiveWidget):
450
450
 
451
451
  channels = []
452
452
  for i in range(len(self.channel_layout.channel_cbs)):
453
- channels.append(self.channel_layout.channel_cbs[i].currentText())
453
+ channels.append(self.channel_layout.channel_cbs[i].currentData())
454
454
 
455
455
  slots_to_keep = np.where(np.array(channels) != "--")[0]
456
456
  while "--" in channels: