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,6 +1,17 @@
1
- from PyQt5.QtWidgets import QApplication, QComboBox, QFrame, QCheckBox, QVBoxLayout, QLabel, QHBoxLayout, QPushButton
1
+ from PyQt5.QtWidgets import (
2
+ QApplication,
3
+ QComboBox,
4
+ QFrame,
5
+ QCheckBox,
6
+ QVBoxLayout,
7
+ QLabel,
8
+ QHBoxLayout,
9
+ QPushButton,
10
+ )
2
11
  from PyQt5.QtCore import Qt, QSize
3
- from celldetective.gui.gui_utils import center_window, ListWidget, DistanceChoice
12
+ from celldetective.gui.gui_utils import DistanceChoice
13
+ from celldetective.gui.base.list_widget import ListWidget
14
+ from celldetective.gui.base.utils import center_window
4
15
  from superqt.fonticon import icon
5
16
  from fonticon_mdi6 import MDI6
6
17
  import numpy as np
@@ -8,492 +19,567 @@ import json
8
19
  import os
9
20
  from glob import glob
10
21
  import pandas as pd
11
- from celldetective.gui.viewers import CellSizeViewer, CellEdgeVisualizer
12
- from celldetective.gui import CelldetectiveWidget
22
+ from celldetective.gui.viewers.contour_viewer import CellEdgeVisualizer
23
+ from celldetective.gui.viewers.size_viewer import CellSizeViewer
24
+
25
+ from celldetective.gui.base.components import CelldetectiveWidget
13
26
 
14
27
 
15
28
  class SettingsNeighborhood(CelldetectiveWidget):
