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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. celldetective/__init__.py +25 -0
  2. celldetective/__main__.py +62 -43
  3. celldetective/_version.py +1 -1
  4. celldetective/extra_properties.py +477 -399
  5. celldetective/filters.py +192 -97
  6. celldetective/gui/InitWindow.py +541 -411
  7. celldetective/gui/__init__.py +0 -15
  8. celldetective/gui/about.py +44 -39
  9. celldetective/gui/analyze_block.py +120 -84
  10. celldetective/gui/base/__init__.py +0 -0
  11. celldetective/gui/base/channel_norm_generator.py +335 -0
  12. celldetective/gui/base/components.py +249 -0
  13. celldetective/gui/base/feature_choice.py +92 -0
  14. celldetective/gui/base/figure_canvas.py +52 -0
  15. celldetective/gui/base/list_widget.py +133 -0
  16. celldetective/gui/{styles.py → base/styles.py} +92 -36
  17. celldetective/gui/base/utils.py +33 -0
  18. celldetective/gui/base_annotator.py +900 -767
  19. celldetective/gui/classifier_widget.py +6 -22
  20. celldetective/gui/configure_new_exp.py +777 -671
  21. celldetective/gui/control_panel.py +635 -524
  22. celldetective/gui/dynamic_progress.py +449 -0
  23. celldetective/gui/event_annotator.py +2023 -1662
  24. celldetective/gui/generic_signal_plot.py +1292 -944
  25. celldetective/gui/gui_utils.py +899 -1289
  26. celldetective/gui/interactions_block.py +658 -0
  27. celldetective/gui/interactive_timeseries_viewer.py +447 -0
  28. celldetective/gui/json_readers.py +48 -15
  29. celldetective/gui/layouts/__init__.py +5 -0
  30. celldetective/gui/layouts/background_model_free_layout.py +537 -0
  31. celldetective/gui/layouts/channel_offset_layout.py +134 -0
  32. celldetective/gui/layouts/local_correction_layout.py +91 -0
  33. celldetective/gui/layouts/model_fit_layout.py +372 -0
  34. celldetective/gui/layouts/operation_layout.py +68 -0
  35. celldetective/gui/layouts/protocol_designer_layout.py +96 -0
  36. celldetective/gui/pair_event_annotator.py +3130 -2435
  37. celldetective/gui/plot_measurements.py +586 -267
  38. celldetective/gui/plot_signals_ui.py +724 -506
  39. celldetective/gui/preprocessing_block.py +395 -0
  40. celldetective/gui/process_block.py +1678 -1831
  41. celldetective/gui/seg_model_loader.py +580 -473
  42. celldetective/gui/settings/__init__.py +0 -7
  43. celldetective/gui/settings/_cellpose_model_params.py +181 -0
  44. celldetective/gui/settings/_event_detection_model_params.py +95 -0
  45. celldetective/gui/settings/_segmentation_model_params.py +159 -0
  46. celldetective/gui/settings/_settings_base.py +77 -65
  47. celldetective/gui/settings/_settings_event_model_training.py +752 -526
  48. celldetective/gui/settings/_settings_measurements.py +1133 -964
  49. celldetective/gui/settings/_settings_neighborhood.py +574 -488
  50. celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
  51. celldetective/gui/settings/_settings_signal_annotator.py +329 -305
  52. celldetective/gui/settings/_settings_tracking.py +1304 -1094
  53. celldetective/gui/settings/_stardist_model_params.py +98 -0
  54. celldetective/gui/survival_ui.py +422 -312
  55. celldetective/gui/tableUI.py +1665 -1701
  56. celldetective/gui/table_ops/_maths.py +295 -0
  57. celldetective/gui/table_ops/_merge_groups.py +140 -0
  58. celldetective/gui/table_ops/_merge_one_hot.py +95 -0
  59. celldetective/gui/table_ops/_query_table.py +43 -0
  60. celldetective/gui/table_ops/_rename_col.py +44 -0
  61. celldetective/gui/thresholds_gui.py +382 -179
  62. celldetective/gui/viewers/__init__.py +0 -0
  63. celldetective/gui/viewers/base_viewer.py +700 -0
  64. celldetective/gui/viewers/channel_offset_viewer.py +331 -0
  65. celldetective/gui/viewers/contour_viewer.py +394 -0
  66. celldetective/gui/viewers/size_viewer.py +153 -0
  67. celldetective/gui/viewers/spot_detection_viewer.py +341 -0
  68. celldetective/gui/viewers/threshold_viewer.py +309 -0
  69. celldetective/gui/workers.py +304 -126
  70. celldetective/log_manager.py +92 -0
  71. celldetective/measure.py +1895 -1478
  72. celldetective/napari/__init__.py +0 -0
  73. celldetective/napari/utils.py +1025 -0
  74. celldetective/neighborhood.py +1914 -1448
  75. celldetective/preprocessing.py +1620 -1220
  76. celldetective/processes/__init__.py +0 -0
  77. celldetective/processes/background_correction.py +271 -0
  78. celldetective/processes/compute_neighborhood.py +894 -0
  79. celldetective/processes/detect_events.py +246 -0
  80. celldetective/processes/measure_cells.py +565 -0
  81. celldetective/processes/segment_cells.py +760 -0
  82. celldetective/processes/track_cells.py +435 -0
  83. celldetective/processes/train_segmentation_model.py +694 -0
  84. celldetective/processes/train_signal_model.py +265 -0
  85. celldetective/processes/unified_process.py +292 -0
  86. celldetective/regionprops/_regionprops.py +358 -317
  87. celldetective/relative_measurements.py +987 -710
  88. celldetective/scripts/measure_cells.py +313 -212
  89. celldetective/scripts/measure_relative.py +90 -46
  90. celldetective/scripts/segment_cells.py +165 -104
  91. celldetective/scripts/segment_cells_thresholds.py +96 -68
  92. celldetective/scripts/track_cells.py +198 -149
  93. celldetective/scripts/train_segmentation_model.py +324 -201
  94. celldetective/scripts/train_signal_model.py +87 -45
  95. celldetective/segmentation.py +844 -749
  96. celldetective/signals.py +3514 -2861
  97. celldetective/tracking.py +30 -15
  98. celldetective/utils/__init__.py +0 -0
  99. celldetective/utils/cellpose_utils/__init__.py +133 -0
  100. celldetective/utils/color_mappings.py +42 -0
  101. celldetective/utils/data_cleaning.py +630 -0
  102. celldetective/utils/data_loaders.py +450 -0
  103. celldetective/utils/dataset_helpers.py +207 -0
  104. celldetective/utils/downloaders.py +197 -0
  105. celldetective/utils/event_detection/__init__.py +8 -0
  106. celldetective/utils/experiment.py +1782 -0
  107. celldetective/utils/image_augmenters.py +308 -0
  108. celldetective/utils/image_cleaning.py +74 -0
  109. celldetective/utils/image_loaders.py +926 -0
  110. celldetective/utils/image_transforms.py +335 -0
  111. celldetective/utils/io.py +62 -0
  112. celldetective/utils/mask_cleaning.py +348 -0
  113. celldetective/utils/mask_transforms.py +5 -0
  114. celldetective/utils/masks.py +184 -0
  115. celldetective/utils/maths.py +351 -0
  116. celldetective/utils/model_getters.py +325 -0
  117. celldetective/utils/model_loaders.py +296 -0
  118. celldetective/utils/normalization.py +380 -0
  119. celldetective/utils/parsing.py +465 -0
  120. celldetective/utils/plots/__init__.py +0 -0
  121. celldetective/utils/plots/regression.py +53 -0
  122. celldetective/utils/resources.py +34 -0
  123. celldetective/utils/stardist_utils/__init__.py +104 -0
  124. celldetective/utils/stats.py +90 -0
  125. celldetective/utils/types.py +21 -0
  126. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
  127. celldetective-1.5.0b0.dist-info/RECORD +187 -0
  128. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
  129. tests/gui/test_new_project.py +129 -117
  130. tests/gui/test_project.py +127 -79
  131. tests/test_filters.py +39 -15
  132. tests/test_notebooks.py +8 -0
  133. tests/test_tracking.py +232 -13
  134. tests/test_utils.py +123 -77
  135. celldetective/gui/base_components.py +0 -23
  136. celldetective/gui/layouts.py +0 -1602
  137. celldetective/gui/processes/compute_neighborhood.py +0 -594
  138. celldetective/gui/processes/measure_cells.py +0 -360
  139. celldetective/gui/processes/segment_cells.py +0 -499
  140. celldetective/gui/processes/track_cells.py +0 -303
  141. celldetective/gui/processes/train_segmentation_model.py +0 -270
  142. celldetective/gui/processes/train_signal_model.py +0 -108
  143. celldetective/gui/table_ops/merge_groups.py +0 -118
  144. celldetective/gui/viewers.py +0 -1354
  145. celldetective/io.py +0 -3663
  146. celldetective/utils.py +0 -3108
  147. celldetective-1.4.2.dist-info/RECORD +0 -123
  148. /celldetective/{gui/processes → processes}/downloader.py +0 -0
  149. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
  150. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
  151. {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,308 @@
1
+ import random
2
+
3
+ import numpy as np
4
+ from scipy.ndimage import shift
5
+ from skimage.filters import gaussian
6
+ from skimage.util import random_noise
7
+
8
+ def augmenter(
9
+ x,
10
+ y,
11
+ flip=True,
12
+ gauss_blur=True,
13
+ noise_option=True,
14
+ shift=True,
15
+ channel_extinction=True,
16
+ extinction_probability=0.1,
17
+ clip=False,
18
+ max_sigma_blur=4,
19
+ apply_noise_probability=0.5,
20
+ augment_probability=0.9,
21
+ ):
22
+ """
23
+ Applies a series of augmentation techniques to images and their corresponding masks for deep learning training.
24
+
25
+ This function randomly applies a set of transformations including flipping, rotation, Gaussian blur,
26
+ additive noise, shifting, and channel extinction to input images (x) and their masks (y) based on specified
27
+ probabilities. These augmentations introduce variability in the training dataset, potentially improving model
28
+ generalization.
29
+
30
+ Parameters
31
+ ----------
32
+ x : ndarray
33
+ The input image to be augmented, with dimensions (height, width, channels).
34
+ y : ndarray
35
+ The corresponding mask or label image for `x`, with the same spatial dimensions.
36
+ flip : bool, optional
37
+ Whether to randomly flip and rotate the images. Default is True.
38
+ gauss_blur : bool, optional
39
+ Whether to apply Gaussian blur to the images. Default is True.
40
+ noise_option : bool, optional
41
+ Whether to add random noise to the images. Default is True.
42
+ shift : bool, optional
43
+ Whether to randomly shift the images. Default is True.
44
+ channel_extinction : bool, optional
45
+ Whether to randomly set entire channels of the image to zero. Default is False.
46
+ extinction_probability : float, optional
47
+ The probability of an entire channel being set to zero. Default is 0.1.
48
+ clip : bool, optional
49
+ Whether to clip the noise-added images to stay within valid intensity values. Default is False.
50
+ max_sigma_blur : int, optional
51
+ The maximum sigma value for Gaussian blur. Default is 4.
52
+ apply_noise_probability : float, optional
53
+ The probability of applying noise to the image. Default is 0.5.
54
+ augment_probability : float, optional
55
+ The overall probability of applying any augmentation to the image. Default is 0.9.
56
+
57
+ Returns
58
+ -------
59
+ tuple
60
+ A tuple containing the augmented image and mask `(x, y)`.
61
+
62
+ Raises
63
+ ------
64
+ AssertionError
65
+ If `extinction_probability` is not within the range [0, 1].
66
+
67
+ Notes
68
+ -----
69
+ - The augmentations are applied randomly based on the specified probabilities, allowing for
70
+ a diverse set of transformed images from the original inputs.
71
+ - This function is designed to be part of a preprocessing pipeline for training deep learning models,
72
+ especially in tasks requiring spatial invariance and robustness to noise.
73
+
74
+ Examples
75
+ --------
76
+ >>> import numpy as np
77
+ >>> x = np.random.rand(128, 128, 3) # Sample image
78
+ >>> y = np.random.randint(2, size=(128, 128)) # Sample binary mask
79
+ >>> x_aug, y_aug = augmenter(x, y)
80
+ # The returned `x_aug` and `y_aug` are augmented versions of `x` and `y`.
81
+
82
+ """
83
+
84
+ r = random.random()
85
+ if r <= augment_probability:
86
+
87
+ if flip:
88
+ x, y = random_fliprot(x, y)
89
+
90
+ if gauss_blur:
91
+ x = blur(x, max_sigma=max_sigma_blur)
92
+
93
+ if noise_option:
94
+ x = noise(x, apply_probability=apply_noise_probability, clip_option=clip)
95
+
96
+ if shift:
97
+ x, y = random_shift(x, y)
98
+
99
+ if channel_extinction:
100
+ assert (
101
+ extinction_probability <= 1.0
102
+ ), "The extinction probability must be a number between 0 and 1."
103
+ channel_off = [
104
+ np.random.random() < extinction_probability for i in range(x.shape[-1])
105
+ ]
106
+ channel_off[0] = False
107
+ x[:, :, np.array(channel_off, dtype=bool)] = 0.0
108
+
109
+ return x, y
110
+
111
+
112
+ def noise(x, apply_probability=0.5, clip_option=False):
113
+ """
114
+ Applies random noise to each channel of a multichannel image based on a specified probability.
115
+
116
+ This function introduces various types of random noise to an image. Each channel of the image can be
117
+ modified independently with different noise models chosen randomly from a predefined list. The application
118
+ of noise to any given channel is determined by a specified probability, allowing for selective noise
119
+ addition.
120
+
121
+ Parameters
122
+ ----------
123
+ x : ndarray
124
+ The input multichannel image to which noise will be added. The image should be in format with channels
125
+ as the last dimension (e.g., height x width x channels).
126
+ apply_probability : float, optional
127
+ The probability with which noise is applied to each channel of the image. Default is 0.5.
128
+ clip_option : bool, optional
129
+ Specifies whether to clip the corrupted data to stay within the valid range after noise addition.
130
+ If True, the output array will be clipped to the range [0, 1] or [0, 255] depending on the input
131
+ data type. Default is False.
132
+
133
+ Returns
134
+ -------
135
+ ndarray
136
+ The noised image. This output has the same shape as the input but potentially altered intensity values
137
+ due to noise addition.
138
+
139
+ Notes
140
+ -----
141
+ - The types of noise that can be applied include 'gaussian', 'localvar', 'poisson', and 'speckle'.
142
+ - The choice of noise type for each channel is randomized and the noise is only applied if a randomly
143
+ generated number is less than or equal to `apply_probability`.
144
+ - Zero-valued pixels in the input image remain zero in the output to preserve background or masked areas.
145
+
146
+ Examples
147
+ --------
148
+ >>> import numpy as np
149
+ >>> x = np.random.rand(256, 256, 3) # Example 3-channel image
150
+ >>> noised_image = noise(x)
151
+ # The image 'x' may have different types of noise applied to each of its channels with a 50% probability.
152
+ """
153
+
154
+ x_noise = x.astype(float).copy()
155
+ loc_i, loc_j, loc_c = np.where(x_noise == 0.0)
156
+ options = ["gaussian", "localvar", "poisson", "speckle"]
157
+
158
+ for k in range(x_noise.shape[-1]):
159
+ mode_order = random.sample(options, len(options))
160
+ for m in mode_order:
161
+ p = np.random.random()
162
+ if p <= apply_probability:
163
+ try:
164
+ x_noise[:, :, k] = random_noise(
165
+ x_noise[:, :, k], mode=m, clip=clip_option
166
+ )
167
+ except:
168
+ pass
169
+
170
+ x_noise[loc_i, loc_j, loc_c] = 0.0
171
+
172
+ return x_noise
173
+
174
+
175
+ def random_fliprot(img, mask):
176
+ """
177
+ Randomly flips and rotates an image and its corresponding mask.
178
+
179
+ This function applies a series of random flips and permutations (rotations) to both the input image and its
180
+ associated mask, ensuring that any transformations applied to the image are also exactly applied to the mask.
181
+ The function is designed to handle multi-dimensional images (e.g., multi-channel images in YXC format where
182
+ channels are last).
183
+
184
+ Parameters
185
+ ----------
186
+ img : ndarray
187
+ The input image to be transformed. This array is expected to have dimensions where the channel axis is last.
188
+ mask : ndarray
189
+ The mask corresponding to `img`, to be transformed in the same way as the image.
190
+
191
+ Returns
192
+ -------
193
+ tuple of ndarray
194
+ A tuple containing the transformed image and mask.
195
+
196
+ Raises
197
+ ------
198
+ AssertionError
199
+ If the number of dimensions of the mask exceeds that of the image, indicating incompatible shapes.
200
+
201
+ """
202
+
203
+ assert img.ndim >= mask.ndim
204
+ axes = tuple(range(mask.ndim))
205
+ perm = tuple(np.random.permutation(axes))
206
+ img = img.transpose(perm + tuple(range(mask.ndim, img.ndim)))
207
+ mask = mask.transpose(perm)
208
+ for ax in axes:
209
+ if np.random.rand() > 0.5:
210
+ img = np.flip(img, axis=ax)
211
+ mask = np.flip(mask, axis=ax)
212
+ return img, mask
213
+
214
+
215
+ def random_shift(image, mask, max_shift_amplitude=0.1):
216
+ """
217
+ Randomly shifts an image and its corresponding mask along the X and Y axes.
218
+
219
+ This function shifts both the image and the mask by a randomly chosen distance up to a maximum
220
+ percentage of the image's dimensions, specified by `max_shift_amplitude`. The shifts are applied
221
+ independently in both the X and Y directions. This type of augmentation can help improve the robustness
222
+ of models to positional variations in images.
223
+
224
+ Parameters
225
+ ----------
226
+ image : ndarray
227
+ The input image to be shifted. Must be in YXC format (height, width, channels).
228
+ mask : ndarray
229
+ The mask corresponding to `image`, to be shifted in the same way as the image.
230
+ max_shift_amplitude : float, optional
231
+ The maximum shift as a fraction of the image's dimension. Default is 0.1 (10% of the image's size).
232
+
233
+ Returns
234
+ -------
235
+ tuple of ndarray
236
+ A tuple containing the shifted image and mask.
237
+
238
+ Notes
239
+ -----
240
+ - The shift values are chosen randomly within the range defined by the maximum amplitude.
241
+ - Shifting is performed using the 'constant' mode where missing values are filled with zeros (cval=0.0),
242
+ which may introduce areas of zero-padding along the edges of the shifted images and masks.
243
+ - This function is designed to support data augmentation for machine learning and image processing tasks,
244
+ particularly in contexts where spatial invariance is beneficial.
245
+
246
+ """
247
+
248
+ input_shape = image.shape[0]
249
+ max_shift = input_shape * max_shift_amplitude
250
+
251
+ shift_value_x = random.choice(np.arange(max_shift))
252
+ if np.random.random() > 0.5:
253
+ shift_value_x *= -1
254
+
255
+ shift_value_y = random.choice(np.arange(max_shift))
256
+ if np.random.random() > 0.5:
257
+ shift_value_y *= -1
258
+
259
+ image = shift(
260
+ image,
261
+ [shift_value_x, shift_value_y, 0],
262
+ output=np.float32,
263
+ order=3,
264
+ mode="constant",
265
+ cval=0.0,
266
+ )
267
+ mask = shift(
268
+ mask, [shift_value_x, shift_value_y], order=0, mode="constant", cval=0.0
269
+ )
270
+
271
+ return image, mask
272
+
273
+
274
+ def blur(x, max_sigma=4.0):
275
+ """
276
+ Applies a random Gaussian blur to an image.
277
+
278
+ This function blurs an image by applying a Gaussian filter with a randomly chosen sigma value. The sigma
279
+ represents the standard deviation for the Gaussian kernel and is selected randomly up to a specified maximum.
280
+ The blurring is applied while preserving the range of the image's intensity values and maintaining any
281
+ zero-valued pixels as they are.
282
+
283
+ Parameters
284
+ ----------
285
+ x : ndarray
286
+ The input image to be blurred. The image can have any number of channels, but must be in a format
287
+ where the channels are the last dimension (YXC format).
288
+ max_sigma : float, optional
289
+ The maximum value for the standard deviation of the Gaussian blur. Default is 4.0.
290
+
291
+ Returns
292
+ -------
293
+ ndarray
294
+ The blurred image. The output will have the same shape and type as the input image.
295
+
296
+ Notes
297
+ -----
298
+ - The function ensures that zero-valued pixels in the input image remain unchanged after the blurring,
299
+ which can be important for maintaining masks or other specific regions within the image.
300
+ - Gaussian blurring is commonly used in image processing to reduce image noise and detail by smoothing.
301
+ """
302
+
303
+ sigma = np.random.random() * max_sigma
304
+ loc_i, loc_j, loc_c = np.where(x == 0.0)
305
+ x = gaussian(x, sigma, channel_axis=-1, preserve_range=True)
306
+ x[loc_i, loc_j, loc_c] = 0.0
307
+
308
+ return x
@@ -0,0 +1,74 @@
1
+ from typing import Union
2
+
3
+ import numpy as np
4
+
5
+
6
+ def _fix_no_contrast(frames: np.ndarray, value: Union[float, int] = 1):
7
+ """
8
+ Ensures that frames with no contrast (i.e., containing only a single unique value) are adjusted.
9
+
10
+ This function modifies frames that lack contrast by adding a small value to the first pixel in
11
+ the affected frame. This prevents downstream issues in image processing pipelines that require
12
+ a minimum level of contrast.
13
+
14
+ Parameters
15
+ ----------
16
+ frames : ndarray
17
+ A 3D array of shape `(H, W, N)`, where:
18
+ - `H` is the height of the frame,
19
+ - `W` is the width of the frame,
20
+ - `N` is the number of frames or channels.
21
+ Each frame (or channel) is independently checked for contrast.
22
+ value : int or float, optional
23
+ The value to add to the first pixel (`frames[0, 0, k]`) of any frame that lacks contrast.
24
+ Default is `1`.
25
+
26
+ Returns
27
+ -------
28
+ ndarray
29
+ The modified `frames` array, where frames with no contrast have been adjusted.
30
+
31
+ Notes
32
+ -----
33
+ - A frame is determined to have "no contrast" if all its pixel values are identical.
34
+ - Only the first pixel (`[0, 0, k]`) of a no-contrast frame is modified, leaving the rest
35
+ of the frame unchanged.
36
+ """
37
+
38
+ for k in range(frames.shape[2]):
39
+ unique_values = np.unique(frames[:, :, k])
40
+ if len(unique_values) == 1:
41
+ frames[0, 0, k] += value
42
+ return frames
43
+
44
+
45
+ def interpolate_nan_multichannel(frames):
46
+ frames = np.moveaxis(
47
+ [interpolate_nan(frames[:, :, c].copy()) for c in range(frames.shape[-1])],
48
+ 0,
49
+ -1,
50
+ )
51
+ return frames
52
+
53
+
54
+ def interpolate_nan(img, method="nearest"):
55
+ """
56
+ Interpolate NaN on single channel array 2D
57
+ """
58
+ from scipy.interpolate import griddata
59
+
60
+ if np.all(img == 0):
61
+ return img
62
+
63
+ if np.any(img.flatten() != img.flatten()):
64
+ # then need to interpolate
65
+ x_grid, y_grid = np.meshgrid(np.arange(img.shape[1]), np.arange(img.shape[0]))
66
+ mask = [~np.isnan(img)][0]
67
+ x = x_grid[mask].reshape(-1)
68
+ y = y_grid[mask].reshape(-1)
69
+ points = np.array([x, y]).T
70
+ values = img[mask].reshape(-1)
71
+ interp_grid = griddata(points, values, (x_grid, y_grid), method=method)
72
+ return interp_grid
73
+ else:
74
+ return img