celldetective 1.4.1.post1__tar.gz → 1.4.2__tar.gz

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 (134) hide show
  1. {celldetective-1.4.1.post1 → celldetective-1.4.2}/PKG-INFO +1 -1
  2. celldetective-1.4.2/celldetective/_version.py +1 -0
  3. celldetective-1.4.2/celldetective/gui/classifier_widget.py +672 -0
  4. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/tableUI.py +3 -2
  5. celldetective-1.4.2/celldetective/tracking.py +1356 -0
  6. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective.egg-info/PKG-INFO +1 -1
  7. celldetective-1.4.2/setup.py +69 -0
  8. celldetective-1.4.2/tests/test_tracking.py +226 -0
  9. celldetective-1.4.1.post1/celldetective/_version.py +0 -1
  10. celldetective-1.4.1.post1/celldetective/gui/classifier_widget.py +0 -568
  11. celldetective-1.4.1.post1/celldetective/tracking.py +0 -1050
  12. celldetective-1.4.1.post1/setup.py +0 -51
  13. celldetective-1.4.1.post1/tests/test_tracking.py +0 -164
  14. {celldetective-1.4.1.post1 → celldetective-1.4.2}/LICENSE +0 -0
  15. {celldetective-1.4.1.post1 → celldetective-1.4.2}/README.md +0 -0
  16. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/__init__.py +0 -0
  17. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/__main__.py +0 -0
  18. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/datasets/segmentation_annotations/blank +0 -0
  19. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/datasets/signal_annotations/blank +0 -0
  20. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/events.py +0 -0
  21. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/exceptions.py +0 -0
  22. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/extra_properties.py +0 -0
  23. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/filters.py +0 -0
  24. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/InitWindow.py +0 -0
  25. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/__init__.py +0 -0
  26. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/about.py +0 -0
  27. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/analyze_block.py +0 -0
  28. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/base_annotator.py +0 -0
  29. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/base_components.py +0 -0
  30. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/configure_new_exp.py +0 -0
  31. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/control_panel.py +0 -0
  32. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/event_annotator.py +0 -0
  33. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/generic_signal_plot.py +0 -0
  34. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/gui_utils.py +0 -0
  35. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/DL-segmentation-strategy.json +0 -0
  36. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/Threshold-vs-DL.json +0 -0
  37. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/cell-populations.json +0 -0
  38. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/exp-structure.json +0 -0
  39. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/feature-btrack.json +0 -0
  40. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/neighborhood.json +0 -0
  41. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/prefilter-for-segmentation.json +0 -0
  42. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/preprocessing.json +0 -0
  43. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/propagate-classification.json +0 -0
  44. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/track-postprocessing.json +0 -0
  45. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/help/tracking.json +0 -0
  46. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/json_readers.py +0 -0
  47. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/layouts.py +0 -0
  48. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/pair_event_annotator.py +0 -0
  49. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/plot_measurements.py +0 -0
  50. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/plot_signals_ui.py +0 -0
  51. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/process_block.py +0 -0
  52. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/processes/compute_neighborhood.py +0 -0
  53. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/processes/downloader.py +0 -0
  54. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/processes/measure_cells.py +0 -0
  55. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/processes/segment_cells.py +0 -0
  56. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/processes/track_cells.py +0 -0
  57. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/processes/train_segmentation_model.py +0 -0
  58. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/processes/train_signal_model.py +0 -0
  59. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/seg_model_loader.py +0 -0
  60. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/__init__.py +0 -0
  61. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/_settings_base.py +0 -0
  62. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/_settings_event_model_training.py +0 -0
  63. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/_settings_measurements.py +0 -0
  64. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/_settings_neighborhood.py +0 -0
  65. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/_settings_segmentation.py +0 -0
  66. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/_settings_segmentation_model_training.py +0 -0
  67. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/_settings_signal_annotator.py +0 -0
  68. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/settings/_settings_tracking.py +0 -0
  69. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/styles.py +0 -0
  70. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/survival_ui.py +0 -0
  71. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/table_ops/__init__.py +0 -0
  72. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/table_ops/merge_groups.py +0 -0
  73. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/thresholds_gui.py +0 -0
  74. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/viewers.py +0 -0
  75. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/gui/workers.py +0 -0
  76. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/logo-large.png +0 -0
  77. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/logo.png +0 -0
  78. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/signals_icon.png +0 -0
  79. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/splash-test.png +0 -0
  80. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/splash.png +0 -0
  81. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/splash0.png +0 -0
  82. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/survival2.png +0 -0
  83. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/vignette_signals2.png +0 -0
  84. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/icons/vignette_signals2.svg +0 -0
  85. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/io.py +0 -0
  86. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/links/zenodo.json +0 -0
  87. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/measure.py +0 -0
  88. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/pair_signal_detection/blank +0 -0
  89. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/segmentation_effectors/blank +0 -0
  90. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/segmentation_generic/blank +0 -0
  91. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/segmentation_targets/blank +0 -0
  92. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/signal_detection/blank +0 -0
  93. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/tracking_configs/biased_motion.json +0 -0
  94. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/tracking_configs/mcf7.json +0 -0
  95. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/tracking_configs/no_z_motion.json +0 -0
  96. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/tracking_configs/ricm.json +0 -0
  97. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/models/tracking_configs/ricm2.json +0 -0
  98. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/neighborhood.py +0 -0
  99. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/preprocessing.py +0 -0
  100. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/regionprops/__init__.py +0 -0
  101. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/regionprops/_regionprops.py +0 -0
  102. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/regionprops/props.json +0 -0
  103. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/relative_measurements.py +0 -0
  104. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/scripts/analyze_signals.py +0 -0
  105. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/scripts/measure_cells.py +0 -0
  106. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/scripts/measure_relative.py +0 -0
  107. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/scripts/segment_cells.py +0 -0
  108. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/scripts/segment_cells_thresholds.py +0 -0
  109. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/scripts/track_cells.py +0 -0
  110. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/scripts/train_segmentation_model.py +0 -0
  111. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/scripts/train_signal_model.py +0 -0
  112. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/segmentation.py +0 -0
  113. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/signals.py +0 -0
  114. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective/utils.py +0 -0
  115. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective.egg-info/SOURCES.txt +0 -0
  116. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective.egg-info/dependency_links.txt +0 -0
  117. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective.egg-info/entry_points.txt +0 -0
  118. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective.egg-info/not-zip-safe +0 -0
  119. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective.egg-info/requires.txt +0 -0
  120. {celldetective-1.4.1.post1 → celldetective-1.4.2}/celldetective.egg-info/top_level.txt +0 -0
  121. {celldetective-1.4.1.post1 → celldetective-1.4.2}/setup.cfg +0 -0
  122. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/__init__.py +0 -0
  123. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/gui/__init__.py +0 -0
  124. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/gui/test_new_project.py +0 -0
  125. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/gui/test_project.py +0 -0
  126. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_events.py +0 -0
  127. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_filters.py +0 -0
  128. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_io.py +0 -0
  129. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_measure.py +0 -0
  130. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_neighborhood.py +0 -0
  131. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_preprocessing.py +0 -0
  132. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_segmentation.py +0 -0
  133. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_signals.py +0 -0
  134. {celldetective-1.4.1.post1 → celldetective-1.4.2}/tests/test_utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: celldetective