16
-
17
- """
18
- Widget to configure neighborhood measurements.
19
-
20
- """
21
-
22
- def __init__(self, neighborhood_type='distance_threshold',neighborhood_parameter_name='threshold distance', parent_window=None, *args, **kwargs):
23
-
24
- super().__init__(*args, **kwargs)
25
- self.parent_window = parent_window
26
- self.attr_parent = self.parent_window.parent_window
27
-
28
- self.neighborhood_type = neighborhood_type
29
- self.neighborhood_parameter_name = neighborhood_parameter_name
30
-
31
- self.setWindowTitle('Configure neighborhoods')
32
- self.neigh_instructions = self.attr_parent.exp_dir + os.sep.join(["configs","neighborhood_instructions.json"])
33
- self.clear_previous = False
34
- self.not_status_reference = False
35
- self.not_status_neighbor = False
36
-
37
- self.screen_height = self.attr_parent.screen_height
38
- self.setMinimumWidth(750)
39
- self.setMinimumHeight(int(0.5*self.screen_height))
40
- self.setMaximumHeight(int(0.95*self.screen_height))
41
-
42
- self.generate_main_layout()
43
- self.load_previous_neighborhood_instructions()
44
- center_window(self)
45
-
46
- def generate_main_layout(self):
47
-
48
- main_layout = QVBoxLayout(self)
49
- main_layout.setContentsMargins(30,30,30,30)
50
-
51
- populations_layout = QHBoxLayout()
52
-
53
- # Reference population
54
- self.reference_population_frame = QFrame()
55
- self.reference_population_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
56
- self.populate_reference_frame()
57
- populations_layout.addWidget(self.reference_population_frame, 50)
58
-
59
- # Neighbor population
60
- self.neigh_population_frame = QFrame()
61
- self.neigh_population_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
62
- self.populate_neighbor_frame()
63
- populations_layout.addWidget(self.neigh_population_frame, 50)
64
- main_layout.addLayout(populations_layout)
65
-
66
- # Measurements
67
-
68
- self.measurement_frame = QFrame()
69
- self.measurement_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
70
- self.populate_measurement_frame()
71
- # if self.neighborhood_type=='distance_threshold':
72
- # self.populate_radii_frame()
73
- # elif self.neighborhood_type=='mask_contact':
74
- # self.populate_contact_frame()
75
- main_layout.addWidget(self.measurement_frame)
76
-
77
- self.clear_previous_btn = QCheckBox('clear previous neighborhoods')
78
- self.clear_previous_btn.setToolTip('Clear all previous neighborhood measurements.')
79
- self.clear_previous_btn.setIcon(icon(MDI6.broom, color='black'))
80
-
81
- main_layout.addWidget(self.clear_previous_btn, alignment=Qt.AlignRight)
82
-
83
- main_layout.addWidget(QLabel(''))
84
- self.submit_btn = QPushButton('Set')
85
- self.submit_btn.setStyleSheet(self.button_style_sheet)
86
- self.submit_btn.clicked.connect(self.write_instructions)
87
- main_layout.addWidget(self.submit_btn)
88
-
89
- self.adjustSize()
90
- QApplication.processEvents()
91
-
92
- def populate_measurement_frame(self):
93
-
94
- """
95
- Add widgets and layout in the radii frame.
96
- """
97
-
98
- grid = QVBoxLayout(self.measurement_frame)
99
-
100
- self.dist_lbl = QLabel(f"NEIGHBORHOOD {self.neighborhood_parameter_name.upper()}")
101
- self.dist_lbl.setStyleSheet(self.block_title)
102
- grid.addWidget(self.dist_lbl, alignment=Qt.AlignCenter)
103
-
104
- self.ContentsMeasurements = QFrame()
105
- layout = QVBoxLayout(self.ContentsMeasurements)
106
- layout.setContentsMargins(0,0,0,0)
107
-
108
- list_header_layout = QHBoxLayout()
109
- meas = self.neighborhood_parameter_name.split(' ')[-1]
110
- lbl = QLabel(f'{meas} [px]:')
111
- lbl.setToolTip('')
112
- list_header_layout.addWidget(lbl, 85)
113
-
114
- self.delete_measurement_btn = QPushButton("")
115
- self.delete_measurement_btn.setStyleSheet(self.button_select_all)
116
- self.delete_measurement_btn.setIcon(icon(MDI6.trash_can,color="black"))
117
- self.delete_measurement_btn.setToolTip("Remove measurement.")
118
- self.delete_measurement_btn.setIconSize(QSize(20, 20))
119
- list_header_layout.addWidget(self.delete_measurement_btn, 5)
120
-
121
- self.add_measurement_btn = QPushButton("")
122
- self.add_measurement_btn.setStyleSheet(self.button_select_all)
123
- self.add_measurement_btn.setIcon(icon(MDI6.plus,color="black"))
124
- self.add_measurement_btn.setToolTip("Add a neighborhood measurement.")
125
- self.add_measurement_btn.setIconSize(QSize(20, 20))
126
- list_header_layout.addWidget(self.add_measurement_btn, 5)
127
-
128
- self.viewer_btn = QPushButton()
129
- self.viewer_btn.setStyleSheet(self.button_select_all)
130
- self.viewer_btn.setIcon(icon(MDI6.image_check, color="black"))
131
- self.viewer_btn.setToolTip("View stack and set measurement.")
132
- self.viewer_btn.setIconSize(QSize(20, 20))
133
- if self.neighborhood_type=='distance_threshold':
134
- self.viewer_btn.clicked.connect(self.view_current_stack_with_circle)
135
- elif self.neighborhood_type=='mask_contact':
136
- self.viewer_btn.clicked.connect(self.view_current_stack_with_edge)
137
- list_header_layout.addWidget(self.viewer_btn, 5)
138
-
139
- layout.addLayout(list_header_layout)
140
-
141
- self.measurements_list = ListWidget(DistanceChoice, initial_features=[], dtype=int)
142
- self.measurements_list.setToolTip('Neighborhoods to compute.')
143
- layout.addWidget(self.measurements_list)
144
-
145
- self.delete_measurement_btn.clicked.connect(self.measurements_list.removeSel)
146
- self.add_measurement_btn.clicked.connect(self.measurements_list.addItem)
147
-
148
- grid.addWidget(self.ContentsMeasurements)
149
-
150
- def view_current_stack_with_circle(self):
151
-
152
- self.parent_window.parent_window.locate_image()
153
- if self.parent_window.parent_window.current_stack is not None:
154
- self.viewer = CellSizeViewer(
155
- initial_diameter = 100,
156
- parent_list_widget = self.measurements_list.list_widget,
157
- set_radius_in_list = True,
158
- stack_path=self.parent_window.parent_window.current_stack,
159
- window_title=f'Position {self.parent_window.parent_window.position_list.currentText()}',
160
- frame_slider = True,
161
- contrast_slider = True,
162
- channel_cb = True,
163
- diameter_slider_range = (0,300),
164
- channel_names = self.parent_window.parent_window.exp_channels,
165
- n_channels = self.parent_window.parent_window.nbr_channels,
166
- PxToUm = 1,
167
- )
168
- self.viewer.show()
169
-
170
- def view_current_stack_with_edge(self):
171
-
172
- self.attr_parent.locate_image()
173
- if self.attr_parent.current_stack is not None:
174
- self.viewer = CellEdgeVisualizer(
175
- cell_type=self.reference_population_cb.currentText(),
176
- edge_range=(1,30),
177
- invert=True,
178
- initial_edge=3,
179
- parent_list_widget = self.measurements_list.list_widget,
180
- stack_path=self.attr_parent.current_stack,
181
- window_title=f'Position {self.attr_parent.position_list.currentText()}',
182
- frame_slider = True,
183
- contrast_slider = True,
184
- channel_cb = True,
185
- channel_names = self.attr_parent.exp_channels,
186
- n_channels = self.attr_parent.nbr_channels,
187
- PxToUm = 1,
188
- )
189
- self.viewer.show()
190
-
191
-
192
- def populate_reference_frame(self):
193
-
194
- """
195
- Add widgets and layout in the reference population frame.
196
- """
197
-
198
- grid = QVBoxLayout(self.reference_population_frame)
199
- grid.setSpacing(15)
200
- self.ref_lbl = QLabel("REFERENCE")
201
- self.ref_lbl.setStyleSheet(self.block_title)
202
- self.ref_lbl.setToolTip('Reference population settings.')
203
- grid.addWidget(self.ref_lbl, 30, alignment=Qt.AlignCenter)
204
-
205
- self.ContentsReference = QFrame()
206
- layout = QVBoxLayout(self.ContentsReference)
207
- layout.setContentsMargins(15,15,15,15)
208
-
209
- population_layout = QHBoxLayout()
210
- population_layout.addWidget(QLabel('population: '),30)
211
- self.reference_population_cb = QComboBox()
212
- self.reference_population_cb.addItems(self.parent_window.parent_window.populations)
213
- self.reference_population_cb.setToolTip('Select a reference population.')
214
- population_layout.addWidget(self.reference_population_cb,70)
215
- layout.addLayout(population_layout)
216
-
217
- status_layout = QHBoxLayout()
218
-
219
- #status_layout.addWidget(QLabel('status: '), 30)
220
-
221
- status_sublayout = QHBoxLayout()
222
- self.reference_population_status_cb = QComboBox()
223
- self.reference_population_status_cb.setToolTip('Status of the reference population.')
224
- self.reference_population_status_cb.hide()
225
- status_sublayout.addWidget(self.reference_population_status_cb,95)
226
-
227
- self.reference_switch_status_btn = QPushButton("")
228
- self.reference_switch_status_btn.setStyleSheet(self.button_select_all)
229
- self.reference_switch_status_btn.setIcon(icon(MDI6.invert_colors,color="black"))
230
- self.reference_switch_status_btn.setIconSize(QSize(20, 20))
231
- self.reference_switch_status_btn.clicked.connect(self.switch_not_reference)
232
- self.reference_switch_status_btn.setToolTip('Invert status values.')
233
- self.reference_switch_status_btn.hide()
234
-
235
- status_sublayout.addWidget(self.reference_switch_status_btn, 5)
236
-
237
- status_layout.addLayout(status_sublayout, 70)
238
- layout.addLayout(status_layout)
239
-
240
- event_layout = QHBoxLayout()
241
- event_layout.addWidget(QLabel('event time: '),30)
242
- self.event_time_cb = QComboBox()
243
- self.event_time_cb.setToolTip('Compute average neighborhood metrics before and after this event time.')
244
- event_layout.addWidget(self.event_time_cb,70)
245
- layout.addLayout(event_layout)
246
-
247
- self.fill_cbs_of_reference_population()
248
- self.reference_population_cb.currentIndexChanged.connect(self.fill_cbs_of_reference_population)
249
-
250
- grid.addWidget(self.ContentsReference, 70)
251
-
252
- def populate_neighbor_frame(self):
253
-
254
- """
255
- Add widgets and layout in the neighbor population frame.
256
- """
257
-
258
- grid = QVBoxLayout(self.neigh_population_frame)
259
- grid.setSpacing(15)
260
- self.ref_lbl = QLabel("NEIGHBOR")
261
- self.ref_lbl.setStyleSheet(self.block_title)
262
- self.ref_lbl.setToolTip('Neighbor population settings.')
263
- grid.addWidget(self.ref_lbl, 30, alignment=Qt.AlignCenter)
264
-
265
- self.ContentsNeigh = QFrame()
266
- layout = QVBoxLayout(self.ContentsNeigh)
267
- layout.setContentsMargins(15,15,15,15)
268
-
269
- population_layout = QHBoxLayout()
270
- population_layout.addWidget(QLabel('population: '),30)
271
- self.neighbor_population_cb = QComboBox()
272
- self.neighbor_population_cb.addItems(self.parent_window.parent_window.populations)
273
- self.neighbor_population_cb.setToolTip('Select a neighbor population.')
274
- population_layout.addWidget(self.neighbor_population_cb,70)
275
- layout.addLayout(population_layout)
276
-
277
- status_layout = QHBoxLayout()
278
-
279
- status_layout.addWidget(QLabel('status: '), 30)
280
- status_sublayout = QHBoxLayout()
281
-
282
- self.neighbor_population_status_cb = QComboBox()
283
- self.neighbor_population_status_cb.setToolTip('Status of the neighbor population.')
284
- status_sublayout.addWidget(self.neighbor_population_status_cb,95)
285
-
286
- self.neighbor_switch_status_btn = QPushButton("")
287
- self.neighbor_switch_status_btn.setStyleSheet(self.button_select_all)
288
- self.neighbor_switch_status_btn.setIcon(icon(MDI6.invert_colors,color="black"))
289
- self.neighbor_switch_status_btn.setToolTip("Invert status values.")
290
- self.neighbor_switch_status_btn.setIconSize(QSize(20, 20))
291
- self.neighbor_switch_status_btn.clicked.connect(self.switch_not_neigh)
292
- status_sublayout.addWidget(self.neighbor_switch_status_btn, 5)
293
- status_layout.addLayout(status_sublayout, 70)
294
- layout.addLayout(status_layout)
295
-
296
- self.cumulated_presence_btn = QCheckBox('cumulated presence')
297
- self.cumulated_presence_btn.setToolTip("Compute the cumulated presence time of each neighbor around a reference cell.")
298
- self.cumulated_presence_btn.setIcon(icon(MDI6.timer_outline, color='black'))
299
-
300
- layout.addWidget(self.cumulated_presence_btn)
301
-
302
- # self.symmetrize_btn = QCheckBox('symmetrize')
303
- # self.symmetrize_btn.setToolTip("Write the neighborhood of the neighbor cells with respect to the reference cells.")
304
- # layout.addWidget(self.symmetrize_btn)
305
-
306
- self.fill_cbs_of_neighbor_population()
307
- self.neighbor_population_cb.currentIndexChanged.connect(self.fill_cbs_of_neighbor_population)
308
-
309
- grid.addWidget(self.ContentsNeigh, 70)
310
-
311
- def fill_cbs_of_neighbor_population(self):
312
-
313
- population = self.neighbor_population_cb.currentText()
314
- class_cols, status_cols, group_cols, time_cols = self.locate_population_specific_columns(population)
315
- self.neighbor_population_status_cb.clear()
316
- self.neighbor_population_status_cb.addItems(list(np.unique(['--','class', 'status']+class_cols+status_cols+group_cols)))
317
-
318
- def fill_cbs_of_reference_population(self):
319
-
320
- population = self.reference_population_cb.currentText()
321
- class_cols, status_cols, group_cols, time_cols = self.locate_population_specific_columns(population)
322
- self.event_time_cb.clear()
323
- self.event_time_cb.addItems(list(np.unique(['--', 't0']+time_cols)))
324
-
325
- def switch_not_reference(self):
326
-
327
- self.not_status_reference = not self.not_status_reference
328
- if self.not_status_reference:
329
- self.reference_switch_status_btn.setIcon(icon(MDI6.invert_colors,color=self.celldetective_blue))
330
- self.reference_switch_status_btn.setIconSize(QSize(20, 20))
331
- else:
332
- self.reference_switch_status_btn.setIcon(icon(MDI6.invert_colors,color="black"))
333
- self.reference_switch_status_btn.setIconSize(QSize(20, 20))
334
-
335
- def switch_not_neigh(self):
336
-
337
- self.not_status_neighbor = not self.not_status_neighbor
338
- if self.not_status_neighbor:
339
- self.neighbor_switch_status_btn.setIcon(icon(MDI6.invert_colors,color=self.celldetective_blue))
340
- self.neighbor_switch_status_btn.setIconSize(QSize(20, 20))
341
- else:
342
- self.neighbor_switch_status_btn.setIcon(icon(MDI6.invert_colors,color="black"))
343
- self.neighbor_switch_status_btn.setIconSize(QSize(20, 20))
344
-
345
-
346
- def locate_population_specific_columns(self, population):
347
-
348
- # Look for all classes and times
349
- tables = glob(self.attr_parent.exp_dir+os.sep.join(['W*','*','output','tables',f'trajectories_{population}.csv']))
350
- self.all_columns = []
351
- for tab in tables:
352
- cols = pd.read_csv(tab, nrows=1).columns.tolist()
353
- self.all_columns.extend(cols)
354
- self.all_columns = np.unique(self.all_columns)
355
-
356
- class_idx = np.array([s.startswith('class_') for s in self.all_columns])
357
- status_idx = np.array([s.startswith('status_') for s in self.all_columns])
358
- group_idx = np.array([s.startswith('group_') for s in self.all_columns])
359
- time_idx = np.array([s.startswith('t_') for s in self.all_columns])
360
-
361
- if len(class_idx)>0:
362
- class_columns = list(self.all_columns[class_idx])
363
- for c in ['class_id', 'class_color']:
364
- if c in class_columns:
365
- class_columns.remove(c)
366
- else:
367
- class_columns = []
368
-
369
- if len(status_idx)>0:
370
- status_columns = list(self.all_columns[status_idx])
371
- else:
372
- status_columns = []
373
-
374
- if len(group_idx)>0:
375
- group_columns = list(self.all_columns[group_idx])
376
- else:
377
- group_columns = []
378
-
379
- if len(time_idx)>0:
380
- time_columns = list(self.all_columns[time_idx])
381
- else:
382
- time_columns = []
383
-
384
- return class_columns, status_columns, group_columns, time_columns
385
-
386
- def write_instructions(self):
387
-
388
- """
389
- Write the selected options in a json file for later reading by the software.
390
- """
391
-
392
- print('Writing instructions...')
393
-
394
- neighborhood_options = {}
395
- pop = [self.reference_population_cb.currentText(), self.neighbor_population_cb.currentText()]
396
- neighborhood_options.update({'population': pop})
397
-
398
- status_options = [self.reference_population_status_cb.currentText(), self.neighbor_population_status_cb.currentText()]
399
- for k in range(2):
400
- if status_options[k]=='--' or status_options[k]=='':
401
- status_options[k] = None
402
- if pop[0]!=pop[1]:
403
- mode = 'two-pop'
404
- else:
405
- mode = 'self'
406
-
407
- # TO ADAPT
408
- distances = self.measurements_list.getItems()
409
- neighborhood_options.update({'neighborhood_type': self.neighborhood_type})
410
- neighborhood_options.update({'distance': distances})
411
- neighborhood_options.update({'clear_neigh': self.clear_previous_btn.isChecked()})
412
- event_time_col = self.event_time_cb.currentText()
413
- if event_time_col=='--':
414
- event_time_col = None
415
- neighborhood_options.update({'event_time_col': event_time_col})
416
-
417
- neighborhood_kwargs = {'mode': mode, 'status': status_options, 'not_status_option': [self.not_status_reference, self.not_status_neighbor],
418
- 'compute_cum_sum': self.cumulated_presence_btn.isChecked(), 'attention_weight': True, 'symmetrize': False,
419
- 'include_dead_weight': True}
420
-
421
- neighborhood_options.update({'neighborhood_kwargs': neighborhood_kwargs})
422
-
423
- print('Neighborhood instructions: ', neighborhood_options)
424
- file_name = self.neigh_instructions
425
- with open(file_name, 'w') as f:
426
- json.dump(neighborhood_options, f, indent=4)
427
-
428
-
429
- self.parent_window.protocols.append(neighborhood_options)
430
- correction_description = ""
431
- for index, (key, value) in enumerate(neighborhood_options.items()):
432
- if index > 0:
433
- correction_description += ", "
434
- correction_description += str(key) + " : " + str(value)
435
- self.parent_window.protocol_list.addItem(correction_description)
436
-
437
- print('Done.')
438
- self.close()
439
-
440
- def load_previous_neighborhood_instructions(self):
441
-
442
- """
443
- Read the measurmeent options from a previously written json file and format properly for the UI.
444
- """
445
-
446
- print('Reading instructions..')
447
- if os.path.exists(self.neigh_instructions):
448
- with open(self.neigh_instructions, 'r') as f:
449
-
450
- neigh_instructions = json.load(f)
451
- print(f"Loading the instructions: {neigh_instructions}...")
452
-
453
- if 'neighborhood_type' not in neigh_instructions:
454
- neigh_instructions.update({'neighborhood_type': self.neighborhood_type})
455
-
456
- if self.neighborhood_type==neigh_instructions['neighborhood_type']:
457
-
458
- if 'distance' in neigh_instructions:
459
- distances = neigh_instructions['distance']
460
- distances = [str(d) for d in distances]
461
- self.measurements_list.list_widget.clear()
462
- self.measurements_list.list_widget.addItems(distances)
463
-
464
- if 'population' in neigh_instructions:
465
-
466
- pop = neigh_instructions['population']
467
- idx0 = self.reference_population_cb.findText(pop[0])
468
- self.reference_population_cb.setCurrentIndex(idx0)
469
- idx1 = self.neighbor_population_cb.findText(pop[1])
470
- self.neighbor_population_cb.setCurrentIndex(idx1)
471
-
472
- if 'clear_neigh' in neigh_instructions:
473
- clear_neigh = neigh_instructions['clear_neigh']
474
- self.clear_previous_btn.setChecked(clear_neigh)
475
-
476
- if 'event_time_col' in neigh_instructions:
477
- event_time_col = neigh_instructions['event_time_col']
478
- if event_time_col is None:
479
- event_time_col = '--'
480
- idx = self.event_time_cb.findText(event_time_col)
481
- self.event_time_cb.setCurrentIndex(idx)
482
-
483
- if 'neighborhood_kwargs' in neigh_instructions:
484
- neighborhood_kwargs = neigh_instructions['neighborhood_kwargs']
485
- if 'compute_cum_sum' in neighborhood_kwargs:
486
- self.cumulated_presence_btn.setChecked(neighborhood_kwargs['compute_cum_sum'])
487
- if 'status' in neighborhood_kwargs:
488
- status_options = neighborhood_kwargs['status']
489
- status_options = ['--' if s is None else s for s in status_options]
490
- idx0 = self.reference_population_status_cb.findText(status_options[0])
491
- self.reference_population_status_cb.setCurrentIndex(idx0)
492
- idx1 = self.neighbor_population_status_cb.findText(status_options[1])
493
- self.neighbor_population_status_cb.setCurrentIndex(idx1)
494
- if 'not_status_option' in neighborhood_kwargs:
495
- not_status_option = neighborhood_kwargs['not_status_option']
496
- if not_status_option[0]:
497
- self.reference_switch_status_btn.click()
498
- if not_status_option[1]:
499
- self.neighbor_switch_status_btn.click()
29
+ """
30
+ Widget to configure neighborhood measurements.
31
+
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ neighborhood_type="distance_threshold",
37
+ neighborhood_parameter_name="threshold distance",
38
+ parent_window=None,
39
+ *args,
40
+ **kwargs,
41
+ ):
42
+
43
+ super().__init__(*args, **kwargs)
44
+ self.parent_window = parent_window
45
+ self.attr_parent = self.parent_window.parent_window
46
+
47
+ self.neighborhood_type = neighborhood_type
48
+ self.neighborhood_parameter_name = neighborhood_parameter_name
49
+
50
+ self.setWindowTitle("Configure neighborhoods")
51
+ self.neigh_instructions = self.attr_parent.exp_dir + os.sep.join(
52
+ ["configs", "neighborhood_instructions.json"]
53
+ )
54
+ self.clear_previous = False
55
+ self.not_status_reference = False
56
+ self.not_status_neighbor = False
57
+
58
+ self.screen_height = self.attr_parent.screen_height
59
+ self.setMinimumWidth(750)
60
+ self.setMinimumHeight(int(0.5 * self.screen_height))
61
+ self.setMaximumHeight(int(0.95 * self.screen_height))
62
+
63
+ self.generate_main_layout()
64
+ self.load_previous_neighborhood_instructions()
65
+ center_window(self)
66
+
67
+ def generate_main_layout(self):
68
+
69
+ main_layout = QVBoxLayout(self)
70
+ main_layout.setContentsMargins(30, 30, 30, 30)
71
+
72
+ populations_layout = QHBoxLayout()
73
+
74
+ # Reference population
75
+ self.reference_population_frame = QFrame()
76
+ self.reference_population_frame.setFrameStyle(
77
+ QFrame.StyledPanel | QFrame.Raised
78
+ )
79
+ self.populate_reference_frame()
80
+ populations_layout.addWidget(self.reference_population_frame, 50)
81
+
82
+ # Neighbor population
83
+ self.neigh_population_frame = QFrame()
84
+ self.neigh_population_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
85
+ self.populate_neighbor_frame()
86
+ populations_layout.addWidget(self.neigh_population_frame, 50)
87
+ main_layout.addLayout(populations_layout)
88
+
89
+ # Measurements
90
+
91
+ self.measurement_frame = QFrame()
92
+ self.measurement_frame.setFrameStyle(QFrame.StyledPanel | QFrame.Raised)
93
+ self.populate_measurement_frame()
94
+ # if self.neighborhood_type=='distance_threshold':
95
+ # self.populate_radii_frame()
96
+ # elif self.neighborhood_type=='mask_contact':
97
+ # self.populate_contact_frame()
98
+ main_layout.addWidget(self.measurement_frame)
99
+
100
+ self.clear_previous_btn = QCheckBox("clear previous neighborhoods")
101
+ self.clear_previous_btn.setToolTip(
102
+ "Clear all previous neighborhood measurements."
103
+ )
104
+ self.clear_previous_btn.setIcon(icon(MDI6.broom, color="black"))
105
+
106
+ main_layout.addWidget(self.clear_previous_btn, alignment=Qt.AlignRight)
107
+
108
+ main_layout.addWidget(QLabel(""))
109
+ self.submit_btn = QPushButton("Set")
110
+ self.submit_btn.setStyleSheet(self.button_style_sheet)
111
+ self.submit_btn.clicked.connect(self.write_instructions)
112
+ main_layout.addWidget(self.submit_btn)
113
+
114
+ self.adjustSize()
115
+ QApplication.processEvents()
116
+
117
+ def populate_measurement_frame(self):
118
+ """
119
+ Add widgets and layout in the radii frame.
120
+ """
121
+
122
+ grid = QVBoxLayout(self.measurement_frame)
123
+
124
+ self.dist_lbl = QLabel(
125
+ f"NEIGHBORHOOD {self.neighborhood_parameter_name.upper()}"
126
+ )
127
+ self.dist_lbl.setStyleSheet(self.block_title)
128
+ grid.addWidget(self.dist_lbl, alignment=Qt.AlignCenter)
129
+
130
+ self.ContentsMeasurements = QFrame()
131
+ layout = QVBoxLayout(self.ContentsMeasurements)
132
+ layout.setContentsMargins(0, 0, 0, 0)
133
+
134
+ list_header_layout = QHBoxLayout()
135
+ meas = self.neighborhood_parameter_name.split(" ")[-1]
136
+ lbl = QLabel(f"{meas} [px]:")
137
+ lbl.setToolTip("")
138
+ list_header_layout.addWidget(lbl, 85)
139
+
140
+ self.delete_measurement_btn = QPushButton("")
141
+ self.delete_measurement_btn.setStyleSheet(self.button_select_all)
142
+ self.delete_measurement_btn.setIcon(icon(MDI6.trash_can, color="black"))
143
+ self.delete_measurement_btn.setToolTip("Remove measurement.")
144
+ self.delete_measurement_btn.setIconSize(QSize(20, 20))
145
+ list_header_layout.addWidget(self.delete_measurement_btn, 5)
146
+
147
+ self.add_measurement_btn = QPushButton("")
148
+ self.add_measurement_btn.setStyleSheet(self.button_select_all)
149
+ self.add_measurement_btn.setIcon(icon(MDI6.plus, color="black"))
150
+ self.add_measurement_btn.setToolTip("Add a neighborhood measurement.")
151
+ self.add_measurement_btn.setIconSize(QSize(20, 20))
152
+ list_header_layout.addWidget(self.add_measurement_btn, 5)
153
+
154
+ self.viewer_btn = QPushButton()
155
+ self.viewer_btn.setStyleSheet(self.button_select_all)
156
+ self.viewer_btn.setIcon(icon(MDI6.image_check, color="black"))
157
+ self.viewer_btn.setToolTip("View stack and set measurement.")
158
+ self.viewer_btn.setIconSize(QSize(20, 20))
159
+ if self.neighborhood_type == "distance_threshold":
160
+ self.viewer_btn.clicked.connect(self.view_current_stack_with_circle)
161
+ elif self.neighborhood_type == "mask_contact":
162
+ self.viewer_btn.clicked.connect(self.view_current_stack_with_edge)
163
+ list_header_layout.addWidget(self.viewer_btn, 5)
164
+
165
+ layout.addLayout(list_header_layout)
166
+
167
+ self.measurements_list = ListWidget(
168
+ DistanceChoice, initial_features=[], dtype=int
169
+ )
170
+ self.measurements_list.setToolTip("Neighborhoods to compute.")
171
+ layout.addWidget(self.measurements_list)
172
+
173
+ self.delete_measurement_btn.clicked.connect(self.measurements_list.removeSel)
174
+ self.add_measurement_btn.clicked.connect(self.measurements_list.addItem)
175
+
176
+ grid.addWidget(self.ContentsMeasurements)
177
+
178
+ def view_current_stack_with_circle(self):
179
+
180
+ self.parent_window.parent_window.locate_image()
181
+ if self.parent_window.parent_window.current_stack is not None:
182
+ self.viewer = CellSizeViewer(
183
+ initial_diameter=100,
184
+ parent_list_widget=self.measurements_list.list_widget,
185
+ set_radius_in_list=True,
186
+ stack_path=self.parent_window.parent_window.current_stack,
187
+ window_title=f"Position {self.parent_window.parent_window.position_list.currentText()}",
188
+ frame_slider=True,
189
+ contrast_slider=True,
190
+ channel_cb=True,
191
+ diameter_slider_range=(0, 300),
192
+ channel_names=self.parent_window.parent_window.exp_channels,
193
+ n_channels=self.parent_window.parent_window.nbr_channels,
194
+ PxToUm=1,
195
+ )
196
+ self.viewer.show()
197
+
198
+ def view_current_stack_with_edge(self):
199
+
200
+ self.attr_parent.locate_image()
201
+ if self.attr_parent.current_stack is not None:
202
+ self.viewer = CellEdgeVisualizer(
203
+ cell_type=self.reference_population_cb.currentText(),
204
+ edge_range=(1, 30),
205
+ invert=True,
206
+ initial_edge=3,
207
+ parent_list_widget=self.measurements_list.list_widget,
208
+ stack_path=self.attr_parent.current_stack,
209
+ window_title=f"Position {self.attr_parent.position_list.currentText()}",
210
+ frame_slider=True,
211
+ contrast_slider=True,
212
+ channel_cb=True,
213
+ channel_names=self.attr_parent.exp_channels,
214
+ n_channels=self.attr_parent.nbr_channels,
215
+ PxToUm=1,
216
+ )
217
+ self.viewer.show()
218
+
219
+ def populate_reference_frame(self):
220
+ """
221
+ Add widgets and layout in the reference population frame.
222
+ """
223
+
224
+ grid = QVBoxLayout(self.reference_population_frame)
225
+ grid.setSpacing(15)
226
+ self.ref_lbl = QLabel("REFERENCE")
227
+ self.ref_lbl.setStyleSheet(self.block_title)
228
+ self.ref_lbl.setToolTip("Reference population settings.")
229
+ grid.addWidget(self.ref_lbl, 30, alignment=Qt.AlignCenter)
230
+
231
+ self.ContentsReference = QFrame()
232
+ layout = QVBoxLayout(self.ContentsReference)
233
+ layout.setContentsMargins(15, 15, 15, 15)
234
+
235
+ population_layout = QHBoxLayout()
236
+ population_layout.addWidget(QLabel("population: "), 30)
237
+ self.reference_population_cb = QComboBox()
238
+ self.reference_population_cb.addItems(
239
+ self.parent_window.parent_window.populations
240
+ )
241
+ self.reference_population_cb.setToolTip("Select a reference population.")
242
+ population_layout.addWidget(self.reference_population_cb, 70)
243
+ layout.addLayout(population_layout)
244
+
245
+ status_layout = QHBoxLayout()
246
+
247
+ # status_layout.addWidget(QLabel('status: '), 30)
248
+
249
+ status_sublayout = QHBoxLayout()
250
+ self.reference_population_status_cb = QComboBox()
251
+ self.reference_population_status_cb.setToolTip(
252
+ "Status of the reference population."
253
+ )
254
+ self.reference_population_status_cb.hide()
255
+ status_sublayout.addWidget(self.reference_population_status_cb, 95)
256
+
257
+ self.reference_switch_status_btn = QPushButton("")
258
+ self.reference_switch_status_btn.setStyleSheet(self.button_select_all)
259
+ self.reference_switch_status_btn.setIcon(
260
+ icon(MDI6.invert_colors, color="black")
261
+ )
262
+ self.reference_switch_status_btn.setIconSize(QSize(20, 20))
263
+ self.reference_switch_status_btn.clicked.connect(self.switch_not_reference)
264
+ self.reference_switch_status_btn.setToolTip("Invert status values.")
265
+ self.reference_switch_status_btn.hide()
266
+
267
+ status_sublayout.addWidget(self.reference_switch_status_btn, 5)
268
+
269
+ status_layout.addLayout(status_sublayout, 70)
270
+ layout.addLayout(status_layout)
271
+
272
+ event_layout = QHBoxLayout()
273
+ event_layout.addWidget(QLabel("event time: "), 30)
274
+ self.event_time_cb = QComboBox()
275
+ self.event_time_cb.setToolTip(
276
+ "Compute average neighborhood metrics before and after this event time."
277
+ )
278
+ event_layout.addWidget(self.event_time_cb, 70)
279
+ layout.addLayout(event_layout)
280
+
281
+ self.fill_cbs_of_reference_population()
282
+ self.reference_population_cb.currentIndexChanged.connect(
283
+ self.fill_cbs_of_reference_population
284
+ )
285
+
286
+ grid.addWidget(self.ContentsReference, 70)
287
+
288
+ def populate_neighbor_frame(self):
289
+ """
290
+ Add widgets and layout in the neighbor population frame.
291
+ """
292
+
293
+ grid = QVBoxLayout(self.neigh_population_frame)
294
+ grid.setSpacing(15)
295
+ self.ref_lbl = QLabel("NEIGHBOR")
296
+ self.ref_lbl.setStyleSheet(self.block_title)
297
+ self.ref_lbl.setToolTip("Neighbor population settings.")
298
+ grid.addWidget(self.ref_lbl, 30, alignment=Qt.AlignCenter)
299
+
300
+ self.ContentsNeigh = QFrame()
301
+ layout = QVBoxLayout(self.ContentsNeigh)
302
+ layout.setContentsMargins(15, 15, 15, 15)
303
+
304
+ population_layout = QHBoxLayout()
305
+ population_layout.addWidget(QLabel("population: "), 30)
306
+ self.neighbor_population_cb = QComboBox()
307
+ self.neighbor_population_cb.addItems(
308
+ self.parent_window.parent_window.populations
309
+ )
310
+ self.neighbor_population_cb.setToolTip("Select a neighbor population.")
311
+ population_layout.addWidget(self.neighbor_population_cb, 70)
312
+ layout.addLayout(population_layout)
313
+
314
+ status_layout = QHBoxLayout()
315
+
316
+ status_layout.addWidget(QLabel("status: "), 30)
317
+ status_sublayout = QHBoxLayout()
318
+
319
+ self.neighbor_population_status_cb = QComboBox()
320
+ self.neighbor_population_status_cb.setToolTip(
321
+ "Status of the neighbor population."
322
+ )
323
+ status_sublayout.addWidget(self.neighbor_population_status_cb, 95)
324
+
325
+ self.neighbor_switch_status_btn = QPushButton("")
326
+ self.neighbor_switch_status_btn.setStyleSheet(self.button_select_all)
327
+ self.neighbor_switch_status_btn.setIcon(icon(MDI6.invert_colors, color="black"))
328
+ self.neighbor_switch_status_btn.setToolTip("Invert status values.")
329
+ self.neighbor_switch_status_btn.setIconSize(QSize(20, 20))
330
+ self.neighbor_switch_status_btn.clicked.connect(self.switch_not_neigh)
331
+ status_sublayout.addWidget(self.neighbor_switch_status_btn, 5)
332
+ status_layout.addLayout(status_sublayout, 70)
333
+ layout.addLayout(status_layout)
334
+
335
+ self.cumulated_presence_btn = QCheckBox("cumulated presence")
336
+ self.cumulated_presence_btn.setToolTip(
337
+ "Compute the cumulated presence time of each neighbor around a reference cell."
338
+ )
339
+ self.cumulated_presence_btn.setIcon(icon(MDI6.timer_outline, color="black"))
340
+
341
+ layout.addWidget(self.cumulated_presence_btn)
342
+
343
+ # self.symmetrize_btn = QCheckBox('symmetrize')
344
+ # self.symmetrize_btn.setToolTip("Write the neighborhood of the neighbor cells with respect to the reference cells.")
345
+ # layout.addWidget(self.symmetrize_btn)
346
+
347
+ self.fill_cbs_of_neighbor_population()
348
+ self.neighbor_population_cb.currentIndexChanged.connect(
349
+ self.fill_cbs_of_neighbor_population
350
+ )
351
+
352
+ grid.addWidget(self.ContentsNeigh, 70)
353
+
354
+ def fill_cbs_of_neighbor_population(self):
355
+
356
+ population = self.neighbor_population_cb.currentText()
357
+ class_cols, status_cols, group_cols, time_cols = (
358
+ self.locate_population_specific_columns(population)
359
+ )
360
+ self.neighbor_population_status_cb.clear()
361
+ self.neighbor_population_status_cb.addItems(
362
+ list(
363
+ np.unique(
364
+ ["--", "class", "status"] + class_cols + status_cols + group_cols
365
+ )
366
+ )
367
+ )
368
+
369
+ def fill_cbs_of_reference_population(self):
370
+
371
+ population = self.reference_population_cb.currentText()
372
+ class_cols, status_cols, group_cols, time_cols = (
373
+ self.locate_population_specific_columns(population)
374
+ )
375
+ self.event_time_cb.clear()
376
+ self.event_time_cb.addItems(list(np.unique(["--", "t0"] + time_cols)))
377
+
378
+ def switch_not_reference(self):
379
+
380
+ self.not_status_reference = not self.not_status_reference
381
+ if self.not_status_reference:
382
+ self.reference_switch_status_btn.setIcon(
383
+ icon(MDI6.invert_colors, color=self.celldetective_blue)
384
+ )
385
+ self.reference_switch_status_btn.setIconSize(QSize(20, 20))
386
+ else:
387
+ self.reference_switch_status_btn.setIcon(
388
+ icon(MDI6.invert_colors, color="black")
389
+ )
390
+ self.reference_switch_status_btn.setIconSize(QSize(20, 20))
391
+
392
+ def switch_not_neigh(self):
393
+
394
+ self.not_status_neighbor = not self.not_status_neighbor
395
+ if self.not_status_neighbor:
396
+ self.neighbor_switch_status_btn.setIcon(
397
+ icon(MDI6.invert_colors, color=self.celldetective_blue)
398
+ )
399
+ self.neighbor_switch_status_btn.setIconSize(QSize(20, 20))
400
+ else:
401
+ self.neighbor_switch_status_btn.setIcon(
402
+ icon(MDI6.invert_colors, color="black")
403
+ )
404
+ self.neighbor_switch_status_btn.setIconSize(QSize(20, 20))
405
+
406
+ def locate_population_specific_columns(self, population):
407
+
408
+ # Look for all classes and times
409
+ tables = glob(
410
+ self.attr_parent.exp_dir
411
+ + os.sep.join(
412
+ ["W*", "*", "output", "tables", f"trajectories_{population}.csv"]
413
+ )
414
+ )
415
+ self.all_columns = []
416
+ for tab in tables:
417
+ cols = pd.read_csv(tab, nrows=1).columns.tolist()
418
+ self.all_columns.extend(cols)
419
+ self.all_columns = np.unique(self.all_columns)
420
+
421
+ class_idx = np.array([s.startswith("class_") for s in self.all_columns])
422
+ status_idx = np.array([s.startswith("status_") for s in self.all_columns])
423
+ group_idx = np.array([s.startswith("group_") for s in self.all_columns])
424
+ time_idx = np.array([s.startswith("t_") for s in self.all_columns])
425
+
426
+ if len(class_idx) > 0:
427
+ class_columns = list(self.all_columns[class_idx])
428
+ for c in ["class_id", "class_color"]:
429
+ if c in class_columns:
430
+ class_columns.remove(c)
431
+ else:
432
+ class_columns = []
433
+
434
+ if len(status_idx) > 0:
435
+ status_columns = list(self.all_columns[status_idx])
436
+ else:
437
+ status_columns = []
438
+
439
+ if len(group_idx) > 0:
440
+ group_columns = list(self.all_columns[group_idx])
441
+ else:
442
+ group_columns = []
443
+
444
+ if len(time_idx) > 0:
445
+ time_columns = list(self.all_columns[time_idx])
446
+ else:
447
+ time_columns = []
448
+
449
+ return class_columns, status_columns, group_columns, time_columns
450
+
451
+ def write_instructions(self):
452
+ """
453
+ Write the selected options in a json file for later reading by the software.
454
+ """
455
+
456
+ print("Writing instructions...")
457
+
458
+ neighborhood_options = {}
459
+ pop = [
460
+ self.reference_population_cb.currentText(),
461
+ self.neighbor_population_cb.currentText(),
462
+ ]
463
+ neighborhood_options.update({"population": pop})
464
+
465
+ status_options = [
466
+ self.reference_population_status_cb.currentText(),
467
+ self.neighbor_population_status_cb.currentText(),
468
+ ]
469
+ for k in range(2):
470
+ if status_options[k] == "--" or status_options[k] == "":
471
+ status_options[k] = None
472
+ if pop[0] != pop[1]:
473
+ mode = "two-pop"
474
+ else:
475
+ mode = "self"
476
+
477
+ # TO ADAPT
478
+ distances = self.measurements_list.getItems()
479
+ neighborhood_options.update({"neighborhood_type": self.neighborhood_type})
480
+ neighborhood_options.update({"distance": distances})
481
+ neighborhood_options.update(
482
+ {"clear_neigh": self.clear_previous_btn.isChecked()}
483
+ )
484
+ event_time_col = self.event_time_cb.currentText()
485
+ if event_time_col == "--":
486
+ event_time_col = None
487
+ neighborhood_options.update({"event_time_col": event_time_col})
488
+
489
+ neighborhood_kwargs = {
490
+ "mode": mode,
491
+ "status": status_options,
492
+ "not_status_option": [self.not_status_reference, self.not_status_neighbor],
493
+ "compute_cum_sum": self.cumulated_presence_btn.isChecked(),
494
+ "attention_weight": True,
495
+ "symmetrize": False,
496
+ "include_dead_weight": True,
497
+ }
498
+
499
+ neighborhood_options.update({"neighborhood_kwargs": neighborhood_kwargs})
500
+
501
+ print("Neighborhood instructions: ", neighborhood_options)
502
+ file_name = self.neigh_instructions
503
+ with open(file_name, "w") as f:
504
+ json.dump(neighborhood_options, f, indent=4)
505
+
506
+ self.parent_window.protocols.append(neighborhood_options)
507
+ correction_description = ""
508
+ for index, (key, value) in enumerate(neighborhood_options.items()):
509
+ if index > 0:
510
+ correction_description += ", "
511
+ correction_description += str(key) + " : " + str(value)
512
+ self.parent_window.protocol_list.addItem(correction_description)
513
+
514
+ print("Done.")
515
+ self.close()
516
+
517
+ def load_previous_neighborhood_instructions(self):
518
+ """
519
+ Read the measurmeent options from a previously written json file and format properly for the UI.
520
+ """
521
+
522
+ print("Reading instructions..")
523
+ if os.path.exists(self.neigh_instructions):
524
+ with open(self.neigh_instructions, "r") as f:
525
+
526
+ neigh_instructions = json.load(f)
527
+ print(f"Loading the instructions: {neigh_instructions}...")
528
+
529
+ if "neighborhood_type" not in neigh_instructions:
530
+ neigh_instructions.update(
531
+ {"neighborhood_type": self.neighborhood_type}
532
+ )
533
+
534
+ if self.neighborhood_type == neigh_instructions["neighborhood_type"]:
535
+
536
+ if "distance" in neigh_instructions:
537
+ distances = neigh_instructions["distance"]
538
+ distances = [str(d) for d in distances]
539
+ self.measurements_list.list_widget.clear()
540
+ self.measurements_list.list_widget.addItems(distances)
541
+
542
+ if "population" in neigh_instructions:
543
+
544
+ pop = neigh_instructions["population"]
545
+ idx0 = self.reference_population_cb.findText(pop[0])
546
+ self.reference_population_cb.setCurrentIndex(idx0)
547
+ idx1 = self.neighbor_population_cb.findText(pop[1])
548
+ self.neighbor_population_cb.setCurrentIndex(idx1)
549
+
550
+ if "clear_neigh" in neigh_instructions:
551
+ clear_neigh = neigh_instructions["clear_neigh"]
552
+ self.clear_previous_btn.setChecked(clear_neigh)
553
+
554
+ if "event_time_col" in neigh_instructions:
555
+ event_time_col = neigh_instructions["event_time_col"]
556
+ if event_time_col is None:
557
+ event_time_col = "--"
558
+ idx = self.event_time_cb.findText(event_time_col)
559
+ self.event_time_cb.setCurrentIndex(idx)
560
+
561
+ if "neighborhood_kwargs" in neigh_instructions:
562
+ neighborhood_kwargs = neigh_instructions["neighborhood_kwargs"]
563
+ if "compute_cum_sum" in neighborhood_kwargs:
564
+ self.cumulated_presence_btn.setChecked(
565
+ neighborhood_kwargs["compute_cum_sum"]
566
+ )
567
+ if "status" in neighborhood_kwargs:
568
+ status_options = neighborhood_kwargs["status"]
569
+ status_options = [
570
+ "--" if s is None else s for s in status_options
571
+ ]
572
+ idx0 = self.reference_population_status_cb.findText(
573
+ status_options[0]
574
+ )
575
+ self.reference_population_status_cb.setCurrentIndex(idx0)
576
+ idx1 = self.neighbor_population_status_cb.findText(
577
+ status_options[1]
578
+ )
579
+ self.neighbor_population_status_cb.setCurrentIndex(idx1)
580
+ if "not_status_option" in neighborhood_kwargs:
581
+ not_status_option = neighborhood_kwargs["not_status_option"]
582
+ if not_status_option[0]:
583
+ self.reference_switch_status_btn.click()
584
+ if not_status_option[1]:
585
+ self.neighbor_switch_status_btn.click()