celldetective 1.5.0b7__py3-none-any.whl → 1.5.0b8__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.
@@ -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]
@@ -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:
@@ -417,8 +417,7 @@ class SettingsEventDetectionModelTraining(CelldetectiveSettingsPanel):
417
417
  self.signals = ["--"] + num_cols_pairs + num_cols_reference + num_cols_neighbor
418
418
 
419
419
  for cb in self.ch_norm.channel_cbs:
420
- cb.clear()
421
- cb.addItems(self.signals)
420
+ self.ch_norm.add_items_truncated(cb, self.signals)
422
421
 
423
422
  def fill_available_neighborhoods(self):
424
423
 
@@ -541,15 +540,36 @@ class SettingsEventDetectionModelTraining(CelldetectiveSettingsPanel):
541
540
  self.model_length_slider.setValue(int(signal_length))
542
541
  self.model_length_slider.setEnabled(False)
543
542
 
544
- for c, cb in zip(channels, self.ch_norm.channel_cbs):
545
- index = cb.findText(c)
543
+ try:
544
+ norm_perc = data["normalization_percentile"]
545
+ if isinstance(norm_perc, bool):
546
+ norm_perc = [norm_perc] * len(channels)
547
+ norm_val = data["normalization_values"]
548
+ if len(norm_val) == 2 and isinstance(norm_val[0], float):
549
+ norm_val = [norm_val] * len(channels)
550
+ norm_clip = data["normalization_clip"]
551
+ if isinstance(norm_clip, bool):
552
+ norm_clip = [norm_clip] * len(channels)
553
+ except Exception:
554
+ norm_perc = [True] * len(channels)
555
+ norm_val = [[0.1, 99.99]] * len(channels)
556
+ norm_clip = [False] * len(channels)
557
+
558
+ for k, (c, cb) in enumerate(zip(channels, self.ch_norm.channel_cbs)):
559
+ index = cb.findData(c)
546
560
  cb.setCurrentIndex(index)
547
561
 
548
- if len(channels) < len(self.ch_norm.channel_cbs):
549
- for k in range(len(self.ch_norm.channel_cbs) - len(channels)):
550
- self.ch_norm.channel_cbs[len(channels) + k].setCurrentIndex(0)
551
- self.ch_norm.channel_cbs[len(channels) + k].setEnabled(False)
552
- self.ch_norm.add_col_btn.setEnabled(False)
562
+ # Set normalization mode
563
+ if self.ch_norm.normalization_mode[k] != norm_perc[k]:
564
+ self.ch_norm.switch_normalization_mode(k)
565
+
566
+ # Set clipping mode
567
+ if self.ch_norm.clip_option[k] != norm_clip[k]:
568
+ self.ch_norm.switch_clipping_mode(k)
569
+
570
+ # Set normalization values
571
+ self.ch_norm.normalization_min_value_le[k].setText(str(norm_val[k][0]))
572
+ self.ch_norm.normalization_max_value_le[k].setText(str(norm_val[k][1]))
553
573
 
554
574
  def adjust_scroll_area(self):
