celldetective 1.5.0b8__py3-none-any.whl → 1.5.0b10__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/gui/base/list_widget.py +1 -1
- celldetective/gui/generic_signal_plot.py +14 -14
- celldetective/gui/gui_utils.py +27 -6
- celldetective/gui/plot_signals_ui.py +32 -15
- celldetective/gui/settings/_settings_measurements.py +10 -2
- celldetective/gui/survival_ui.py +39 -11
- celldetective/gui/tableUI.py +69 -148
- celldetective/gui/thresholds_gui.py +45 -5
- celldetective/gui/viewers/spot_detection_viewer.py +150 -27
- celldetective/measure.py +11 -11
- celldetective/utils/data_cleaning.py +7 -3
- {celldetective-1.5.0b8.dist-info → celldetective-1.5.0b10.dist-info}/METADATA +1 -1
- {celldetective-1.5.0b8.dist-info → celldetective-1.5.0b10.dist-info}/RECORD +21 -20
- tests/gui/test_spot_detection_viewer.py +394 -0
- tests/test_measure.py +231 -129
- tests/test_signals.py +131 -112
- {celldetective-1.5.0b8.dist-info → celldetective-1.5.0b10.dist-info}/WHEEL +0 -0
- {celldetective-1.5.0b8.dist-info → celldetective-1.5.0b10.dist-info}/entry_points.txt +0 -0
- {celldetective-1.5.0b8.dist-info → celldetective-1.5.0b10.dist-info}/licenses/LICENSE +0 -0
- {celldetective-1.5.0b8.dist-info → celldetective-1.5.0b10.dist-info}/top_level.txt +0 -0
celldetective/gui/tableUI.py
CHANGED
|
@@ -21,8 +21,13 @@ from celldetective.gui.gui_utils import (
|
|
|
21
21
|
)
|
|
22
22
|
from celldetective.gui.base.figure_canvas import FigureCanvas
|
|
23
23
|
from celldetective.gui.base.utils import center_window
|
|
24
|
-
from celldetective.gui.table_ops._maths import
|
|
25
|
-
|
|
24
|
+
from celldetective.gui.table_ops._maths import (
|
|
25
|
+
DifferentiateColWidget,
|
|
26
|
+
OperationOnColsWidget,
|
|
27
|
+
CalibrateColWidget,
|
|
28
|
+
AbsColWidget,
|
|
29
|
+
LogColWidget,
|
|
30
|
+
)
|
|
26
31
|
from celldetective.gui.table_ops._merge_one_hot import MergeOneHotWidget
|
|
27
32
|
from celldetective.gui.table_ops._query_table import QueryWidget
|
|
28
33
|
from celldetective.gui.table_ops._rename_col import RenameColWidget
|
|
@@ -55,7 +60,7 @@ class PivotTableUI(CelldetectiveWidget):
|
|
|
55
60
|
self.mode = mode
|
|
56
61
|
|
|
57
62
|
self.setWindowTitle(title)
|
|
58
|
-
|
|
63
|
+
logger.debug(f"Pivot table to show: {self.data.shape}")
|
|
59
64
|
|
|
60
65
|
self.table = QTableView(self)
|
|
61
66
|
|
|
@@ -219,9 +224,35 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
219
224
|
|
|
220
225
|
try:
|
|
221
226
|
self.fig.tight_layout()
|
|
222
|
-
except:
|
|
227
|
+
except AttributeError:
|
|
228
|
+
# fig not yet created
|
|
223
229
|
pass
|
|
224
230
|
|
|
231
|
+
def _get_selected_columns(self, max_cols=None):
|
|
232
|
+
"""
|
|
233
|
+
Get selected column names from the table view.
|
|
234
|
+
|
|
235
|
+
Parameters
|
|
236
|
+
----------
|
|
237
|
+
max_cols : int, optional
|
|
238
|
+
Maximum number of columns to return. Returns all if None.
|
|
239
|
+
|
|
240
|
+
Returns
|
|
241
|
+
-------
|
|
242
|
+
list
|
|
243
|
+
List of selected column names.
|
|
244
|
+
"""
|
|
245
|
+
x = self.table_view.selectedIndexes()
|
|
246
|
+
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
247
|
+
cols = np.array(list(self.data.columns))
|
|
248
|
+
result = []
|
|
249
|
+
if len(col_idx) > 0:
|
|
250
|
+
for i in col_idx:
|
|
251
|
+
result.append(str(cols[i]))
|
|
252
|
+
if max_cols is not None:
|
|
253
|
+
return result[:max_cols]
|
|
254
|
+
return result
|
|
255
|
+
|
|
225
256
|
def _create_actions(self):
|
|
226
257
|
|
|
227
258
|
self.save_as = QAction("&Save as...", self)
|
|
@@ -540,8 +571,8 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
540
571
|
self.renameWidget.show()
|
|
541
572
|
|
|
542
573
|
def save_as_csv_inplace_per_pos(self):
|
|
543
|
-
|
|
544
|
-
|
|
574
|
+
"""Save each position's table in its respective folder."""
|
|
575
|
+
logger.info("Saving each table in its respective position folder...")
|
|
545
576
|
for pos, pos_group in self.data.groupby(["position"]):
|
|
546
577
|
invalid_cols = [
|
|
547
578
|
c for c in list(pos_group.columns) if c.startswith("Unnamed")
|
|
@@ -555,26 +586,12 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
555
586
|
),
|
|
556
587
|
index=False,
|
|
557
588
|
)
|
|
558
|
-
|
|
589
|
+
logger.info("Done saving tables.")
|
|
559
590
|
|
|
560
591
|
def divide_signals(self):
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
565
|
-
cols = np.array(list(self.data.columns))
|
|
566
|
-
if len(col_idx) > 0:
|
|
567
|
-
selected_col1 = str(cols[col_idx[0]])
|
|
568
|
-
if len(col_idx) > 1:
|
|
569
|
-
selected_col2 = str(cols[col_idx[1]])
|
|
570
|
-
else:
|
|
571
|
-
selected_col2 = None
|
|
572
|
-
else:
|
|
573
|
-
selected_col1 = None
|
|
574
|
-
selected_col2 = None
|
|
575
|
-
else:
|
|
576
|
-
selected_col1 = None
|
|
577
|
-
selected_col2 = None
|
|
592
|
+
selected = self._get_selected_columns(max_cols=2)
|
|
593
|
+
selected_col1 = selected[0] if len(selected) > 0 else None
|
|
594
|
+
selected_col2 = selected[1] if len(selected) > 1 else None
|
|
578
595
|
|
|
579
596
|
self.divWidget = OperationOnColsWidget(
|
|
580
597
|
self, column1=selected_col1, column2=selected_col2, operation="divide"
|
|
@@ -582,23 +599,9 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
582
599
|
self.divWidget.show()
|
|
583
600
|
|
|
584
601
|
def multiply_signals(self):
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
589
|
-
cols = np.array(list(self.data.columns))
|
|
590
|
-
if len(col_idx) > 0:
|
|
591
|
-
selected_col1 = str(cols[col_idx[0]])
|
|
592
|
-
if len(col_idx) > 1:
|
|
593
|
-
selected_col2 = str(cols[col_idx[1]])
|
|
594
|
-
else:
|
|
595
|
-
selected_col2 = None
|
|
596
|
-
else:
|
|
597
|
-
selected_col1 = None
|
|
598
|
-
selected_col2 = None
|
|
599
|
-
else:
|
|
600
|
-
selected_col1 = None
|
|
601
|
-
selected_col2 = None
|
|
602
|
+
selected = self._get_selected_columns(max_cols=2)
|
|
603
|
+
selected_col1 = selected[0] if len(selected) > 0 else None
|
|
604
|
+
selected_col2 = selected[1] if len(selected) > 1 else None
|
|
602
605
|
|
|
603
606
|
self.mulWidget = OperationOnColsWidget(
|
|
604
607
|
self, column1=selected_col1, column2=selected_col2, operation="multiply"
|
|
@@ -606,23 +609,9 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
606
609
|
self.mulWidget.show()
|
|
607
610
|
|
|
608
611
|
def add_signals(self):
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
613
|
-
cols = np.array(list(self.data.columns))
|
|
614
|
-
if len(col_idx) > 0:
|
|
615
|
-
selected_col1 = str(cols[col_idx[0]])
|
|
616
|
-
if len(col_idx) > 1:
|
|
617
|
-
selected_col2 = str(cols[col_idx[1]])
|
|
618
|
-
else:
|
|
619
|
-
selected_col2 = None
|
|
620
|
-
else:
|
|
621
|
-
selected_col1 = None
|
|
622
|
-
selected_col2 = None
|
|
623
|
-
else:
|
|
624
|
-
selected_col1 = None
|
|
625
|
-
selected_col2 = None
|
|
612
|
+
selected = self._get_selected_columns(max_cols=2)
|
|
613
|
+
selected_col1 = selected[0] if len(selected) > 0 else None
|
|
614
|
+
selected_col2 = selected[1] if len(selected) > 1 else None
|
|
626
615
|
|
|
627
616
|
self.addiWidget = OperationOnColsWidget(
|
|
628
617
|
self, column1=selected_col1, column2=selected_col2, operation="add"
|
|
@@ -630,23 +619,9 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
630
619
|
self.addiWidget.show()
|
|
631
620
|
|
|
632
621
|
def subtract_signals(self):
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
637
|
-
cols = np.array(list(self.data.columns))
|
|
638
|
-
if len(col_idx) > 0:
|
|
639
|
-
selected_col1 = str(cols[col_idx[0]])
|
|
640
|
-
if len(col_idx) > 1:
|
|
641
|
-
selected_col2 = str(cols[col_idx[1]])
|
|
642
|
-
else:
|
|
643
|
-
selected_col2 = None
|
|
644
|
-
else:
|
|
645
|
-
selected_col1 = None
|
|
646
|
-
selected_col2 = None
|
|
647
|
-
else:
|
|
648
|
-
selected_col1 = None
|
|
649
|
-
selected_col2 = None
|
|
622
|
+
selected = self._get_selected_columns(max_cols=2)
|
|
623
|
+
selected_col1 = selected[0] if len(selected) > 0 else None
|
|
624
|
+
selected_col2 = selected[1] if len(selected) > 1 else None
|
|
650
625
|
|
|
651
626
|
self.subWidget = OperationOnColsWidget(
|
|
652
627
|
self, column1=selected_col1, column2=selected_col2, operation="subtract"
|
|
@@ -654,56 +629,22 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
654
629
|
self.subWidget.show()
|
|
655
630
|
|
|
656
631
|
def differenciate_selected_feature(self):
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
# create new col
|
|
661
|
-
|
|
662
|
-
x = self.table_view.selectedIndexes()
|
|
663
|
-
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
664
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
665
|
-
cols = np.array(list(self.data.columns))
|
|
666
|
-
if len(col_idx) > 0:
|
|
667
|
-
selected_col = str(cols[col_idx[0]])
|
|
668
|
-
else:
|
|
669
|
-
selected_col = None
|
|
670
|
-
else:
|
|
671
|
-
selected_col = None
|
|
672
|
-
|
|
632
|
+
"""Open widget to differentiate the selected column."""
|
|
633
|
+
selected = self._get_selected_columns(max_cols=1)
|
|
634
|
+
selected_col = selected[0] if selected else None
|
|
673
635
|
self.diffWidget = DifferentiateColWidget(self, selected_col)
|
|
674
636
|
self.diffWidget.show()
|
|
675
637
|
|
|
676
638
|
def take_log_of_selected_feature(self):
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
# create new col
|
|
681
|
-
|
|
682
|
-
x = self.table_view.selectedIndexes()
|
|
683
|
-
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
684
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
685
|
-
cols = np.array(list(self.data.columns))
|
|
686
|
-
if len(col_idx) > 0:
|
|
687
|
-
selected_col = str(cols[col_idx[0]])
|
|
688
|
-
else:
|
|
689
|
-
selected_col = None
|
|
690
|
-
else:
|
|
691
|
-
selected_col = None
|
|
692
|
-
|
|
639
|
+
"""Open widget to take log of the selected column."""
|
|
640
|
+
selected = self._get_selected_columns(max_cols=1)
|
|
641
|
+
selected_col = selected[0] if selected else None
|
|
693
642
|
self.LogWidget = LogColWidget(self, selected_col)
|
|
694
643
|
self.LogWidget.show()
|
|
695
644
|
|
|
696
645
|
def merge_classification_features(self):
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
700
|
-
|
|
701
|
-
col_selection = []
|
|
702
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
703
|
-
cols = np.array(list(self.data.columns))
|
|
704
|
-
if len(col_idx) > 0:
|
|
705
|
-
selected_cols = cols[col_idx]
|
|
706
|
-
col_selection.extend(selected_cols)
|
|
646
|
+
"""Open widget to merge selected classification columns."""
|
|
647
|
+
col_selection = self._get_selected_columns()
|
|
707
648
|
|
|
708
649
|
# Lazy load MergeGroupWidget
|
|
709
650
|
from celldetective.gui.table_ops._merge_groups import MergeGroupWidget
|
|
@@ -712,38 +653,16 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
712
653
|
self.merge_classification_widget.show()
|
|
713
654
|
|
|
714
655
|
def calibrate_selected_feature(self):
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
719
|
-
cols = np.array(list(self.data.columns))
|
|
720
|
-
if len(col_idx) > 0:
|
|
721
|
-
selected_col = str(cols[col_idx[0]])
|
|
722
|
-
else:
|
|
723
|
-
selected_col = None
|
|
724
|
-
else:
|
|
725
|
-
selected_col = None
|
|
726
|
-
|
|
656
|
+
"""Open widget to calibrate the selected column."""
|
|
657
|
+
selected = self._get_selected_columns(max_cols=1)
|
|
658
|
+
selected_col = selected[0] if selected else None
|
|
727
659
|
self.calWidget = CalibrateColWidget(self, selected_col)
|
|
728
660
|
self.calWidget.show()
|
|
729
661
|
|
|
730
662
|
def take_abs_of_selected_feature(self):
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
# create new col
|
|
735
|
-
|
|
736
|
-
x = self.table_view.selectedIndexes()
|
|
737
|
-
col_idx = np.unique(np.array([l.column() for l in x]))
|
|
738
|
-
if isinstance(col_idx, (list, np.ndarray)):
|
|
739
|
-
cols = np.array(list(self.data.columns))
|
|
740
|
-
if len(col_idx) > 0:
|
|
741
|
-
selected_col = str(cols[col_idx[0]])
|
|
742
|
-
else:
|
|
743
|
-
selected_col = None
|
|
744
|
-
else:
|
|
745
|
-
selected_col = None
|
|
746
|
-
|
|
663
|
+
"""Open widget to take absolute value of the selected column."""
|
|
664
|
+
selected = self._get_selected_columns(max_cols=1)
|
|
665
|
+
selected_col = selected[0] if selected else None
|
|
747
666
|
self.absWidget = AbsColWidget(self, selected_col)
|
|
748
667
|
self.absWidget.show()
|
|
749
668
|
|
|
@@ -991,7 +910,8 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
991
910
|
y = column_names[unique_cols]
|
|
992
911
|
idx = self.y_cb.findText(y)
|
|
993
912
|
self.y_cb.setCurrentIndex(idx)
|
|
994
|
-
except:
|
|
913
|
+
except (IndexError, KeyError):
|
|
914
|
+
# No column selected or invalid selection
|
|
995
915
|
pass
|
|
996
916
|
|
|
997
917
|
hbox = QHBoxLayout()
|
|
@@ -1018,7 +938,8 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
1018
938
|
if hasattr(matplotlib.cm, str(cm).lower()):
|
|
1019
939
|
try:
|
|
1020
940
|
self.cmap_cb.addColormap(cm.lower())
|
|
1021
|
-
except:
|
|
941
|
+
except Exception:
|
|
942
|
+
# Some colormaps may fail to add
|
|
1022
943
|
pass
|
|
1023
944
|
|
|
1024
945
|
hbox = QHBoxLayout()
|
|
@@ -1057,7 +978,7 @@ class TableUI(CelldetectiveMainWindow):
|
|
|
1057
978
|
cmap(i / len(self.data[self.hue_variable].unique()))
|
|
1058
979
|
for i in range(len(self.data[self.hue_variable].unique()))
|
|
1059
980
|
]
|
|
1060
|
-
except:
|
|
981
|
+
except (KeyError, ZeroDivisionError):
|
|
1061
982
|
colors = None
|
|
1062
983
|
|
|
1063
984
|
if self.hue_cb.currentText() == "--":
|
|
@@ -74,6 +74,7 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
74
74
|
|
|
75
75
|
super().__init__()
|
|
76
76
|
self.parent_window = parent_window
|
|
77
|
+
# Navigate explicit parent chain: SegModelLoader -> ControlPanel -> ProcessPanel -> MainWindow
|
|
77
78
|
self.screen_height = (
|
|
78
79
|
self.parent_window.parent_window.parent_window.parent_window.screen_height
|
|
79
80
|
)
|
|
@@ -124,6 +125,17 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
124
125
|
self.bg_loader = BackgroundLoader()
|
|
125
126
|
self.bg_loader.start()
|
|
126
127
|
|
|
128
|
+
def closeEvent(self, event):
|
|
129
|
+
"""Clean up resources on close."""
|
|
130
|
+
if hasattr(self, "bg_loader") and self.bg_loader.isRunning():
|
|
131
|
+
self.bg_loader.quit()
|
|
132
|
+
self.bg_loader.wait()
|
|
133
|
+
# Clear large arrays
|
|
134
|
+
for attr in ["img", "labels", "edt_map", "props", "coords"]:
|
|
135
|
+
if hasattr(self, attr):
|
|
136
|
+
delattr(self, attr)
|
|
137
|
+
super().closeEvent(event)
|
|
138
|
+
|
|
127
139
|
def _create_menu_bar(self):
|
|
128
140
|
menu_bar = self.menuBar()
|
|
129
141
|
# Creating menus using a QMenu object
|
|
@@ -810,7 +822,8 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
810
822
|
for i in range(2):
|
|
811
823
|
try:
|
|
812
824
|
self.features_cb[i].disconnect()
|
|
813
|
-
except
|
|
825
|
+
except TypeError:
|
|
826
|
+
# No connections to disconnect
|
|
814
827
|
pass
|
|
815
828
|
self.features_cb[i].clear()
|
|
816
829
|
|
|
@@ -879,12 +892,39 @@ class ThresholdConfigWizard(CelldetectiveMainWindow):
|
|
|
879
892
|
self.exp_dir + f"configs/threshold_config_{self.mode}.json",
|
|
880
893
|
"JSON (*.json)",
|
|
881
894
|
)[0]
|
|
882
|
-
|
|
883
|
-
|
|
895
|
+
|
|
896
|
+
if not self.previous_instruction_file:
|
|
897
|
+
return # User cancelled
|
|
898
|
+
|
|
899
|
+
try:
|
|
900
|
+
with open(self.previous_instruction_file, "r") as f:
|
|
901
|
+
threshold_instructions = json.load(f)
|
|
902
|
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
903
|
+
generic_message(f"Could not load config: {e}")
|
|
904
|
+
return
|
|
905
|
+
|
|
906
|
+
# Validate required keys
|
|
907
|
+
required_keys = [
|
|
908
|
+
"target_channel",
|
|
909
|
+
"filters",
|
|
910
|
+
"thresholds",
|
|
911
|
+
"marker_footprint_size",
|
|
912
|
+
"marker_min_distance",
|
|
913
|
+
"feature_queries",
|
|
914
|
+
]
|
|
915
|
+
missing_keys = [k for k in required_keys if k not in threshold_instructions]
|
|
916
|
+
if missing_keys:
|
|
917
|
+
generic_message(f"Config file is missing required keys: {missing_keys}")
|
|
918
|
+
return
|
|
884
919
|
|
|
885
920
|
target_channel = threshold_instructions["target_channel"]
|
|
886
|
-
index = self.viewer.
|
|
887
|
-
|
|
921
|
+
index = self.viewer.channel_cb.findText(target_channel)
|
|
922
|
+
if index >= 0:
|
|
923
|
+
self.viewer.channel_cb.setCurrentIndex(index)
|
|
924
|
+
else:
|
|
925
|
+
logger.warning(
|
|
926
|
+
f"Channel '{target_channel}' not found in available channels"
|
|
927
|
+
)
|
|
888
928
|
|
|
889
929
|
filters = threshold_instructions["filters"]
|
|
890
930
|
items_to_add = [f[0] + "_filter" for f in filters]
|