celldetective 1.4.2__py3-none-any.whl → 1.5.0b0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. celldetective/__init__.py +25 -0
  2. celldetective/__main__.py +62 -43
  3. celldetective/_version.py +1 -1
  4. celldetective/extra_properties.py +477 -399
  5. celldetective/filters.py +192 -97
  6. celldetective/gui/InitWindow.py +541 -411
  7. celldetective/gui/__init__.py +0 -15
  8. celldetective/gui/about.py +44 -39
  9. celldetective/gui/analyze_block.py +120 -84
  10. celldetective/gui/base/__init__.py +0 -0
  11. celldetective/gui/base/channel_norm_generator.py +335 -0
  12. celldetective/gui/base/components.py +249 -0
  13. celldetective/gui/base/feature_choice.py +92 -0
  14. celldetective/gui/base/figure_canvas.py +52 -0
  15. celldetective/gui/base/list_widget.py +133 -0
  16. celldetective/gui/{styles.py → base/styles.py} +92 -36
  17. celldetective/gui/base/utils.py +33 -0
  18. celldetective/gui/base_annotator.py +900 -767
  19. celldetective/gui/classifier_widget.py +6 -22
  20. celldetective/gui/configure_new_exp.py +777 -671
  21. celldetective/gui/control_panel.py +635 -524
  22. celldetective/gui/dynamic_progress.py +449 -0
  23. celldetective/gui/event_annotator.py +2023 -1662
  24. celldetective/gui/generic_signal_plot.py +1292 -944
  25. celldetective/gui/gui_utils.py +899 -1289
  26. celldetective/gui/interactions_block.py +658 -0
  27. celldetective/gui/interactive_timeseries_viewer.py +447 -0
  28. celldetective/gui/json_readers.py +48 -15
  29. celldetective/gui/layouts/__init__.py +5 -0
  30. celldetective/gui/layouts/background_model_free_layout.py +537 -0
  31. celldetective/gui/layouts/channel_offset_layout.py +134 -0
  32. celldetective/gui/layouts/local_correction_layout.py +91 -0
  33. celldetective/gui/layouts/model_fit_layout.py +372 -0
  34. celldetective/gui/layouts/operation_layout.py +68 -0
  35. celldetective/gui/layouts/protocol_designer_layout.py +96 -0
  36. celldetective/gui/pair_event_annotator.py +3130 -2435
  37. celldetective/gui/plot_measurements.py +586 -267
  38. celldetective/gui/plot_signals_ui.py +724 -506
  39. celldetective/gui/preprocessing_block.py +395 -0
  40. celldetective/gui/process_block.py +1678 -1831
  41. celldetective/gui/seg_model_loader.py +580 -473
  42. celldetective/gui/settings/__init__.py +0 -7
  43. celldetective/gui/settings/_cellpose_model_params.py +181 -0
  44. celldetective/gui/settings/_event_detection_model_params.py +95 -0
  45. celldetective/gui/settings/_segmentation_model_params.py +159 -0
  46. celldetective/gui/settings/_settings_base.py +77 -65
  47. celldetective/gui/settings/_settings_event_model_training.py +752 -526
  48. celldetective/gui/settings/_settings_measurements.py +1133 -964
  49. celldetective/gui/settings/_settings_neighborhood.py +574 -488
  50. celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
  51. celldetective/gui/settings/_settings_signal_annotator.py +329 -305
  52. celldetective/gui/settings/_settings_tracking.py +1304 -1094
  53. celldetective/gui/settings/_stardist_model_params.py +98 -0
  54. celldetective/gui/survival_ui.py +422 -312
  55. celldetective/gui/tableUI.py +1665 -1701
  56. celldetective/gui/table_ops/_maths.py +295 -0
  57. celldetective/gui/table_ops/_merge_groups.py +140 -0
  58. celldetective/gui/table_ops/_merge_one_hot.py +95 -0
  59. celldetective/gui/table_ops/_query_table.py +43 -0
  60. celldetective/gui/table_ops/_rename_col.py +44 -0
  61. celldetective/gui/thresholds_gui.py +382 -179
  62. celldetective/gui/viewers/__init__.py +0 -0
  63. celldetective/gui/viewers/base_viewer.py +700 -0
  64. celldetective/gui/viewers/channel_offset_viewer.py +331 -0
  65. celldetective/gui/viewers/contour_viewer.py +394 -0
  66. celldetective/gui/viewers/size_viewer.py +153 -0
  67. celldetective/gui/viewers/spot_detection_viewer.py +341 -0
  68. celldetective/gui/viewers/threshold_viewer.py +309 -0
  69. celldetective/gui/workers.py +304 -126
  70. celldetective/log_manager.py +92 -0
  71. celldetective/measure.py +1895 -1478
  72. celldetective/napari/__init__.py +0 -0
  73. celldetective/napari/utils.py +1025 -0
  74. celldetective/neighborhood.py +1914 -1448
  75. celldetective/preprocessing.py +1620 -1220
  76. celldetective/processes/__init__.py +0 -0
  77. celldetective/processes/background_correction.py +271 -0
  78. celldetective/processes/compute_neighborhood.py +894 -0
  79. celldetective/processes/detect_events.py +246 -0
  80. celldetective/processes/measure_cells.py +565 -0
  81. celldetective/processes/segment_cells.py +760 -0
  82. celldetective/processes/track_cells.py +435 -0
  83. celldetective/processes/train_segmentation_model.py +694 -0
  84. celldetective/processes/train_signal_model.py +265 -0
  85. celldetective/processes/unified_process.py +292 -0
  86. celldetective/regionprops/_regionprops.py +358 -317
  87. celldetective/relative_measurements.py +987 -710
  88. celldetective/scripts/measure_cells.py +313 -212
  89. celldetective/scripts/measure_relative.py +90 -46
  90. celldetective/scripts/segment_cells.py +165 -104
  91. celldetective/scripts/segment_cells_thresholds.py +96 -68
  92. celldetective/scripts/track_cells.py +198 -149
  93. celldetective/scripts/train_segmentation_model.py +324 -201
  94. celldetective/scripts/train_signal_model.py +87 -45
  95. celldetective/segmentation.py +844 -749
  96. celldetective/signals.py +3514 -2861
  97. celldetective/tracking.py +30 -15
  98. celldetective/utils/__init__.py +0 -0
  99. celldetective/utils/cellpose_utils/__init__.py +133 -0
  100. celldetective/utils/color_mappings.py +42 -0
  101. celldetective/utils/data_cleaning.py +630 -0
  102. celldetective/utils/data_loaders.py +450 -0
  103. celldetective/utils/dataset_helpers.py +207 -0
  104. celldetective/utils/downloaders.py +197 -0
  105. celldetective/utils/event_detection/__init__.py +8 -0
  106. celldetective/utils/experiment.py +1782 -0
  107. celldetective/utils/image_augmenters.py +308 -0
  108. celldetective/utils/image_cleaning.py +74 -0
  109. celldetective/utils/image_loaders.py +926 -0
  110. celldetective/utils/image_transforms.py +335 -0
  111. celldetective/utils/io.py +62 -0
  112. celldetective/utils/mask_cleaning.py +348 -0
  113. celldetective/utils/mask_transforms.py +5 -0
  114. celldetective/utils/masks.py +184 -0
  115. celldetective/utils/maths.py +351 -0
  116. celldetective/utils/model_getters.py +325 -0
  117. celldetective/utils/model_loaders.py +296 -0
  118. celldetective/utils/normalization.py +380 -0
  119. celldetective/utils/parsing.py +465 -0
  120. celldetective/utils/plots/__init__.py +0 -0
  121. celldetective/utils/plots/regression.py +53 -0
  122. celldetective/utils/resources.py +34 -0
  123. celldetective/utils/stardist_utils/__init__.py +104 -0
  124. celldetective/utils/stats.py +90 -0
  125. celldetective/utils/types.py +21 -0
  126. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
  127. celldetective-1.5.0b0.dist-info/RECORD +187 -0
  128. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
  129. tests/gui/test_new_project.py +129 -117
  130. tests/gui/test_project.py +127 -79
  131. tests/test_filters.py +39 -15
  132. tests/test_notebooks.py +8 -0
  133. tests/test_tracking.py +232 -13
  134. tests/test_utils.py +123 -77
  135. celldetective/gui/base_components.py +0 -23
  136. celldetective/gui/layouts.py +0 -1602
  137. celldetective/gui/processes/compute_neighborhood.py +0 -594
  138. celldetective/gui/processes/measure_cells.py +0 -360
  139. celldetective/gui/processes/segment_cells.py +0 -499
  140. celldetective/gui/processes/track_cells.py +0 -303
  141. celldetective/gui/processes/train_segmentation_model.py +0 -270
  142. celldetective/gui/processes/train_signal_model.py +0 -108
  143. celldetective/gui/table_ops/merge_groups.py +0 -118
  144. celldetective/gui/viewers.py +0 -1354
  145. celldetective/io.py +0 -3663
  146. celldetective/utils.py +0 -3108
  147. celldetective-1.4.2.dist-info/RECORD +0 -123
  148. /celldetective/{gui/processes → processes}/downloader.py +0 -0
  149. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/top_level.txt +0 -0
@@ -1,499 +1,113 @@
1
- from PyQt5.QtWidgets import QRadioButton, QButtonGroup, QTableView, QAction, QMenu,QFileDialog, QLineEdit, QHBoxLayout, QPushButton, QVBoxLayout, QComboBox, QLabel, QCheckBox, QMessageBox
1
+ from PyQt5.QtWidgets import (
2
+ QRadioButton,
3
+ QButtonGroup,
4
+ QTableView,
5
+ QAction,
6
+ QMenu,
7
+ QFileDialog,
8
+ QHBoxLayout,
9
+ QPushButton,
10
+ QVBoxLayout,
11
+ QComboBox,
12
+ QLabel,
13
+ QCheckBox,
14
+ QMessageBox,
15
+ )
2
16
  from PyQt5.QtCore import Qt
3
- from PyQt5.QtGui import QBrush, QColor, QDoubleValidator
4
- import pandas as pd
5
- import matplotlib.pyplot as plt
6
-
7
- from celldetective.gui.table_ops.merge_groups import MergeGroupWidget
8
-
9
- plt.rcParams['svg.fonttype'] = 'none'
10
- from celldetective.gui.gui_utils import FigureCanvas, center_window, QHSeperationLine, GenericOpColWidget, PandasModel
11
- from celldetective.utils import differentiate_per_track, collapse_trajectories_by_status, test_2samp_generic, safe_log
12
- from celldetective.neighborhood import extract_neighborhood_in_pair_table
17
+ from PyQt5.QtGui import QBrush, QColor
18
+
19
+ from celldetective.gui.gui_utils import (
20
+ PandasModel,
21
+ )
22
+ from celldetective.gui.base.figure_canvas import FigureCanvas
23
+ from celldetective.gui.base.utils import center_window
24
+ from celldetective.gui.table_ops._maths import DifferentiateColWidget, OperationOnColsWidget, CalibrateColWidget, \
25
+ AbsColWidget, LogColWidget
26
+ from celldetective.gui.table_ops._merge_one_hot import MergeOneHotWidget
27
+ from celldetective.gui.table_ops._query_table import QueryWidget
28
+ from celldetective.gui.table_ops._rename_col import RenameColWidget
13
29
  from celldetective.relative_measurements import expand_pair_table
30
+ from celldetective.utils.data_cleaning import collapse_trajectories_by_status
31
+ from celldetective.utils.stats import test_2samp_generic
14
32
  import numpy as np
15
- import seaborn as sns
16
- import matplotlib.cm as mcm
17
33
  import os
18
- from celldetective.gui import CelldetectiveWidget, CelldetectiveMainWindow
19
- from superqt import QColormapComboBox, QLabeledSlider, QSearchableComboBox
20
- from superqt.fonticon import icon
21
- from fonticon_mdi6 import MDI6
34
+ from celldetective.gui.base.components import (
35
+ CelldetectiveWidget,
36
+ CelldetectiveMainWindow,
37
+ QHSeperationLine,
38
+ )
39
+ from superqt import QColormapComboBox, QSearchableComboBox
22
40
  from math import floor
41
+ from celldetective import get_logger
42
+ from celldetective.utils.types import test_bool_array
23
43
 
24
- from matplotlib import colormaps
25
- import matplotlib.cm
26
-
27
-
28
- class QueryWidget(CelldetectiveWidget):
29
-
30
- def __init__(self, parent_window):
31
-
32
- super().__init__()
33
- self.parent_window = parent_window
34
-
35
- self.setWindowTitle("Filter table")
36
- # Create the QComboBox and add some items
44
+ logger = get_logger(__name__)
37
45
 
38
- layout = QHBoxLayout(self)
39
- layout.setContentsMargins(30,30,30,30)
40
- self.query_le = QLineEdit()
41
- layout.addWidget(self.query_le, 70)
42
-
43
- self.submit_btn = QPushButton('submit')
44
- self.submit_btn.clicked.connect(self.filter_table)
45
- layout.addWidget(self.submit_btn, 30)
46
- center_window(self)
47
-
48
- def filter_table(self):
49
- try:
50
- query_text = self.query_le.text() #.replace('class', '`class`')
51
- tab = self.parent_window.data.query(query_text)
52
- self.subtable = TableUI(tab, query_text, plot_mode="static", population=self.parent_window.population)
53
- self.subtable.show()
54
- self.close()
55
- except Exception as e:
56
- print(e)
57
- return None
58
46
 
47
+ class PivotTableUI(CelldetectiveWidget):
59
48
 
60
- class MergeOneHotWidget(CelldetectiveWidget):
49
+ def __init__(self, data, title="", mode=None, *args, **kwargs):
61
50
 
62
- def __init__(self, parent_window, selected_columns=None):
51
+ CelldetectiveWidget.__init__(self, *args, **kwargs)
63
52
 
64
- super().__init__()
65
- self.parent_window = parent_window
66
- self.selected_columns = selected_columns
53
+ self.data = data
54
+ self.title = title
55
+ self.mode = mode
67
56
 
68
- self.setWindowTitle("Merge one-hot encoded columns...")
69
- # Create the QComboBox and add some items
70
- center_window(self)
71
-
72
- self.layout = QVBoxLayout(self)
73
- self.layout.setContentsMargins(30,30,30,30)
74
-
75
- if self.selected_columns is not None:
76
- n_cols = len(self.selected_columns)
77
- else:
78
- n_cols = 2
79
-
80
- name_hbox = QHBoxLayout()
81
- name_hbox.addWidget(QLabel('New categorical column: '), 33)
82
- self.new_col_le = QLineEdit()
83
- self.new_col_le.setText('categorical_')
84
- self.new_col_le.textChanged.connect(self.allow_merge)
85
- name_hbox.addWidget(self.new_col_le, 66)
86
- self.layout.addLayout(name_hbox)
87
-
88
-
89
- self.layout.addWidget(QLabel('Source columns: '))
90
-
91
- self.cbs = [QSearchableComboBox() for i in range(n_cols)]
92
- self.cbs_layout = QVBoxLayout()
93
-
94
- for i in range(n_cols):
95
- lay = QHBoxLayout()
96
- lay.addWidget(QLabel(f'column {i}: '), 33)
97
- self.cbs[i].addItems(['--']+list(self.parent_window.data.columns))
98
- if self.selected_columns is not None:
99
- self.cbs[i].setCurrentText(self.selected_columns[i])
100
- lay.addWidget(self.cbs[i], 66)
101
- self.cbs_layout.addLayout(lay)
102
-
103
- self.layout.addLayout(self.cbs_layout)
104
-
105
- hbox = QHBoxLayout()
106
- self.add_col_btn = QPushButton('Add column')
107
- self.add_col_btn.clicked.connect(self.add_col)
108
- self.add_col_btn.setStyleSheet(self.button_add)
109
- self.add_col_btn.setIcon(icon(MDI6.plus,color="black"))
110
-
111
- hbox.addWidget(QLabel(''), 50)
112
- hbox.addWidget(self.add_col_btn, 50, alignment=Qt.AlignRight)
113
- self.layout.addLayout(hbox)
114
-
115
- self.submit_btn = QPushButton('Merge')
116
- self.submit_btn.setStyleSheet(self.button_style_sheet)
117
- self.submit_btn.clicked.connect(self.merge_cols)
118
- self.layout.addWidget(self.submit_btn, 30)
119
-
120
- self.setAttribute(Qt.WA_DeleteOnClose)
121
-
122
- def add_col(self):
123
- self.cbs.append(QSearchableComboBox())
124
- self.cbs[-1].addItems(['--']+list(self.parent_window.data.columns))
125
- lay = QHBoxLayout()
126
- lay.addWidget(QLabel(f'column {len(self.cbs)-1}: '), 33)
127
- lay.addWidget(self.cbs[-1], 66)
128
- self.cbs_layout.addLayout(lay)
129
-
130
- def merge_cols(self):
131
-
132
- self.parent_window.data[self.new_col_le.text()] = self.parent_window.data.loc[:,list(self.selected_columns)].idxmax(axis=1)
133
- self.parent_window.model = PandasModel(self.parent_window.data)
134
- self.parent_window.table_view.setModel(self.parent_window.model)
135
- self.close()
136
-
137
- def allow_merge(self):
138
-
139
- if self.new_col_le.text()=='':
140
- self.submit_btn.setEnabled(False)
141
- else:
142
- self.submit_btn.setEnabled(True)
143
-
144
-
145
- class DifferentiateColWidget(CelldetectiveWidget):
146
-
147
- def __init__(self, parent_window, column=None):
148
-
149
- super().__init__()
150
- self.parent_window = parent_window
151
- self.column = column
152
-
153
- self.setWindowTitle("d/dt")
154
- # Create the QComboBox and add some items
155
- center_window(self)
156
-
157
- layout = QVBoxLayout(self)
158
- layout.setContentsMargins(30,30,30,30)
159
-
160
- self.measurements_cb = QComboBox()
161
- self.measurements_cb.addItems(list(self.parent_window.data.columns))
162
- if self.column is not None:
163
- idx = self.measurements_cb.findText(self.column)
164
- self.measurements_cb.setCurrentIndex(idx)
165
-
166
- measurement_layout = QHBoxLayout()
167
- measurement_layout.addWidget(QLabel('measurements: '), 25)
168
- measurement_layout.addWidget(self.measurements_cb, 75)
169
- layout.addLayout(measurement_layout)
170
-
171
- self.window_size_slider = QLabeledSlider()
172
- self.window_size_slider.setRange(1,int(np.nanmax(self.parent_window.data.FRAME.to_numpy())))
173
- self.window_size_slider.setValue(3)
174
- window_layout = QHBoxLayout()
175
- window_layout.addWidget(QLabel('window size: '), 25)
176
- window_layout.addWidget(self.window_size_slider, 75)
177
- layout.addLayout(window_layout)
178
-
179
- self.backward_btn = QRadioButton('backward')
180
- self.bi_btn = QRadioButton('bi')
181
- self.bi_btn.click()
182
- self.forward_btn = QRadioButton('forward')
183
- self.mode_btn_group = QButtonGroup()
184
- self.mode_btn_group.addButton(self.backward_btn)
185
- self.mode_btn_group.addButton(self.bi_btn)
186
- self.mode_btn_group.addButton(self.forward_btn)
187
-
188
- mode_layout = QHBoxLayout()
189
- mode_layout.addWidget(QLabel('mode: '),25)
190
- mode_sublayout = QHBoxLayout()
191
- mode_sublayout.addWidget(self.backward_btn, 33, alignment=Qt.AlignCenter)
192
- mode_sublayout.addWidget(self.bi_btn, 33, alignment=Qt.AlignCenter)
193
- mode_sublayout.addWidget(self.forward_btn, 33, alignment=Qt.AlignCenter)
194
- mode_layout.addLayout(mode_sublayout, 75)
195
- layout.addLayout(mode_layout)
196
-
197
- self.submit_btn = QPushButton('Compute')
198
- self.submit_btn.setStyleSheet(self.button_style_sheet)
199
- self.submit_btn.clicked.connect(self.compute_derivative_and_add_new_column)
200
- layout.addWidget(self.submit_btn, 30)
201
-
202
- self.setAttribute(Qt.WA_DeleteOnClose)
203
-
204
-
205
- def compute_derivative_and_add_new_column(self):
206
-
207
- if self.bi_btn.isChecked():
208
- mode = 'bi'
209
- elif self.forward_btn.isChecked():
210
- mode = 'forward'
211
- elif self.backward_btn.isChecked():
212
- mode = 'backward'
213
- self.parent_window.data = differentiate_per_track(self.parent_window.data,
214
- self.measurements_cb.currentText(),
215
- window_size=self.window_size_slider.value(),
216
- mode=mode)
217
- self.parent_window.model = PandasModel(self.parent_window.data)
218
- self.parent_window.table_view.setModel(self.parent_window.model)
219
- self.close()
220
-
221
-
222
-
223
- class OperationOnColsWidget(CelldetectiveWidget):
224
-
225
- def __init__(self, parent_window, column1=None, column2=None, operation='divide'):
226
-
227
- super().__init__()
228
- self.parent_window = parent_window
229
- self.column1 = column1
230
- self.column2 = column2
231
- self.operation = operation
232
-
233
- self.setWindowTitle(self.operation)
234
- # Create the QComboBox and add some items
235
- center_window(self)
236
-
237
- layout = QVBoxLayout(self)
238
- layout.setContentsMargins(30,30,30,30)
239
-
240
- self.col1_cb = QComboBox()
241
- self.col1_cb.addItems(list(self.parent_window.data.columns))
242
- if self.column1 is not None:
243
- idx = self.col1_cb.findText(self.column1)
244
- self.col1_cb.setCurrentIndex(idx)
245
-
246
- numerator_layout = QHBoxLayout()
247
- numerator_layout.addWidget(QLabel('column 1: '), 25)
248
- numerator_layout.addWidget(self.col1_cb, 75)
249
- layout.addLayout(numerator_layout)
250
-
251
- self.col2_cb = QComboBox()
252
- self.col2_cb.addItems(list(self.parent_window.data.columns))
253
- if self.column2 is not None:
254
- idx = self.col2_cb.findText(self.column2)
255
- self.col2_cb.setCurrentIndex(idx)
256
-
257
- denominator_layout = QHBoxLayout()
258
- denominator_layout.addWidget(QLabel('column 2: '), 25)
259
- denominator_layout.addWidget(self.col2_cb, 75)
260
- layout.addLayout(denominator_layout)
261
-
262
- self.submit_btn = QPushButton('Compute')
263
- self.submit_btn.setStyleSheet(self.button_style_sheet)
264
- self.submit_btn.clicked.connect(self.compute)
265
- layout.addWidget(self.submit_btn, 30)
266
-
267
- self.setAttribute(Qt.WA_DeleteOnClose)
268
-
269
- def compute(self):
270
-
271
- test = self._check_cols_before_operation()
272
- if not test:
273
- msgBox = QMessageBox()
274
- msgBox.setIcon(QMessageBox.Warning)
275
- msgBox.setText(f"Operation could not be performed, one of the column types is object...")
276
- msgBox.setWindowTitle("Warning")
277
- msgBox.setStandardButtons(QMessageBox.Ok)
278
- returnValue = msgBox.exec()
279
- if returnValue == QMessageBox.Ok:
280
- return None
281
- else:
282
- return None
283
- else:
284
- if self.operation=='divide':
285
- name = f"{self.col1_txt}/{self.col2_txt}"
286
- with np.errstate(divide='ignore', invalid='ignore'):
287
- res = np.true_divide(self.col1, self.col2)
288
- res[res == np.inf] = np.nan
289
- res[self.col1!=self.col1] = np.nan
290
- res[self.col2!=self.col2] = np.nan
291
- self.parent_window.data[name] = res
292
-
293
- elif self.operation=='multiply':
294
- name = f"{self.col1_txt}*{self.col2_txt}"
295
- res = np.multiply(self.col1, self.col2)
296
-
297
- elif self.operation=='add':
298
- name = f"{self.col1_txt}+{self.col2_txt}"
299
- res = np.add(self.col1, self.col2)
300
-
301
- elif self.operation=='subtract':
302
- name = f"{self.col1_txt}-{self.col2_txt}"
303
- res = np.subtract(self.col1, self.col2)
304
-
305
- self.parent_window.data[name] = res
306
- self.parent_window.model = PandasModel(self.parent_window.data)
307
- self.parent_window.table_view.setModel(self.parent_window.model)
308
- self.close()
309
-
310
- def _check_cols_before_operation(self):
311
-
312
- self.col1_txt = self.col1_cb.currentText()
313
- self.col2_txt = self.col2_cb.currentText()
314
-
315
- self.col1 = self.parent_window.data[self.col1_txt].to_numpy()
316
- self.col2 = self.parent_window.data[self.col2_txt].to_numpy()
317
-
318
- test = np.all([self.col1.dtype!='O', self.col2.dtype!='O'])
319
-
320
- return test
321
-
322
-
323
- class CalibrateColWidget(GenericOpColWidget):
324
-
325
- def __init__(self, *args, **kwargs):
326
-
327
- super().__init__(title="Calibrate data", *args, **kwargs)
328
-
329
- self.floatValidator = QDoubleValidator()
330
- self.calibration_factor_le = QLineEdit('1')
331
- self.calibration_factor_le.setPlaceholderText('multiplicative calibration factor...')
332
- self.calibration_factor_le.setValidator(self.floatValidator)
333
-
334
- self.units_le = QLineEdit('um')
335
- self.units_le.setPlaceholderText('units...')
336
-
337
- self.calibration_factor_le.textChanged.connect(self.check_valid_params)
338
- self.units_le.textChanged.connect(self.check_valid_params)
339
-
340
- calib_layout = QHBoxLayout()
341
- calib_layout.addWidget(QLabel('calibration factor: '), 33)
342
- calib_layout.addWidget(self.calibration_factor_le, 66)
343
- self.sublayout.addLayout(calib_layout)
344
-
345
- units_layout = QHBoxLayout()
346
- units_layout.addWidget(QLabel('units: '), 33)
347
- units_layout.addWidget(self.units_le, 66)
348
- self.sublayout.addLayout(units_layout)
349
-
350
- # info_layout = QHBoxLayout()
351
- # info_layout.addWidget(QLabel('For reference: '))
352
- # self.sublayout.addLayout(info_layout)
353
-
354
- # info_layout2 = QHBoxLayout()
355
- # info_layout2.addWidget(QLabel(f'PxToUm = {self.parent_window.parent_window.parent_window.PxToUm}'), 50)
356
- # info_layout2.addWidget(QLabel(f'FrameToMin = {self.parent_window.parent_window.parent_window.FrameToMin}'), 50)
357
- # self.sublayout.addLayout(info_layout2)
358
-
359
- def check_valid_params(self):
360
-
361
- try:
362
- factor = float(self.calibration_factor_le.text().replace(',','.'))
363
- factor_valid = True
364
- except Exception as e:
365
- factor_valid = False
366
-
367
- if self.units_le.text()=='':
368
- units_valid = False
369
- else:
370
- units_valid = True
371
-
372
- if factor_valid and units_valid:
373
- self.submit_btn.setEnabled(True)
374
- else:
375
- self.submit_btn.setEnabled(False)
376
-
377
- def compute(self):
378
- self.parent_window.data[self.measurements_cb.currentText()+f'[{self.units_le.text()}]'] = self.parent_window.data[self.measurements_cb.currentText()] * float(self.calibration_factor_le.text().replace(',','.'))
379
-
380
-
381
- class AbsColWidget(GenericOpColWidget):
382
-
383
- def __init__(self, *args, **kwargs):
384
-
385
- super().__init__(title="abs(.)", *args, **kwargs)
386
-
387
- def compute(self):
388
- self.parent_window.data['|'+self.measurements_cb.currentText()+'|'] = self.parent_window.data[self.measurements_cb.currentText()].abs()
57
+ self.setWindowTitle(title)
58
+ print("tab to show: ", self.data)
389
59
 
390
- class LogColWidget(GenericOpColWidget):
391
-
392
- def __init__(self, *args, **kwargs):
60
+ self.table = QTableView(self)
393
61
 
394
- super().__init__(title="log10(.)", *args, **kwargs)
62
+ self.v_layout = QVBoxLayout()
63
+ self.information_label = QLabel("Information about color code...")
64
+ self.v_layout.addWidget(self.information_label)
65
+ self.v_layout.addWidget(self.table)
66
+ self.setLayout(self.v_layout)
395
67
 
396
- def compute(self):
397
- self.parent_window.data['log10('+self.measurements_cb.currentText()+')'] = safe_log(self.parent_window.data[self.measurements_cb.currentText()].values)
68
+ self.showdata()
398
69
 
70
+ if self.mode == "cliff":
71
+ self.color_cells_cliff()
72
+ elif self.mode == "pvalue":
73
+ self.color_cells_pvalue()
399
74
 
400
- class RenameColWidget(CelldetectiveWidget):
75
+ self.table.resizeColumnsToContents()
76
+ self.setAttribute(Qt.WA_DeleteOnClose)
77
+ center_window(self)
401
78
 
402
- def __init__(self, parent_window, column=None):
79
+ def showdata(self):
80
+ self.model = PandasModel(self.data)
81
+ self.table.setModel(self.model)
403
82
 
404
- super().__init__()
405
- self.parent_window = parent_window
406
- self.column = column
407
- if self.column is None:
408
- self.column = ''
83
+ def set_cell_color(self, row, column, color="red"):
84
+ self.model.change_color(
85
+ row, column, QBrush(QColor(color))
86
+ ) # eval(f"Qt.{color}")
409
87
 
410
- self.setWindowTitle("Rename column")
411
- # Create the QComboBox and add some items
412
- center_window(self)
413
-
414
- layout = QHBoxLayout(self)
415
- layout.setContentsMargins(30,30,30,30)
416
- self.new_col_name = QLineEdit()
417
- self.new_col_name.setText(self.column)
418
- layout.addWidget(self.new_col_name, 70)
88
+ def color_cells_cliff(self):
419
89
 
420
- self.submit_btn = QPushButton('rename')
421
- self.submit_btn.clicked.connect(self.rename_col)
422
- layout.addWidget(self.submit_btn, 30)
423
- self.setAttribute(Qt.WA_DeleteOnClose)
90
+ color_codes = {
91
+ "negligible": "#eff3ff", # Green
92
+ "small": "#bdd7e7", # Yellow
93
+ "medium": "#6baed6", # Orange
94
+ "large": "#2171b5", # Red
95
+ }
424
96
 
425
- def rename_col(self):
426
-
427
- old_name = self.column
428
- new_name = self.new_col_name.text()
429
- self.parent_window.data = self.parent_window.data.rename(columns={old_name: new_name})
430
-
431
- self.parent_window.model = PandasModel(self.parent_window.data)
432
- self.parent_window.table_view.setModel(self.parent_window.model)
433
- self.close()
97
+ for i in range(self.data.shape[0]):
98
+ for j in range(self.data.shape[1]):
99
+ value = self.data.iloc[i, j]
100
+ if value < 0.147:
101
+ self.set_cell_color(i, j, color_codes["negligible"])
102
+ elif value < 0.33:
103
+ self.set_cell_color(i, j, color_codes["small"])
104
+ elif value < 0.474:
105
+ self.set_cell_color(i, j, color_codes["medium"])
106
+ elif value >= 0.474:
107
+ self.set_cell_color(i, j, color_codes["large"])
434
108
 
435
- class PivotTableUI(CelldetectiveWidget):
436
-
437
- def __init__(self, data, title="", mode=None, *args, **kwargs):
438
-
439
- CelldetectiveWidget.__init__(self, *args, **kwargs)
440
-
441
- self.data = data
442
- self.title = title
443
- self.mode = mode
444
-
445
- self.setWindowTitle(title)
446
- print("tab to show: ",self.data)
447
-
448
- self.table = QTableView(self)
449
-
450
- self.v_layout = QVBoxLayout()
451
- self.information_label = QLabel('Information about color code...')
452
- self.v_layout.addWidget(self.information_label)
453
- self.v_layout.addWidget(self.table)
454
- self.setLayout(self.v_layout)
455
-
456
- self.showdata()
457
-
458
- if self.mode=="cliff":
459
- self.color_cells_cliff()
460
- elif self.mode=="pvalue":
461
- self.color_cells_pvalue()
462
-
463
- self.table.resizeColumnsToContents()
464
- self.setAttribute(Qt.WA_DeleteOnClose)
465
- center_window(self)
466
-
467
- def showdata(self):
468
- self.model = PandasModel(self.data)
469
- self.table.setModel(self.model)
470
-
471
- def set_cell_color(self, row, column, color='red'):
472
- self.model.change_color(row, column, QBrush(QColor(color))) #eval(f"Qt.{color}")
473
-
474
- def color_cells_cliff(self):
475
-
476
- color_codes = {
477
- "negligible": "#eff3ff", # Green
478
- "small": "#bdd7e7", # Yellow
479
- "medium": "#6baed6", # Orange
480
- "large": "#2171b5" # Red
481
- }
482
-
483
- for i in range(self.data.shape[0]):
484
- for j in range(self.data.shape[1]):
485
- value = self.data.iloc[i,j]
486
- if value < 0.147:
487
- self.set_cell_color(i,j,color_codes['negligible'])
488
- elif value < 0.33:
489
- self.set_cell_color(i,j,color_codes['small'])
490
- elif value < 0.474:
491
- self.set_cell_color(i,j,color_codes['medium'])
492
- elif value >= 0.474:
493
- self.set_cell_color(i,j,color_codes['large'])
494
-
495
- # Create the HTML text for the label
496
- html_caption = f"""
109
+ # Create the HTML text for the label
110
+ html_caption = f"""
497
111
  <p style="background-color:black; padding: 5px; font-weight:bold;">
498
112
  <span style="color:{color_codes['negligible']}">Negligible</span>,
499
113
  <span style="color:{color_codes['small']}">Small</span>,
@@ -501,33 +115,33 @@ class PivotTableUI(CelldetectiveWidget):
501
115
  <span style="color:{color_codes['large']}">Large</span>
502
116
  </p>
503
117
  """
504
- self.information_label.setText(html_caption)
505
-
506
- def color_cells_pvalue(self):
507
-
508
- color_codes = {
509
- "ns": "#fee5d9",
510
- "*": "#fcae91",
511
- "**": "#fb6a4a",
512
- "***": "#de2d26",
513
- "****": "#a50f15"
514
- }
515
-
516
- for i in range(self.data.shape[0]):
517
- for j in range(self.data.shape[1]):
518
- value = self.data.iloc[i,j]
519
- if value <= 0.0001:
520
- self.set_cell_color(i,j,color_codes['****'])
521
- elif value <= 0.001:
522
- self.set_cell_color(i,j,color_codes['***'])
523
- elif value <= 0.01:
524
- self.set_cell_color(i,j,color_codes['**'])
525
- elif value <= 0.05:
526
- self.set_cell_color(i,j,color_codes['*'])
527
- elif value > 0.05:
528
- self.set_cell_color(i,j,color_codes['ns'])
529
-
530
- html_caption = f"""
118
+ self.information_label.setText(html_caption)
119
+
120
+ def color_cells_pvalue(self):
121
+
122
+ color_codes = {
123
+ "ns": "#fee5d9",
124
+ "*": "#fcae91",
125
+ "**": "#fb6a4a",
126
+ "***": "#de2d26",
127
+ "****": "#a50f15",
128
+ }
129
+
130
+ for i in range(self.data.shape[0]):
131
+ for j in range(self.data.shape[1]):
132
+ value = self.data.iloc[i, j]
133
+ if value <= 0.0001:
134
+ self.set_cell_color(i, j, color_codes["****"])
135
+ elif value <= 0.001:
136
+ self.set_cell_color(i, j, color_codes["***"])
137
+ elif value <= 0.01:
138
+ self.set_cell_color(i, j, color_codes["**"])
139
+ elif value <= 0.05:
140
+ self.set_cell_color(i, j, color_codes["*"])
141
+ elif value > 0.05:
142
+ self.set_cell_color(i, j, color_codes["ns"])
143
+
144
+ html_caption = f"""
531
145
  <p style="background-color:black; padding: 5px; font-weight:bold;">
532
146
  <span style="color:{color_codes['ns']}">ns</span>,
533
147
  <span style="color:{color_codes['*']}">*</span>,
@@ -536,1207 +150,1557 @@ class PivotTableUI(CelldetectiveWidget):
536
150
  <span style="color:{color_codes['****']}">****</span>
537
151
  </p>
538
152
  """
539
- self.information_label.setText(html_caption)
540
-
541
- class TableUI(CelldetectiveMainWindow):
542
-
543
- def __init__(self, data, title, population='targets',plot_mode="plot_track_signals", save_inplace_option=False, collapse_tracks_option=True, *args, **kwargs):
544
-
545
- CelldetectiveMainWindow.__init__(self, *args, **kwargs)
546
-
547
- self.setWindowTitle(title)
548
- self.setGeometry(100,100,1000,400)
549
- center_window(self)
550
- self.title = title
551
- self.plot_mode = plot_mode
552
- self.population = population
553
- self.numerics = ['int16', 'int32', 'int64', 'float16', 'float32', 'float64']
554
- self.groupby_cols = ['position', 'TRACK_ID']
555
- self.tracks = False
556
- self.save_inplace_option = save_inplace_option
557
- self.collapse_tracks_option = collapse_tracks_option
558
-
559
- if self.population=='pairs':
560
- self.groupby_cols = ['position','reference_population', 'neighbor_population','REFERENCE_ID', 'NEIGHBOR_ID']
561
- self.tracks = True # for now
562
- else:
563
- if 'TRACK_ID' in data.columns:
564
- if not np.all(data['TRACK_ID'].isnull()):
565
- self.tracks = True
566
-
567
- self.data = data
568
-
569
- self._createMenuBar()
570
- self._createActions()
571
-
572
- self.table_view = QTableView(self)
573
- self.setCentralWidget(self.table_view)
574
-
575
- # Set the model for the table view
576
-
577
- self.model = PandasModel(data)
578
- self.table_view.setModel(self.model)
579
- self.table_view.resizeColumnsToContents()
580
-
581
- def resizeEvent(self, event):
582
-
583
- super().resizeEvent(event)
584
-
585
- try:
586
- self.fig.tight_layout()
587
- except:
588
- pass
589
-
590
- def _createActions(self):
591
-
592
- self.save_as = QAction("&Save as...", self)
593
- self.save_as.triggered.connect(self.save_as_csv)
594
- self.save_as.setShortcut("Ctrl+s")
595
- self.fileMenu.addAction(self.save_as)
596
-
597
- if self.save_inplace_option:
598
- self.save_inplace = QAction("&Save inplace...", self)
599
- self.save_inplace.triggered.connect(self.save_as_csv_inplace_per_pos)
600
- #self.save_inplace.setShortcut("Ctrl+s")
601
- self.fileMenu.addAction(self.save_inplace)
602
-
603
- self.plot_action = QAction("&Plot...", self)
604
- self.plot_action.triggered.connect(self.plot)
605
- self.plot_action.setShortcut("Ctrl+p")
606
- self.fileMenu.addAction(self.plot_action)
607
-
608
- self.plot_inst_action = QAction("&Plot instantaneous...", self)
609
- self.plot_inst_action.triggered.connect(self.plot_instantaneous)
610
- self.plot_inst_action.setShortcut("Ctrl+i")
611
- self.fileMenu.addAction(self.plot_inst_action)
612
-
613
- self.groupby_action = QAction("&Collapse tracks...", self)
614
- self.groupby_action.triggered.connect(self.set_projection_mode_tracks)
615
- self.groupby_action.setShortcut("Ctrl+g")
616
- self.fileMenu.addAction(self.groupby_action)
617
- if not self.tracks or not self.collapse_tracks_option:
618
- self.groupby_action.setEnabled(False)
619
-
620
- if self.population=='pairs':
621
-
622
- self.groupby_pairs_in_neigh_action = QAction("&Collapse pairs in neighborhood...", self)
623
- self.groupby_pairs_in_neigh_action.triggered.connect(self.collapse_pairs_in_neigh)
624
- self.fileMenu.addAction(self.groupby_pairs_in_neigh_action)
625
-
626
- if 'FRAME' in list(self.data.columns):
627
- self.groupby_time_action = QAction("&Group by frames...", self)
628
- self.groupby_time_action.triggered.connect(self.groupby_time_table)
629
- self.groupby_time_action.setShortcut("Ctrl+t")
630
- self.fileMenu.addAction(self.groupby_time_action)
631
-
632
- self.query_action = QAction('Query...', self)
633
- self.query_action.triggered.connect(self.perform_query)
634
- self.fileMenu.addAction(self.query_action)
635
-
636
- self.delete_action = QAction('&Delete...', self)
637
- self.delete_action.triggered.connect(self.delete_columns)
638
- self.delete_action.setShortcut(Qt.Key_Delete)
639
- self.editMenu.addAction(self.delete_action)
640
-
641
- self.rename_col_action = QAction('&Rename...', self)
642
- self.rename_col_action.triggered.connect(self.rename_column)
643
- #self.rename_col_action.setShortcut(Qt.Key_Delete)
644
- self.editMenu.addAction(self.rename_col_action)
645
-
646
- if self.population=='pairs':
647
- self.merge_action = QAction('&Merge...', self)
648
- self.merge_action.triggered.connect(self.merge_tables)
649
- #self.rename_col_action.setShortcut(Qt.Key_Delete)
650
- self.editMenu.addAction(self.merge_action)
651
-
652
- self.calibrate_action = QAction('&Calibrate...', self)
653
- self.calibrate_action.triggered.connect(self.calibrate_selected_feature)
654
- self.calibrate_action.setShortcut("Ctrl+C")
655
- self.mathMenu.addAction(self.calibrate_action)
656
-
657
- self.merge_classification_action = QAction('&Merge states...', self)
658
- self.merge_classification_action.triggered.connect(self.merge_classification_features)
659
- self.mathMenu.addAction(self.merge_classification_action)
660
-
661
- self.derivative_action = QAction('&Differentiate...', self)
662
- self.derivative_action.triggered.connect(self.differenciate_selected_feature)
663
- self.derivative_action.setShortcut("Ctrl+D")
664
- self.mathMenu.addAction(self.derivative_action)
665
- if not self.tracks:
666
- self.derivative_action.setEnabled(False)
667
-
668
- self.abs_action = QAction('&Absolute value...', self)
669
- self.abs_action.triggered.connect(self.take_abs_of_selected_feature)
670
- #self.derivative_action.setShortcut("Ctrl+D")
671
- self.mathMenu.addAction(self.abs_action)
672
-
673
- self.log_action = QAction('&Log (decimal)...', self)
674
- self.log_action.triggered.connect(self.take_log_of_selected_feature)
675
- #self.derivative_action.setShortcut("Ctrl+D")
676
- self.mathMenu.addAction(self.log_action)
677
-
678
-
679
- self.divide_action = QAction('&Divide...', self)
680
- self.divide_action.triggered.connect(self.divide_signals)
681
- #self.derivative_action.setShortcut("Ctrl+D")
682
- self.mathMenu.addAction(self.divide_action)
683
-
684
- self.multiply_action = QAction('&Multiply...', self)
685
- self.multiply_action.triggered.connect(self.multiply_signals)
686
- #self.derivative_action.setShortcut("Ctrl+D")
687
- self.mathMenu.addAction(self.multiply_action)
688
-
689
- self.add_action = QAction('&Add...', self)
690
- self.add_action.triggered.connect(self.add_signals)
691
- #self.derivative_action.setShortcut("Ctrl+D")
692
- self.mathMenu.addAction(self.add_action)
693
-
694
- self.subtract_action = QAction('&Subtract...', self)
695
- self.subtract_action.triggered.connect(self.subtract_signals)
696
- #self.derivative_action.setShortcut("Ctrl+D")
697
- self.mathMenu.addAction(self.subtract_action)
698
-
699
-
700
- # self.onehot_action = QAction('&One hot to categorical...', self)
701
- # self.onehot_action.triggered.connect(self.transform_one_hot_cols_to_categorical)
702
- # #self.onehot_action.setShortcut("Ctrl+D")
703
- # self.mathMenu.addAction(self.onehot_action)
704
-
705
- def collapse_pairs_in_neigh(self):
706
-
707
- self.selectNeighWidget = CelldetectiveWidget()
708
- self.selectNeighWidget.setMinimumWidth(480)
709
- self.selectNeighWidget.setWindowTitle('Set neighborhood of interest')
710
-
711
- layout = QVBoxLayout()
712
- self.selectNeighWidget.setLayout(layout)
713
-
714
- self.reference_lbl = QLabel('reference population: ')
715
- self.reference_pop_cb = QComboBox()
716
- ref_pops = self.data['reference_population'].unique()
717
- self.reference_pop_cb.addItems(ref_pops)
718
- self.reference_pop_cb.currentIndexChanged.connect(self.update_neighborhoods)
719
-
720
- reference_hbox = QHBoxLayout()
721
- reference_hbox.addWidget(self.reference_lbl, 33)
722
- reference_hbox.addWidget(self.reference_pop_cb, 66)
723
- layout.addLayout(reference_hbox)
724
-
725
- self.neigh_lbl = QLabel('neighborhod: ')
726
- self.neigh_cb = QComboBox()
727
- neigh_cols = [c.replace('status_','') for c in list(self.data.loc[self.data['reference_population']==self.reference_pop_cb.currentText()].columns) if c.startswith('status_neighborhood')]
728
- self.neigh_cb.addItems(neigh_cols)
729
-
730
- neigh_hbox = QHBoxLayout()
731
- neigh_hbox.addWidget(self.neigh_lbl, 33)
732
- neigh_hbox.addWidget(self.neigh_cb, 66)
733
- layout.addLayout(neigh_hbox)
734
-
735
- contact_hbox = QHBoxLayout()
736
- self.contact_only_check = QCheckBox('keep only pairs in contact')
737
- self.contact_only_check.setChecked(True)
738
- contact_hbox.addWidget(self.contact_only_check, alignment=Qt.AlignLeft)
739
- layout.addLayout(contact_hbox)
740
-
741
- self.groupby_pair_rb = QRadioButton('Group by pair')
742
- self.groupby_reference_rb = QRadioButton('Group by reference')
743
- self.groupby_pair_rb.setChecked(True)
744
-
745
- groupby_hbox = QHBoxLayout()
746
- groupby_hbox.addWidget(QLabel('collapse option: '), 33)
747
- groupby_hbox.addWidget(self.groupby_pair_rb, (100-33)//2)
748
- groupby_hbox.addWidget(self.groupby_reference_rb, (100-33)//2)
749
- layout.addLayout(groupby_hbox)
750
-
751
- self.apply_neigh_btn = QPushButton('Set')
752
- self.apply_neigh_btn.setStyleSheet(self.button_style_sheet)
753
- self.apply_neigh_btn.clicked.connect(self.prepare_table_at_neighborhood)
754
-
755
- apply_hbox = QHBoxLayout()
756
- apply_hbox.addWidget(QLabel(''),33)
757
- apply_hbox.addWidget(self.apply_neigh_btn,66)
758
- layout.addLayout(apply_hbox)
759
-
760
- self.selectNeighWidget.show()
761
- center_window(self.selectNeighWidget)
762
-
763
- def prepare_table_at_neighborhood(self):
764
-
765
- ref_pop = self.reference_pop_cb.currentText()
766
- neighborhood = self.neigh_cb.currentText()
767
- status_neigh = 'status_'+neighborhood
768
-
769
- if 'self' in neighborhood:
770
- neighbor_pop = ref_pop
771
-
772
- neigh_col = neighborhood.replace('status_','')
773
- if '_(' in neigh_col and ')_' in neigh_col:
774
- neighbor_pop = neigh_col.split('_(')[-1].split(')_')[0].split('-')[-1]
775
- else:
776
- if ref_pop=='targets':
777
- neighbor_pop = 'effectors'
778
- if ref_pop=='effectors':
779
- neighbor_pop = "targets"
780
-
781
- data = extract_neighborhood_in_pair_table(self.data, neighborhood_key=neighborhood, contact_only=self.contact_only_check.isChecked(), reference_population=ref_pop)
782
-
783
- if self.groupby_pair_rb.isChecked():
784
- self.groupby_cols = ['position', 'REFERENCE_ID', 'NEIGHBOR_ID']
785
- elif self.groupby_reference_rb.isChecked():
786
- self.groupby_cols = ['position', 'REFERENCE_ID']
787
-
788
- self.current_data = data
789
- skip_projection = False
790
- if 'reference_tracked' in list(self.current_data.columns):
791
- print(f"{self.current_data['reference_tracked']=} {(self.current_data['reference_tracked']==False)=} {np.all(self.current_data['reference_tracked']==False)=}")
792
- if np.all(self.current_data['reference_tracked'].astype(bool)==False):
793
- # reference not tracked
794
- if self.groupby_reference_rb.isChecked():
795
- self.groupby_cols = ['position', 'FRAME', 'REFERENCE_ID']
796
- elif self.groupby_pair_rb.isChecked():
797
- print('The reference cells seem to not be tracked. No collapse can be performed.')
798
- skip_projection=True
799
- else:
800
- if np.all(self.current_data['neighbors_tracked'].astype(bool)==False):
801
- # neighbors not tracked
802
- if self.groupby_pair_rb.isChecked():
803
- print('The neighbor cells seem to not be tracked. No collapse can be performed.')
804
- skip_projection=True
805
- elif self.groupby_reference_rb.isChecked():
806
- self.groupby_cols = ['position', 'REFERENCE_ID'] # think about what would be best
807
-
808
- if not skip_projection:
809
- self.set_projection_mode_tracks()
810
-
811
- def update_neighborhoods(self):
812
-
813
- neigh_cols = [c.replace('status_','') for c in list(self.data.loc[self.data['reference_population']==self.reference_pop_cb.currentText()].columns) if c.startswith('status_neighborhood')]
814
- self.neigh_cb.clear()
815
- self.neigh_cb.addItems(neigh_cols)
816
-
817
- def merge_tables(self):
818
-
819
- df_expanded = expand_pair_table(self.data)
820
- self.subtable = TableUI(df_expanded, 'merge', plot_mode = "static", population='pairs')
821
- self.subtable.show()
822
-
823
-
824
- def delete_columns(self):
825
-
826
- x = self.table_view.selectedIndexes()
827
- col_idx = np.unique(np.array([l.column() for l in x]))
828
- cols = np.array(list(self.data.columns))
829
-
830
- msgBox = QMessageBox()
831
- msgBox.setIcon(QMessageBox.Question)
832
- msgBox.setText(f"You are about to delete columns {cols[col_idx]}... Do you want to proceed?")
833
- msgBox.setWindowTitle("Info")
834
- msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
835
- returnValue = msgBox.exec()
836
- if returnValue == QMessageBox.No:
837
- return None
838
-
839
- self.data = self.data.drop(list(cols[col_idx]),axis=1)
840
- self.model = PandasModel(self.data)
841
- self.table_view.setModel(self.model)
842
-
843
- def rename_column(self):
844
-
845
- x = self.table_view.selectedIndexes()
846
- col_idx = np.unique(np.array([l.column() for l in x]))
847
-
848
- if len(col_idx) == 0:
849
- msgBox = QMessageBox()
850
- msgBox.setIcon(QMessageBox.Question)
851
- msgBox.setText(f"Please select a column first.")
852
- msgBox.setWindowTitle("Warning")
853
- msgBox.setStandardButtons(QMessageBox.Ok)
854
- returnValue = msgBox.exec()
855
- if returnValue == QMessageBox.Ok:
856
- return None
857
- else:
858
- return None
859
-
860
- cols = np.array(list(self.data.columns))
861
- selected_col = str(cols[col_idx][0])
862
-
863
- self.renameWidget = RenameColWidget(self, selected_col)
864
- self.renameWidget.show()
865
-
866
- def save_as_csv_inplace_per_pos(self):
867
-
868
- print("Saving each table in its respective position folder...")
869
- for pos,pos_group in self.data.groupby(['position']):
870
- invalid_cols = [c for c in list(pos_group.columns) if c.startswith('Unnamed')]
871
- if len(invalid_cols)>0:
872
- pos_group = pos_group.drop(invalid_cols, axis=1)
873
- pos_group.to_csv(pos[0]+os.sep.join(['output', 'tables', f'trajectories_{self.population}.csv']), index=False)
874
- print("Done...")
875
-
876
- def divide_signals(self):
877
-
878
- x = self.table_view.selectedIndexes()
879
- col_idx = np.unique(np.array([l.column() for l in x]))
880
- if isinstance(col_idx, (list, np.ndarray)):
881
- cols = np.array(list(self.data.columns))
882
- if len(col_idx)>0:
883
- selected_col1 = str(cols[col_idx[0]])
884
- if len(col_idx)>1:
885
- selected_col2 = str(cols[col_idx[1]])
886
- else:
887
- selected_col2 = None
888
- else:
889
- selected_col1 = None
890
- selected_col2 = None
891
- else:
892
- selected_col1 = None
893
- selected_col2 = None
894
-
895
- self.divWidget = OperationOnColsWidget(self, column1=selected_col1, column2=selected_col2, operation='divide')
896
- self.divWidget.show()
897
-
898
-
899
- def multiply_signals(self):
900
-
901
- x = self.table_view.selectedIndexes()
902
- col_idx = np.unique(np.array([l.column() for l in x]))
903
- if isinstance(col_idx, (list, np.ndarray)):
904
- cols = np.array(list(self.data.columns))
905
- if len(col_idx)>0:
906
- selected_col1 = str(cols[col_idx[0]])
907
- if len(col_idx)>1:
908
- selected_col2 = str(cols[col_idx[1]])
909
- else:
910
- selected_col2 = None
911
- else:
912
- selected_col1 = None
913
- selected_col2 = None
914
- else:
915
- selected_col1 = None
916
- selected_col2 = None
917
-
918
- self.mulWidget = OperationOnColsWidget(self, column1=selected_col1, column2=selected_col2, operation='multiply')
919
- self.mulWidget.show()
920
-
921
- def add_signals(self):
922
-
923
- x = self.table_view.selectedIndexes()
924
- col_idx = np.unique(np.array([l.column() for l in x]))
925
- if isinstance(col_idx, (list, np.ndarray)):
926
- cols = np.array(list(self.data.columns))
927
- if len(col_idx)>0:
928
- selected_col1 = str(cols[col_idx[0]])
929
- if len(col_idx)>1:
930
- selected_col2 = str(cols[col_idx[1]])
931
- else:
932
- selected_col2 = None
933
- else:
934
- selected_col1 = None
935
- selected_col2 = None
936
- else:
937
- selected_col1 = None
938
- selected_col2 = None
939
-
940
- self.addiWidget = OperationOnColsWidget(self, column1=selected_col1, column2=selected_col2, operation='add')
941
- self.addiWidget.show()
942
-
943
- def subtract_signals(self):
944
-
945
- x = self.table_view.selectedIndexes()
946
- col_idx = np.unique(np.array([l.column() for l in x]))
947
- if isinstance(col_idx, (list, np.ndarray)):
948
- cols = np.array(list(self.data.columns))
949
- if len(col_idx)>0:
950
- selected_col1 = str(cols[col_idx[0]])
951
- if len(col_idx)>1:
952
- selected_col2 = str(cols[col_idx[1]])
953
- else:
954
- selected_col2 = None
955
- else:
956
- selected_col1 = None
957
- selected_col2 = None
958
- else:
959
- selected_col1 = None
960
- selected_col2 = None
961
-
962
- self.subWidget = OperationOnColsWidget(self, column1=selected_col1, column2=selected_col2, operation='subtract')
963
- self.subWidget.show()
964
-
965
-
966
- def differenciate_selected_feature(self):
967
-
968
- # check only one col selected and assert is numerical
969
- # open widget to select window parameters, directionality
970
- # create new col
971
-
972
- x = self.table_view.selectedIndexes()
973
- col_idx = np.unique(np.array([l.column() for l in x]))
974
- if isinstance(col_idx, (list, np.ndarray)):
975
- cols = np.array(list(self.data.columns))
976
- if len(col_idx)>0:
977
- selected_col = str(cols[col_idx[0]])
978
- else:
979
- selected_col = None
980
- else:
981
- selected_col = None
982
-
983
- self.diffWidget = DifferentiateColWidget(self, selected_col)
984
- self.diffWidget.show()
985
-
986
- def take_log_of_selected_feature(self):
987
-
988
- # check only one col selected and assert is numerical
989
- # open widget to select window parameters, directionality
990
- # create new col
991
-
992
- x = self.table_view.selectedIndexes()
993
- col_idx = np.unique(np.array([l.column() for l in x]))
994
- if isinstance(col_idx, (list, np.ndarray)):
995
- cols = np.array(list(self.data.columns))
996
- if len(col_idx)>0:
997
- selected_col = str(cols[col_idx[0]])
998
- else:
999
- selected_col = None
1000
- else:
1001
- selected_col = None
1002
-
1003
- self.LogWidget = LogColWidget(self, selected_col)
1004
- self.LogWidget.show()
1005
-
1006
- def merge_classification_features(self):
1007
-
1008
- x = self.table_view.selectedIndexes()
1009
- col_idx = np.unique(np.array([l.column() for l in x]))
1010
-
1011
- col_selection = []
1012
- if isinstance(col_idx, (list, np.ndarray)):
1013
- cols = np.array(list(self.data.columns))
1014
- if len(col_idx) > 0:
1015
- selected_cols = cols[col_idx]
1016
- col_selection.extend(selected_cols)
1017
-
1018
- self.merge_classification_widget = MergeGroupWidget(self, columns = col_selection)
1019
- self.merge_classification_widget.show()
1020
-
1021
-
1022
- def calibrate_selected_feature(self):
1023
-
1024
- x = self.table_view.selectedIndexes()
1025
- col_idx = np.unique(np.array([l.column() for l in x]))
1026
- if isinstance(col_idx, (list, np.ndarray)):
1027
- cols = np.array(list(self.data.columns))
1028
- if len(col_idx)>0:
1029
- selected_col = str(cols[col_idx[0]])
1030
- else:
1031
- selected_col = None
1032
- else:
1033
- selected_col = None
1034
-
1035
- self.calWidget = CalibrateColWidget(self, selected_col)
1036
- self.calWidget.show()
1037
-
1038
-
1039
- def take_abs_of_selected_feature(self):
1040
-
1041
- # check only one col selected and assert is numerical
1042
- # open widget to select window parameters, directionality
1043
- # create new col
1044
-
1045
- x = self.table_view.selectedIndexes()
1046
- col_idx = np.unique(np.array([l.column() for l in x]))
1047
- if isinstance(col_idx, (list, np.ndarray)):
1048
- cols = np.array(list(self.data.columns))
1049
- if len(col_idx)>0:
1050
- selected_col = str(cols[col_idx[0]])
1051
- else:
1052
- selected_col = None
1053
- else:
1054
- selected_col = None
1055
-
1056
- self.absWidget = AbsColWidget(self, selected_col)
1057
- self.absWidget.show()
1058
-
1059
-
1060
- def transform_one_hot_cols_to_categorical(self):
1061
-
1062
- x = self.table_view.selectedIndexes()
1063
- col_idx = np.unique(np.array([l.column() for l in x]))
1064
- selected_cols = None
1065
- if isinstance(col_idx, (list, np.ndarray)):
1066
- cols = np.array(list(self.data.columns))
1067
- if len(col_idx)>0:
1068
- selected_col = str(cols[col_idx[0]])
1069
-
1070
- self.mergewidget = MergeOneHotWidget(self, selected_columns=selected_cols)
1071
- self.mergewidget.show()
1072
-
1073
-
1074
- def groupby_time_table(self):
1075
-
1076
- """
1077
-
1078
- Perform a time average across each track for all features
1079
-
1080
- """
153
+ self.information_label.setText(html_caption)
1081
154
 
1082
- num_df = self.data.select_dtypes(include=self.numerics)
1083
155
 
1084
- timeseries = num_df.groupby(["FRAME"]).sum().copy()
1085
- timeseries["timeline"] = timeseries.index
1086
- self.subtable = TableUI(timeseries,"Group by frames", plot_mode="plot_timeseries")
1087
- self.subtable.show()
1088
-
1089
- def perform_query(self):
1090
-
1091
- """
1092
-
1093
- Perform a time average across each track for all features
156
+ class TableUI(CelldetectiveMainWindow):
1094
157
 
1095
- """
1096
- self.query_widget = QueryWidget(self)
1097
- self.query_widget.show()
1098
-
1099
- # num_df = self.data.select_dtypes(include=self.numerics)
1100
-
1101
- # timeseries = num_df.groupby("FRAME").mean().copy()
1102
- # timeseries["timeline"] = timeseries.index
1103
- # self.subtable = TableUI(timeseries,"Group by frames", plot_mode="plot_timeseries")
1104
- # self.subtable.show()
1105
-
1106
- def set_projection_mode_neigh(self):
1107
-
1108
- self.groupby_cols = ['position', 'reference_population', 'neighbor_population', 'NEIGHBOR_ID', 'FRAME']
1109
- self.current_data = self.data
1110
- self.set_projection_mode_tracks()
1111
-
1112
- def set_projection_mode_ref(self):
1113
-
1114
- self.groupby_cols = ['position', 'reference_population', 'neighbor_population', 'REFERENCE_ID', 'FRAME']
1115
- self.current_data = self.data
1116
- self.set_projection_mode_tracks()
1117
-
1118
- def set_projection_mode_tracks(self):
1119
-
1120
- self.current_data = self.data
1121
-
1122
- self.projectionWidget = CelldetectiveWidget()
1123
- self.projectionWidget.setMinimumWidth(500)
1124
- self.projectionWidget.setWindowTitle('Set projection mode')
1125
-
1126
- layout = QVBoxLayout()
1127
- self.projectionWidget.setLayout(layout)
1128
-
1129
- self.projection_option = QRadioButton('global operation: ')
1130
- self.projection_option.setToolTip('Collapse the cell track measurements with an operation over each track.')
1131
- self.projection_option.setChecked(True)
1132
- self.projection_option.toggled.connect(self.enable_projection_options)
1133
- self.projection_op_cb = QComboBox()
1134
- self.projection_op_cb.addItems(['mean','median','min','max','first','last','prod','sum'])
1135
-
1136
- projection_layout = QHBoxLayout()
1137
- projection_layout.addWidget(self.projection_option, 33)
1138
- projection_layout.addWidget(self.projection_op_cb, 66)
1139
- layout.addLayout(projection_layout)
1140
-
1141
- self.event_time_option = QRadioButton('@event time: ')
1142
- self.event_time_option.setToolTip('Pick the measurements at a specific event time.')
1143
- self.event_time_option.toggled.connect(self.enable_projection_options)
1144
- self.event_times_cb = QComboBox()
1145
- cols = np.array(self.data.columns)
1146
- time_cols = np.array([c.startswith('t_') for c in cols])
1147
- time_cols = list(cols[time_cols])
1148
- if 't0' in list(self.data.columns):
1149
- time_cols.append('t0')
1150
- self.event_times_cb.addItems(time_cols)
1151
- self.event_times_cb.setEnabled(False)
1152
-
1153
- event_time_layout = QHBoxLayout()
1154
- event_time_layout.addWidget(self.event_time_option, 33)
1155
- event_time_layout.addWidget(self.event_times_cb, 66)
1156
- layout.addLayout(event_time_layout)
1157
-
1158
-
1159
- self.per_status_option = QRadioButton('per status: ')
1160
- self.per_status_option.setToolTip('Collapse the cell track measurements independently for each of the cell state.')
1161
- self.per_status_option.toggled.connect(self.enable_projection_options)
1162
- self.per_status_cb = QComboBox()
1163
- self.status_operation = QComboBox()
1164
- self.status_operation.setEnabled(False)
1165
- self.status_operation.addItems(['mean','median','min','max', 'prod', 'sum'])
1166
-
1167
- status_cols = np.array([c.startswith('status_') or c.startswith('group_') for c in cols])
1168
- status_cols = list(cols[status_cols])
1169
- if 'status' in list(self.data.columns):
1170
- status_cols.append('status')
1171
- self.per_status_cb.addItems(status_cols)
1172
- self.per_status_cb.setEnabled(False)
1173
-
1174
- per_status_layout = QHBoxLayout()
1175
- per_status_layout.addWidget(self.per_status_option, 33)
1176
- per_status_layout.addWidget(self.per_status_cb, 66)
1177
- layout.addLayout(per_status_layout)
1178
-
1179
- status_operation_layout = QHBoxLayout()
1180
- status_operation_layout.addWidget(QLabel('operation: '), 33, alignment=Qt.AlignRight)
1181
- status_operation_layout.addWidget(self.status_operation, 66)
1182
- layout.addLayout(status_operation_layout)
1183
-
1184
- self.btn_projection_group = QButtonGroup()
1185
- self.btn_projection_group.addButton(self.projection_option)
1186
- self.btn_projection_group.addButton(self.event_time_option)
1187
- self.btn_projection_group.addButton(self.per_status_option)
1188
-
1189
- apply_layout = QHBoxLayout()
1190
-
1191
- self.set_projection_btn = QPushButton('Apply')
1192
- self.set_projection_btn.setStyleSheet(self.button_style_sheet)
1193
- self.set_projection_btn.clicked.connect(self.set_proj_mode)
1194
- apply_layout.addWidget(QLabel(''), 33)
1195
- apply_layout.addWidget(self.set_projection_btn,33)
1196
- apply_layout.addWidget(QLabel(''),33)
1197
- layout.addLayout(apply_layout)
1198
-
1199
- self.projectionWidget.show()
1200
- center_window(self.projectionWidget)
1201
-
1202
- def enable_projection_options(self):
1203
-
1204
- if self.projection_option.isChecked():
1205
- self.projection_op_cb.setEnabled(True)
1206
- self.event_times_cb.setEnabled(False)
1207
- self.per_status_cb.setEnabled(False)
1208
- self.status_operation.setEnabled(False)
1209
- elif self.event_time_option.isChecked():
1210
- self.projection_op_cb.setEnabled(False)
1211
- self.event_times_cb.setEnabled(True)
1212
- self.per_status_cb.setEnabled(False)
1213
- self.status_operation.setEnabled(False)
1214
- elif self.per_status_option.isChecked():
1215
- self.projection_op_cb.setEnabled(False)
1216
- self.event_times_cb.setEnabled(False)
1217
- self.per_status_cb.setEnabled(True)
1218
- self.status_operation.setEnabled(True)
1219
-
1220
- def set_1D_plot_params(self):
1221
-
1222
- self.plot1Dparams = CelldetectiveWidget()
1223
- self.plot1Dparams.setWindowTitle('Set 1D plot parameters')
1224
-
1225
- layout = QVBoxLayout()
1226
- self.plot1Dparams.setLayout(layout)
1227
-
1228
- layout.addWidget(QLabel('Representations: '))
1229
- self.hist_check = QCheckBox('histogram')
1230
- self.kde_check = QCheckBox('KDE plot')
1231
- self.count_check = QCheckBox('countplot')
1232
- self.ecdf_check = QCheckBox('ECDF plot')
1233
- self.line_check = QCheckBox('line plot')
1234
- self.scat_check = QCheckBox('scatter plot')
1235
- self.swarm_check = QCheckBox('swarm')
1236
- self.violin_check = QCheckBox('violin')
1237
- self.strip_check = QCheckBox('strip')
1238
- self.box_check = QCheckBox('boxplot')
1239
- self.boxenplot_check = QCheckBox('boxenplot')
1240
-
1241
- self.sep_line = QHSeperationLine()
1242
- self.pvalue_check = QCheckBox("Compute KS test p-value?")
1243
- self.effect_size_check = QCheckBox("Compute effect size?\n(Cliff's Delta)")
1244
-
1245
- layout.addWidget(self.hist_check)
1246
- layout.addWidget(self.kde_check)
1247
- layout.addWidget(self.count_check)
1248
- layout.addWidget(self.ecdf_check)
1249
- layout.addWidget(self.line_check)
1250
- layout.addWidget(self.scat_check)
1251
- layout.addWidget(self.swarm_check)
1252
- layout.addWidget(self.violin_check)
1253
- layout.addWidget(self.strip_check)
1254
- layout.addWidget(self.box_check)
1255
- layout.addWidget(self.boxenplot_check)
1256
- layout.addWidget(self.sep_line)
1257
- layout.addWidget(self.pvalue_check)
1258
- layout.addWidget(self.effect_size_check)
1259
-
1260
- self.x_cb = QSearchableComboBox()
1261
- self.x_cb.addItems(['--']+list(self.data.columns))
1262
-
1263
- self.y_cb = QSearchableComboBox()
1264
- self.y_cb.addItems(['--']+list(self.data.columns))
1265
-
1266
- self.hue_cb = QSearchableComboBox()
1267
- self.hue_cb.addItems(['--']+list(self.data.columns))
1268
- idx = self.hue_cb.findText('--')
1269
- self.hue_cb.setCurrentIndex(idx)
1270
-
1271
- # Set selected column
1272
-
1273
- try:
1274
- x = self.table_view.selectedIndexes()
1275
- col_idx = np.array([l.column() for l in x])
1276
- row_idx = np.array([l.row() for l in x])
1277
- column_names = self.data.columns
1278
- unique_cols = np.unique(col_idx)[0]
1279
- y = column_names[unique_cols]
1280
- idx = self.y_cb.findText(y)
1281
- self.y_cb.setCurrentIndex(idx)
1282
- except:
1283
- pass
1284
-
1285
- hbox = QHBoxLayout()
1286
- hbox.addWidget(QLabel('x: '), 33)
1287
- hbox.addWidget(self.x_cb, 66)
1288
- layout.addLayout(hbox)
1289
-
1290
- hbox = QHBoxLayout()
1291
- hbox.addWidget(QLabel('y: '), 33)
1292
- hbox.addWidget(self.y_cb, 66)
1293
- layout.addLayout(hbox)
1294
-
1295
- hbox = QHBoxLayout()
1296
- hbox.addWidget(QLabel('hue: '), 33)
1297
- hbox.addWidget(self.hue_cb, 66)
1298
- layout.addLayout(hbox)
1299
-
1300
- self.cmap_cb = QColormapComboBox()
1301
- all_cms = list(colormaps)
1302
- for cm in all_cms:
1303
- if hasattr(matplotlib.cm, str(cm).lower()):
1304
- try:
1305
- self.cmap_cb.addColormap(cm.lower())
1306
- except:
1307
- pass
1308
-
1309
- hbox = QHBoxLayout()
1310
- hbox.addWidget(QLabel('colormap: '), 33)
1311
- hbox.addWidget(self.cmap_cb, 66)
1312
- layout.addLayout(hbox)
1313
-
1314
- self.plot1d_btn = QPushButton('set')
1315
- self.plot1d_btn.setStyleSheet(self.button_style_sheet)
1316
- self.plot1d_btn.clicked.connect(self.plot1d)
1317
- layout.addWidget(self.plot1d_btn)
1318
-
1319
- self.plot1Dparams.show()
1320
- center_window(self.plot1Dparams)
1321
-
1322
-
1323
- def plot1d(self):
1324
-
1325
- self.x_option = False
1326
- if self.x_cb.currentText()!='--':
1327
- self.x_option = True
1328
- self.x = self.x_cb.currentText()
1329
-
1330
- self.fig, self.ax = plt.subplots(1,1,figsize=(4,3))
1331
- self.plot1dWindow = FigureCanvas(self.fig, title="scatter")
1332
- self.ax.clear()
1333
-
1334
- cmap = getattr(mcm, self.cmap_cb.currentText())
1335
-
1336
- try:
1337
- self.hue_variable = self.hue_cb.currentText()
1338
- colors = [cmap(i / len(self.data[self.hue_variable].unique())) for i in range(len(self.data[self.hue_variable].unique()))]
1339
- except:
1340
- colors = None
1341
-
1342
- if self.hue_cb.currentText()=='--':
1343
- self.hue_variable = None
1344
-
1345
- if self.y_cb.currentText()=='--':
1346
- self.y = None
1347
- else:
1348
- self.y = self.y_cb.currentText()
1349
-
1350
- if self.x_cb.currentText()=='--':
1351
- self.x = None
1352
- else:
1353
- self.x = self.x_cb.currentText()
1354
-
1355
- legend=True
1356
-
1357
- if self.hist_check.isChecked():
1358
- if self.x is not None:
1359
- sns.histplot(data=self.data, x=self.x, hue=self.hue_variable, legend=legend, ax=self.ax, palette=colors, kde=True, common_norm=False, stat='density')
1360
- legend = False
1361
- elif self.x is None and self.y is not None:
1362
- sns.histplot(data=self.data, x=self.y, hue=self.hue_variable, legend=legend, ax=self.ax, palette=colors, kde=True, common_norm=False, stat='density')
1363
- legend = False
1364
- else:
1365
- pass
1366
-
1367
- if self.kde_check.isChecked():
1368
- if self.x is not None:
1369
- sns.kdeplot(data=self.data, x=self.x, hue=self.hue_variable, legend=legend, ax=self.ax, palette=colors, cut=0)
1370
- legend = False
1371
- elif self.x is None and self.y is not None:
1372
- sns.kdeplot(data=self.data, x=self.y, hue=self.hue_variable, legend=legend, ax=self.ax, palette=colors, cut=0)
1373
- legend = False
1374
- else:
1375
- pass
1376
-
1377
- if self.count_check.isChecked():
1378
- sns.countplot(data=self.data, x=self.x, hue=self.hue_variable, legend=legend, ax=self.ax, palette=colors)
1379
- legend = False
1380
-
1381
-
1382
- if self.ecdf_check.isChecked():
1383
- if self.x is not None:
1384
- sns.ecdfplot(data=self.data, x=self.x, hue=self.hue_variable, legend=legend, ax=self.ax, palette=colors)
1385
- legend = False
1386
- elif self.x is None and self.y is not None:
1387
- sns.ecdfplot(data=self.data, x=self.y, hue=self.hue_variable, legend=legend, ax=self.ax, palette=colors)
1388
- legend = False
1389
- else:
1390
- pass
1391
-
1392
- if self.line_check.isChecked():
1393
- if self.x_option:
1394
- sns.lineplot(data=self.data, x=self.x,y=self.y, hue=self.hue_variable,legend=legend, ax=self.ax, palette=colors)
1395
- legend = False
1396
- else:
1397
- print('please provide a -x variable...')
1398
- pass
1399
-
1400
- if self.scat_check.isChecked():
1401
- if self.x_option:
1402
- sns.scatterplot(data=self.data, x=self.x,y=self.y, hue=self.hue_variable,legend=legend, ax=self.ax, palette=colors)
1403
- legend = False
1404
- else:
1405
- print('please provide a -x variable...')
1406
- pass
1407
-
1408
- if self.swarm_check.isChecked():
1409
- if self.x_option:
1410
- sns.swarmplot(data=self.data, x=self.x,y=self.y,dodge=True, hue=self.hue_variable,legend=legend, ax=self.ax, palette=colors)
1411
- legend = False
1412
- else:
1413
- sns.swarmplot(data=self.data, y=self.y,dodge=True, hue=self.hue_variable,legend=legend, ax=self.ax, palette=colors)
1414
- legend = False
1415
-
1416
- if self.violin_check.isChecked():
1417
- if self.x_option:
1418
- sns.violinplot(data=self.data,x=self.x, y=self.y,dodge=True, ax=self.ax, hue=self.hue_variable, legend=legend, palette=colors)
1419
- legend = False
1420
- else:
1421
- sns.violinplot(data=self.data, y=self.y,dodge=True, hue=self.hue_variable,legend=legend, ax=self.ax, palette=colors, cut=0)
1422
- legend = False
1423
-
1424
- if self.box_check.isChecked():
1425
- if self.x_option:
1426
- sns.boxplot(data=self.data, x=self.x, y=self.y,dodge=True, hue=self.hue_variable,legend=legend, ax=self.ax, fill=False,palette=colors, linewidth=2,)
1427
- legend = False
1428
- else:
1429
- sns.boxplot(data=self.data, y=self.y,dodge=True, hue=self.hue_variable,legend=legend, ax=self.ax, fill=False,palette=colors, linewidth=2,)
1430
- legend = False
1431
-
1432
- if self.boxenplot_check.isChecked():
1433
- if self.x_option:
1434
- sns.boxenplot(data=self.data, x=self.x, y=self.y,dodge=True, hue=self.hue_variable,legend=legend, ax=self.ax, fill=False,palette=colors, linewidth=2,)
1435
- legend = False
1436
- else:
1437
- sns.boxenplot(data=self.data, y=self.y,dodge=True, hue=self.hue_variable,legend=legend, ax=self.ax, fill=False,palette=colors, linewidth=2,)
1438
- legend = False
1439
-
1440
- if self.strip_check.isChecked():
1441
- if self.x_option:
1442
- sns.stripplot(data=self.data, x = self.x, y=self.y,dodge=True, ax=self.ax, hue=self.hue_variable, legend=legend, palette=colors)
1443
- legend = False
1444
- else:
1445
- sns.stripplot(data=self.data, y=self.y,dodge=True, ax=self.ax, hue=self.hue_variable, legend=legend, palette=colors)
1446
- legend = False
1447
-
1448
- plt.tight_layout()
1449
- self.fig.set_facecolor('none') # or 'None'
1450
- self.fig.canvas.setStyleSheet("background-color: transparent;")
1451
- self.plot1dWindow.canvas.draw()
1452
- self.plot1dWindow.show()
1453
-
1454
- if self.effect_size_check.isChecked():
1455
- self.compute_effect_size()
1456
- if self.pvalue_check.isChecked():
1457
- self.compute_pvalue()
1458
-
1459
- def extract_groupby_cols(self):
1460
-
1461
- x = self.x
1462
- y = self.y
1463
- hue_variable = self.hue_variable
1464
-
1465
- if self.hist_check.isChecked() or self.ecdf_check.isChecked() or self.kde_check.isChecked():
1466
- y = self.x
1467
- x = None
1468
-
1469
- groupby_cols = []
1470
- if x is not None:
1471
- groupby_cols.append(x)
1472
- if hue_variable is not None:
1473
- groupby_cols.append(hue_variable)
1474
-
1475
- return groupby_cols, y
1476
-
1477
- def compute_effect_size(self):
1478
-
1479
- if self.count_check.isChecked() or self.scat_check.isChecked():
1480
- print('Please select a valid plot representation to compute effect size (histogram, boxplot, etc.)...')
1481
- return None
1482
-
1483
- groupby_cols, y = self.extract_groupby_cols()
1484
- pivot = test_2samp_generic(self.data, feature=y, groupby_cols=groupby_cols, method="cliffs_delta")
1485
- self.effect_size_table = PivotTableUI(pivot, title="Effect size (Cliff's Delta)", mode="cliff")
1486
- self.effect_size_table.show()
1487
-
1488
- def compute_pvalue(self):
1489
-
1490
- if self.count_check.isChecked() or self.scat_check.isChecked():
1491
- print('Please select a valid plot representation to compute effect size (histogram, boxplot, etc.)...')
1492
- return None
1493
-
1494
- groupby_cols, y = self.extract_groupby_cols()
1495
- pivot = test_2samp_generic(self.data, feature=y, groupby_cols=groupby_cols, method="ks_2samp")
1496
- self.pval_table = PivotTableUI(pivot, title="p-value (1-sided KS test)", mode="pvalue")
1497
- self.pval_table.show()
1498
-
1499
-
1500
- def set_proj_mode(self):
1501
-
1502
- self.static_columns = ['well_index', 'well_name', 'pos_name', 'position', 'well', 'status', 't0', 'class','cell_type','concentration', 'antibody', 'pharmaceutical_agent','TRACK_ID','position', 'neighbor_population', 'reference_population', 'NEIGHBOR_ID', 'REFERENCE_ID', 'FRAME']
1503
-
1504
- if self.projection_option.isChecked():
1505
-
1506
- self.projection_mode = self.projection_op_cb.currentText()
1507
- group_table = getattr(
1508
- self.current_data.groupby(self.groupby_cols), self.projection_mode
1509
- )(numeric_only=True)
1510
-
1511
- for c in self.static_columns:
1512
- try:
1513
- group_table[c] = self.current_data.groupby(self.groupby_cols)[c].apply(lambda x: x.unique()[0])
1514
- except Exception as e:
1515
- print(e)
1516
- pass
1517
-
1518
- if self.population=='pairs':
1519
- for col in reversed(self.groupby_cols): #['neighbor_population', 'reference_population', 'NEIGHBOR_ID', 'REFERENCE_ID']
1520
- if col in group_table:
1521
- first_column = group_table.pop(col)
1522
- group_table.insert(0, col, first_column)
1523
- else:
1524
- for col in ['TRACK_ID']:
1525
- first_column = group_table.pop(col)
1526
- group_table.insert(0, col, first_column)
1527
- group_table.pop('FRAME')
1528
-
1529
-
1530
- elif self.event_time_option.isChecked():
1531
-
1532
- time_of_interest = self.event_times_cb.currentText()
1533
- self.projection_mode = f"measurements at {time_of_interest}"
1534
- new_table = []
1535
- for tid,group in self.current_data.groupby(self.groupby_cols):
1536
- time = group[time_of_interest].values[0]
1537
- if time==time:
1538
- time = floor(time) # floor for onset
1539
- else:
1540
- continue
1541
- frames = group['FRAME'].values
1542
- values = group.loc[group['FRAME']==time,:].to_numpy()
1543
- if len(values)>0:
1544
- values = dict(zip(list(self.current_data.columns), values[0]))
1545
- for k,c in enumerate(self.groupby_cols):
1546
- values.update({c: tid[k]})
1547
- new_table.append(values)
1548
-
1549
- group_table = pd.DataFrame(new_table)
1550
- if self.population=='pairs':
1551
- for col in self.groupby_cols[1:]:
1552
- first_column = group_table.pop(col)
1553
- group_table.insert(0, col, first_column)
1554
- else:
1555
- for col in ['TRACK_ID']:
1556
- first_column = group_table.pop(col)
1557
- group_table.insert(0, col, first_column)
1558
-
1559
- group_table = group_table.sort_values(by=self.groupby_cols+['FRAME'],ignore_index=True)
1560
- group_table = group_table.reset_index(drop=True)
1561
-
1562
-
1563
- elif self.per_status_option.isChecked():
1564
- self.projection_mode = self.status_operation.currentText()
1565
- group_table = collapse_trajectories_by_status(self.current_data, status=self.per_status_cb.currentText(),population=self.population, projection=self.status_operation.currentText(), groupby_columns=self.groupby_cols)
1566
-
1567
- self.subtable = TableUI(group_table,f"Group by tracks: {self.projection_mode}", plot_mode="static", collapse_tracks_option=False)
1568
- self.subtable.show()
1569
-
1570
- self.projectionWidget.close()
1571
-
1572
- # def groupby_track_table(self):
1573
-
1574
- # """
1575
-
1576
- # Perform a time average across each track for all features
1577
-
1578
- # """
1579
-
1580
- # self.subtable = TrajectoryTablePanel(self.data.groupby("TRACK_ID").mean(),"Group by tracks", plot_mode="scatter")
1581
- # self.subtable.show()
1582
-
1583
- def _createMenuBar(self):
1584
- menuBar = self.menuBar()
1585
- self.fileMenu = QMenu("&File", self)
1586
- menuBar.addMenu(self.fileMenu)
1587
- self.editMenu = QMenu("&Edit", self)
1588
- menuBar.addMenu(self.editMenu)
1589
- self.mathMenu = QMenu('&Math', self)
1590
- menuBar.addMenu(self.mathMenu)
1591
-
1592
- def save_as_csv(self):
1593
- options = QFileDialog.Options()
1594
- options |= QFileDialog.ReadOnly
1595
- file_name, _ = QFileDialog.getSaveFileName(self, "Save as .csv", "","CSV Files (*.csv);;All Files (*)", options=options)
1596
- if file_name:
1597
- if not file_name.endswith(".csv"):
1598
- file_name += ".csv"
1599
- invalid_cols = [c for c in list(self.data.columns) if c.startswith('Unnamed')]
1600
- if len(invalid_cols)>0:
1601
- self.data = self.data.drop(invalid_cols, axis=1)
1602
- self.data.to_csv(file_name, index=False)
1603
-
1604
- def test_bool(self, array):
1605
- if array.dtype=="bool":
1606
- return np.array(array, dtype=int)
1607
- else:
1608
- return array
1609
-
1610
- def plot_instantaneous(self):
1611
-
1612
- if self.plot_mode=='plot_track_signals':
1613
- self.plot_mode = 'static'
1614
- self.plot()
1615
- self.plot_mode = 'plot_track_signals'
1616
- elif self.plot_mode=="static":
1617
- self.plot()
1618
-
1619
- def plot(self):
1620
- if self.plot_mode == "static":
1621
-
1622
- x = self.table_view.selectedIndexes()
1623
- col_idx = [l.column() for l in x]
1624
- row_idx = [l.row() for l in x]
1625
- column_names = self.data.columns
1626
- unique_cols = np.unique(col_idx)
1627
-
1628
- if len(unique_cols)==1 or len(unique_cols)==0:
1629
- self.set_1D_plot_params()
1630
-
1631
- if len(unique_cols) == 2:
1632
-
1633
- print("two columns, plot mode")
1634
- x1 = self.test_bool(self.data.iloc[row_idx, unique_cols[0]])
1635
- x2 = self.test_bool(self.data.iloc[row_idx, unique_cols[1]])
1636
-
1637
- self.fig, self.ax = plt.subplots(1, 1, figsize=(4,3))
1638
- self.scatter_wdw = FigureCanvas(self.fig, title="scatter")
1639
- self.ax.clear()
1640
- self.ax.scatter(x1,x2)
1641
- self.ax.set_xlabel(column_names[unique_cols[0]])
1642
- self.ax.set_ylabel(column_names[unique_cols[1]])
1643
- plt.tight_layout()
1644
- self.fig.set_facecolor('none') # or 'None'
1645
- self.fig.canvas.setStyleSheet("background-color: transparent;")
1646
- self.scatter_wdw.canvas.draw()
1647
- self.scatter_wdw.show()
1648
-
1649
- else:
1650
- print("please select less columns")
1651
-
1652
- elif self.plot_mode == "plot_timeseries":
1653
- print("mode plot frames")
1654
- x = self.table_view.selectedIndexes()
1655
- col_idx = np.array([l.column() for l in x])
1656
- row_idx = np.array([l.row() for l in x])
1657
- column_names = self.data.columns
1658
- unique_cols = np.unique(col_idx)
1659
-
1660
- self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
1661
- self.plot_wdw = FigureCanvas(self.fig, title="scatter")
1662
- self.ax.clear()
1663
- for k in range(len(unique_cols)):
1664
- row_idx_i = row_idx[np.where(col_idx == unique_cols[k])[0]]
1665
- y = self.data.iloc[row_idx_i, unique_cols[k]]
1666
- self.ax.plot(self.data["timeline"][row_idx_i], y, label=column_names[unique_cols[k]])
1667
-
1668
- self.ax.legend()
1669
- self.ax.set_xlabel("time [frame]")
1670
- self.ax.set_ylabel(self.title)
1671
- plt.tight_layout()
1672
- self.fig.set_facecolor('none') # or 'None'
1673
- self.fig.canvas.setStyleSheet("background-color: transparent;")
1674
- self.plot_wdw.canvas.draw()
1675
- plt.show()
1676
-
1677
- elif self.plot_mode == "plot_track_signals":
1678
-
1679
- print("mode plot track signals")
1680
- print('we plot here')
1681
-
1682
- x = self.table_view.selectedIndexes()
1683
- col_idx = np.array([l.column() for l in x])
1684
- row_idx = np.array([l.row() for l in x])
1685
- column_names = self.data.columns
1686
- unique_cols = np.unique(col_idx)
1687
-
1688
- if len(unique_cols) > 2:
1689
- fig,ax = plt.subplots(1, 1, figsize=(7, 5.5))
1690
- for k in range(len(unique_cols)):
1691
-
1692
- row_idx_i = row_idx[np.where(col_idx == unique_cols[k])[0]]
1693
- y = self.data.iloc[row_idx_i, unique_cols[k]]
1694
- print(unique_cols[k])
1695
- for w,well_group in self.data.groupby(['well_name']):
1696
- for pos,pos_group in well_group.groupby(['pos_name']):
1697
- for tid,group_track in pos_group.groupby(self.groupby_cols[1:]):
1698
- ax.plot(group_track["FRAME"], group_track[column_names[unique_cols[k]]],label=column_names[unique_cols[k]])
1699
- #ax.plot(self.data["FRAME"][row_idx_i], y, label=column_names[unique_cols[k]])
1700
- ax.legend()
1701
- ax.set_xlabel("time [frame]")
1702
- ax.set_ylabel(self.title)
1703
- plt.tight_layout()
1704
- plt.show(block=False)
1705
-
1706
- if len(unique_cols) == 2:
1707
-
1708
- self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
1709
- self.scatter_wdw = FigureCanvas(self.fig, title="scatter")
1710
- self.ax.clear()
1711
- for tid,group in self.data.groupby(self.groupby_cols[1:]):
1712
- self.ax.plot(group[column_names[unique_cols[0]]], group[column_names[unique_cols[1]]], marker="o")
1713
- self.ax.set_xlabel(column_names[unique_cols[0]])
1714
- self.ax.set_ylabel(column_names[unique_cols[1]])
1715
- plt.tight_layout()
1716
- self.fig.set_facecolor('none') # or 'None'
1717
- self.fig.canvas.setStyleSheet("background-color: transparent;")
1718
- self.scatter_wdw.canvas.draw()
1719
- self.scatter_wdw.show()
1720
-
1721
- if len(unique_cols) == 1:
1722
-
1723
- self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
1724
- self.plot_wdw = FigureCanvas(self.fig, title="scatter")
1725
- self.ax.clear()
1726
-
1727
- # if 't0' in list(self.data.columns):
1728
- # ref_time_col = 't0'
1729
- # else:
1730
- # ref_time_col = 'FRAME'
1731
-
1732
- for w,well_group in self.data.groupby(['well_name']):
1733
- for pos,pos_group in well_group.groupby(['pos_name']):
1734
- for tid,group_track in pos_group.groupby(self.groupby_cols[1:]):
1735
- self.ax.plot(group_track["FRAME"], group_track[column_names[unique_cols[0]]],c="k", alpha = 0.1)
1736
- self.ax.set_xlabel(r"$t$ [frame]")
1737
- self.ax.set_ylabel(column_names[unique_cols[0]])
1738
- plt.tight_layout()
1739
- self.fig.set_facecolor('none') # or 'None'
1740
- self.fig.canvas.setStyleSheet("background-color: transparent;")
1741
- self.plot_wdw.canvas.draw()
1742
- self.plot_wdw.show()
158
+ def __init__(
159
+ self,
160
+ data,
161
+ title,
162
+ population="targets",
163
+ plot_mode="plot_track_signals",
164
+ save_inplace_option=False,
165
+ collapse_tracks_option=True,
166
+ *args,
167
+ **kwargs,
168
+ ):
169
+
170
+ CelldetectiveMainWindow.__init__(self, *args, **kwargs)
171
+
172
+ self.setWindowTitle(title)
173
+ self.setGeometry(100, 100, 1000, 400)
174
+ center_window(self)
175
+ self.title = title
176
+ self.plot_mode = plot_mode
177
+ self.population = population
178
+ self.numerics = ["int16", "int32", "int64", "float16", "float32", "float64"]
179
+ self.groupby_cols = ["position", "TRACK_ID"]
180
+ self.tracks = False
181
+ self.save_inplace_option = save_inplace_option
182
+ self.collapse_tracks_option = collapse_tracks_option
183
+
184
+ if self.population == "pairs":
185
+ self.groupby_cols = [
186
+ "position",
187
+ "reference_population",
188
+ "neighbor_population",
189
+ "REFERENCE_ID",
190
+ "NEIGHBOR_ID",
191
+ ]
192
+ self.tracks = True # for now
193
+ else:
194
+ if "TRACK_ID" in data.columns:
195
+ if not np.all(data["TRACK_ID"].isnull()):
196
+ self.tracks = True
197
+
198
+ self.data = data
199
+
200
+ self._createMenuBar()
201
+ self._create_actions()
202
+
203
+ self.table_view = QTableView(self)
204
+ self.setCentralWidget(self.table_view)
205
+
206
+ # Set the model for the table view
207
+
208
+ import matplotlib.pyplot as plt
209
+
210
+ plt.rcParams["svg.fonttype"] = "none"
211
+
212
+ self.model = PandasModel(data)
213
+ self.table_view.setModel(self.model)
214
+ self.table_view.resizeColumnsToContents()
215
+
216
+ def resizeEvent(self, event):
217
+
218
+ super().resizeEvent(event)
219
+
220
+ try:
221
+ self.fig.tight_layout()
222
+ except:
223
+ pass
224
+
225
+ def _create_actions(self):
226
+
227
+ self.save_as = QAction("&Save as...", self)
228
+ self.save_as.triggered.connect(self.save_as_csv)
229
+ self.save_as.setShortcut("Ctrl+s")
230
+ self.fileMenu.addAction(self.save_as)
231
+
232
+ if self.save_inplace_option:
233
+ self.save_inplace = QAction("&Save inplace...", self)
234
+ self.save_inplace.triggered.connect(self.save_as_csv_inplace_per_pos)
235
+ # self.save_inplace.setShortcut("Ctrl+s")
236
+ self.fileMenu.addAction(self.save_inplace)
237
+
238
+ self.plot_action = QAction("&Plot...", self)
239
+ self.plot_action.triggered.connect(self.plot)
240
+ self.plot_action.setShortcut("Ctrl+p")
241
+ self.fileMenu.addAction(self.plot_action)
242
+
243
+ self.plot_inst_action = QAction("&Plot instantaneous...", self)
244
+ self.plot_inst_action.triggered.connect(self.plot_instantaneous)
245
+ self.plot_inst_action.setShortcut("Ctrl+i")
246
+ self.fileMenu.addAction(self.plot_inst_action)
247
+
248
+ self.groupby_action = QAction("&Collapse tracks...", self)
249
+ self.groupby_action.triggered.connect(self.set_projection_mode_tracks)
250
+ self.groupby_action.setShortcut("Ctrl+g")
251
+ self.fileMenu.addAction(self.groupby_action)
252
+ if not self.tracks or not self.collapse_tracks_option:
253
+ self.groupby_action.setEnabled(False)
254
+
255
+ if self.population == "pairs":
256
+
257
+ self.groupby_pairs_in_neigh_action = QAction(
258
+ "&Collapse pairs in neighborhood...", self
259
+ )
260
+ self.groupby_pairs_in_neigh_action.triggered.connect(
261
+ self.collapse_pairs_in_neigh
262
+ )
263
+ self.fileMenu.addAction(self.groupby_pairs_in_neigh_action)
264
+
265
+ if "FRAME" in list(self.data.columns):
266
+ self.groupby_time_action = QAction("&Group by frames...", self)
267
+ self.groupby_time_action.triggered.connect(self.groupby_time_table)
268
+ self.groupby_time_action.setShortcut("Ctrl+t")
269
+ self.fileMenu.addAction(self.groupby_time_action)
270
+
271
+ self.query_action = QAction("Query...", self)
272
+ self.query_action.triggered.connect(self.perform_query)
273
+ self.fileMenu.addAction(self.query_action)
274
+
275
+ self.delete_action = QAction("&Delete...", self)
276
+ self.delete_action.triggered.connect(self.delete_columns)
277
+ self.delete_action.setShortcut(Qt.Key_Delete)
278
+ self.editMenu.addAction(self.delete_action)
279
+
280
+ self.rename_col_action = QAction("&Rename...", self)
281
+ self.rename_col_action.triggered.connect(self.rename_column)
282
+ # self.rename_col_action.setShortcut(Qt.Key_Delete)
283
+ self.editMenu.addAction(self.rename_col_action)
284
+
285
+ if self.population == "pairs":
286
+ self.merge_action = QAction("&Merge...", self)
287
+ self.merge_action.triggered.connect(self.merge_tables)
288
+ # self.rename_col_action.setShortcut(Qt.Key_Delete)
289
+ self.editMenu.addAction(self.merge_action)
290
+
291
+ self.calibrate_action = QAction("&Calibrate...", self)
292
+ self.calibrate_action.triggered.connect(self.calibrate_selected_feature)
293
+ self.calibrate_action.setShortcut("Ctrl+C")
294
+ self.mathMenu.addAction(self.calibrate_action)
295
+
296
+ self.merge_classification_action = QAction("&Merge states...", self)
297
+ self.merge_classification_action.triggered.connect(
298
+ self.merge_classification_features
299
+ )
300
+ self.mathMenu.addAction(self.merge_classification_action)
301
+
302
+ self.derivative_action = QAction("&Differentiate...", self)
303
+ self.derivative_action.triggered.connect(self.differenciate_selected_feature)
304
+ self.derivative_action.setShortcut("Ctrl+D")
305
+ self.mathMenu.addAction(self.derivative_action)
306
+ if not self.tracks:
307
+ self.derivative_action.setEnabled(False)
308
+
309
+ self.abs_action = QAction("&Absolute value...", self)
310
+ self.abs_action.triggered.connect(self.take_abs_of_selected_feature)
311
+ # self.derivative_action.setShortcut("Ctrl+D")
312
+ self.mathMenu.addAction(self.abs_action)
313
+
314
+ self.log_action = QAction("&Log (decimal)...", self)
315
+ self.log_action.triggered.connect(self.take_log_of_selected_feature)
316
+ # self.derivative_action.setShortcut("Ctrl+D")
317
+ self.mathMenu.addAction(self.log_action)
318
+
319
+ self.divide_action = QAction("&Divide...", self)
320
+ self.divide_action.triggered.connect(self.divide_signals)
321
+ # self.derivative_action.setShortcut("Ctrl+D")
322
+ self.mathMenu.addAction(self.divide_action)
323
+
324
+ self.multiply_action = QAction("&Multiply...", self)
325
+ self.multiply_action.triggered.connect(self.multiply_signals)
326
+ # self.derivative_action.setShortcut("Ctrl+D")
327
+ self.mathMenu.addAction(self.multiply_action)
328
+
329
+ self.add_action = QAction("&Add...", self)
330
+ self.add_action.triggered.connect(self.add_signals)
331
+ # self.derivative_action.setShortcut("Ctrl+D")
332
+ self.mathMenu.addAction(self.add_action)
333
+
334
+ self.subtract_action = QAction("&Subtract...", self)
335
+ self.subtract_action.triggered.connect(self.subtract_signals)
336
+ # self.derivative_action.setShortcut("Ctrl+D")
337
+ self.mathMenu.addAction(self.subtract_action)
338
+
339
+ # self.onehot_action = QAction('&One hot to categorical...', self)
340
+ # self.onehot_action.triggered.connect(self.transform_one_hot_cols_to_categorical)
341
+ # #self.onehot_action.setShortcut("Ctrl+D")
342
+ # self.mathMenu.addAction(self.onehot_action)
343
+
344
+ def collapse_pairs_in_neigh(self):
345
+
346
+ self.selectNeighWidget = CelldetectiveWidget()
347
+ self.selectNeighWidget.setMinimumWidth(480)
348
+ self.selectNeighWidget.setWindowTitle("Set neighborhood of interest")
349
+
350
+ layout = QVBoxLayout()
351
+ self.selectNeighWidget.setLayout(layout)
352
+
353
+ self.reference_lbl = QLabel("reference population: ")
354
+ self.reference_pop_cb = QComboBox()
355
+ ref_pops = self.data["reference_population"].unique()
356
+ self.reference_pop_cb.addItems(ref_pops)
357
+ self.reference_pop_cb.currentIndexChanged.connect(self.update_neighborhoods)
358
+
359
+ reference_hbox = QHBoxLayout()
360
+ reference_hbox.addWidget(self.reference_lbl, 33)
361
+ reference_hbox.addWidget(self.reference_pop_cb, 66)
362
+ layout.addLayout(reference_hbox)
363
+
364
+ self.neigh_lbl = QLabel("neighborhod: ")
365
+ self.neigh_cb = QComboBox()
366
+ neigh_cols = [
367
+ c.replace("status_", "")
368
+ for c in list(
369
+ self.data.loc[
370
+ self.data["reference_population"]
371
+ == self.reference_pop_cb.currentText()
372
+ ].columns
373
+ )
374
+ if c.startswith("status_neighborhood")
375
+ ]
376
+ self.neigh_cb.addItems(neigh_cols)
377
+
378
+ neigh_hbox = QHBoxLayout()
379
+ neigh_hbox.addWidget(self.neigh_lbl, 33)
380
+ neigh_hbox.addWidget(self.neigh_cb, 66)
381
+ layout.addLayout(neigh_hbox)
382
+
383
+ contact_hbox = QHBoxLayout()
384
+ self.contact_only_check = QCheckBox("keep only pairs in contact")
385
+ self.contact_only_check.setChecked(True)
386
+ contact_hbox.addWidget(self.contact_only_check, alignment=Qt.AlignLeft)
387
+ layout.addLayout(contact_hbox)
388
+
389
+ self.groupby_pair_rb = QRadioButton("Group by pair")
390
+ self.groupby_reference_rb = QRadioButton("Group by reference")
391
+ self.groupby_pair_rb.setChecked(True)
392
+
393
+ groupby_hbox = QHBoxLayout()
394
+ groupby_hbox.addWidget(QLabel("collapse option: "), 33)
395
+ groupby_hbox.addWidget(self.groupby_pair_rb, (100 - 33) // 2)
396
+ groupby_hbox.addWidget(self.groupby_reference_rb, (100 - 33) // 2)
397
+ layout.addLayout(groupby_hbox)
398
+
399
+ self.apply_neigh_btn = QPushButton("Set")
400
+ self.apply_neigh_btn.setStyleSheet(self.button_style_sheet)
401
+ self.apply_neigh_btn.clicked.connect(self.prepare_table_at_neighborhood)
402
+
403
+ apply_hbox = QHBoxLayout()
404
+ apply_hbox.addWidget(QLabel(""), 33)
405
+ apply_hbox.addWidget(self.apply_neigh_btn, 66)
406
+ layout.addLayout(apply_hbox)
407
+
408
+ self.selectNeighWidget.show()
409
+ center_window(self.selectNeighWidget)
410
+
411
+ def prepare_table_at_neighborhood(self):
412
+
413
+ ref_pop = self.reference_pop_cb.currentText()
414
+ neighborhood = self.neigh_cb.currentText()
415
+ status_neigh = "status_" + neighborhood
416
+
417
+ if "self" in neighborhood:
418
+ neighbor_pop = ref_pop
419
+
420
+ neigh_col = neighborhood.replace("status_", "")
421
+ if "_(" in neigh_col and ")_" in neigh_col:
422
+ neighbor_pop = neigh_col.split("_(")[-1].split(")_")[0].split("-")[-1]
423
+ else:
424
+ if ref_pop == "targets":
425
+ neighbor_pop = "effectors"
426
+ if ref_pop == "effectors":
427
+ neighbor_pop = "targets"
428
+
429
+ from celldetective.neighborhood import extract_neighborhood_in_pair_table
430
+
431
+ data = extract_neighborhood_in_pair_table(
432
+ self.data,
433
+ neighborhood_key=neighborhood,
434
+ contact_only=self.contact_only_check.isChecked(),
435
+ reference_population=ref_pop,
436
+ )
437
+
438
+ if self.groupby_pair_rb.isChecked():
439
+ self.groupby_cols = ["position", "REFERENCE_ID", "NEIGHBOR_ID"]
440
+ elif self.groupby_reference_rb.isChecked():
441
+ self.groupby_cols = ["position", "REFERENCE_ID"]
442
+
443
+ self.current_data = data
444
+ skip_projection = False
445
+ if "reference_tracked" in list(self.current_data.columns):
446
+ print(
447
+ f"{self.current_data['reference_tracked']=} {(self.current_data['reference_tracked']==False)=} {np.all(self.current_data['reference_tracked']==False)=}"
448
+ )
449
+ if np.all(self.current_data["reference_tracked"].astype(bool) == False):
450
+ # reference not tracked
451
+ if self.groupby_reference_rb.isChecked():
452
+ self.groupby_cols = ["position", "FRAME", "REFERENCE_ID"]
453
+ elif self.groupby_pair_rb.isChecked():
454
+ print(
455
+ "The reference cells seem to not be tracked. No collapse can be performed."
456
+ )
457
+ skip_projection = True
458
+ else:
459
+ if np.all(self.current_data["neighbors_tracked"].astype(bool) == False):
460
+ # neighbors not tracked
461
+ if self.groupby_pair_rb.isChecked():
462
+ print(
463
+ "The neighbor cells seem to not be tracked. No collapse can be performed."
464
+ )
465
+ skip_projection = True
466
+ elif self.groupby_reference_rb.isChecked():
467
+ self.groupby_cols = [
468
+ "position",
469
+ "REFERENCE_ID",
470
+ ] # think about what would be best
471
+
472
+ if not skip_projection:
473
+ self.set_projection_mode_tracks()
474
+
475
+ def update_neighborhoods(self):
476
+
477
+ neigh_cols = [
478
+ c.replace("status_", "")
479
+ for c in list(
480
+ self.data.loc[
481
+ self.data["reference_population"]
482
+ == self.reference_pop_cb.currentText()
483
+ ].columns
484
+ )
485
+ if c.startswith("status_neighborhood")
486
+ ]
487
+ self.neigh_cb.clear()
488
+ self.neigh_cb.addItems(neigh_cols)
489
+
490
+ def merge_tables(self):
491
+
492
+ df_expanded = expand_pair_table(self.data)
493
+ self.subtable = TableUI(
494
+ df_expanded, "merge", plot_mode="static", population="pairs"
495
+ )
496
+ self.subtable.show()
497
+
498
+ def delete_columns(self):
499
+
500
+ x = self.table_view.selectedIndexes()
501
+ col_idx = np.unique(np.array([l.column() for l in x]))
502
+ cols = np.array(list(self.data.columns))
503
+
504
+ msgBox = QMessageBox()
505
+ msgBox.setIcon(QMessageBox.Question)
506
+ msgBox.setText(
507
+ f"You are about to delete columns {cols[col_idx]}... Do you want to proceed?"
508
+ )
509
+ msgBox.setWindowTitle("Info")
510
+ msgBox.setStandardButtons(QMessageBox.Yes | QMessageBox.No)
511
+ return_value = msgBox.exec()
512
+ if return_value == QMessageBox.No:
513
+ return None
514
+
515
+ self.data = self.data.drop(list(cols[col_idx]), axis=1)
516
+ self.model = PandasModel(self.data)
517
+ self.table_view.setModel(self.model)
518
+
519
+ def rename_column(self):
520
+
521
+ x = self.table_view.selectedIndexes()
522
+ col_idx = np.unique(np.array([l.column() for l in x]))
523
+
524
+ if len(col_idx) == 0:
525
+ msg_box = QMessageBox()
526
+ msg_box.setIcon(QMessageBox.Question)
527
+ msg_box.setText(f"Please select a column first.")
528
+ msg_box.setWindowTitle("Warning")
529
+ msg_box.setStandardButtons(QMessageBox.Ok)
530
+ returnValue = msg_box.exec()
531
+ if returnValue == QMessageBox.Ok:
532
+ return None
533
+ else:
534
+ return None
535
+
536
+ cols = np.array(list(self.data.columns))
537
+ selected_col = str(cols[col_idx][0])
538
+
539
+ self.renameWidget = RenameColWidget(self, selected_col)
540
+ self.renameWidget.show()
541
+
542
+ def save_as_csv_inplace_per_pos(self):
543
+
544
+ print("Saving each table in its respective position folder...")
545
+ for pos, pos_group in self.data.groupby(["position"]):
546
+ invalid_cols = [
547
+ c for c in list(pos_group.columns) if c.startswith("Unnamed")
548
+ ]
549
+ if len(invalid_cols) > 0:
550
+ pos_group = pos_group.drop(invalid_cols, axis=1)
551
+ pos_group.to_csv(
552
+ pos[0]
553
+ + os.sep.join(
554
+ ["output", "tables", f"trajectories_{self.population}.csv"]
555
+ ),
556
+ index=False,
557
+ )
558
+ print("Done...")
559
+
560
+ def divide_signals(self):
561
+
562
+ x = self.table_view.selectedIndexes()
563
+ col_idx = np.unique(np.array([l.column() for l in x]))
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
578
+
579
+ self.divWidget = OperationOnColsWidget(
580
+ self, column1=selected_col1, column2=selected_col2, operation="divide"
581
+ )
582
+ self.divWidget.show()
583
+
584
+ def multiply_signals(self):
585
+
586
+ x = self.table_view.selectedIndexes()
587
+ col_idx = np.unique(np.array([l.column() for l in x]))
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
+
603
+ self.mulWidget = OperationOnColsWidget(
604
+ self, column1=selected_col1, column2=selected_col2, operation="multiply"
605
+ )
606
+ self.mulWidget.show()
607
+
608
+ def add_signals(self):
609
+
610
+ x = self.table_view.selectedIndexes()
611
+ col_idx = np.unique(np.array([l.column() for l in x]))
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
626
+
627
+ self.addiWidget = OperationOnColsWidget(
628
+ self, column1=selected_col1, column2=selected_col2, operation="add"
629
+ )
630
+ self.addiWidget.show()
631
+
632
+ def subtract_signals(self):
633
+
634
+ x = self.table_view.selectedIndexes()
635
+ col_idx = np.unique(np.array([l.column() for l in x]))
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
650
+
651
+ self.subWidget = OperationOnColsWidget(
652
+ self, column1=selected_col1, column2=selected_col2, operation="subtract"
653
+ )
654
+ self.subWidget.show()
655
+
656
+ def differenciate_selected_feature(self):
657
+
658
+ # check only one col selected and assert is numerical
659
+ # open widget to select window parameters, directionality
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
+
673
+ self.diffWidget = DifferentiateColWidget(self, selected_col)
674
+ self.diffWidget.show()
675
+
676
+ def take_log_of_selected_feature(self):
677
+
678
+ # check only one col selected and assert is numerical
679
+ # open widget to select window parameters, directionality
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
+
693
+ self.LogWidget = LogColWidget(self, selected_col)
694
+ self.LogWidget.show()
695
+
696
+ def merge_classification_features(self):
697
+
698
+ x = self.table_view.selectedIndexes()
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)
707
+
708
+ # Lazy load MergeGroupWidget
709
+ from celldetective.gui.table_ops._merge_groups import MergeGroupWidget
710
+
711
+ self.merge_classification_widget = MergeGroupWidget(self, columns=col_selection)
712
+ self.merge_classification_widget.show()
713
+
714
+ def calibrate_selected_feature(self):
715
+
716
+ x = self.table_view.selectedIndexes()
717
+ col_idx = np.unique(np.array([l.column() for l in x]))
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
+
727
+ self.calWidget = CalibrateColWidget(self, selected_col)
728
+ self.calWidget.show()
729
+
730
+ def take_abs_of_selected_feature(self):
731
+
732
+ # check only one col selected and assert is numerical
733
+ # open widget to select window parameters, directionality
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
+
747
+ self.absWidget = AbsColWidget(self, selected_col)
748
+ self.absWidget.show()
749
+
750
+ def transform_one_hot_cols_to_categorical(self):
751
+
752
+ x = self.table_view.selectedIndexes()
753
+ col_idx = np.unique(np.array([l.column() for l in x]))
754
+ selected_cols = None
755
+ if isinstance(col_idx, (list, np.ndarray)):
756
+ cols = np.array(list(self.data.columns))
757
+ if len(col_idx) > 0:
758
+ selected_col = str(cols[col_idx[0]])
759
+
760
+ self.mergewidget = MergeOneHotWidget(self, selected_columns=selected_cols)
761
+ self.mergewidget.show()
762
+
763
+ def groupby_time_table(self):
764
+ """
765
+
766
+ Perform a time average across each track for all features
767
+
768
+ """
769
+
770
+ num_df = self.data.select_dtypes(include=self.numerics)
771
+
772
+ timeseries = num_df.groupby(["FRAME"]).sum().copy()
773
+ timeseries["timeline"] = timeseries.index
774
+ self.subtable = TableUI(
775
+ timeseries, "Group by frames", plot_mode="plot_timeseries"
776
+ )
777
+ self.subtable.show()
778
+
779
+ def perform_query(self):
780
+ """
781
+
782
+ Perform a time average across each track for all features
783
+
784
+ """
785
+ self.query_widget = QueryWidget(self)
786
+ self.query_widget.show()
787
+
788
+ # num_df = self.data.select_dtypes(include=self.numerics)
789
+
790
+ # timeseries = num_df.groupby("FRAME").mean().copy()
791
+ # timeseries["timeline"] = timeseries.index
792
+ # self.subtable = TableUI(timeseries,"Group by frames", plot_mode="plot_timeseries")
793
+ # self.subtable.show()
794
+
795
+ def set_projection_mode_neigh(self):
796
+
797
+ self.groupby_cols = [
798
+ "position",
799
+ "reference_population",
800
+ "neighbor_population",
801
+ "NEIGHBOR_ID",
802
+ "FRAME",
803
+ ]
804
+ self.current_data = self.data
805
+ self.set_projection_mode_tracks()
806
+
807
+ def set_projection_mode_ref(self):
808
+
809
+ self.groupby_cols = [
810
+ "position",
811
+ "reference_population",
812
+ "neighbor_population",
813
+ "REFERENCE_ID",
814
+ "FRAME",
815
+ ]
816
+ self.current_data = self.data
817
+ self.set_projection_mode_tracks()
818
+
819
+ def set_projection_mode_tracks(self):
820
+
821
+ self.current_data = self.data
822
+
823
+ self.projectionWidget = CelldetectiveWidget()
824
+ self.projectionWidget.setMinimumWidth(500)
825
+ self.projectionWidget.setWindowTitle("Set projection mode")
826
+
827
+ layout = QVBoxLayout()
828
+ self.projectionWidget.setLayout(layout)
829
+
830
+ self.projection_option = QRadioButton("global operation: ")
831
+ self.projection_option.setToolTip(
832
+ "Collapse the cell track measurements with an operation over each track."
833
+ )
834
+ self.projection_option.setChecked(True)
835
+ self.projection_option.toggled.connect(self.enable_projection_options)
836
+ self.projection_op_cb = QComboBox()
837
+ self.projection_op_cb.addItems(
838
+ ["mean", "median", "min", "max", "first", "last", "prod", "sum"]
839
+ )
840
+
841
+ projection_layout = QHBoxLayout()
842
+ projection_layout.addWidget(self.projection_option, 33)
843
+ projection_layout.addWidget(self.projection_op_cb, 66)
844
+ layout.addLayout(projection_layout)
845
+
846
+ self.event_time_option = QRadioButton("@event time: ")
847
+ self.event_time_option.setToolTip(
848
+ "Pick the measurements at a specific event time."
849
+ )
850
+ self.event_time_option.toggled.connect(self.enable_projection_options)
851
+ self.event_times_cb = QComboBox()
852
+ cols = np.array(self.data.columns)
853
+ time_cols = np.array([c.startswith("t_") for c in cols])
854
+ time_cols = list(cols[time_cols])
855
+ if "t0" in list(self.data.columns):
856
+ time_cols.append("t0")
857
+ self.event_times_cb.addItems(time_cols)
858
+ self.event_times_cb.setEnabled(False)
859
+
860
+ event_time_layout = QHBoxLayout()
861
+ event_time_layout.addWidget(self.event_time_option, 33)
862
+ event_time_layout.addWidget(self.event_times_cb, 66)
863
+ layout.addLayout(event_time_layout)
864
+
865
+ self.per_status_option = QRadioButton("per status: ")
866
+ self.per_status_option.setToolTip(
867
+ "Collapse the cell track measurements independently for each of the cell state."
868
+ )
869
+ self.per_status_option.toggled.connect(self.enable_projection_options)
870
+ self.per_status_cb = QComboBox()
871
+ self.status_operation = QComboBox()
872
+ self.status_operation.setEnabled(False)
873
+ self.status_operation.addItems(["mean", "median", "min", "max", "prod", "sum"])
874
+
875
+ status_cols = np.array(
876
+ [c.startswith("status_") or c.startswith("group_") for c in cols]
877
+ )
878
+ status_cols = list(cols[status_cols])
879
+ if "status" in list(self.data.columns):
880
+ status_cols.append("status")
881
+ self.per_status_cb.addItems(status_cols)
882
+ self.per_status_cb.setEnabled(False)
883
+
884
+ per_status_layout = QHBoxLayout()
885
+ per_status_layout.addWidget(self.per_status_option, 33)
886
+ per_status_layout.addWidget(self.per_status_cb, 66)
887
+ layout.addLayout(per_status_layout)
888
+
889
+ status_operation_layout = QHBoxLayout()
890
+ status_operation_layout.addWidget(
891
+ QLabel("operation: "), 33, alignment=Qt.AlignRight
892
+ )
893
+ status_operation_layout.addWidget(self.status_operation, 66)
894
+ layout.addLayout(status_operation_layout)
895
+
896
+ self.btn_projection_group = QButtonGroup()
897
+ self.btn_projection_group.addButton(self.projection_option)
898
+ self.btn_projection_group.addButton(self.event_time_option)
899
+ self.btn_projection_group.addButton(self.per_status_option)
900
+
901
+ apply_layout = QHBoxLayout()
902
+
903
+ self.set_projection_btn = QPushButton("Apply")
904
+ self.set_projection_btn.setStyleSheet(self.button_style_sheet)
905
+ self.set_projection_btn.clicked.connect(self.set_proj_mode)
906
+ apply_layout.addWidget(QLabel(""), 33)
907
+ apply_layout.addWidget(self.set_projection_btn, 33)
908
+ apply_layout.addWidget(QLabel(""), 33)
909
+ layout.addLayout(apply_layout)
910
+
911
+ self.projectionWidget.show()
912
+ center_window(self.projectionWidget)
913
+
914
+ def enable_projection_options(self):
915
+
916
+ if self.projection_option.isChecked():
917
+ self.projection_op_cb.setEnabled(True)
918
+ self.event_times_cb.setEnabled(False)
919
+ self.per_status_cb.setEnabled(False)
920
+ self.status_operation.setEnabled(False)
921
+ elif self.event_time_option.isChecked():
922
+ self.projection_op_cb.setEnabled(False)
923
+ self.event_times_cb.setEnabled(True)
924
+ self.per_status_cb.setEnabled(False)
925
+ self.status_operation.setEnabled(False)
926
+ elif self.per_status_option.isChecked():
927
+ self.projection_op_cb.setEnabled(False)
928
+ self.event_times_cb.setEnabled(False)
929
+ self.per_status_cb.setEnabled(True)
930
+ self.status_operation.setEnabled(True)
931
+
932
+ def set_1D_plot_params(self):
933
+
934
+ self.plot1Dparams = CelldetectiveWidget()
935
+ self.plot1Dparams.setWindowTitle("Set 1D plot parameters")
936
+
937
+ layout = QVBoxLayout()
938
+ self.plot1Dparams.setLayout(layout)
939
+
940
+ layout.addWidget(QLabel("Representations: "))
941
+ self.hist_check = QCheckBox("histogram")
942
+ self.kde_check = QCheckBox("KDE plot")
943
+ self.count_check = QCheckBox("countplot")
944
+ self.ecdf_check = QCheckBox("ECDF plot")
945
+ self.line_check = QCheckBox("line plot")
946
+ self.scat_check = QCheckBox("scatter plot")
947
+ self.swarm_check = QCheckBox("swarm")
948
+ self.violin_check = QCheckBox("violin")
949
+ self.strip_check = QCheckBox("strip")
950
+ self.box_check = QCheckBox("boxplot")
951
+ self.boxenplot_check = QCheckBox("boxenplot")
952
+
953
+ self.sep_line = QHSeperationLine()
954
+ self.pvalue_check = QCheckBox("Compute KS test p-value?")
955
+ self.effect_size_check = QCheckBox("Compute effect size?\n(Cliff's Delta)")
956
+
957
+ layout.addWidget(self.hist_check)
958
+ layout.addWidget(self.kde_check)
959
+ layout.addWidget(self.count_check)
960
+ layout.addWidget(self.ecdf_check)
961
+ layout.addWidget(self.line_check)
962
+ layout.addWidget(self.scat_check)
963
+ layout.addWidget(self.swarm_check)
964
+ layout.addWidget(self.violin_check)
965
+ layout.addWidget(self.strip_check)
966
+ layout.addWidget(self.box_check)
967
+ layout.addWidget(self.boxenplot_check)
968
+ layout.addWidget(self.sep_line)
969
+ layout.addWidget(self.pvalue_check)
970
+ layout.addWidget(self.effect_size_check)
971
+
972
+ self.x_cb = QSearchableComboBox()
973
+ self.x_cb.addItems(["--"] + list(self.data.columns))
974
+
975
+ self.y_cb = QSearchableComboBox()
976
+ self.y_cb.addItems(["--"] + list(self.data.columns))
977
+
978
+ self.hue_cb = QSearchableComboBox()
979
+ self.hue_cb.addItems(["--"] + list(self.data.columns))
980
+ idx = self.hue_cb.findText("--")
981
+ self.hue_cb.setCurrentIndex(idx)
982
+
983
+ # Set selected column
984
+
985
+ try:
986
+ x = self.table_view.selectedIndexes()
987
+ col_idx = np.array([l.column() for l in x])
988
+ row_idx = np.array([l.row() for l in x])
989
+ column_names = self.data.columns
990
+ unique_cols = np.unique(col_idx)[0]
991
+ y = column_names[unique_cols]
992
+ idx = self.y_cb.findText(y)
993
+ self.y_cb.setCurrentIndex(idx)
994
+ except:
995
+ pass
996
+
997
+ hbox = QHBoxLayout()
998
+ hbox.addWidget(QLabel("x: "), 33)
999
+ hbox.addWidget(self.x_cb, 66)
1000
+ layout.addLayout(hbox)
1001
+
1002
+ hbox = QHBoxLayout()
1003
+ hbox.addWidget(QLabel("y: "), 33)
1004
+ hbox.addWidget(self.y_cb, 66)
1005
+ layout.addLayout(hbox)
1006
+
1007
+ hbox = QHBoxLayout()
1008
+ hbox.addWidget(QLabel("hue: "), 33)
1009
+ hbox.addWidget(self.hue_cb, 66)
1010
+ layout.addLayout(hbox)
1011
+
1012
+ from matplotlib import colormaps
1013
+ import matplotlib.cm
1014
+
1015
+ self.cmap_cb = QColormapComboBox()
1016
+ all_cms = list(colormaps)
1017
+ for cm in all_cms:
1018
+ if hasattr(matplotlib.cm, str(cm).lower()):
1019
+ try:
1020
+ self.cmap_cb.addColormap(cm.lower())
1021
+ except:
1022
+ pass
1023
+
1024
+ hbox = QHBoxLayout()
1025
+ hbox.addWidget(QLabel("colormap: "), 33)
1026
+ hbox.addWidget(self.cmap_cb, 66)
1027
+ layout.addLayout(hbox)
1028
+
1029
+ self.plot1d_btn = QPushButton("set")
1030
+ self.plot1d_btn.setStyleSheet(self.button_style_sheet)
1031
+ self.plot1d_btn.clicked.connect(self.plot1d)
1032
+ layout.addWidget(self.plot1d_btn)
1033
+
1034
+ self.plot1Dparams.show()
1035
+ center_window(self.plot1Dparams)
1036
+
1037
+ def plot1d(self):
1038
+
1039
+ self.x_option = False
1040
+ if self.x_cb.currentText() != "--":
1041
+ self.x_option = True
1042
+ self.x = self.x_cb.currentText()
1043
+
1044
+ import matplotlib.pyplot as plt
1045
+ import seaborn as sns
1046
+ import matplotlib.cm as mcm
1047
+
1048
+ self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
1049
+ self.plot1dWindow = FigureCanvas(self.fig, title="scatter")
1050
+ self.ax.clear()
1051
+
1052
+ cmap = getattr(mcm, self.cmap_cb.currentText())
1053
+
1054
+ try:
1055
+ self.hue_variable = self.hue_cb.currentText()
1056
+ colors = [
1057
+ cmap(i / len(self.data[self.hue_variable].unique()))
1058
+ for i in range(len(self.data[self.hue_variable].unique()))
1059
+ ]
1060
+ except:
1061
+ colors = None
1062
+
1063
+ if self.hue_cb.currentText() == "--":
1064
+ self.hue_variable = None
1065
+
1066
+ if self.y_cb.currentText() == "--":
1067
+ self.y = None
1068
+ else:
1069
+ self.y = self.y_cb.currentText()
1070
+
1071
+ if self.x_cb.currentText() == "--":
1072
+ self.x = None
1073
+ else:
1074
+ self.x = self.x_cb.currentText()
1075
+
1076
+ legend = True
1077
+
1078
+ if self.hist_check.isChecked():
1079
+ if self.x is not None:
1080
+ sns.histplot(
1081
+ data=self.data,
1082
+ x=self.x,
1083
+ hue=self.hue_variable,
1084
+ legend=legend,
1085
+ ax=self.ax,
1086
+ palette=colors,
1087
+ kde=True,
1088
+ common_norm=False,
1089
+ stat="density",
1090
+ )
1091
+ legend = False
1092
+ elif self.x is None and self.y is not None:
1093
+ sns.histplot(
1094
+ data=self.data,
1095
+ x=self.y,
1096
+ hue=self.hue_variable,
1097
+ legend=legend,
1098
+ ax=self.ax,
1099
+ palette=colors,
1100
+ kde=True,
1101
+ common_norm=False,
1102
+ stat="density",
1103
+ )
1104
+ legend = False
1105
+ else:
1106
+ pass
1107
+
1108
+ if self.kde_check.isChecked():
1109
+ if self.x is not None:
1110
+ sns.kdeplot(
1111
+ data=self.data,
1112
+ x=self.x,
1113
+ hue=self.hue_variable,
1114
+ legend=legend,
1115
+ ax=self.ax,
1116
+ palette=colors,
1117
+ cut=0,
1118
+ )
1119
+ legend = False
1120
+ elif self.x is None and self.y is not None:
1121
+ sns.kdeplot(
1122
+ data=self.data,
1123
+ x=self.y,
1124
+ hue=self.hue_variable,
1125
+ legend=legend,
1126
+ ax=self.ax,
1127
+ palette=colors,
1128
+ cut=0,
1129
+ )
1130
+ legend = False
1131
+ else:
1132
+ pass
1133
+
1134
+ if self.count_check.isChecked():
1135
+ sns.countplot(
1136
+ data=self.data,
1137
+ x=self.x,
1138
+ hue=self.hue_variable,
1139
+ legend=legend,
1140
+ ax=self.ax,
1141
+ palette=colors,
1142
+ )
1143
+ legend = False
1144
+
1145
+ if self.ecdf_check.isChecked():
1146
+ if self.x is not None:
1147
+ sns.ecdfplot(
1148
+ data=self.data,
1149
+ x=self.x,
1150
+ hue=self.hue_variable,
1151
+ legend=legend,
1152
+ ax=self.ax,
1153
+ palette=colors,
1154
+ )
1155
+ legend = False
1156
+ elif self.x is None and self.y is not None:
1157
+ sns.ecdfplot(
1158
+ data=self.data,
1159
+ x=self.y,
1160
+ hue=self.hue_variable,
1161
+ legend=legend,
1162
+ ax=self.ax,
1163
+ palette=colors,
1164
+ )
1165
+ legend = False
1166
+ else:
1167
+ pass
1168
+
1169
+ if self.line_check.isChecked():
1170
+ if self.x_option:
1171
+ sns.lineplot(
1172
+ data=self.data,
1173
+ x=self.x,
1174
+ y=self.y,
1175
+ hue=self.hue_variable,
1176
+ legend=legend,
1177
+ ax=self.ax,
1178
+ palette=colors,
1179
+ )
1180
+ legend = False
1181
+ else:
1182
+ print("please provide a -x variable...")
1183
+ pass
1184
+
1185
+ if self.scat_check.isChecked():
1186
+ if self.x_option:
1187
+ sns.scatterplot(
1188
+ data=self.data,
1189
+ x=self.x,
1190
+ y=self.y,
1191
+ hue=self.hue_variable,
1192
+ legend=legend,
1193
+ ax=self.ax,
1194
+ palette=colors,
1195
+ )
1196
+ legend = False
1197
+ else:
1198
+ print("please provide a -x variable...")
1199
+ pass
1200
+
1201
+ if self.swarm_check.isChecked():
1202
+ if self.x_option:
1203
+ sns.swarmplot(
1204
+ data=self.data,
1205
+ x=self.x,
1206
+ y=self.y,
1207
+ dodge=True,
1208
+ hue=self.hue_variable,
1209
+ legend=legend,
1210
+ ax=self.ax,
1211
+ palette=colors,
1212
+ )
1213
+ legend = False
1214
+ else:
1215
+ sns.swarmplot(
1216
+ data=self.data,
1217
+ y=self.y,
1218
+ dodge=True,
1219
+ hue=self.hue_variable,
1220
+ legend=legend,
1221
+ ax=self.ax,
1222
+ palette=colors,
1223
+ )
1224
+ legend = False
1225
+
1226
+ if self.violin_check.isChecked():
1227
+ if self.x_option:
1228
+ sns.violinplot(
1229
+ data=self.data,
1230
+ x=self.x,
1231
+ y=self.y,
1232
+ dodge=True,
1233
+ ax=self.ax,
1234
+ hue=self.hue_variable,
1235
+ legend=legend,
1236
+ palette=colors,
1237
+ )
1238
+ legend = False
1239
+ else:
1240
+ sns.violinplot(
1241
+ data=self.data,
1242
+ y=self.y,
1243
+ dodge=True,
1244
+ hue=self.hue_variable,
1245
+ legend=legend,
1246
+ ax=self.ax,
1247
+ palette=colors,
1248
+ cut=0,
1249
+ )
1250
+ legend = False
1251
+
1252
+ if self.box_check.isChecked():
1253
+ if self.x_option:
1254
+ sns.boxplot(
1255
+ data=self.data,
1256
+ x=self.x,
1257
+ y=self.y,
1258
+ dodge=True,
1259
+ hue=self.hue_variable,
1260
+ legend=legend,
1261
+ ax=self.ax,
1262
+ fill=False,
1263
+ palette=colors,
1264
+ linewidth=2,
1265
+ )
1266
+ legend = False
1267
+ else:
1268
+ sns.boxplot(
1269
+ data=self.data,
1270
+ y=self.y,
1271
+ dodge=True,
1272
+ hue=self.hue_variable,
1273
+ legend=legend,
1274
+ ax=self.ax,
1275
+ fill=False,
1276
+ palette=colors,
1277
+ linewidth=2,
1278
+ )
1279
+ legend = False
1280
+
1281
+ if self.boxenplot_check.isChecked():
1282
+ if self.x_option:
1283
+ sns.boxenplot(
1284
+ data=self.data,
1285
+ x=self.x,
1286
+ y=self.y,
1287
+ dodge=True,
1288
+ hue=self.hue_variable,
1289
+ legend=legend,
1290
+ ax=self.ax,
1291
+ fill=False,
1292
+ palette=colors,
1293
+ linewidth=2,
1294
+ )
1295
+ legend = False
1296
+ else:
1297
+ sns.boxenplot(
1298
+ data=self.data,
1299
+ y=self.y,
1300
+ dodge=True,
1301
+ hue=self.hue_variable,
1302
+ legend=legend,
1303
+ ax=self.ax,
1304
+ fill=False,
1305
+ palette=colors,
1306
+ linewidth=2,
1307
+ )
1308
+ legend = False
1309
+
1310
+ if self.strip_check.isChecked():
1311
+ if self.x_option:
1312
+ sns.stripplot(
1313
+ data=self.data,
1314
+ x=self.x,
1315
+ y=self.y,
1316
+ dodge=True,
1317
+ ax=self.ax,
1318
+ hue=self.hue_variable,
1319
+ legend=legend,
1320
+ palette=colors,
1321
+ )
1322
+ legend = False
1323
+ else:
1324
+ sns.stripplot(
1325
+ data=self.data,
1326
+ y=self.y,
1327
+ dodge=True,
1328
+ ax=self.ax,
1329
+ hue=self.hue_variable,
1330
+ legend=legend,
1331
+ palette=colors,
1332
+ )
1333
+ legend = False
1334
+
1335
+ plt.tight_layout()
1336
+ self.fig.set_facecolor("none") # or 'None'
1337
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
1338
+ self.plot1dWindow.canvas.draw()
1339
+ self.plot1dWindow.show()
1340
+
1341
+ if self.effect_size_check.isChecked():
1342
+ self.compute_effect_size()
1343
+ if self.pvalue_check.isChecked():
1344
+ self.compute_pvalue()
1345
+
1346
+ def extract_groupby_cols(self):
1347
+
1348
+ x = self.x
1349
+ y = self.y
1350
+ hue_variable = self.hue_variable
1351
+
1352
+ if (
1353
+ self.hist_check.isChecked()
1354
+ or self.ecdf_check.isChecked()
1355
+ or self.kde_check.isChecked()
1356
+ ):
1357
+ y = self.x
1358
+ x = None
1359
+
1360
+ groupby_cols = []
1361
+ if x is not None:
1362
+ groupby_cols.append(x)
1363
+ if hue_variable is not None:
1364
+ groupby_cols.append(hue_variable)
1365
+
1366
+ return groupby_cols, y
1367
+
1368
+ def compute_effect_size(self):
1369
+
1370
+ if self.count_check.isChecked() or self.scat_check.isChecked():
1371
+ print(
1372
+ "Please select a valid plot representation to compute effect size (histogram, boxplot, etc.)..."
1373
+ )
1374
+ return None
1375
+
1376
+ groupby_cols, y = self.extract_groupby_cols()
1377
+ pivot = test_2samp_generic(
1378
+ self.data, feature=y, groupby_cols=groupby_cols, method="cliffs_delta"
1379
+ )
1380
+ self.effect_size_table = PivotTableUI(
1381
+ pivot, title="Effect size (Cliff's Delta)", mode="cliff"
1382
+ )
1383
+ self.effect_size_table.show()
1384
+
1385
+ def compute_pvalue(self):
1386
+
1387
+ if self.count_check.isChecked() or self.scat_check.isChecked():
1388
+ print(
1389
+ "Please select a valid plot representation to compute effect size (histogram, boxplot, etc.)..."
1390
+ )
1391
+ return None
1392
+
1393
+ groupby_cols, y = self.extract_groupby_cols()
1394
+ pivot = test_2samp_generic(
1395
+ self.data, feature=y, groupby_cols=groupby_cols, method="ks_2samp"
1396
+ )
1397
+ self.pval_table = PivotTableUI(
1398
+ pivot, title="p-value (1-sided KS test)", mode="pvalue"
1399
+ )
1400
+ self.pval_table.show()
1401
+
1402
+ def set_proj_mode(self):
1403
+
1404
+ self.static_columns = [
1405
+ "well_index",
1406
+ "well_name",
1407
+ "pos_name",
1408
+ "position",
1409
+ "well",
1410
+ "status",
1411
+ "t0",
1412
+ "class",
1413
+ "cell_type",
1414
+ "concentration",
1415
+ "antibody",
1416
+ "pharmaceutical_agent",
1417
+ "TRACK_ID",
1418
+ "position",
1419
+ "neighbor_population",
1420
+ "reference_population",
1421
+ "NEIGHBOR_ID",
1422
+ "REFERENCE_ID",
1423
+ "FRAME",
1424
+ ]
1425
+
1426
+ if self.projection_option.isChecked():
1427
+
1428
+ self.projection_mode = self.projection_op_cb.currentText()
1429
+ op = getattr(
1430
+ self.current_data.groupby(self.groupby_cols), self.projection_mode
1431
+ )
1432
+ group_table = op(self.current_data.groupby(self.groupby_cols))
1433
+
1434
+ for c in self.static_columns:
1435
+ try:
1436
+ group_table[c] = self.current_data.groupby(self.groupby_cols)[
1437
+ c
1438
+ ].apply(lambda x: x.unique()[0])
1439
+ except Exception as e:
1440
+ print(e)
1441
+ pass
1442
+
1443
+ if self.population == "pairs":
1444
+ for col in reversed(
1445
+ self.groupby_cols
1446
+ ): # ['neighbor_population', 'reference_population', 'NEIGHBOR_ID', 'REFERENCE_ID']
1447
+ if col in group_table:
1448
+ first_column = group_table.pop(col)
1449
+ group_table.insert(0, col, first_column)
1450
+ else:
1451
+ for col in ["TRACK_ID"]:
1452
+ first_column = group_table.pop(col)
1453
+ group_table.insert(0, col, first_column)
1454
+ group_table.pop("FRAME")
1455
+
1456
+ elif self.event_time_option.isChecked():
1457
+
1458
+ time_of_interest = self.event_times_cb.currentText()
1459
+ self.projection_mode = f"measurements at {time_of_interest}"
1460
+ new_table = []
1461
+ for tid, group in self.current_data.groupby(self.groupby_cols):
1462
+ time = group[time_of_interest].values[0]
1463
+ if time == time:
1464
+ time = floor(time) # floor for onset
1465
+ else:
1466
+ continue
1467
+ frames = group["FRAME"].values
1468
+ values = group.loc[group["FRAME"] == time, :].to_numpy()
1469
+ if len(values) > 0:
1470
+ values = dict(zip(list(self.current_data.columns), values[0]))
1471
+ for k, c in enumerate(self.groupby_cols):
1472
+ values.update({c: tid[k]})
1473
+ new_table.append(values)
1474
+ import pandas as pd
1475
+
1476
+ group_table = pd.DataFrame(new_table)
1477
+ if self.population == "pairs":
1478
+ for col in self.groupby_cols[1:]:
1479
+ first_column = group_table.pop(col)
1480
+ group_table.insert(0, col, first_column)
1481
+ else:
1482
+ for col in ["TRACK_ID"]:
1483
+ first_column = group_table.pop(col)
1484
+ group_table.insert(0, col, first_column)
1485
+
1486
+ group_table = group_table.sort_values(
1487
+ by=self.groupby_cols + ["FRAME"], ignore_index=True
1488
+ )
1489
+ group_table = group_table.reset_index(drop=True)
1490
+
1491
+ elif self.per_status_option.isChecked():
1492
+ self.projection_mode = self.status_operation.currentText()
1493
+ group_table = collapse_trajectories_by_status(
1494
+ self.current_data,
1495
+ status=self.per_status_cb.currentText(),
1496
+ population=self.population,
1497
+ projection=self.status_operation.currentText(),
1498
+ groupby_columns=self.groupby_cols,
1499
+ )
1500
+
1501
+ self.subtable = TableUI(
1502
+ group_table,
1503
+ f"Group by tracks: {self.projection_mode}",
1504
+ plot_mode="static",
1505
+ collapse_tracks_option=False,
1506
+ )
1507
+ self.subtable.show()
1508
+
1509
+ self.projectionWidget.close()
1510
+
1511
+ # def groupby_track_table(self):
1512
+
1513
+ # """
1514
+
1515
+ # Perform a time average across each track for all features
1516
+
1517
+ # """
1518
+
1519
+ # self.subtable = TrajectoryTablePanel(self.data.groupby("TRACK_ID").mean(),"Group by tracks", plot_mode="scatter")
1520
+ # self.subtable.show()
1521
+
1522
+ def _createMenuBar(self):
1523
+ menuBar = self.menuBar()
1524
+ self.fileMenu = QMenu("&File", self)
1525
+ menuBar.addMenu(self.fileMenu)
1526
+ self.editMenu = QMenu("&Edit", self)
1527
+ menuBar.addMenu(self.editMenu)
1528
+ self.mathMenu = QMenu("&Math", self)
1529
+ menuBar.addMenu(self.mathMenu)
1530
+
1531
+ def save_as_csv(self):
1532
+ options = QFileDialog.Options()
1533
+ options |= QFileDialog.ReadOnly
1534
+ file_name, _ = QFileDialog.getSaveFileName(
1535
+ self,
1536
+ "Save as .csv",
1537
+ "",
1538
+ "CSV Files (*.csv);;All Files (*)",
1539
+ options=options,
1540
+ )
1541
+ if file_name:
1542
+ if not file_name.endswith(".csv"):
1543
+ file_name += ".csv"
1544
+ invalid_cols = [
1545
+ c for c in list(self.data.columns) if c.startswith("Unnamed")
1546
+ ]
1547
+ if len(invalid_cols) > 0:
1548
+ self.data = self.data.drop(invalid_cols, axis=1)
1549
+ self.data.to_csv(file_name, index=False)
1550
+
1551
+ def plot_instantaneous(self):
1552
+
1553
+ if self.plot_mode == "plot_track_signals":
1554
+ self.plot_mode = "static"
1555
+ self.plot()
1556
+ self.plot_mode = "plot_track_signals"
1557
+ elif self.plot_mode == "static":
1558
+ self.plot()
1559
+
1560
+ def plot(self):
1561
+ import matplotlib.pyplot as plt
1562
+
1563
+ if self.plot_mode == "static":
1564
+
1565
+ x = self.table_view.selectedIndexes()
1566
+ col_idx = [l.column() for l in x]
1567
+ row_idx = [l.row() for l in x]
1568
+ column_names = self.data.columns
1569
+ unique_cols = np.unique(col_idx)
1570
+
1571
+ if len(unique_cols) == 1 or len(unique_cols) == 0:
1572
+ self.set_1D_plot_params()
1573
+
1574
+ if len(unique_cols) == 2:
1575
+
1576
+ print("two columns, plot mode")
1577
+ x1 = test_bool_array(self.data.iloc[row_idx, unique_cols[0]])
1578
+ x2 = test_bool_array(self.data.iloc[row_idx, unique_cols[1]])
1579
+
1580
+ self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
1581
+ self.scatter_wdw = FigureCanvas(self.fig, title="scatter")
1582
+ self.ax.clear()
1583
+ self.ax.scatter(x1, x2)
1584
+ self.ax.set_xlabel(column_names[unique_cols[0]])
1585
+ self.ax.set_ylabel(column_names[unique_cols[1]])
1586
+ plt.tight_layout()
1587
+ self.fig.set_facecolor("none") # or 'None'
1588
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
1589
+ self.scatter_wdw.canvas.draw()
1590
+ self.scatter_wdw.show()
1591
+
1592
+ else:
1593
+ print("please select less columns")
1594
+
1595
+ elif self.plot_mode == "plot_timeseries":
1596
+ print("mode plot frames")
1597
+ x = self.table_view.selectedIndexes()
1598
+ col_idx = np.array([l.column() for l in x])
1599
+ row_idx = np.array([l.row() for l in x])
1600
+ column_names = self.data.columns
1601
+ unique_cols = np.unique(col_idx)
1602
+
1603
+ self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
1604
+ self.plot_wdw = FigureCanvas(self.fig, title="scatter")
1605
+ self.ax.clear()
1606
+ for k in range(len(unique_cols)):
1607
+ row_idx_i = row_idx[np.where(col_idx == unique_cols[k])[0]]
1608
+ y = self.data.iloc[row_idx_i, unique_cols[k]]
1609
+ self.ax.plot(
1610
+ self.data["timeline"][row_idx_i],
1611
+ y,
1612
+ label=column_names[unique_cols[k]],
1613
+ )
1614
+
1615
+ self.ax.legend()
1616
+ self.ax.set_xlabel("time [frame]")
1617
+ self.ax.set_ylabel(self.title)
1618
+ plt.tight_layout()
1619
+ self.fig.set_facecolor("none") # or 'None'
1620
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
1621
+ self.plot_wdw.canvas.draw()
1622
+ plt.show()
1623
+
1624
+ elif self.plot_mode == "plot_track_signals":
1625
+
1626
+ print("mode plot track signals")
1627
+ print("we plot here")
1628
+
1629
+ x = self.table_view.selectedIndexes()
1630
+ col_idx = np.array([l.column() for l in x])
1631
+ row_idx = np.array([l.row() for l in x])
1632
+ column_names = self.data.columns
1633
+ unique_cols = np.unique(col_idx)
1634
+
1635
+ if len(unique_cols) > 2:
1636
+ fig, ax = plt.subplots(1, 1, figsize=(7, 5.5))
1637
+ for k in range(len(unique_cols)):
1638
+
1639
+ row_idx_i = row_idx[np.where(col_idx == unique_cols[k])[0]]
1640
+ y = self.data.iloc[row_idx_i, unique_cols[k]]
1641
+ print(unique_cols[k])
1642
+ for w, well_group in self.data.groupby(["well_name"]):
1643
+ for pos, pos_group in well_group.groupby(["pos_name"]):
1644
+ for tid, group_track in pos_group.groupby(
1645
+ self.groupby_cols[1:]
1646
+ ):
1647
+ ax.plot(
1648
+ group_track["FRAME"],
1649
+ group_track[column_names[unique_cols[k]]],
1650
+ label=column_names[unique_cols[k]],
1651
+ )
1652
+ # ax.plot(self.data["FRAME"][row_idx_i], y, label=column_names[unique_cols[k]])
1653
+ ax.legend()
1654
+ ax.set_xlabel("time [frame]")
1655
+ ax.set_ylabel(self.title)
1656
+ plt.tight_layout()
1657
+ plt.show(block=False)
1658
+
1659
+ if len(unique_cols) == 2:
1660
+
1661
+ self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
1662
+ self.scatter_wdw = FigureCanvas(self.fig, title="scatter")
1663
+ self.ax.clear()
1664
+ for tid, group in self.data.groupby(self.groupby_cols[1:]):
1665
+ self.ax.plot(
1666
+ group[column_names[unique_cols[0]]],
1667
+ group[column_names[unique_cols[1]]],
1668
+ marker="o",
1669
+ )
1670
+ self.ax.set_xlabel(column_names[unique_cols[0]])
1671
+ self.ax.set_ylabel(column_names[unique_cols[1]])
1672
+ plt.tight_layout()
1673
+ self.fig.set_facecolor("none") # or 'None'
1674
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
1675
+ self.scatter_wdw.canvas.draw()
1676
+ self.scatter_wdw.show()
1677
+
1678
+ if len(unique_cols) == 1:
1679
+
1680
+ self.fig, self.ax = plt.subplots(1, 1, figsize=(4, 3))
1681
+ self.plot_wdw = FigureCanvas(self.fig, title="scatter")
1682
+ self.ax.clear()
1683
+
1684
+ # if 't0' in list(self.data.columns):
1685
+ # ref_time_col = 't0'
1686
+ # else:
1687
+ # ref_time_col = 'FRAME'
1688
+
1689
+ for w, well_group in self.data.groupby(["well_name"]):
1690
+ for pos, pos_group in well_group.groupby(["pos_name"]):
1691
+ for tid, group_track in pos_group.groupby(
1692
+ self.groupby_cols[1:]
1693
+ ):
1694
+ self.ax.plot(
1695
+ group_track["FRAME"],
1696
+ group_track[column_names[unique_cols[0]]],
1697
+ c="k",
1698
+ alpha=0.1,
1699
+ )
1700
+ self.ax.set_xlabel(r"$t$ [frame]")
1701
+ self.ax.set_ylabel(column_names[unique_cols[0]])
1702
+ plt.tight_layout()
1703
+ self.fig.set_facecolor("none") # or 'None'
1704
+ self.fig.canvas.setStyleSheet("background-color: transparent;")
1705
+ self.plot_wdw.canvas.draw()
1706
+ self.plot_wdw.show()