555
575
  """
@@ -581,7 +601,7 @@ class SettingsEventDetectionModelTraining(CelldetectiveSettingsPanel):
581
601
 
582
602
  channels = []
583
603
  for i in range(len(self.ch_norm.channel_cbs)):
584
- channels.append(self.ch_norm.channel_cbs[i].currentText())
604
+ channels.append(self.ch_norm.channel_cbs[i].currentData())
585
605
 
586
606
  slots_to_keep = np.where(np.array(channels) != "--")[0]
587
607
  while "--" in channels:
@@ -750,10 +770,8 @@ class SettingsEventDetectionModelTraining(CelldetectiveSettingsPanel):
750
770
 
751
771
  # Assuming signal/event models directory. Verifying path would be better but this fits pattern.
752
772
  # If exact attribute unknown, use os.path.dirname logic or config methods.
753
- # Safe bet: self.parent_window.signal_models_dir based on refresh call.
754
- model_path = os.path.join(
755
- self.parent_window.signal_models_dir, self.modelname_le.text()
756
- )
773
+ # Safe bet: self.signal_models_dir based on init.
774
+ model_path = os.path.join(self.signal_models_dir, self.modelname_le.text())
757
775
  if os.path.exists(model_path):
758
776
  time.sleep(0.5)
759
777
  shutil.rmtree(model_path)
@@ -355,7 +355,7 @@ class SettingsSegmentationModelTraining(CelldetectiveSettingsPanel):
355
355
  not current_name in models
356
356
  and not self.spatial_calib_le.text() == ""
357
357
  and not np.all(
358
- [cb.currentText() == "--" for cb in self.ch_norm.channel_cbs]
358
+ [cb.currentData() == "--" for cb in self.ch_norm.channel_cbs]
359
359
  )
360
360
  ):
361
361
  self.submit_btn.setEnabled(True)
@@ -370,7 +370,7 @@ class SettingsSegmentationModelTraining(CelldetectiveSettingsPanel):
370
370
  self.submit_warning.setText(
371
371
  "Please provide a valid spatial calibration..."
372
372
  )
373
- elif np.all([cb.currentText() == "--" for cb in self.ch_norm.channel_cbs]):
373
+ elif np.all([cb.currentData() == "--" for cb in self.ch_norm.channel_cbs]):
374
374
  self.submit_warning.setText("Please provide valid channels...")
375
375
 
376
376
  def rescale_slider(self):
@@ -459,7 +459,7 @@ class SettingsSegmentationModelTraining(CelldetectiveSettingsPanel):
459
459
 
460
460
  for k in range(len(self.diamWidget.cellpose_channel_cb)):
461
461
  ch = self.diamWidget.cellpose_channel_cb[k].currentText()
462
- idx = self.ch_norm.channel_cbs[k].findText(ch)
462
+ idx = self.ch_norm.channel_cbs[k].findData(ch)
463
463
  self.ch_norm.channel_cbs[k].setCurrentIndex(idx)
464
464
 
465
465
  self.diamWidget.close()
@@ -561,7 +561,7 @@ class SettingsSegmentationModelTraining(CelldetectiveSettingsPanel):
561
561
  self.cellpose_model.setChecked(True)
562
562
 
563
563
  for c, cb in zip(channels, self.ch_norm.channel_cbs):
564
- index = cb.findText(c)
564
+ index = cb.findData(c)
565
565
  cb.setCurrentIndex(index)
566
566
 
567
567
  for i in range(len(channels)):
@@ -622,7 +622,7 @@ class SettingsSegmentationModelTraining(CelldetectiveSettingsPanel):
622
622
 
623
623
  channels = []
624
624
  for i in range(len(self.ch_norm.channel_cbs)):
625
- channels.append(self.ch_norm.channel_cbs[i].currentText())
625
+ channels.append(self.ch_norm.channel_cbs[i].currentData())
626
626
 
627
627
  slots_to_keep = np.where(np.array(channels) != "--")[0]
628
628
  while "--" in channels:
@@ -100,11 +100,6 @@ class SettingsSignalAnnotator(CelldetectiveSettingsPanel):
100
100
  hbox_frac.addWidget(self.fraction_slider, 80)
101
101
  sub_layout.addLayout(hbox_frac)
102
102
 
103
- hbox_interval = QHBoxLayout()
104
- hbox_interval.addWidget(self._interval_lbl, 20)
105
- hbox_interval.addWidget(self.interval_slider, 80)
106
- sub_layout.addLayout(hbox_interval)
107
-
108
103
  self._layout.addLayout(sub_layout)
109
104
 
110
105
  self._layout.addWidget(self.submit_btn)
@@ -118,7 +113,6 @@ class SettingsSignalAnnotator(CelldetectiveSettingsPanel):
118
113
 
119
114
  self._modality_lbl = QLabel("Modality: ")
120
115
  self._fraction_lbl = QLabel("fraction: ")
121
- self._interval_lbl = QLabel("interval [ms]: ")
122
116
 
123
117
  self.gs_btn = QRadioButton("grayscale")
124
118
  self.gs_btn.setChecked(True)
@@ -173,14 +167,6 @@ class SettingsSignalAnnotator(CelldetectiveSettingsPanel):
173
167
  self.fraction_slider.setRange(0.1, 1)
174
168
  self.fraction_slider.setValue(0.25)
175
169
 
176
- self.interval_slider = QLabeledSlider()
177
- self.interval_slider.setSingleStep(1)
178
- self.interval_slider.setTickInterval(1)
179
- self.interval_slider.setSingleStep(1)
180
- self.interval_slider.setOrientation(Qt.Horizontal)
181
- self.interval_slider.setRange(1, 1000)
182
- self.interval_slider.setValue(1)
183
-
184
170
  def enable_channels(self):
185
171
  """
186
172
  Enable three channels when RGB mode is checked.
@@ -255,7 +241,6 @@ class SettingsSignalAnnotator(CelldetectiveSettingsPanel):
255
241
  "rgb_mode": self.rgb_btn.isChecked(),
256
242
  "percentile_mode": self.percentile_mode,
257
243
  "fraction": float(self.fraction_slider.value()),
258
- "interval": int(self.interval_slider.value()),
259
244
  "log": self.log_option,
260
245
  }
261
246
  max_i = 3 if self.rgb_btn.isChecked() else 1
@@ -322,10 +307,6 @@ class SettingsSignalAnnotator(CelldetectiveSettingsPanel):
322
307
  fraction = instructions["fraction"]
323
308
  self.fraction_slider.setValue(fraction)
324
309
 
325
- if "interval" in instructions:
326
- interval = instructions["interval"]
327
- self.interval_slider.setValue(interval)
328
-
329
310
  if "log" in instructions:
330
311
  self.log_option = not instructions["log"]
331
312
  self.switch_to_log()
@@ -6,6 +6,7 @@ from PyQt5.QtWidgets import QHBoxLayout, QAction, QLabel, QComboBox
6
6
  from fonticon_mdi6 import MDI6
7
7
  from superqt import QLabeledDoubleRangeSlider, QLabeledSlider
8
8
  from superqt.fonticon import icon
9
+ import matplotlib.gridspec as gridspec
9
10
 
10
11
  from celldetective.gui.base.components import CelldetectiveWidget
11
12
  from celldetective.gui.base.utils import center_window
@@ -102,8 +103,7 @@ class StackLoader(QThread):
102
103
  self.mutex.unlock()
103
104
 
104
105
  except Exception as e:
105
- pass
106
- # logger.error(f"Error loading frame {frame_to_load}: {e}")
106
+ logger.debug(f"Error loading frame {frame_to_load}: {e}")
107
107
  # Prepare to wait to avoid spin loop on error
108
108
  self.msleep(100)
109
109
 
@@ -169,10 +169,14 @@ class StackVisualizer(CelldetectiveWidget):
169
169
  window_title="View",
170
170
  PxToUm=None,
171
171
  background_color="transparent",
172
- imshow_kwargs={},
172
+ imshow_kwargs=None,
173
173
  ):
174
174
  super().__init__()
175
175
 
176
+ # Default mutable argument handling
177
+ if imshow_kwargs is None:
178
+ imshow_kwargs = {}
179
+
176
180
  # self.setWindowTitle(window_title)
177
181
  self.window_title = window_title
178
182
 
@@ -265,7 +269,6 @@ class StackVisualizer(CelldetectiveWidget):
265
269
  self.canvas.toolbar.insertAction(insert_before, self.lock_y_action)
266
270
  else:
267
271
  if len(actions) > 5:
268
- self.canvas.toolbar.insertAction(actions[5], self.line_action)
269
272
  self.canvas.toolbar.insertAction(actions[5], self.line_action)
