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.
- celldetective/_version.py +1 -1
- celldetective/event_detection_models.py +2463 -0
- celldetective/gui/base/channel_norm_generator.py +19 -3
- celldetective/gui/base/figure_canvas.py +1 -1
- celldetective/gui/base/list_widget.py +1 -1
- celldetective/gui/base_annotator.py +2 -5
- celldetective/gui/event_annotator.py +248 -138
- celldetective/gui/generic_signal_plot.py +14 -14
- celldetective/gui/gui_utils.py +27 -6
- celldetective/gui/pair_event_annotator.py +146 -20
- celldetective/gui/plot_signals_ui.py +32 -15
- celldetective/gui/process_block.py +2 -2
- celldetective/gui/seg_model_loader.py +4 -4
- celldetective/gui/settings/_settings_event_model_training.py +32 -14
- celldetective/gui/settings/_settings_segmentation_model_training.py +5 -5
- celldetective/gui/settings/_settings_signal_annotator.py +0 -19
- celldetective/gui/survival_ui.py +39 -11
- celldetective/gui/tableUI.py +69 -148
- celldetective/gui/thresholds_gui.py +45 -5
- celldetective/gui/viewers/base_viewer.py +17 -20
- celldetective/gui/viewers/spot_detection_viewer.py +136 -27
- celldetective/processes/train_signal_model.py +1 -1
- celldetective/scripts/train_signal_model.py +1 -1
- celldetective/signals.py +4 -2426
- celldetective/utils/event_detection/__init__.py +1 -1
- {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/METADATA +1 -1
- {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/RECORD +33 -31
- tests/gui/test_spot_detection_viewer.py +187 -0
- tests/test_signals.py +135 -116
- {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/WHEEL +0 -0
- {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/entry_points.txt +0 -0
- {celldetective-1.5.0b7.dist-info → celldetective-1.5.0b9.dist-info}/licenses/LICENSE +0 -0
- {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
|
-
|
|
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
|
-
|
|
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
|
|
519
|
-
|
|
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
|
-
|
|
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
|
-
|
|
889
|
+
logger.debug(f"Error plotting signals: {e}")
|
|
890
890
|
|
|
891
891
|
def switch_cell_lines(self):
|
|
892
892
|
|
celldetective/gui/gui_utils.py
CHANGED
|
@@ -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__(
|
|
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
|
-
|
|
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__(
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
516
|
-
generic_message("No table could be found to compute
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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].
|
|
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].
|
|
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:
|