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
@@ -0,0 +1,331 @@
1
+ import gc
2
+
3
+ import numpy as np
4
+ from PyQt5.QtCore import Qt
5
+ from PyQt5.QtGui import QKeySequence
6
+ from PyQt5.QtWidgets import QHBoxLayout, QLabel, QComboBox, QPushButton, QShortcut
7
+ from superqt import QLabeledDoubleSlider, QLabeledDoubleRangeSlider
8
+
9
+ from celldetective.gui.base.components import QHSeperationLine
10
+ from celldetective.gui.gui_utils import QuickSliderLayout, ThresholdLineEdit
11
+ from celldetective.gui.viewers.base_viewer import StackVisualizer
12
+ from celldetective.utils.image_loaders import load_frames, auto_load_number_of_frames, _get_img_num_per_channel
13
+ from celldetective import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+ class ChannelOffsetViewer(StackVisualizer):
18
+
19
+ def __init__(self, parent_window=None, *args, **kwargs):
20
+
21
+ self.parent_window = parent_window
22
+ self.overlay_target_channel = -1
23
+ self.shift_vertical = 0
24
+ self.shift_horizontal = 0
25
+ super().__init__(*args, **kwargs)
26
+
27
+ self.load_stack()
28
+ self.canvas.layout.addWidget(QHSeperationLine())
29
+
30
+ self.generate_overlay_channel_cb()
31
+ self.generate_overlay_imshow()
32
+
33
+ self.generate_overlay_alpha_slider()
34
+ self.generate_overlay_contrast_slider()
35
+
36
+ self.generate_overlay_shift()
37
+ self.generate_add_to_parent_btn()
38
+
39
+ if self.overlay_target_channel == -1:
40
+ index = len(self.channel_names) - 1
41
+ else:
42
+ index = self.overlay_target_channel
43
+ self.channels_overlay_cb.setCurrentIndex(index)
44
+ self.frame_slider.valueChanged.connect(self.change_overlay_frame)
45
+
46
+ self.define_keyboard_shortcuts()
47
+
48
+ self.channels_overlay_cb.setCurrentIndex(
49
+ self.parent_window.channels_cb.currentIndex()
50
+ )
51
+ self.set_channel_index(0)
52
+
53
+ self.setAttribute(Qt.WA_DeleteOnClose)
54
+
55
+ def generate_overlay_imshow(self):
56
+ self.im_overlay = self.ax.imshow(
57
+ self.overlay_init_frame,
58
+ cmap="Blues",
59
+ interpolation="none",
60
+ alpha=0.5,
61
+ **self.imshow_kwargs,
62
+ )
63
+
64
+ def generate_overlay_alpha_slider(self):
65
+ # Generate the contrast slider if enabled
66
+
67
+ self.overlay_alpha_slider = QLabeledDoubleSlider()
68
+ alpha_layout = QuickSliderLayout(
69
+ label="Overlay\ntransparency: ",
70
+ slider=self.overlay_alpha_slider,
71
+ slider_initial_value=0.5,
72
+ slider_range=(0, 1.0),
73
+ decimal_option=True,
74
+ precision=5,
75
+ )
76
+ alpha_layout.setContentsMargins(15, 0, 15, 0)
77
+ self.overlay_alpha_slider.valueChanged.connect(self.change_alpha_overlay)
78
+ self.canvas.layout.addLayout(alpha_layout)
79
+
80
+ def generate_overlay_contrast_slider(self):
81
+ # Generate the contrast slider if enabled
82
+
83
+ self.overlay_contrast_slider = QLabeledDoubleRangeSlider()
84
+ contrast_layout = QuickSliderLayout(
85
+ label="Overlay contrast: ",
86
+ slider=self.overlay_contrast_slider,
87
+ slider_initial_value=[
88
+ np.nanpercentile(self.overlay_init_frame, 0.1),
89
+ np.nanpercentile(self.overlay_init_frame, 99.99),
90
+ ],
91
+ slider_range=(
92
+ np.nanmin(self.overlay_init_frame),
93
+ np.nanmax(self.overlay_init_frame),
94
+ ),
95
+ decimal_option=True,
96
+ precision=5,
97
+ )
98
+ contrast_layout.setContentsMargins(15, 0, 15, 0)
99
+ self.im_overlay.set_clim(
100
+ vmin=np.nanpercentile(self.overlay_init_frame, 0.1),
101
+ vmax=np.nanpercentile(self.overlay_init_frame, 99.99),
102
+ )
103
+ self.overlay_contrast_slider.valueChanged.connect(self.change_contrast_overlay)
104
+ self.canvas.layout.addLayout(contrast_layout)
105
+
106
+ def set_overlay_channel_index(self, value):
107
+ # Set the channel index based on dropdown value
108
+
109
+ self.overlay_target_channel = value
110
+ self.overlay_init_contrast = True
111
+ if self.mode == "direct":
112
+ self.overlay_last_frame = self.stack[-1, :, :, self.overlay_target_channel]
113
+ elif self.mode == "virtual":
114
+ self.overlay_last_frame = load_frames(
115
+ self.img_num_per_channel[
116
+ self.overlay_target_channel, self.stack_length - 1
117
+ ],
118
+ self.stack_path,
119
+ normalize_input=False,
120
+ ).astype(float)[:, :, 0]
121
+ self.change_overlay_frame(self.frame_slider.value())
122
+ self.overlay_init_contrast = False
123
+
124
+ def generate_overlay_channel_cb(self):
125
+
126
+ assert self.channel_names is not None
127
+ assert len(self.channel_names) == self.n_channels
128
+
129
+ channel_layout = QHBoxLayout()
130
+ channel_layout.setContentsMargins(15, 0, 15, 0)
131
+ channel_layout.addWidget(QLabel("Overlay channel: "), 25)
132
+
133
+ self.channels_overlay_cb = QComboBox()
134
+ self.channels_overlay_cb.addItems(self.channel_names)
135
+ self.channels_overlay_cb.currentIndexChanged.connect(
136
+ self.set_overlay_channel_index
137
+ )
138
+ channel_layout.addWidget(self.channels_overlay_cb, 75)
139
+ self.canvas.layout.addLayout(channel_layout)
140
+
141
+ def generate_overlay_shift(self):
142
+
143
+ shift_layout = QHBoxLayout()
144
+ shift_layout.setContentsMargins(15, 0, 15, 0)
145
+ shift_layout.addWidget(QLabel("shift (h): "), 20, alignment=Qt.AlignRight)
146
+
147
+ self.apply_shift_btn = QPushButton("Apply")
148
+ self.apply_shift_btn.setStyleSheet(self.button_style_sheet_2)
149
+ self.apply_shift_btn.setToolTip("Apply the shift to the overlay channel.")
150
+ self.apply_shift_btn.clicked.connect(self.shift_generic)
151
+
152
+ self.set_shift_btn = QPushButton("Set")
153
+
154
+ self.horizontal_shift_le = ThresholdLineEdit(
155
+ init_value=self.shift_horizontal,
156
+ connected_buttons=[self.apply_shift_btn, self.set_shift_btn],
157
+ placeholder="horizontal shift [pixels]",
158
+ value_type="float",
159
+ )
160
+ shift_layout.addWidget(self.horizontal_shift_le, 20)
161
+
162
+ shift_layout.addWidget(QLabel("shift (v): "), 20, alignment=Qt.AlignRight)
163
+
164
+ self.vertical_shift_le = ThresholdLineEdit(
165
+ init_value=self.shift_vertical,
166
+ connected_buttons=[self.apply_shift_btn, self.set_shift_btn],
167
+ placeholder="vertical shift [pixels]",
168
+ value_type="float",
169
+ )
170
+ shift_layout.addWidget(self.vertical_shift_le, 20)
171
+
172
+ shift_layout.addWidget(self.apply_shift_btn, 20)
173
+
174
+ self.canvas.layout.addLayout(shift_layout)
175
+
176
+ def change_overlay_frame(self, value):
177
+ # Change the displayed frame based on slider value
178
+
179
+ if self.mode == "virtual":
180
+
181
+ self.overlay_init_frame = load_frames(
182
+ self.img_num_per_channel[self.overlay_target_channel, value],
183
+ self.stack_path,
184
+ normalize_input=False,
185
+ ).astype(float)[:, :, 0]
186
+ elif self.mode == "direct":
187
+ self.overlay_init_frame = self.stack[
188
+ value, :, :, self.overlay_target_channel
189
+ ].copy()
190
+
191
+ self.im_overlay.set_data(self.overlay_init_frame)
192
+
193
+ if self.overlay_init_contrast:
194
+ self.im_overlay.autoscale()
195
+ I_min, I_max = self.im_overlay.get_clim()
196
+ self.overlay_contrast_slider.setRange(
197
+ np.nanmin([self.overlay_init_frame, self.overlay_last_frame]),
198
+ np.nanmax([self.overlay_init_frame, self.overlay_last_frame]),
199
+ )
200
+ self.overlay_contrast_slider.setValue((I_min, I_max))
201
+
202
+ if self.create_contrast_slider:
203
+ self.change_contrast_overlay(self.overlay_contrast_slider.value())
204
+
205
+ def locate_image_virtual(self):
206
+ from tifffile import imread
207
+
208
+ # Locate the stack of images if provided as a file
209
+ self.stack_length = auto_load_number_of_frames(self.stack_path)
210
+ if self.stack_length is None:
211
+ stack = imread(self.stack_path)
212
+ self.stack_length = len(stack)
213
+ del stack
214
+ gc.collect()
215
+
216
+ self.mid_time = self.stack_length // 2
217
+ self.img_num_per_channel = _get_img_num_per_channel(
218
+ np.arange(self.n_channels), self.stack_length, self.n_channels
219
+ )
220
+
221
+ self.init_frame = load_frames(
222
+ self.img_num_per_channel[self.target_channel, self.mid_time],
223
+ self.stack_path,
224
+ normalize_input=False,
225
+ ).astype(float)[:, :, 0]
226
+ self.last_frame = load_frames(
227
+ self.img_num_per_channel[self.target_channel, self.stack_length - 1],
228
+ self.stack_path,
229
+ normalize_input=False,
230
+ ).astype(float)[:, :, 0]
231
+ self.overlay_init_frame = load_frames(
232
+ self.img_num_per_channel[self.overlay_target_channel, self.mid_time],
233
+ self.stack_path,
234
+ normalize_input=False,
235
+ ).astype(float)[:, :, 0]
236
+ self.overlay_last_frame = load_frames(
237
+ self.img_num_per_channel[
238
+ self.overlay_target_channel, self.stack_length - 1
239
+ ],
240
+ self.stack_path,
241
+ normalize_input=False,
242
+ ).astype(float)[:, :, 0]
243
+
244
+ def change_contrast_overlay(self, value):
245
+ # Change contrast based on slider value
246
+
247
+ vmin = value[0]
248
+ vmax = value[1]
249
+ self.im_overlay.set_clim(vmin=vmin, vmax=vmax)
250
+ self.fig.canvas.draw_idle()
251
+
252
+ def change_alpha_overlay(self, value):
253
+ # Change contrast based on slider value
254
+
255
+ alpha = value
256
+ self.im_overlay.set_alpha(alpha)
257
+ self.fig.canvas.draw_idle()
258
+
259
+ def define_keyboard_shortcuts(self):
260
+
261
+ self.shift_up_shortcut = QShortcut(QKeySequence(Qt.Key_Up), self.canvas)
262
+ self.shift_up_shortcut.activated.connect(self.shift_overlay_up)
263
+
264
+ self.shift_down_shortcut = QShortcut(QKeySequence(Qt.Key_Down), self.canvas)
265
+ self.shift_down_shortcut.activated.connect(self.shift_overlay_down)
266
+
267
+ self.shift_left_shortcut = QShortcut(QKeySequence(Qt.Key_Left), self.canvas)
268
+ self.shift_left_shortcut.activated.connect(self.shift_overlay_left)
269
+
270
+ self.shift_right_shortcut = QShortcut(QKeySequence(Qt.Key_Right), self.canvas)
271
+ self.shift_right_shortcut.activated.connect(self.shift_overlay_right)
272
+
273
+ def shift_overlay_up(self):
274
+ self.shift_vertical -= 2
275
+ self.vertical_shift_le.set_threshold(self.shift_vertical)
276
+ # self.shift_generic()
277
+ self.apply_shift_btn.click()
278
+
279
+ def shift_overlay_down(self):
280
+ self.shift_vertical += 2
281
+ self.vertical_shift_le.set_threshold(self.shift_vertical)
282
+ # self.shift_generic()
283
+ self.apply_shift_btn.click()
284
+
285
+ def shift_overlay_left(self):
286
+ self.shift_horizontal -= 2
287
+ self.horizontal_shift_le.set_threshold(self.shift_horizontal)
288
+ # self.shift_generic()
289
+ self.apply_shift_btn.click()
290
+
291
+ def shift_overlay_right(self):
292
+ self.shift_horizontal += 2
293
+ self.horizontal_shift_le.set_threshold(self.shift_horizontal)
294
+ # self.shift_generic()
295
+ self.apply_shift_btn.click()
296
+
297
+ def shift_generic(self):
298
+ from scipy.ndimage import shift
299
+
300
+ self.shift_vertical = self.vertical_shift_le.get_threshold()
301
+ self.shift_horizontal = self.horizontal_shift_le.get_threshold()
302
+ self.shifted_frame = shift(
303
+ self.overlay_init_frame,
304
+ [self.shift_vertical, self.shift_horizontal],
305
+ prefilter=False,
306
+ )
307
+ self.im_overlay.set_data(self.shifted_frame)
308
+ self.fig.canvas.draw_idle()
309
+
310
+ def generate_add_to_parent_btn(self):
311
+
312
+ add_hbox = QHBoxLayout()
313
+ add_hbox.setContentsMargins(0, 5, 0, 5)
314
+ self.set_shift_btn.clicked.connect(self.set_parent_attributes)
315
+ self.set_shift_btn.setStyleSheet(self.button_style_sheet)
316
+ add_hbox.addWidget(QLabel(""), 33)
317
+ add_hbox.addWidget(self.set_shift_btn, 33)
318
+ add_hbox.addWidget(QLabel(""), 33)
319
+ self.canvas.layout.addLayout(add_hbox)
320
+
321
+ def set_parent_attributes(self):
322
+
323
+ idx = self.channels_overlay_cb.currentIndex()
324
+ self.parent_window.channels_cb.setCurrentIndex(idx)
325
+ self.parent_window.vertical_shift_le.set_threshold(
326
+ self.vertical_shift_le.get_threshold()
327
+ )
328
+ self.parent_window.horizontal_shift_le.set_threshold(
329
+ self.horizontal_shift_le.get_threshold()
330
+ )
331
+ self.close()