270
273
  self.canvas.toolbar.insertAction(actions[5], self.lock_y_action)
271
274
  else:
@@ -313,8 +316,6 @@ class StackVisualizer(CelldetectiveWidget):
313
316
  # Use GridSpec for robust layout
314
317
  # 2 rows: Main Image (top, ~75%), Profile (bottom, ~25%)
315
318
  # Add margins to ensure axis labels and text are visible
316
- import matplotlib.gridspec as gridspec
317
-
318
319
  gs = gridspec.GridSpec(
319
320
  2,
320
321
  1,
@@ -406,10 +407,6 @@ class StackVisualizer(CelldetectiveWidget):
406
407
  self.ax_profile = None
407
408
 
408
409
  # Restore original layout
409
- # if hasattr(self, "ax_original_pos"):
410
- # standard 1x1 GridSpec or manual restore
411
- import matplotlib.gridspec as gridspec
412
-
413
410
  gs = gridspec.GridSpec(1, 1)
414
411
  self.ax.set_subplotspec(gs[0])
415
412
  # self.ax.set_position(gs[0].get_position(self.fig))
@@ -505,9 +502,8 @@ class StackVisualizer(CelldetectiveWidget):
505
502
  profile = np.zeros_like(profile)
506
503
  profile[:] = np.nan
507
504
 
508
- # Distance in microns if available
505
+ # Distance in pixels
509
506
  dist_axis = np.arange(num_points)
510
- x_label = "Distance (px)"
511
507
 
512
508
  # Only show pixel length, rounded to integer
513
509
  title_str = f"{round(length_px,2)} [px]"
@@ -525,11 +521,8 @@ class StackVisualizer(CelldetectiveWidget):
525
521
  if hasattr(self, "profile_line") and self.profile_line:
526
522
  try:
527
523
  self.profile_line.remove()
528
- except:
529
- pass
530
-
531
- # Distance in microns if available
532
- dist_axis = np.arange(num_points)
524
+ except ValueError:
525
+ pass # Already removed
533
526
 
534
527
  (self.profile_line,) = self.ax_profile.plot(
535
528
  dist_axis, profile, color="black", linestyle="-"
@@ -850,12 +843,16 @@ class StackVisualizer(CelldetectiveWidget):
850
843
 
851
844
  if curr_min < self._min:
852
845
  self._min = curr_min
853
- rescale_constrast = True
846
+ rescale_contrast = True
854
847
  if curr_max > self._max:
855
848
  self._max = curr_max
856
849
  rescale_contrast = True
857
850
 
858
- if rescale_contrast:
851
+ if (
852
+ rescale_contrast
853
+ and self.create_contrast_slider
854
+ and hasattr(self, "contrast_slider")
855
+ ):
859
856
  self.contrast_slider.setRange(self._min, self._max)
860
857
  self.canvas.canvas.draw_idle()
861
858
  self.update_profile()
@@ -888,5 +885,5 @@ class StackVisualizer(CelldetectiveWidget):
888
885
  try:
889
886
  if hasattr(self, "loader_thread") and self.loader_thread:
890
887
  self.loader_thread.stop()
891
- except:
888
+ except Exception:
892
889
  pass
@@ -7,7 +7,7 @@ import numpy as np
7
7
  from art import tprint
8
8
  from tensorflow.python.keras.callbacks import Callback
9
9
 
10
- from celldetective.signals import SignalDetectionModel
10
+ from celldetective.event_detection_models import SignalDetectionModel
11
11
  from celldetective.log_manager import get_logger
12
12
  from celldetective.utils.model_loaders import locate_signal_model
13
13
 
@@ -8,7 +8,7 @@ import json
8
8
  from glob import glob
9
9
  import numpy as np
10
10
  from art import tprint
11
- from celldetective.signals import SignalDetectionModel
11
+ from celldetective.event_detection_models import SignalDetectionModel
12
12
  from celldetective.utils.model_loaders import locate_signal_model
13
13
 
14
14
  tprint("Train")