3
- Version: 1.4.1.post1
3
+ Version: 1.4.2
4
4
  Summary: description
5
5
  Home-page: http://github.com/remyeltorro/celldetective
6
6
  Author: Rémy Torro
@@ -0,0 +1 @@
1
+ __version__ = "1.4.2"
@@ -0,0 +1,672 @@
1
+ from PyQt5.QtWidgets import (
2
+ QLineEdit,
3
+ QMessageBox,
4
+ QHBoxLayout,
5
+ QVBoxLayout,
6
+ QPushButton,
7
+ QLabel,
8
+ QCheckBox,
9
+ QRadioButton,
10
+ QButtonGroup,
11
+ QComboBox,
12
+ )
13
+ from PyQt5.QtCore import Qt, QSize
14
+ from superqt import QLabeledSlider, QLabeledDoubleSlider, QSearchableComboBox
15
+ from superqt.fonticon import icon
16
+ from fonticon_mdi6 import MDI6
17
+
18
+ import os
19
+ import numpy as np
20
+ import matplotlib.pyplot as plt
21
+ import json
22
+
23
+ from celldetective.exceptions import EmptyQueryError, MissingColumnsError, QueryError
24
+ from celldetective.gui.gui_utils import (
25
+ FigureCanvas,
26
+ center_window,
27
+ color_from_status,
28
+ help_generic,
29
+ )
30
+ from celldetective.gui.base_components import CelldetectiveWidget
31
+ from celldetective.utils import get_software_location
32
+ from celldetective.measure import (
33
+ classify_cells_from_query,
34
+ interpret_track_classification,
35
+ )
36
+
37
+
38
+ class ClassifierWidget(CelldetectiveWidget):
39
+
40
+ def __init__(self, parent_window):
41
+
42
+ super().__init__()
43
+
44
+ self.parent_window = parent_window
45
+ self.screen_height = (
46
+ self.parent_window.parent_window.parent_window.screen_height
47
+ )
48
+ self.screen_width = self.parent_window.parent_window.parent_window.screen_width
49
+ self.currentAlpha = 1.0
50
+
51
+ self.setWindowTitle("Custom classification")
52
+
53
+ self.mode = self.parent_window.mode
54
+ self.df = self.parent_window.df
55
+
56
+ def _is_numeric_dtype(x):
57
+ try:
58
+ return np.issubdtype(x, np.number)
59
+ except TypeError:
60
+ # Extension dtypes like StringDtype cannot be interpreted by np.issubdtype
61
+ return False
62
+
63
+ is_number = np.vectorize(_is_numeric_dtype)
64
+ is_number_test = is_number(self.df.dtypes)
65
+ self.cols = [col for t, col in zip(is_number_test, self.df.columns) if t]
66
+
67
+ self.class_name = "custom"
68
+ self.name_le = QLineEdit(self.class_name)
69
+ self.init_class()
70
+
71
+ # Create the QComboBox and add some items
72
+ center_window(self)
73
+
74
+ layout = QVBoxLayout(self)
75
+ layout.setContentsMargins(30, 30, 30, 30)
76
+
77
+ name_layout = QHBoxLayout()
78
+ name_layout.addWidget(QLabel("class name: "), 33)
79
+ name_layout.addWidget(self.name_le, 66)
80
+ layout.addLayout(name_layout)
81
+
82
+ fig_btn_hbox = QHBoxLayout()
83
+ fig_btn_hbox.addWidget(QLabel(""), 95)
84
+ self.project_times_btn = QPushButton("")
85
+ self.project_times_btn.setStyleSheet(
86
+ self.parent_window.parent_window.parent_window.button_select_all
87
+ )
88
+ self.project_times_btn.setIcon(icon(MDI6.math_integral, color="black"))
89
+ self.project_times_btn.setToolTip("Project measurements at all times.")
90
+ self.project_times_btn.setIconSize(QSize(20, 20))
91
+ self.project_times = False
92
+ self.project_times_btn.clicked.connect(self.switch_projection)
93
+ fig_btn_hbox.addWidget(self.project_times_btn, 5)
94
+ layout.addLayout(fig_btn_hbox)
95
+
96
+ # Figure
97
+ self.initalize_props_scatter()
98
+ layout.addWidget(self.propscanvas)
99
+
100
+ # slider
101
+ self.frame_slider = QLabeledSlider()
102
+ self.frame_slider.setSingleStep(1)
103
+ self.frame_slider.setOrientation(Qt.Horizontal)
104
+ self.frame_slider.setRange(0, int(self.df.FRAME.max()) - 1)
105
+ self.frame_slider.setValue(0)
106
+ self.currentFrame = 0
107
+
108
+ slider_hbox = QHBoxLayout()
109
+ slider_hbox.addWidget(QLabel("frame: "), 10)
110
+ slider_hbox.addWidget(self.frame_slider, 90)
111
+ layout.addLayout(slider_hbox)
112
+
113
+ # transparency slider
114
+ self.alpha_slider = QLabeledDoubleSlider()
115
+ self.alpha_slider.setSingleStep(0.001)
116
+ self.alpha_slider.setOrientation(Qt.Horizontal)
117
+ self.alpha_slider.setRange(0, 1)
118
+ self.alpha_slider.setValue(1.0)
119
+ self.alpha_slider.setDecimals(3)
120
+
121
+ slider_alpha_hbox = QHBoxLayout()
122
+ slider_alpha_hbox.addWidget(QLabel("transparency: "), 10)
123
+ slider_alpha_hbox.addWidget(self.alpha_slider, 90)
124
+ layout.addLayout(slider_alpha_hbox)
125
+
126
+ self.features_cb = [QSearchableComboBox() for _ in range(2)]
127
+ self.log_btns = [QPushButton() for _ in range(2)]
128
+
129
+ for i in range(2):
130
+ hbox_feat = QHBoxLayout()
131
+ hbox_feat.addWidget(QLabel(f"feature {i}: "), 20)
132
+ hbox_feat.addWidget(self.features_cb[i], 75)
133
+ hbox_feat.addWidget(self.log_btns[i], 5)
134
+ layout.addLayout(hbox_feat)
135
+
136
+ self.features_cb[i].clear()
137
+ self.features_cb[i].addItems(sorted(list(self.cols), key=str.lower))
138
+ self.features_cb[i].currentTextChanged.connect(self.update_props_scatter)
139
+ self.features_cb[i].setCurrentIndex(i)
140
+
141
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
142
+ self.log_btns[i].setStyleSheet(self.button_select_all)
143
+ self.log_btns[i].clicked.connect(lambda ch, i=i: self.switch_to_log(i))
144
+
145
+ hbox_classify = QHBoxLayout()
146
+ hbox_classify.addWidget(QLabel("classify: "), 10)
147
+ self.property_query_le = QLineEdit()
148
+ self.property_query_le.setPlaceholderText(
149
+ "classify points using a query such as: area > 100 or eccentricity > 0.95"
150
+ )
151
+ self.property_query_le.setToolTip(
152
+ 'Classify points using a query on measurements.\nYou can use "and" and "or" conditions to combine\nmeasurements (e.g. "area > 100 or eccentricity > 0.95").'
153
+ )
154
+ self.property_query_le.textChanged.connect(self.activate_submit_btn)
155
+ hbox_classify.addWidget(self.property_query_le, 70)
156
+ self.submit_query_btn = QPushButton("Submit...")
157
+ self.submit_query_btn.clicked.connect(self.apply_property_query)
158
+ self.submit_query_btn.setEnabled(False)
159
+ hbox_classify.addWidget(self.submit_query_btn, 20)
160
+ layout.addLayout(hbox_classify)
161
+
162
+ self.time_corr = QCheckBox("Time correlated")
163
+ if "TRACK_ID" in self.df.columns:
164
+ self.time_corr.setEnabled(True)
165
+ else:
166
+ self.time_corr.setEnabled(False)
167
+
168
+ time_prop_hbox = QHBoxLayout()
169
+ time_prop_hbox.addWidget(self.time_corr, alignment=Qt.AlignCenter)
170
+
171
+ self.help_propagate_btn = QPushButton()
172
+ self.help_propagate_btn.setIcon(icon(MDI6.help_circle, color=self.help_color))
173
+ self.help_propagate_btn.setIconSize(QSize(20, 20))
174
+ self.help_propagate_btn.clicked.connect(self.help_propagate)
175
+ self.help_propagate_btn.setStyleSheet(self.button_select_all)
176
+ self.help_propagate_btn.setToolTip("Help.")
177
+ time_prop_hbox.addWidget(self.help_propagate_btn, 5, alignment=Qt.AlignRight)
178
+
179
+ layout.addLayout(time_prop_hbox)
180
+
181
+ self.irreversible_event_btn = QRadioButton("irreversible event")
182
+ self.unique_state_btn = QRadioButton("unique state")
183
+ self.transient_event_btn = QRadioButton("transient event")
184
+ time_corr_btn_group = QButtonGroup()
185
+ self.unique_state_btn.click()
186
+
187
+ time_corr_layout = QHBoxLayout()
188
+ time_corr_layout.addWidget(self.unique_state_btn, 33, alignment=Qt.AlignCenter)
189
+ time_corr_layout.addWidget(
190
+ self.irreversible_event_btn, 33, alignment=Qt.AlignCenter
191
+ )
192
+ time_corr_layout.addWidget(
193
+ self.transient_event_btn, 33, alignment=Qt.AlignCenter
194
+ )
195
+ layout.addLayout(time_corr_layout)
196
+
197
+ self.prereq_event_check = QCheckBox("prerequisite event:")
198
+ self.prereq_event_check.toggled.connect(self.activate_prereq_cb)
199
+ self.prereq_event_cb = QComboBox()
200
+ event_cols = ["--"] + [
201
+ c.replace("t_", "") for c in self.cols if c.startswith("t_")
202
+ ]
203
+ self.prereq_event_cb.addItems(event_cols)
204
+ self.prereq_event_check.setEnabled(False)
205
+ self.prereq_event_cb.setEnabled(False)
206
+
207
+ self.r2_slider = QLabeledDoubleSlider()
208
+ self.r2_slider.setValue(0.75)
209
+ self.r2_slider.setRange(0, 1)
210
+ self.r2_slider.setSingleStep(0.01)
211
+ self.r2_slider.setOrientation(Qt.Horizontal)
212
+ self.r2_label = QLabel("R2 tolerance:")
213
+ self.r2_label.setToolTip(
214
+ "Minimum R2 between the fit sigmoid and the binary response to the filters to accept the event."
215
+ )
216
+
217
+ r2_threshold_layout = QHBoxLayout()
218
+ r2_threshold_layout.addWidget(QLabel(""), 33)
219
+ r2_threshold_layout.addWidget(self.r2_label, 13)
220
+ r2_threshold_layout.addWidget(self.r2_slider, 20)
221
+ r2_threshold_layout.addWidget(QLabel(""), 33)
222
+
223
+ layout.addLayout(r2_threshold_layout)
224
+
225
+ self.time_corr_options = [
226
+ self.irreversible_event_btn,
227
+ self.unique_state_btn,
228
+ self.prereq_event_check,
229
+ self.prereq_event_cb,
230
+ self.transient_event_btn,
231
+ ]
232
+ for btn in [
233
+ self.irreversible_event_btn,
234
+ self.unique_state_btn,
235
+ self.transient_event_btn,
236
+ ]:
237
+ time_corr_btn_group.addButton(btn)
238
+ btn.setEnabled(False)
239
+ self.time_corr.toggled.connect(self.activate_time_corr_options)
240
+
241
+ self.irreversible_event_btn.clicked.connect(self.activate_r2)
242
+ self.unique_state_btn.clicked.connect(self.activate_r2)
243
+ self.transient_event_btn.clicked.connect(self.activate_r2)
244
+
245
+ for wg in [self.r2_slider, self.r2_label]:
246
+ wg.setEnabled(False)
247
+
248
+ prereq_layout = QHBoxLayout()
249
+ prereq_layout.setContentsMargins(30, 0, 0, 0)
250
+ prereq_layout.addWidget(self.prereq_event_check, 20)
251
+ prereq_layout.addWidget(self.prereq_event_cb, 80)
252
+ layout.addLayout(prereq_layout)
253
+
254
+ layout.addWidget(QLabel())
255
+
256
+ self.submit_btn = QPushButton("apply")
257
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
258
+ self.submit_btn.clicked.connect(self.submit_classification)
259
+ self.submit_btn.setEnabled(False)
260
+ layout.addWidget(self.submit_btn, 30)
261
+
262
+ self.frame_slider.valueChanged.connect(self.set_frame)
263
+ self.alpha_slider.valueChanged.connect(self.set_transparency)
264
+
265
+ def activate_prereq_cb(self):
266
+ if self.prereq_event_check.isChecked():
267
+ self.prereq_event_cb.setEnabled(True)
268
+ else:
269
+ self.prereq_event_cb.setEnabled(False)
270
+
271
+ def activate_submit_btn(self):
272
+
273
+ if self.property_query_le.text() == "":
274
+ self.submit_query_btn.setEnabled(False)
275
+ self.submit_btn.setEnabled(False)
276
+ else:
277
+ self.submit_query_btn.setEnabled(True)
278
+ self.submit_btn.setEnabled(True)
279
+
280
+ def activate_r2(self):
281
+ if self.irreversible_event_btn.isChecked() and self.time_corr.isChecked():
282
+ for wg in [self.r2_slider, self.r2_label]:
283
+ wg.setEnabled(True)
284
+ else:
285
+ for wg in [self.r2_slider, self.r2_label]:
286
+ wg.setEnabled(False)
287
+
288
+ def activate_time_corr_options(self):
289
+
290
+ if self.time_corr.isChecked():
291
+ for btn in self.time_corr_options:
292
+ btn.setEnabled(True)
293
+ if self.irreversible_event_btn.isChecked():
294
+ for wg in [self.r2_slider, self.r2_label]:
295
+ wg.setEnabled(True)
296
+ else:
297
+ for wg in [self.r2_slider, self.r2_label]:
298
+ wg.setEnabled(False)
299
+ else:
300
+ for btn in self.time_corr_options:
301
+ btn.setEnabled(False)
302
+ for wg in [self.r2_slider, self.r2_label]:
303
+ wg.setEnabled(False)
304
+
305
+ def init_class(self):
306
+
307
+ self.class_name = "custom"
308
+ i = 1
309
+ while self.class_name in self.df.columns:
310
+ self.class_name = f"custom_{i}"
311
+ i += 1
312
+ self.name_le.setText(self.class_name)
313
+ self.df.loc[:, self.class_name] = 1
314
+
315
+ def initalize_props_scatter(self):
316
+ """
317
+ Define properties scatter.
318
+ """
319
+
320
+ self.fig_props, self.ax_props = plt.subplots(figsize=(4, 4), tight_layout=True)
321
+ self.propscanvas = FigureCanvas(self.fig_props, interactive=True)
322
+ self.fig_props.set_facecolor("none")
323
+ self.scat_props = self.ax_props.scatter(
324
+ [], [], color="k", alpha=self.currentAlpha
325
+ )
326
+ self.propscanvas.canvas.draw_idle()
327
+ self.propscanvas.canvas.setMinimumHeight(self.screen_height // 5)
328
+
329
+ def closeEvent(self, event):
330
+ self.ax_props.cla()
331
+ self.fig_props.clf()
332
+ plt.close(self.fig_props)
333
+ super().closeEvent(event)
334
+
335
+ def update_props_scatter(self, feature_changed=True):
336
+
337
+ try:
338
+ if np.any(self.df[self.features_cb[0].currentText()].to_numpy() <= 0.0):
339
+ if self.ax_props.get_yscale() == "log":
340
+ self.log_btns[0].click()
341
+ self.log_btns[0].setEnabled(False)
342
+ else:
343
+ self.log_btns[0].setEnabled(True)
344
+
345
+ if np.any(self.df[self.features_cb[1].currentText()].to_numpy() <= 0.0):
346
+ if self.ax_props.get_xscale() == "log":
347
+ self.log_btns[1].click()
348
+ self.log_btns[1].setEnabled(False)
349
+ else:
350
+ self.log_btns[1].setEnabled(True)
351
+ except Exception as e:
352
+ print(e)
353
+
354
+ class_name = self.class_name
355
+
356
+ try:
357
+
358
+ if not self.project_times:
359
+ self.scat_props.set_offsets(
360
+ self.df.loc[
361
+ self.df["FRAME"] == self.currentFrame,
362
+ [
363
+ self.features_cb[1].currentText(),
364
+ self.features_cb[0].currentText(),
365
+ ],
366
+ ].to_numpy()
367
+ )
368
+ colors = [
369
+ color_from_status(c)
370
+ for c in self.df.loc[
371
+ self.df["FRAME"] == self.currentFrame, class_name
372
+ ].to_numpy()
373
+ ]
374
+ self.scat_props.set_facecolor(colors)
375
+ else:
376
+ self.scat_props.set_offsets(
377
+ self.df[
378
+ [
379
+ self.features_cb[1].currentText(),
380
+ self.features_cb[0].currentText(),
381
+ ]
382
+ ].to_numpy()
383
+ )
384
+ colors = [color_from_status(c) for c in self.df[class_name].to_numpy()]
385
+ self.scat_props.set_facecolor(colors)
386
+
387
+ self.scat_props.set_alpha(self.currentAlpha)
388
+
389
+ if feature_changed:
390
+
391
+ self.ax_props.set_xlabel(self.features_cb[1].currentText())
392
+ self.ax_props.set_ylabel(self.features_cb[0].currentText())
393
+
394
+ feat_x = self.features_cb[1].currentText()
395
+ feat_y = self.features_cb[0].currentText()
396
+ min_x = self.df.dropna(subset=feat_x)[feat_x].min()
397
+ max_x = self.df.dropna(subset=feat_x)[feat_x].max()
398
+ min_y = self.df.dropna(subset=feat_y)[feat_y].min()
399
+ max_y = self.df.dropna(subset=feat_y)[feat_y].max()
400
+
401
+ x_padding = (max_x - min_x) * 0.05
402
+ y_padding = (max_y - min_y) * 0.05
403
+ if x_padding == 0:
404
+ x_padding = 0.05
405
+ if y_padding == 0:
406
+ y_padding = 0.05
407
+
408
+ if min_x == min_x and max_x == max_x:
409
+ if self.ax_props.get_xscale() == "linear":
410
+ self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
411
+ else:
412
+ self.ax_props.set_xlim(min_x, max_x)
413
+ if min_y == min_y and max_y == max_y:
414
+ if self.ax_props.get_yscale() == "linear":
415
+ self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
416
+ else:
417
+ self.ax_props.set_ylim(min_y, max_y)
418
+
419
+ self.propscanvas.canvas.toolbar.update()
420
+
421
+ self.propscanvas.canvas.draw_idle()
422
+
423
+ except Exception as e:
424
+ print("Exception L355 ", e)
425
+
426
+ def show_warning(self, message: str):
427
+ msgBox = QMessageBox()
428
+ msgBox.setIcon(QMessageBox.Warning)
429
+ link = (
430
+ "https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.query.html"
431
+ )
432
+ msgBox.setText(f"{message}<br><br>See <a href='{link}'>documentation</a>.")
433
+ msgBox.setWindowTitle("Warning")
434
+ msgBox.setStandardButtons(QMessageBox.Ok)
435
+ msgBox.exec()
436
+
437
+ def apply_property_query(self):
438
+
439
+ query = self.property_query_le.text()
440
+ try:
441
+ self.df = classify_cells_from_query(self.df, self.name_le.text(), query)
442
+ except EmptyQueryError as e:
443
+ self.show_warning(str(e))
444
+ except MissingColumnsError as e:
445
+ self.show_warning(f"{e}. Please check your column names.")
446
+ except QueryError as e:
447
+ self.show_warning(f"{e}. Wrap features in backticks if needed.")
448
+ except Exception as e:
449
+ self.show_warning(f"Unexpected error: {e}")
450
+
451
+ self.class_name = "status_" + self.name_le.text()
452
+ if self.df is None:
453
+ msgBox = QMessageBox()
454
+ msgBox.setIcon(QMessageBox.Warning)
455
+ msgBox.setText(
456
+ f"The query could not be understood. No filtering was applied."
457
+ )
458
+ msgBox.setWindowTitle("Warning")
459
+ msgBox.setStandardButtons(QMessageBox.Ok)
460
+ returnValue = msgBox.exec()
461
+ if returnValue == QMessageBox.Ok:
462
+ self.auto_close = False
463
+ return None
464
+
465
+ self.update_props_scatter(feature_changed=False)
466
+
467
+ def set_frame(self, value):
468
+ xlim = self.ax_props.get_xlim()
469
+ ylim = self.ax_props.get_ylim()
470
+ self.currentFrame = value
471
+ self.update_props_scatter(feature_changed=False)
472
+ self.ax_props.set_xlim(xlim)
473
+ self.ax_props.set_ylim(ylim)
474
+
475
+ def set_transparency(self, value):
476
+ xlim = self.ax_props.get_xlim()
477
+ ylim = self.ax_props.get_ylim()
478
+ self.currentAlpha = value
479
+
480
+ self.update_props_scatter(feature_changed=False)
481
+ self.ax_props.set_xlim(xlim)
482
+ self.ax_props.set_ylim(ylim)
483
+
484
+ def switch_projection(self):
485
+ if self.project_times:
486
+ self.project_times = False
487
+ self.project_times_btn.setIcon(icon(MDI6.math_integral, color="black"))
488
+ self.project_times_btn.setIconSize(QSize(20, 20))
489
+ self.frame_slider.setEnabled(True)
490
+ else:
491
+ self.project_times = True
492
+ self.project_times_btn.setIcon(icon(MDI6.math_integral_box, color="black"))
493
+ self.project_times_btn.setIconSize(QSize(20, 20))
494
+ self.frame_slider.setEnabled(False)
495
+ self.update_props_scatter(feature_changed=False)
496
+
497
+ def submit_classification(self):
498
+
499
+ self.auto_close = True
500
+ self.apply_property_query()
501
+ if not self.auto_close:
502
+ return None
503
+
504
+ if self.time_corr.isChecked():
505
+ self.class_name_user = "class_" + self.name_le.text()
506
+ print(f"User defined class name: {self.class_name_user}...")
507
+ if self.class_name_user in self.df.columns:
508
+
509
+ msgBox = QMessageBox()
510
+ msgBox.setIcon(QMessageBox.Information)
511
+ msgBox.setText(
512
+ f"The class column {self.class_name_user} already exists in the table.\nProceeding will "
513
+ f"reclassify. Do you want to continue?"
514
+ )
515
+ msgBox.setWindowTitle("Warning")
516
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
517
+ returnValue = msgBox.exec()
518
+ if returnValue == QMessageBox.Yes:
519
+ pass
520
+ else:
521
+ return None
522
+
523
+ name_map = {self.class_name: self.class_name_user}
524
+ self.df = self.df.drop(
525
+ list(set(name_map.values()) & set(self.df.columns)), axis=1
526
+ ).rename(columns=name_map)
527
+ self.df.reset_index(inplace=True, drop=True)
528
+
529
+ pre_event = None
530
+ if (
531
+ self.prereq_event_check.isChecked()
532
+ and "t_" + self.prereq_event_cb.currentText() in self.cols
533
+ ):
534
+ pre_event = self.prereq_event_cb.currentText()
535
+
536
+ self.df = interpret_track_classification(
537
+ self.df,
538
+ self.class_name_user,
539
+ irreversible_event=self.irreversible_event_btn.isChecked(),
540
+ unique_state=self.unique_state_btn.isChecked(),
541
+ transient_event=self.transient_event_btn.isChecked(),
542
+ r2_threshold=self.r2_slider.value(),
543
+ pre_event=pre_event,
544
+ )
545
+
546
+ else:
547
+ self.group_name_user = "group_" + self.name_le.text()
548
+ print(f"User defined characteristic group name: {self.group_name_user}.")
549
+ if self.group_name_user in self.df.columns:
550
+
551
+ msgBox = QMessageBox()
552
+ msgBox.setIcon(QMessageBox.Information)
553
+ msgBox.setText(
554
+ f"The group column {self.group_name_user} already exists in the table.\nProceeding will "
555
+ f"reclassify. Do you want to continue?"
556
+ )
557
+ msgBox.setWindowTitle("Warning")
558
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
559
+ returnValue = msgBox.exec()
560
+ if returnValue == QMessageBox.Yes:
561
+ pass
562
+ else:
563
+ return None
564
+
565
+ name_map = {self.class_name: self.group_name_user}
566
+ self.df = self.df.drop(
567
+ list(set(name_map.values()) & set(self.df.columns)), axis=1
568
+ ).rename(columns=name_map)
569
+ print(self.df.columns)
570
+ # self.df[self.group_name_user] = self.df[self.group_name_user].replace({0: 1, 1: 0})
571
+ self.df.reset_index(inplace=True, drop=True)
572
+
573
+ if "custom" in list(self.df.columns):
574
+ self.df = self.df.drop(["custom"], axis=1)
575
+
576
+ self.fig_props.set_size_inches(4, 3)
577
+ self.fig_props.suptitle(self.property_query_le.text(), fontsize=10)
578
+ self.fig_props.tight_layout()
579
+ for pos, pos_group in self.df.groupby("position"):
580
+ self.fig_props.savefig(
581
+ str(pos) + os.sep.join(["output", f"{self.class_name}.png"]),
582
+ bbox_inches="tight",
583
+ dpi=300,
584
+ )
585
+ pos_group.to_csv(
586
+ str(pos)
587
+ + os.sep.join(["output", "tables", f"trajectories_{self.mode}.csv"]),
588
+ index=False,
589
+ )
590
+
591
+ self.parent_window.parent_window.update_position_options()
592
+ self.close()
593
+
594
+ def help_propagate(self):
595
+ """
596
+ Helper for segmentation strategy between threshold-based and Deep learning.
597
+ """
598
+
599
+ dict_path = os.sep.join(
600
+ [
601
+ get_software_location(),
602
+ "celldetective",
603
+ "gui",
604
+ "help",
605
+ "propagate-classification.json",
606
+ ]
607
+ )
608
+
609
+ with open(dict_path) as f:
610
+ d = json.load(f)
611
+
612
+ suggestion = help_generic(d)
613
+ if isinstance(suggestion, str):
614
+ print(f"{suggestion=}")
615
+ msgBox = QMessageBox()
616
+ msgBox.setIcon(QMessageBox.Information)
617
+ msgBox.setTextFormat(Qt.RichText)
618
+ msgBox.setText(rf"{suggestion}")
619
+ msgBox.setWindowTitle("Info")
620
+ msgBox.setStandardButtons(QMessageBox.Ok)
621
+ returnValue = msgBox.exec()
622
+ if returnValue == QMessageBox.Ok:
623
+ return None
624
+
625
+ def switch_to_log(self, i):
626
+ """
627
+ Switch threshold histogram to log scale. Auto adjust.
628
+ """
629
+
630
+ if i == 1:
631
+ try:
632
+ feat_x = self.features_cb[1].currentText()
633
+ min_x = self.df.dropna(subset=feat_x)[feat_x].min()
634
+ max_x = self.df.dropna(subset=feat_x)[feat_x].max()
635
+ x_padding = (max_x - min_x) * 0.05
636
+ if x_padding == 0:
637
+ x_padding = 0.05
638
+
639
+ if self.ax_props.get_xscale() == "linear":
640
+ self.ax_props.set_xlim(min_x, max_x)
641
+ self.ax_props.set_xscale("log")
642
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="#1565c0"))
643
+ else:
644
+ self.ax_props.set_xscale("linear")
645
+ self.ax_props.set_xlim(min_x - x_padding, max_x + x_padding)
646
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
647
+ except Exception as e:
648
+ print(e)
649
+ elif i == 0:
650
+ try:
651
+ feat_y = self.features_cb[0].currentText()
652
+ min_y = self.df.dropna(subset=feat_y)[feat_y].min()
653
+ max_y = self.df.dropna(subset=feat_y)[feat_y].max()
654
+ y_padding = (max_y - min_y) * 0.05
655
+ if y_padding == 0:
656
+ y_padding = 0.05
657
+
658
+ if self.ax_props.get_yscale() == "linear":
659
+ self.ax_props.set_ylim(min_y, max_y)
660
+ self.ax_props.set_yscale("log")
661
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="#1565c0"))
662
+ else:
663
+ self.ax_props.set_yscale("linear")
664
+ self.ax_props.set_ylim(min_y - y_padding, max_y + y_padding)
665
+ self.log_btns[i].setIcon(icon(MDI6.math_log, color="black"))
666
+ except Exception as e:
667
+ print(e)
668
+
669
+ self.ax_props.autoscale()
670
+ self.propscanvas.canvas.draw_idle()
671
+
672
+ print("Done.")
@@ -1504,8 +1504,9 @@ class TableUI(CelldetectiveMainWindow):
1504
1504
  if self.projection_option.isChecked():
1505
1505
 
1506
1506
  self.projection_mode = self.projection_op_cb.currentText()
1507
- op = getattr(self.current_data.groupby(self.groupby_cols), self.projection_mode)
1508
- group_table = op(self.current_data.groupby(self.groupby_cols))
1507
+ group_table = getattr(
1508
+ self.current_data.groupby(self.groupby_cols), self.projection_mode
1509
+ )(numeric_only=True)
1509
1510
 
1510
1511
  for c in self.static_columns:
1511
1512
  try: