celldetective 1.4.2__py3-none-any.whl → 1.5.0b1__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 (152) 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 +403 -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/downloader.py +137 -0
  81. celldetective/processes/measure_cells.py +565 -0
  82. celldetective/processes/segment_cells.py +760 -0
  83. celldetective/processes/track_cells.py +435 -0
  84. celldetective/processes/train_segmentation_model.py +694 -0
  85. celldetective/processes/train_signal_model.py +265 -0
  86. celldetective/processes/unified_process.py +292 -0
  87. celldetective/regionprops/_regionprops.py +358 -317
  88. celldetective/relative_measurements.py +987 -710
  89. celldetective/scripts/measure_cells.py +313 -212
  90. celldetective/scripts/measure_relative.py +90 -46
  91. celldetective/scripts/segment_cells.py +165 -104
  92. celldetective/scripts/segment_cells_thresholds.py +96 -68
  93. celldetective/scripts/track_cells.py +198 -149
  94. celldetective/scripts/train_segmentation_model.py +324 -201
  95. celldetective/scripts/train_signal_model.py +87 -45
  96. celldetective/segmentation.py +844 -749
  97. celldetective/signals.py +3514 -2861
  98. celldetective/tracking.py +30 -15
  99. celldetective/utils/__init__.py +0 -0
  100. celldetective/utils/cellpose_utils/__init__.py +133 -0
  101. celldetective/utils/color_mappings.py +42 -0
  102. celldetective/utils/data_cleaning.py +630 -0
  103. celldetective/utils/data_loaders.py +450 -0
  104. celldetective/utils/dataset_helpers.py +207 -0
  105. celldetective/utils/downloaders.py +235 -0
  106. celldetective/utils/event_detection/__init__.py +8 -0
  107. celldetective/utils/experiment.py +1782 -0
  108. celldetective/utils/image_augmenters.py +308 -0
  109. celldetective/utils/image_cleaning.py +74 -0
  110. celldetective/utils/image_loaders.py +926 -0
  111. celldetective/utils/image_transforms.py +335 -0
  112. celldetective/utils/io.py +62 -0
  113. celldetective/utils/mask_cleaning.py +348 -0
  114. celldetective/utils/mask_transforms.py +5 -0
  115. celldetective/utils/masks.py +184 -0
  116. celldetective/utils/maths.py +351 -0
  117. celldetective/utils/model_getters.py +325 -0
  118. celldetective/utils/model_loaders.py +296 -0
  119. celldetective/utils/normalization.py +380 -0
  120. celldetective/utils/parsing.py +465 -0
  121. celldetective/utils/plots/__init__.py +0 -0
  122. celldetective/utils/plots/regression.py +53 -0
  123. celldetective/utils/resources.py +34 -0
  124. celldetective/utils/stardist_utils/__init__.py +104 -0
  125. celldetective/utils/stats.py +90 -0
  126. celldetective/utils/types.py +21 -0
  127. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/METADATA +1 -1
  128. celldetective-1.5.0b1.dist-info/RECORD +187 -0
  129. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/WHEEL +1 -1
  130. tests/gui/test_new_project.py +129 -117
  131. tests/gui/test_project.py +127 -79
  132. tests/test_filters.py +39 -15
  133. tests/test_notebooks.py +8 -0
  134. tests/test_tracking.py +232 -13
  135. tests/test_utils.py +123 -77
  136. celldetective/gui/base_components.py +0 -23
  137. celldetective/gui/layouts.py +0 -1602
  138. celldetective/gui/processes/compute_neighborhood.py +0 -594
  139. celldetective/gui/processes/downloader.py +0 -111
  140. celldetective/gui/processes/measure_cells.py +0 -360
  141. celldetective/gui/processes/segment_cells.py +0 -499
  142. celldetective/gui/processes/track_cells.py +0 -303
  143. celldetective/gui/processes/train_segmentation_model.py +0 -270
  144. celldetective/gui/processes/train_signal_model.py +0 -108
  145. celldetective/gui/table_ops/merge_groups.py +0 -118
  146. celldetective/gui/viewers.py +0 -1354
  147. celldetective/io.py +0 -3663
  148. celldetective/utils.py +0 -3108
  149. celldetective-1.4.2.dist-info/RECORD +0 -123
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/entry_points.txt +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/licenses/LICENSE +0 -0
  152. {celldetective-1.4.2.dist-info → celldetective-1.5.0b1.dist-info}/top_level.txt +0 -0
@@ -1,22 +1,35 @@
1
- from PyQt5.QtWidgets import QComboBox, \
2
- QCheckBox, QLineEdit, QVBoxLayout, QLabel, QHBoxLayout, QPushButton
1
+ from PyQt5.QtWidgets import (
2
+ QComboBox,
3
+ QCheckBox,
4
+ QLineEdit,
5
+ QVBoxLayout,
6
+ QLabel,
7
+ QHBoxLayout,
8
+ QPushButton,
9
+ )
3
10
  from PyQt5.QtCore import Qt
4
11
  from PyQt5.QtGui import QDoubleValidator
5
12
 
6
- from celldetective.gui.gui_utils import center_window, generic_message
13
+ from celldetective.gui.base.components import generic_message
14
+ from celldetective.gui.base.utils import center_window
7
15
  from celldetective.gui.generic_signal_plot import GenericSignalPlotWidget
8
16
  from superqt import QLabeledSlider, QColormapComboBox, QSearchableComboBox
9
- from celldetective.utils import get_software_location, _extract_labels_from_config, extract_cols_from_table_list
10
- from celldetective.io import load_experiment_tables
17
+ from celldetective import (
18
+ get_software_location,
19
+ )
20
+ from celldetective.utils.data_cleaning import extract_cols_from_table_list
21
+ from celldetective.utils.parsing import _extract_labels_from_config
22
+ from celldetective.utils.data_loaders import load_experiment_tables
11
23
  from celldetective.signals import mean_signal
12
24
  import numpy as np
13
25
  import os
14
26
  import matplotlib.pyplot as plt
15
- plt.rcParams['svg.fonttype'] = 'none'
27
+
28
+ plt.rcParams["svg.fonttype"] = "none"
16
29
  from glob import glob
17
30
  from natsort import natsorted
18
31
  import math
19
- from celldetective.gui import CelldetectiveWidget
32
+ from celldetective.gui.base.components import CelldetectiveWidget
20
33
  from matplotlib import colormaps
21
34
  import matplotlib.cm
22
35
  from celldetective.relative_measurements import expand_pair_table
@@ -24,504 +37,709 @@ from celldetective.neighborhood import extract_neighborhood_in_pair_table
24
37
 
25
38
 
26
39
  class ConfigSignalPlot(CelldetectiveWidget):
27
-
28
- """
29
- UI to set survival instructions.
30
-
31
- """
32
-
33
- def __init__(self, parent_window=None):
34
-
35
- super().__init__()
36
- self.parent_window = parent_window
37
- self.setWindowTitle("Configure signal plot")
38
- self.exp_dir = self.parent_window.exp_dir
39
- self.soft_path = get_software_location()
40
- self.exp_config = self.exp_dir +"config.ini"
41
- self.wells = np.array(self.parent_window.parent_window.wells,dtype=str)
42
- self.well_labels = _extract_labels_from_config(self.exp_config,len(self.wells))
43
- self.FrameToMin = self.parent_window.parent_window.FrameToMin
44
- self.float_validator = QDoubleValidator()
45
- self.target_class = [0,1]
46
- self.show_ci = True
47
- self.show_cell_lines = False
48
- self.ax2=None
49
- self.auto_close = False
50
-
51
- self.well_option = self.parent_window.parent_window.well_list.getSelectedIndices()
52
- self.position_option = self.parent_window.parent_window.position_list.getSelectedIndices()
53
- self.interpret_pos_location()
54
-
55
- self.screen_height = self.parent_window.parent_window.parent_window.screen_height
56
- center_window(self)
57
- self.populate_widget()
58
-
59
- if self.auto_close:
60
- self.close()
61
-
62
- def interpret_pos_location(self):
63
-
64
- """
65
- Read the well/position selection from the control panel to decide which data to load
66
- Set position_indices to None if all positions must be taken
67
-
68
- """
69
-
70
- self.well_indices = self.parent_window.parent_window.well_list.getSelectedIndices()
71
- self.position_indices = self.parent_window.parent_window.position_list.getSelectedIndices()
72
- if not self.parent_window.parent_window.position_list.isAnySelected():
73
- self.position_indices = None
74
-
75
-
76
- def populate_widget(self):
77
-
78
- """
79
- Create the multibox design.
80
-
81
- """
82
-
83
- # Create button widget and layout
84
- main_layout = QVBoxLayout()
85
- self.setLayout(main_layout)
86
- main_layout.setContentsMargins(30,30,30,30)
87
- panel_title = QLabel('Options')
88
- panel_title.setStyleSheet("""
40
+ """
41
+ UI to set survival instructions.
42
+
43
+ """
44
+
45
+ def __init__(self, parent_window=None):
46
+
47
+ super().__init__()
48
+ self.parent_window = parent_window
49
+ self.setWindowTitle("Configure signal plot")
50
+ self.exp_dir = self.parent_window.exp_dir
51
+ self.soft_path = get_software_location()
52
+ self.exp_config = self.exp_dir + "config.ini"
53
+ self.wells = np.array(self.parent_window.parent_window.wells, dtype=str)
54
+ self.well_labels = _extract_labels_from_config(self.exp_config, len(self.wells))
55
+ self.FrameToMin = self.parent_window.parent_window.FrameToMin
56
+ self.float_validator = QDoubleValidator()
57
+ self.target_class = [0, 1]
58
+ self.show_ci = True
59
+ self.show_cell_lines = False
60
+ self.ax2 = None
61
+ self.auto_close = False
62
+
63
+ self.well_option = (
64
+ self.parent_window.parent_window.well_list.getSelectedIndices()
65
+ )
66
+ self.position_option = (
67
+ self.parent_window.parent_window.position_list.getSelectedIndices()
68
+ )
69
+ self.interpret_pos_location()
70
+
71
+ self.screen_height = (
72
+ self.parent_window.parent_window.parent_window.screen_height
73
+ )
74
+ self.populate_widget()
75
+
76
+ if self.auto_close:
77
+ self.close()
78
+
79
+ def interpret_pos_location(self):
80
+ """
81
+ Read the well/position selection from the control panel to decide which data to load
82
+ Set position_indices to None if all positions must be taken
83
+
84
+ """
85
+
86
+ self.well_indices = (
87
+ self.parent_window.parent_window.well_list.getSelectedIndices()
88
+ )
89
+ self.position_indices = (
90
+ self.parent_window.parent_window.position_list.getSelectedIndices()
91
+ )
92
+ if not self.parent_window.parent_window.position_list.isAnySelected():
93
+ self.position_indices = None
94
+
95
+ def populate_widget(self):
96
+ """
97
+ Create the multibox design.
98
+
99
+ """
100
+
101
+ # Create button widget and layout
102
+ main_layout = QVBoxLayout()
103
+ self.setLayout(main_layout)
104
+ main_layout.setContentsMargins(30, 30, 30, 30)
105
+ panel_title = QLabel("Options")
106
+ panel_title.setStyleSheet(
107
+ """
89
108
  font-weight: bold;
90
109
  padding: 0px;
91
- """)
92
- main_layout.addWidget(panel_title, alignment=Qt.AlignCenter)
93
-
94
- pops = []
95
- self.cols_per_pop = {}
96
- for population in self.parent_window.parent_window.populations:
97
- tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{population}.csv']))
98
- if len(tables)>0:
99
- pops.append(population)
100
- cols = extract_cols_from_table_list(tables)
101
-
102
- # check for neighbor pairs
103
- neigh_cols = [c for c in cols if c.startswith('inclusive_count_neighborhood')]
104
- neigh_pairs = [c.split('_(')[-1].split(')_')[0].split('-') for c in neigh_cols]
105
- neigh_pairs = ['-'.join(c) for c in neigh_pairs]
106
- for k in range(len(neigh_pairs)):
107
- if "_self_" in neigh_pairs[k]:
108
- neigh_pairs[k] = '-'.join([population, population])
109
- pops.extend(neigh_pairs)
110
-
111
- self.cols_per_pop.update({population: cols})
112
-
113
- # pops = []
114
- # for population in self.parent_window.parent_window.populations+['pairs']:
115
- # tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{population}.csv']))
116
- # if len(tables)>0:
117
- # pops.append(population)
118
-
119
-
120
- labels = [QLabel('population: '), QLabel('class: '), QLabel('time of\ninterest: '), QLabel('cmap: ')]
121
- self.cb_options = [pops,[], [], []]
122
- self.cbs = [QComboBox() for i in range(len(labels))]
123
- self.cbs[-1] = QColormapComboBox()
124
-
125
- self.cbs[0].currentIndexChanged.connect(self.set_classes_and_times)
126
-
127
- choice_layout = QVBoxLayout()
128
- choice_layout.setContentsMargins(20,20,20,20)
129
- for i in range(len(labels)):
130
- hbox = QHBoxLayout()
131
- hbox.addWidget(labels[i], 33)
132
- hbox.addWidget(self.cbs[i],66)
133
- if i < len(labels)-1:
134
- self.cbs[i].addItems(self.cb_options[i])
135
- choice_layout.addLayout(hbox)
136
-
137
- all_cms = list(colormaps)
138
- for cm in all_cms:
139
- if hasattr(matplotlib.cm, str(cm).lower()):
140
- try:
141
- self.cbs[-1].addColormap(cm.lower())
142
- except:
143
- pass
144
-
145
- self.cbs[0].setCurrentIndex(1)
146
- self.cbs[0].setCurrentIndex(0)
147
-
148
- self.abs_time_checkbox = QCheckBox('absolute time')
149
- self.frame_slider = QLabeledSlider()
150
- self.frame_slider.setSingleStep(1)
151
- self.frame_slider.setOrientation(Qt.Horizontal)
152
- self.frame_slider.setRange(0,self.parent_window.parent_window.len_movie)
153
- self.frame_slider.setValue(0)
154
- self.frame_slider.setEnabled(False)
155
- slider_hbox = QHBoxLayout()
156
- slider_hbox.addWidget(self.abs_time_checkbox, 33)
157
- slider_hbox.addWidget(self.frame_slider, 66)
158
- choice_layout.addLayout(slider_hbox)
159
- main_layout.addLayout(choice_layout)
160
-
161
- self.abs_time_checkbox.stateChanged.connect(self.switch_ref_time_mode)
162
-
163
- select_layout = QHBoxLayout()
164
- select_layout.setContentsMargins(20,3,20,3)
165
- select_layout.addWidget(QLabel('select cells\nwith query: '), 33)
166
- self.query_le = QLineEdit()
167
- select_layout.addWidget(self.query_le, 66)
168
- main_layout.addLayout(select_layout)
169
-
170
- time_calib_layout = QHBoxLayout()
171
- time_calib_layout.setContentsMargins(20,3,20,3)
172
- time_calib_layout.addWidget(QLabel('time calibration\n(frame to min)'), 33)
173
- self.time_calibration_le = QLineEdit(str(self.FrameToMin).replace('.',','))
174
- self.time_calibration_le.setValidator(self.float_validator)
175
- time_calib_layout.addWidget(self.time_calibration_le, 66)
176
- #time_calib_layout.addWidget(QLabel(' min'))
177
- main_layout.addLayout(time_calib_layout)
178
-
179
- pool_layout = QHBoxLayout()
180
- pool_layout.setContentsMargins(20,3,20,3)
181
- self.pool_option_cb = QComboBox()
182
- self.pool_option_cb.addItems(['mean','median'])
183
- pool_layout.addWidget(QLabel('pool\nprojection:'), 33)
184
- pool_layout.addWidget(self.pool_option_cb, 66)
185
- main_layout.addLayout(pool_layout)
186
-
187
- n_cells_layout = QHBoxLayout()
188
- n_cells_layout.setContentsMargins(20,3,20,3)
189
- self.n_cells_slider = QLabeledSlider()
190
- self.n_cells_slider.setSingleStep(1)
191
- self.n_cells_slider.setOrientation(Qt.Horizontal)
192
- self.n_cells_slider.setRange(1,100)
193
- self.n_cells_slider.setValue(2)
194
- n_cells_layout.addWidget(QLabel('min # cells\nfor pool:'), 33)
195
- n_cells_layout.addWidget(self.n_cells_slider, 66)
196
- main_layout.addLayout(n_cells_layout)
197
-
198
-
199
- self.submit_btn = QPushButton('Submit')
200
- self.submit_btn.setStyleSheet(self.button_style_sheet)
201
- self.submit_btn.clicked.connect(self.process_signal)
202
- main_layout.addWidget(self.submit_btn)
203
-
204
- #self.populate_left_panel()
205
- #grid.addLayout(self.left_side, 0, 0, 1, 1)
206
-
207
- # self.setCentralWidget(self.scroll_area)
208
- # self.show()
209
-
210
- def set_classes_and_times(self):
211
-
212
- # Look for all classes and times
213
- self.neighborhood_keys = None
214
- self.population = self.cbs[0].currentText()
215
- pop_split = self.population.split('-')
216
-
217
- if len(pop_split)==2:
218
-
219
- self.population = 'pairs'
220
- tables_pairs = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_pairs.csv']))
221
- if not tables_pairs:
222
- print('No pair table found... please compute the pair measurements...')
223
- return None
224
- self.cols_pairs = extract_cols_from_table_list(tables_pairs)
225
-
226
- self.population_reference = pop_split[0]
227
- self.population_neigh = pop_split[1]
228
-
229
- cols_ref = self.cols_per_pop[self.population_reference]
230
- cols_neigh = self.cols_per_pop[self.population_neigh]
231
-
232
- time_cols_ref = np.array([s.startswith('t_') or s=='t0' for s in cols_ref])
233
- if len(time_cols_ref)>0:
234
- time_cols_ref = list(cols_ref[time_cols_ref])
235
- time_cols_ref = ['reference_'+t for t in time_cols_ref]
236
-
237
- time_cols_neigh = np.array([s.startswith('t_') or s=='t0' for s in cols_neigh])
238
- if len(time_cols_neigh)>0:
239
- time_cols_neigh = list(cols_neigh[time_cols_neigh])
240
- time_cols_neigh = ['neighbor_'+t for t in time_cols_neigh]
241
-
242
- if self.population_reference!=self.population_neigh:
243
- self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and str(self.population_neigh) in c]
244
- else:
245
- self.neighborhood_keys = [c[16:] for c in cols_ref if c.startswith('inclusive_count_neighborhood') and str(self.population_neigh) not in c]
246
-
247
- time_idx = np.array([s.startswith('t_') or s.startswith('t0') for s in self.cols_pairs])
248
- time_cols_pairs = list(self.cols_pairs[time_idx])
249
- time_columns = time_cols_ref + time_cols_neigh + time_cols_pairs
250
-
251
- class_cols_ref = [c.replace('reference_t_','reference_class_') for c in time_cols_ref]
252
- class_cols_neigh = [c.replace('neighbor_t_','neighbor_class_') for c in time_cols_neigh]
253
- class_cols_pairs = [c.replace('t_','class_') for c in time_cols_neigh if c.startswith('t_')]
254
- class_columns = class_cols_ref + class_cols_neigh + class_cols_pairs
255
- else:
256
- tables = natsorted(glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{self.population}.csv'])))
257
- self.all_columns = extract_cols_from_table_list(tables)
258
-
259
- class_idx = np.array([s.startswith('class_') for s in self.all_columns])
260
- time_idx = np.array([s.startswith('t_') or s.startswith('t0_') for s in self.all_columns])
261
- print(f'{class_idx=} {time_idx=} {self.all_columns=}')
262
-
263
- try:
264
- if len(class_idx)>0:
265
- class_columns = list(self.all_columns[class_idx])
266
- else:
267
- class_columns = []
268
- if len(time_idx)>0:
269
- time_columns = list(self.all_columns[time_idx])
270
- else:
271
- time_columns = []
272
- except Exception as e:
273
- print(f'L266 columns not found {e}')
274
- self.auto_close = True
275
- return None
276
-
277
- if 'class' in self.all_columns:
278
- class_columns.append("class")
279
- if 't0' in self.all_columns:
280
- time_columns.append('t0')
281
-
282
- self.class_columns = np.unique(class_columns)
283
- self.time_columns = np.unique(time_columns)
284
- thresh = 30
285
- self.class_truncated = [w[:thresh - 3]+'...' if len(w)>thresh else w for w in self.class_columns]
286
- self.time_truncated = [w[:thresh - 3]+'...' if len(w)>thresh else w for w in self.time_columns]
287
-
288
- self.cbs[2].clear()
289
- self.cbs[2].addItems(self.time_truncated)
290
- for i in range(len(self.time_columns)):
291
- self.cbs[2].setItemData(i, self.time_columns[i], Qt.ToolTipRole)
292
-
293
- self.cbs[1].clear()
294
- self.cbs[1].addItems(self.class_truncated)
295
- for i in range(len(self.class_columns)):
296
- self.cbs[1].setItemData(i, self.class_columns[i], Qt.ToolTipRole)
297
-
298
- def ask_for_feature(self):
299
-
300
- cols = np.array(list(self.df.columns))
301
- is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
302
- feats = cols[is_number(self.df.dtypes)]
303
-
304
- self.feature_choice_widget = CelldetectiveWidget()
305
- self.feature_choice_widget.setWindowTitle("Select numeric feature")
306
- layout = QVBoxLayout()
307
- self.feature_choice_widget.setLayout(layout)
308
- self.feature_cb = QComboBox()
309
- self.feature_cb.addItems(feats)
310
- hbox = QHBoxLayout()
311
- hbox.addWidget(QLabel('feature: '), 33)
312
- hbox.addWidget(self.feature_cb, 66)
313
- layout.addLayout(hbox)
314
-
315
- self.set_feature_btn = QPushButton('set')
316
- self.set_feature_btn.clicked.connect(self.compute_signals)
317
- layout.addWidget(self.set_feature_btn)
318
- self.feature_choice_widget.show()
319
- center_window(self.feature_choice_widget)
320
-
321
- def ask_for_features(self):
322
-
323
- cols = np.array(list(self.df.columns))
324
- is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
325
- feats = cols[is_number(self.df.dtypes)]
326
-
327
- self.feature_choice_widget = CelldetectiveWidget()
328
- self.feature_choice_widget.setWindowTitle("Select numeric feature")
329
- layout = QVBoxLayout()
330
- self.feature_choice_widget.setLayout(layout)
331
- self.feature_cb = QSearchableComboBox()
332
- self.feature_cb.addItems(feats)
333
- hbox = QHBoxLayout()
334
- hbox.addWidget(QLabel('feature: '), 33)
335
- hbox.addWidget(self.feature_cb, 66)
336
- #hbox.addWidget((QLabel('Plot two features')))
337
- layout.addLayout(hbox)
338
-
339
- self.set_feature_btn = QPushButton('set')
340
- self.set_feature_btn.setStyleSheet(self.button_style_sheet)
341
- self.set_feature_btn.clicked.connect(self.compute_signals)
342
- layout.addWidget(self.set_feature_btn)
343
- self.feature_choice_widget.show()
344
- center_window(self.feature_choice_widget)
345
-
346
- # def enable_second_feature(self):
347
- # if self.checkBox_feature.isChecked():
348
- # self.feature_two_cb.setEnabled(True)
349
- # else:
350
- # self.feature_two_cb.setEnabled(False)
351
-
352
- def compute_signals(self):
353
-
354
- if self.df is not None:
355
-
356
- try:
357
- query_text = self.query_le.text()
358
- if query_text != '':
359
- self.df = self.df.query(query_text)
360
- except Exception as e:
361
- print(e, ' The query is misunderstood and will not be applied...')
362
-
363
- self.feature_selected = self.feature_cb.currentText()
364
- self.feature_choice_widget.close()
365
- self.compute_signal_functions()
366
- if self.open_widget:
367
- self.interpret_pos_location()
368
- try:
369
- self.plot_window = GenericSignalPlotWidget(parent_window=self, df=self.df, df_pos_info = self.df_pos_info, df_well_info = self.df_well_info, feature_selected=self.feature_selected, title='plot signals')
370
- self.plot_window.show()
371
- except Exception as e:
372
- print(f"{e=}")
373
-
374
- def process_signal(self):
375
-
376
- self.FrameToMin = float(self.time_calibration_le.text().replace(',','.'))
377
- print(f'Time calibration set to 1 frame = {self.FrameToMin} min...')
378
-
379
- # read instructions from combobox options
380
- self.load_available_tables()
381
- class_col = self.class_columns[self.cbs[1].currentIndex()]
382
- print(f"{class_col=}")
383
-
384
- if self.df is not None:
385
-
386
- if class_col not in list(self.df.columns):
387
- generic_message("The class of interest could not be found in the data. Abort.")
388
- return None
389
- else:
390
- self.ask_for_features()
391
- else:
392
- return None
393
-
394
- #self.plotvbox.addWidget(self.line_choice_widget, alignment=Qt.AlignCenter)
395
-
396
- def load_available_tables(self):
397
-
398
- """
399
- Load the tables of the selected wells/positions from the control Panel for the population of interest
400
-
401
- """
402
-
403
- self.well_option = self.parent_window.parent_window.well_list.getSelectedIndices()
404
- self.position_option = self.parent_window.parent_window.position_list.getSelectedIndices()
405
-
406
- self.df, self.df_pos_info = load_experiment_tables(self.exp_dir, well_option=self.well_option, position_option=self.position_option, population=self.population, return_pos_info=True)
407
-
408
- if self.population=='pairs':
409
- self.df = expand_pair_table(self.df)
410
- self.df = extract_neighborhood_in_pair_table(self.df, reference_population=self.population_reference, neighbor_population=self.population_neigh, neighborhood_key=self.neighborhood_keys[0], contact_only=True)
411
-
412
- if self.df is None:
413
- print('No table could be found...')
414
- generic_message("No table could be found to compute survival...")
415
- self.close()
416
- return None
417
- else:
418
- self.df_well_info = self.df_pos_info.loc[:,['well_path', 'well_index', 'well_name', 'well_number', 'well_alias']].drop_duplicates()
419
-
420
- def compute_signal_functions(self):
421
-
422
- # Check to move at the beginning
423
- self.open_widget = True
424
- if len(self.time_columns)==0:
425
- generic_message("No synchronizing time is available...")
426
- self.open_widget = False
427
- return None
428
-
429
- # Per position signal
430
- self.df = self.df.dropna(subset=['FRAME'])
431
- if len(self.df)==0:
432
- print('Warning... The dataset is empty. Please check your filters. Abort...')
433
- return None
434
-
435
- pairs=False
436
- if self.population=='pairs':
437
- pairs=True
438
-
439
- max_time = int(self.df.FRAME.max()) + 1
440
- class_col = self.class_columns[self.cbs[1].currentIndex()]
441
- time_col = self.time_columns[self.cbs[2].currentIndex()]
442
- if self.abs_time_checkbox.isChecked():
443
- time_col = self.frame_slider.value()
444
-
445
- for block,movie_group in self.df.groupby(['well','position']):
446
-
447
- well_signal_mean, well_std_mean, timeline_all, matrix_all = mean_signal(movie_group, self.feature_selected, class_col, time_col=time_col, class_value=None, return_matrix=True, forced_max_duration=max_time, projection=self.pool_option_cb.currentText(), min_nbr_values=self.n_cells_slider.value(),pairs=pairs)
448
- well_signal_event, well_std_event, timeline_event, matrix_event = mean_signal(movie_group, self.feature_selected, class_col, time_col=time_col, class_value=[0], return_matrix=True, forced_max_duration=max_time, projection=self.pool_option_cb.currentText(), min_nbr_values=self.n_cells_slider.value(),pairs=pairs)
449
- well_signal_no_event, well_std_no_event, timeline_no_event, matrix_no_event = mean_signal(movie_group, self.feature_selected, class_col, time_col=time_col, class_value=[1], return_matrix=True, forced_max_duration=max_time, projection=self.pool_option_cb.currentText(), min_nbr_values=self.n_cells_slider.value(),pairs=pairs)
450
- self.mean_plots_timeline = timeline_all
451
-
452
- self.df_pos_info.loc[self.df_pos_info['pos_path'] == block[1], 'signal'] = [
453
- {'mean_all': well_signal_mean, 'std_all': well_std_mean, 'matrix_all': matrix_all,
454
- 'mean_event': well_signal_event, 'std_event': well_std_event,
455
- 'matrix_event': matrix_event, 'mean_no_event': well_signal_no_event,
456
- 'std_no_event': well_std_no_event, 'matrix_no_event': matrix_no_event,
457
- 'timeline': self.mean_plots_timeline}]
458
-
459
- # Per well
460
- for well,well_group in self.df.groupby('well'):
461
-
462
- well_signal_mean, well_std_mean, timeline_all, matrix_all = mean_signal(well_group, self.feature_selected, class_col, time_col=time_col, class_value=None, return_matrix=True, forced_max_duration=max_time, projection=self.pool_option_cb.currentText(), min_nbr_values=self.n_cells_slider.value(),pairs=pairs)
463
- well_signal_event, well_std_event, timeline_event, matrix_event = mean_signal(well_group, self.feature_selected, class_col, time_col=time_col, class_value=[0], return_matrix=True, forced_max_duration=max_time, projection=self.pool_option_cb.currentText(), min_nbr_values=self.n_cells_slider.value(),pairs=pairs)
464
- well_signal_no_event, well_std_no_event, timeline_no_event, matrix_no_event = mean_signal(well_group, self.feature_selected, class_col, time_col=time_col, class_value=[1], return_matrix=True, forced_max_duration=max_time, projection=self.pool_option_cb.currentText(), min_nbr_values=self.n_cells_slider.value(),pairs=pairs)
465
-
466
- self.df_well_info.loc[self.df_well_info['well_path']==well,'signal'] = [{'mean_all': well_signal_mean, 'std_all': well_std_mean,'matrix_all': matrix_all,'mean_event': well_signal_event, 'std_event': well_std_event,
467
- 'matrix_event': matrix_event,'mean_no_event': well_signal_no_event, 'std_no_event': well_std_no_event, 'matrix_no_event': matrix_no_event, 'timeline': self.mean_plots_timeline}]
468
-
469
- self.df_pos_info.loc[:,'select'] = True
470
- self.df_well_info.loc[:,'select'] = True
471
-
472
-
473
- def generate_synchronized_matrix(self, well_group, feature_selected, cclass, max_time):
474
-
475
- if isinstance(cclass,int):
476
- cclass = [cclass]
477
-
478
- class_col = self.class_columns[self.cbs[1].currentIndex()]
479
- time_col = self.time_columns[self.cbs[2].currentIndex()]
480
-
481
- n_cells = len(well_group.groupby(['position','TRACK_ID']))
482
- depth = int(2*max_time + 3)
483
- matrix = np.zeros((n_cells, depth))
484
- matrix[:,:] = np.nan
485
- mapping = np.arange(-max_time-1, max_time+2)
486
- cid=0
487
- for block,movie_group in well_group.groupby('position'):
488
- for tid,track_group in movie_group.loc[movie_group[class_col].isin(cclass)].groupby('TRACK_ID'):
489
- try:
490
- timeline = track_group['FRAME'].to_numpy().astype(int)
491
- feature = track_group[feature_selected].to_numpy()
492
- if self.checkBox_feature.isChecked():
493
- second_feature=track_group[self.second_feature_selected].to_numpy()
494
- if self.cbs[2].currentText().startswith('t') and not self.abs_time_checkbox.isChecked():
495
- t0 = math.floor(track_group[time_col].to_numpy()[0])
496
- timeline -= t0
497
- elif self.cbs[2].currentText()=='first detection' and not self.abs_time_checkbox.isChecked():
498
-
499
- if 'area' in list(track_group.columns):
500
- print('area in list')
501
- feat = track_group['area'].values
502
- else:
503
- feat = feature
504
-
505
- first_detection = timeline[feat==feat][0]
506
- timeline -= first_detection
507
-
508
- elif self.abs_time_checkbox.isChecked():
509
- timeline -= int(self.frame_slider.value())
510
-
511
- loc_t = [np.where(mapping==t)[0][0] for t in timeline]
512
- matrix[cid,loc_t] = feature
513
- if second_feature:
514
- matrix[cid,loc_t+1]=second_feature
515
-
516
- cid+=1
517
- except:
518
- pass
519
- return matrix
520
-
521
- def switch_ref_time_mode(self):
522
- if self.abs_time_checkbox.isChecked():
523
- self.frame_slider.setEnabled(True)
524
- self.cbs[-2].setEnabled(False)
525
- else:
526
- self.frame_slider.setEnabled(False)
527
- self.cbs[-2].setEnabled(True)
110
+ """
111
+ )
112
+ main_layout.addWidget(panel_title, alignment=Qt.AlignCenter)
113
+
114
+ pops = []
115
+ self.cols_per_pop = {}
116
+ for population in self.parent_window.parent_window.populations:
117
+ tables = glob(
118
+ self.exp_dir
119
+ + os.sep.join(
120
+ ["W*", "*", "output", "tables", f"trajectories_{population}.csv"]
121
+ )
122
+ )
123
+ if len(tables) > 0:
124
+ pops.append(population)
125
+ cols = extract_cols_from_table_list(tables)
126
+
127
+ # check for neighbor pairs
128
+ neigh_cols = [
129
+ c for c in cols if c.startswith("inclusive_count_neighborhood")
130
+ ]
131
+ neigh_pairs = [
132
+ c.split("_(")[-1].split(")_")[0].split("-") for c in neigh_cols
133
+ ]
134
+ neigh_pairs = ["-".join(c) for c in neigh_pairs]
135
+ for k in range(len(neigh_pairs)):
136
+ if "_self_" in neigh_pairs[k]:
137
+ neigh_pairs[k] = "-".join([population, population])
138
+ pops.extend(neigh_pairs)
139
+
140
+ self.cols_per_pop.update({population: cols})
141
+
142
+ # pops = []
143
+ # for population in self.parent_window.parent_window.populations+['pairs']:
144
+ # tables = glob(self.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{population}.csv']))
145
+ # if len(tables)>0:
146
+ # pops.append(population)
147
+
148
+ labels = [
149
+ QLabel("population: "),
150
+ QLabel("class: "),
151
+ QLabel("time of\ninterest: "),
152
+ QLabel("cmap: "),
153
+ ]
154
+ self.cb_options = [pops, [], [], []]
155
+ self.cbs = [QComboBox() for i in range(len(labels))]
156
+ self.cbs[-1] = QColormapComboBox()
157
+
158
+ self.cbs[0].currentIndexChanged.connect(self.set_classes_and_times)
159
+
160
+ choice_layout = QVBoxLayout()
161
+ choice_layout.setContentsMargins(20, 20, 20, 20)
162
+ for i in range(len(labels)):
163
+ hbox = QHBoxLayout()
164
+ hbox.addWidget(labels[i], 33)
165
+ hbox.addWidget(self.cbs[i], 66)
166
+ if i < len(labels) - 1:
167
+ self.cbs[i].addItems(self.cb_options[i])
168
+ choice_layout.addLayout(hbox)
169
+
170
+ all_cms = list(colormaps)
171
+ for cm in all_cms:
172
+ if hasattr(matplotlib.cm, str(cm).lower()):
173
+ try:
174
+ self.cbs[-1].addColormap(cm.lower())
175
+ except:
176
+ pass
177
+
178
+ self.cbs[0].setCurrentIndex(1)
179
+ self.cbs[0].setCurrentIndex(0)
180
+
181
+ self.abs_time_checkbox = QCheckBox("absolute time")
182
+ self.frame_slider = QLabeledSlider()
183
+ self.frame_slider.setSingleStep(1)
184
+ self.frame_slider.setOrientation(Qt.Horizontal)
185
+ self.frame_slider.setRange(0, self.parent_window.parent_window.len_movie)
186
+ self.frame_slider.setValue(0)
187
+ self.frame_slider.setEnabled(False)
188
+ slider_hbox = QHBoxLayout()
189
+ slider_hbox.addWidget(self.abs_time_checkbox, 33)
190
+ slider_hbox.addWidget(self.frame_slider, 66)
191
+ choice_layout.addLayout(slider_hbox)
192
+ main_layout.addLayout(choice_layout)
193
+
194
+ self.abs_time_checkbox.stateChanged.connect(self.switch_ref_time_mode)
195
+
196
+ select_layout = QHBoxLayout()
197
+ select_layout.setContentsMargins(20, 3, 20, 3)
198
+ select_layout.addWidget(QLabel("select cells\nwith query: "), 33)
199
+ self.query_le = QLineEdit()
200
+ select_layout.addWidget(self.query_le, 66)
201
+ main_layout.addLayout(select_layout)
202
+
203
+ time_calib_layout = QHBoxLayout()
204
+ time_calib_layout.setContentsMargins(20, 3, 20, 3)
205
+ time_calib_layout.addWidget(QLabel("time calibration\n(frame to min)"), 33)
206
+ self.time_calibration_le = QLineEdit(str(self.FrameToMin).replace(".", ","))
207
+ self.time_calibration_le.setValidator(self.float_validator)
208
+ time_calib_layout.addWidget(self.time_calibration_le, 66)
209
+ # time_calib_layout.addWidget(QLabel(' min'))
210
+ main_layout.addLayout(time_calib_layout)
211
+
212
+ pool_layout = QHBoxLayout()
213
+ pool_layout.setContentsMargins(20, 3, 20, 3)
214
+ self.pool_option_cb = QComboBox()
215
+ self.pool_option_cb.addItems(["mean", "median"])
216
+ pool_layout.addWidget(QLabel("pool\nprojection:"), 33)
217
+ pool_layout.addWidget(self.pool_option_cb, 66)
218
+ main_layout.addLayout(pool_layout)
219
+
220
+ n_cells_layout = QHBoxLayout()
221
+ n_cells_layout.setContentsMargins(20, 3, 20, 3)
222
+ self.n_cells_slider = QLabeledSlider()
223
+ self.n_cells_slider.setSingleStep(1)
224
+ self.n_cells_slider.setOrientation(Qt.Horizontal)
225
+ self.n_cells_slider.setRange(1, 100)
226
+ self.n_cells_slider.setValue(2)
227
+ n_cells_layout.addWidget(QLabel("min # cells\nfor pool:"), 33)
228
+ n_cells_layout.addWidget(self.n_cells_slider, 66)
229
+ main_layout.addLayout(n_cells_layout)
230
+
231
+ self.submit_btn = QPushButton("Submit")
232
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
233
+ self.submit_btn.clicked.connect(self.process_signal)
234
+ main_layout.addWidget(self.submit_btn)
235
+
236
+ # self.populate_left_panel()
237
+ # grid.addLayout(self.left_side, 0, 0, 1, 1)
238
+
239
+ # self.setCentralWidget(self.scroll_area)
240
+ # self.show()
241
+
242
+ def set_classes_and_times(self):
243
+
244
+ # Look for all classes and times
245
+ self.neighborhood_keys = None
246
+ self.population = self.cbs[0].currentText()
247
+ pop_split = self.population.split("-")
248
+
249
+ if len(pop_split) == 2:
250
+
251
+ self.population = "pairs"
252
+ tables_pairs = glob(
253
+ self.exp_dir
254
+ + os.sep.join(
255
+ ["W*", "*", "output", "tables", f"trajectories_pairs.csv"]
256
+ )
257
+ )
258
+ if not tables_pairs:
259
+ print("No pair table found... please compute the pair measurements...")
260
+ return None
261
+ self.cols_pairs = extract_cols_from_table_list(tables_pairs)
262
+
263
+ self.population_reference = pop_split[0]
264
+ self.population_neigh = pop_split[1]
265
+
266
+ cols_ref = self.cols_per_pop[self.population_reference]
267
+ cols_neigh = self.cols_per_pop[self.population_neigh]
268
+
269
+ time_cols_ref = np.array(
270
+ [s.startswith("t_") or s == "t0" for s in cols_ref]
271
+ )
272
+ if len(time_cols_ref) > 0:
273
+ time_cols_ref = list(cols_ref[time_cols_ref])
274
+ time_cols_ref = ["reference_" + t for t in time_cols_ref]
275
+
276
+ time_cols_neigh = np.array(
277
+ [s.startswith("t_") or s == "t0" for s in cols_neigh]
278
+ )
279
+ if len(time_cols_neigh) > 0:
280
+ time_cols_neigh = list(cols_neigh[time_cols_neigh])
281
+ time_cols_neigh = ["neighbor_" + t for t in time_cols_neigh]
282
+
283
+ if self.population_reference != self.population_neigh:
284
+ self.neighborhood_keys = [
285
+ c[16:]
286
+ for c in cols_ref
287
+ if c.startswith("inclusive_count_neighborhood")
288
+ and str(self.population_neigh) in c
289
+ ]
290
+ else:
291
+ self.neighborhood_keys = [
292
+ c[16:]
293
+ for c in cols_ref
294
+ if c.startswith("inclusive_count_neighborhood")
295
+ and str(self.population_neigh) not in c
296
+ ]
297
+
298
+ time_idx = np.array(
299
+ [s.startswith("t_") or s.startswith("t0") for s in self.cols_pairs]
300
+ )
301
+ time_cols_pairs = list(self.cols_pairs[time_idx])
302
+ time_columns = time_cols_ref + time_cols_neigh + time_cols_pairs
303
+
304
+ class_cols_ref = [
305
+ c.replace("reference_t_", "reference_class_") for c in time_cols_ref
306
+ ]
307
+ class_cols_neigh = [
308
+ c.replace("neighbor_t_", "neighbor_class_") for c in time_cols_neigh
309
+ ]
310
+ class_cols_pairs = [
311
+ c.replace("t_", "class_") for c in time_cols_neigh if c.startswith("t_")
312
+ ]
313
+ class_columns = class_cols_ref + class_cols_neigh + class_cols_pairs
314
+ else:
315
+ tables = natsorted(
316
+ glob(
317
+ self.exp_dir
318
+ + os.sep.join(
319
+ [
320
+ "W*",
321
+ "*",
322
+ "output",
323
+ "tables",
324
+ f"trajectories_{self.population}.csv",
325
+ ]
326
+ )
327
+ )
328
+ )
329
+ self.all_columns = extract_cols_from_table_list(tables)
330
+
331
+ class_idx = np.array([s.startswith("class_") for s in self.all_columns])
332
+ time_idx = np.array(
333
+ [s.startswith("t_") or s.startswith("t0_") for s in self.all_columns]
334
+ )
335
+ print(f"{class_idx=} {time_idx=} {self.all_columns=}")
336
+
337
+ try:
338
+ if len(class_idx) > 0:
339
+ class_columns = list(self.all_columns[class_idx])
340
+ else:
341
+ class_columns = []
342
+ if len(time_idx) > 0:
343
+ time_columns = list(self.all_columns[time_idx])
344
+ else:
345
+ time_columns = []
346
+ except Exception as e:
347
+ print(f"L266 columns not found {e}")
348
+ self.auto_close = True
349
+ return None
350
+
351
+ if "class" in self.all_columns:
352
+ class_columns.append("class")
353
+ if "t0" in self.all_columns:
354
+ time_columns.append("t0")
355
+
356
+ self.class_columns = np.unique(class_columns)
357
+ self.time_columns = np.unique(time_columns)
358
+ thresh = 30
359
+ self.class_truncated = [
360
+ w[: thresh - 3] + "..." if len(w) > thresh else w
361
+ for w in self.class_columns
362
+ ]
363
+ self.time_truncated = [
364
+ w[: thresh - 3] + "..." if len(w) > thresh else w for w in self.time_columns
365
+ ]
366
+
367
+ self.cbs[2].clear()
368
+ self.cbs[2].addItems(self.time_truncated)
369
+ for i in range(len(self.time_columns)):
370
+ self.cbs[2].setItemData(i, self.time_columns[i], Qt.ToolTipRole)
371
+
372
+ self.cbs[1].clear()
373
+ self.cbs[1].addItems(self.class_truncated)
374
+ for i in range(len(self.class_columns)):
375
+ self.cbs[1].setItemData(i, self.class_columns[i], Qt.ToolTipRole)
376
+
377
+ def ask_for_feature(self):
378
+
379
+ cols = np.array(list(self.df.columns))
380
+ is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
381
+ feats = cols[is_number(self.df.dtypes)]
382
+
383
+ self.feature_choice_widget = CelldetectiveWidget()
384
+ self.feature_choice_widget.setWindowTitle("Select numeric feature")
385
+ layout = QVBoxLayout()
386
+ self.feature_choice_widget.setLayout(layout)
387
+ self.feature_cb = QComboBox()
388
+ self.feature_cb.addItems(feats)
389
+ hbox = QHBoxLayout()
390
+ hbox.addWidget(QLabel("feature: "), 33)
391
+ hbox.addWidget(self.feature_cb, 66)
392
+ layout.addLayout(hbox)
393
+
394
+ self.set_feature_btn = QPushButton("set")
395
+ self.set_feature_btn.clicked.connect(self.compute_signals)
396
+ layout.addWidget(self.set_feature_btn)
397
+ self.feature_choice_widget.show()
398
+ center_window(self.feature_choice_widget)
399
+
400
+ def ask_for_features(self):
401
+
402
+ cols = np.array(list(self.df.columns))
403
+ is_number = np.vectorize(lambda x: np.issubdtype(x, np.number))
404
+ feats = cols[is_number(self.df.dtypes)]
405
+
406
+ self.feature_choice_widget = CelldetectiveWidget()
407
+ self.feature_choice_widget.setWindowTitle("Select numeric feature")
408
+ layout = QVBoxLayout()
409
+ self.feature_choice_widget.setLayout(layout)
410
+ self.feature_cb = QSearchableComboBox()
411
+ self.feature_cb.addItems(feats)
412
+ hbox = QHBoxLayout()
413
+ hbox.addWidget(QLabel("feature: "), 33)
414
+ hbox.addWidget(self.feature_cb, 66)
415
+ # hbox.addWidget((QLabel('Plot two features')))
416
+ layout.addLayout(hbox)
417
+
418
+ self.set_feature_btn = QPushButton("set")
419
+ self.set_feature_btn.setStyleSheet(self.button_style_sheet)
420
+ self.set_feature_btn.clicked.connect(self.compute_signals)
421
+ layout.addWidget(self.set_feature_btn)
422
+ self.feature_choice_widget.show()
423
+ center_window(self.feature_choice_widget)
424
+
425
+ # def enable_second_feature(self):
426
+ # if self.checkBox_feature.isChecked():
427
+ # self.feature_two_cb.setEnabled(True)
428
+ # else:
429
+ # self.feature_two_cb.setEnabled(False)
430
+
431
+ def compute_signals(self):
432
+
433
+ if self.df is not None:
434
+
435
+ try:
436
+ query_text = self.query_le.text()
437
+ if query_text != "":
438
+ self.df = self.df.query(query_text)
439
+ except Exception as e:
440
+ print(e, " The query is misunderstood and will not be applied...")
441
+
442
+ self.feature_selected = self.feature_cb.currentText()
443
+ self.feature_choice_widget.close()
444
+ self.compute_signal_functions()
445
+ if self.open_widget:
446
+ self.interpret_pos_location()
447
+ try:
448
+ self.plot_window = GenericSignalPlotWidget(
449
+ parent_window=self,
450
+ df=self.df,
451
+ df_pos_info=self.df_pos_info,
452
+ df_well_info=self.df_well_info,
453
+ feature_selected=self.feature_selected,
454
+ title="plot signals",
455
+ )
456
+ self.plot_window.show()
457
+ except Exception as e:
458
+ print(f"{e=}")
459
+
460
+ def process_signal(self):
461
+
462
+ self.FrameToMin = float(self.time_calibration_le.text().replace(",", "."))
463
+ print(f"Time calibration set to 1 frame = {self.FrameToMin} min...")
464
+
465
+ # read instructions from combobox options
466
+ self.load_available_tables()
467
+ class_col = self.class_columns[self.cbs[1].currentIndex()]
468
+ print(f"{class_col=}")
469
+
470
+ if self.df is not None:
471
+
472
+ if class_col not in list(self.df.columns):
473
+ generic_message(
474
+ "The class of interest could not be found in the data. Abort."
475
+ )
476
+ return None
477
+ else:
478
+ self.ask_for_features()
479
+ else:
480
+ return None
481
+
482
+ # self.plotvbox.addWidget(self.line_choice_widget, alignment=Qt.AlignCenter)
483
+
484
+ def load_available_tables(self):
485
+ """
486
+ Load the tables of the selected wells/positions from the control Panel for the population of interest
487
+
488
+ """
489
+
490
+ self.well_option = (
491
+ self.parent_window.parent_window.well_list.getSelectedIndices()
492
+ )
493
+ self.position_option = (
494
+ self.parent_window.parent_window.position_list.getSelectedIndices()
495
+ )
496
+
497
+ self.df, self.df_pos_info = load_experiment_tables(
498
+ self.exp_dir,
499
+ well_option=self.well_option,
500
+ position_option=self.position_option,
501
+ population=self.population,
502
+ return_pos_info=True,
503
+ )
504
+
505
+ if self.population == "pairs":
506
+ self.df = expand_pair_table(self.df)
507
+ self.df = extract_neighborhood_in_pair_table(
508
+ self.df,
509
+ reference_population=self.population_reference,
510
+ neighbor_population=self.population_neigh,
511
+ neighborhood_key=self.neighborhood_keys[0],
512
+ contact_only=True,
513
+ )
514
+
515
+ if self.df is None:
516
+ print("No table could be found...")
517
+ generic_message("No table could be found to compute survival...")
518
+ self.close()
519
+ return None
520
+ else:
521
+ self.df_well_info = self.df_pos_info.loc[
522
+ :, ["well_path", "well_index", "well_name", "well_number", "well_alias"]
523
+ ].drop_duplicates()
524
+
525
+ def compute_signal_functions(self):
526
+
527
+ # Check to move at the beginning
528
+ self.open_widget = True
529
+ if len(self.time_columns) == 0:
530
+ generic_message("No synchronizing time is available...")
531
+ self.open_widget = False
532
+ return None
533
+
534
+ # Per position signal
535
+ self.df = self.df.dropna(subset=["FRAME"])
536
+ if len(self.df) == 0:
537
+ print(
538
+ "Warning... The dataset is empty. Please check your filters. Abort..."
539
+ )
540
+ return None
541
+
542
+ pairs = False
543
+ if self.population == "pairs":
544
+ pairs = True
545
+
546
+ max_time = int(self.df.FRAME.max()) + 1
547
+ class_col = self.class_columns[self.cbs[1].currentIndex()]
548
+ time_col = self.time_columns[self.cbs[2].currentIndex()]
549
+ if self.abs_time_checkbox.isChecked():
550
+ time_col = self.frame_slider.value()
551
+
552
+ for block, movie_group in self.df.groupby(["well", "position"]):
553
+
554
+ well_signal_mean, well_std_mean, timeline_all, matrix_all = mean_signal(
555
+ movie_group,
556
+ self.feature_selected,
557
+ class_col,
558
+ time_col=time_col,
559
+ class_value=None,
560
+ return_matrix=True,
561
+ forced_max_duration=max_time,
562
+ projection=self.pool_option_cb.currentText(),
563
+ min_nbr_values=self.n_cells_slider.value(),
564
+ pairs=pairs,
565
+ )
566
+ well_signal_event, well_std_event, timeline_event, matrix_event = (
567
+ mean_signal(
568
+ movie_group,
569
+ self.feature_selected,
570
+ class_col,
571
+ time_col=time_col,
572
+ class_value=[0],
573
+ return_matrix=True,
574
+ forced_max_duration=max_time,
575
+ projection=self.pool_option_cb.currentText(),
576
+ min_nbr_values=self.n_cells_slider.value(),
577
+ pairs=pairs,
578
+ )
579
+ )
580
+ (
581
+ well_signal_no_event,
582
+ well_std_no_event,
583
+ timeline_no_event,
584
+ matrix_no_event,
585
+ ) = mean_signal(
586
+ movie_group,
587
+ self.feature_selected,
588
+ class_col,
589
+ time_col=time_col,
590
+ class_value=[1],
591
+ return_matrix=True,
592
+ forced_max_duration=max_time,
593
+ projection=self.pool_option_cb.currentText(),
594
+ min_nbr_values=self.n_cells_slider.value(),
595
+ pairs=pairs,
596
+ )
597
+ self.mean_plots_timeline = timeline_all
598
+
599
+ self.df_pos_info.loc[self.df_pos_info["pos_path"] == block[1], "signal"] = [
600
+ {
601
+ "mean_all": well_signal_mean,
602
+ "std_all": well_std_mean,
603
+ "matrix_all": matrix_all,
604
+ "mean_event": well_signal_event,
605
+ "std_event": well_std_event,
606
+ "matrix_event": matrix_event,
607
+ "mean_no_event": well_signal_no_event,
608
+ "std_no_event": well_std_no_event,
609
+ "matrix_no_event": matrix_no_event,
610
+ "timeline": self.mean_plots_timeline,
611
+ }
612
+ ]
613
+
614
+ # Per well
615
+ for well, well_group in self.df.groupby("well"):
616
+
617
+ well_signal_mean, well_std_mean, timeline_all, matrix_all = mean_signal(
618
+ well_group,
619
+ self.feature_selected,
620
+ class_col,
621
+ time_col=time_col,
622
+ class_value=None,
623
+ return_matrix=True,
624
+ forced_max_duration=max_time,
625
+ projection=self.pool_option_cb.currentText(),
626
+ min_nbr_values=self.n_cells_slider.value(),
627
+ pairs=pairs,
628
+ )
629
+ well_signal_event, well_std_event, timeline_event, matrix_event = (
630
+ mean_signal(
631
+ well_group,
632
+ self.feature_selected,
633
+ class_col,
634
+ time_col=time_col,
635
+ class_value=[0],
636
+ return_matrix=True,
637
+ forced_max_duration=max_time,
638
+ projection=self.pool_option_cb.currentText(),
639
+ min_nbr_values=self.n_cells_slider.value(),
640
+ pairs=pairs,
641
+ )
642
+ )
643
+ (
644
+ well_signal_no_event,
645
+ well_std_no_event,
646
+ timeline_no_event,
647
+ matrix_no_event,
648
+ ) = mean_signal(
649
+ well_group,
650
+ self.feature_selected,
651
+ class_col,
652
+ time_col=time_col,
653
+ class_value=[1],
654
+ return_matrix=True,
655
+ forced_max_duration=max_time,
656
+ projection=self.pool_option_cb.currentText(),
657
+ min_nbr_values=self.n_cells_slider.value(),
658
+ pairs=pairs,
659
+ )
660
+
661
+ self.df_well_info.loc[self.df_well_info["well_path"] == well, "signal"] = [
662
+ {
663
+ "mean_all": well_signal_mean,
664
+ "std_all": well_std_mean,
665
+ "matrix_all": matrix_all,
666
+ "mean_event": well_signal_event,
667
+ "std_event": well_std_event,
668
+ "matrix_event": matrix_event,
669
+ "mean_no_event": well_signal_no_event,
670
+ "std_no_event": well_std_no_event,
671
+ "matrix_no_event": matrix_no_event,
672
+ "timeline": self.mean_plots_timeline,
673
+ }
674
+ ]
675
+
676
+ self.df_pos_info.loc[:, "select"] = True
677
+ self.df_well_info.loc[:, "select"] = True
678
+
679
+ def generate_synchronized_matrix(
680
+ self, well_group, feature_selected, cclass, max_time
681
+ ):
682
+
683
+ if isinstance(cclass, int):
684
+ cclass = [cclass]
685
+
686
+ class_col = self.class_columns[self.cbs[1].currentIndex()]
687
+ time_col = self.time_columns[self.cbs[2].currentIndex()]
688
+
689
+ n_cells = len(well_group.groupby(["position", "TRACK_ID"]))
690
+ depth = int(2 * max_time + 3)
691
+ matrix = np.zeros((n_cells, depth))
692
+ matrix[:, :] = np.nan
693
+ mapping = np.arange(-max_time - 1, max_time + 2)
694
+ cid = 0
695
+ for block, movie_group in well_group.groupby("position"):
696
+ for tid, track_group in movie_group.loc[
697
+ movie_group[class_col].isin(cclass)
698
+ ].groupby("TRACK_ID"):
699
+ try:
700
+ timeline = track_group["FRAME"].to_numpy().astype(int)
701
+ feature = track_group[feature_selected].to_numpy()
702
+ if self.checkBox_feature.isChecked():
703
+ second_feature = track_group[
704
+ self.second_feature_selected
705
+ ].to_numpy()
706
+ if (
707
+ self.cbs[2].currentText().startswith("t")
708
+ and not self.abs_time_checkbox.isChecked()
709
+ ):
710
+ t0 = math.floor(track_group[time_col].to_numpy()[0])
711
+ timeline -= t0
712
+ elif (
713
+ self.cbs[2].currentText() == "first detection"
714
+ and not self.abs_time_checkbox.isChecked()
715
+ ):
716
+
717
+ if "area" in list(track_group.columns):
718
+ print("area in list")
719
+ feat = track_group["area"].values
720
+ else:
721
+ feat = feature
722
+
723
+ first_detection = timeline[feat == feat][0]
724
+ timeline -= first_detection
725
+
726
+ elif self.abs_time_checkbox.isChecked():
727
+ timeline -= int(self.frame_slider.value())
728
+
729
+ loc_t = [np.where(mapping == t)[0][0] for t in timeline]
730
+ matrix[cid, loc_t] = feature
731
+ if second_feature:
732
+ matrix[cid, loc_t + 1] = second_feature
733
+
734
+ cid += 1
735
+ except:
736
+ pass
737
+ return matrix
738
+
739
+ def switch_ref_time_mode(self):
740
+ if self.abs_time_checkbox.isChecked():
741
+ self.frame_slider.setEnabled(True)
742
+ self.cbs[-2].setEnabled(False)
743
+ else:
744
+ self.frame_slider.setEnabled(False)
745
+ self.cbs[-2].setEnabled(True)