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,335 @@
1
+ import collections
2
+
3
+ import numpy as np
4
+
5
+
6
+ def consume(iterator):
7
+ """
8
+ adapted from https://github.com/CSBDeep/CSBDeep/blob/main/csbdeep/utils/utils.py
9
+ """
10
+ collections.deque(iterator, maxlen=0)
11
+
12
+
13
+ def axes_check_and_normalize(axes, length=None, disallowed=None, return_allowed=False):
14
+ """
15
+ adapted from https://github.com/CSBDeep/CSBDeep/blob/main/csbdeep/utils/utils.py
16
+ S(ample), T(ime), C(hannel), Z, Y, X
17
+ """
18
+ allowed = "STCZYX"
19
+ assert axes is not None,ValueError("axis cannot be None.")
20
+ axes = str(axes).upper()
21
+ consume(a in allowed for a in axes)
22
+ disallowed is None or consume(a not in disallowed for a in axes)
23
+ consume(axes.count(a) == 1 for a in axes)
24
+ length is None or len(axes) == length
25
+ return (axes, allowed) if return_allowed else axes
26
+
27
+
28
+ def axes_dict(axes):
29
+ """
30
+ adapted from https://github.com/CSBDeep/CSBDeep/blob/main/csbdeep/utils/utils.py
31
+ from axes string to dict
32
+ """
33
+ axes, allowed = axes_check_and_normalize(axes, return_allowed=True)
34
+ return {a: None if axes.find(a) == -1 else axes.find(a) for a in allowed}
35
+ # return collections.namedtuple('Axes',list(allowed))(*[None if axes.find(a) == -1 else axes.find(a) for a in allowed ])
36
+
37
+
38
+ def move_image_axes(x, fr, to, adjust_singletons=False):
39
+ """
40
+ adapted from https://github.com/CSBDeep/CSBDeep/blob/main/csbdeep/utils/utils.py
41
+ x: ndarray
42
+ fr,to: axes string (see `axes_dict`)
43
+ """
44
+ fr = axes_check_and_normalize(fr, length=x.ndim)
45
+ to = axes_check_and_normalize(to)
46
+
47
+ fr_initial = fr
48
+ x_shape_initial = x.shape
49
+ adjust_singletons = bool(adjust_singletons)
50
+ if adjust_singletons:
51
+ # remove axes not present in 'to'
52
+ slices = [slice(None) for _ in x.shape]
53
+ for i, a in enumerate(fr):
54
+ if (a not in to) and (x.shape[i] == 1):
55
+ # remove singleton axis
56
+ slices[i] = 0
57
+ fr = fr.replace(a, "")
58
+ x = x[tuple(slices)]
59
+ # add dummy axes present in 'to'
60
+ for i, a in enumerate(to):
61
+ if a not in fr:
62
+ # add singleton axis
63
+ x = np.expand_dims(x, -1)
64
+ fr += a
65
+
66
+ if set(fr) != set(to):
67
+ _adjusted = (
68
+ "(adjusted to %s and %s) " % (x.shape, fr) if adjust_singletons else ""
69
+ )
70
+ raise ValueError(
71
+ "image with shape %s and axes %s %snot compatible with target axes %s."
72
+ % (x_shape_initial, fr_initial, _adjusted, to)
73
+ )
74
+
75
+ ax_from, ax_to = axes_dict(fr), axes_dict(to)
76
+ if fr == to:
77
+ return x
78
+ return np.moveaxis(x, [ax_from[a] for a in fr], [ax_to[a] for a in fr])
79
+
80
+
81
+ def estimate_unreliable_edge(activation_protocol=[["gauss", 2], ["std", 4]]):
82
+ """
83
+ Safely estimate the distance to the edge of an image in which the filtered image values can be artefactual.
84
+
85
+ Parameters
86
+ ----------
87
+ activation_protocol : list of list, optional
88
+ A list of lists, where each sublist contains a string naming the filter function, followed by its arguments (usually a kernel size).
89
+ Default is [['gauss', 2], ['std', 4]].
90
+
91
+ Returns
92
+ -------
93
+ int or None
94
+ The sum of the kernel sizes in the activation protocol if the protocol
95
+ is not empty. Returns None if the activation protocol is empty.
96
+
97
+ Notes
98
+ -----
99
+ This function assumes that the second element of each sublist in the
100
+ activation protocol is a kernel size.
101
+
102
+ Examples
103
+ --------
104
+ >>> estimate_unreliable_edge([['gauss', 2], ['std', 4]])
105
+ 6
106
+ >>> estimate_unreliable_edge([])
107
+ None
108
+ """
109
+
110
+ if activation_protocol == []:
111
+ return None
112
+ else:
113
+ edge = 0
114
+ for fct in activation_protocol:
115
+ if isinstance(fct[1], (int, np.int_)) and not fct[0] == "invert":
116
+ edge += fct[1]
117
+ return edge
118
+
119
+
120
+ def unpad(img, pad):
121
+ """
122
+ Remove padding from an image.
123
+
124
+ This function removes the specified amount of padding from the borders
125
+ of an image. The padding is assumed to be the same on all sides.
126
+
127
+ Parameters
128
+ ----------
129
+ img : ndarray
130
+ The input image from which the padding will be removed.
131
+ pad : int
132
+ The amount of padding to remove from each side of the image.
133
+
134
+ Returns
135
+ -------
136
+ ndarray
137
+ The image with the padding removed.
138
+
139
+ Raises
140
+ ------
141
+ ValueError
142
+ If `pad` is greater than or equal to half of the smallest dimension
143
+ of `img`.
144
+
145
+ See Also
146
+ --------
147
+ numpy.pad : Pads an array.
148
+
149
+ Notes
150
+ -----
151
+ This function assumes that the input image is a 2D array.
152
+
153
+ Examples
154
+ --------
155
+ >>> import numpy as np
156
+ >>> img = np.array([[0, 0, 0, 0, 0],
157
+ ... [0, 1, 1, 1, 0],
158
+ ... [0, 1, 1, 1, 0],
159
+ ... [0, 1, 1, 1, 0],
160
+ ... [0, 0, 0, 0, 0]])
161
+ >>> unpad(img, 1)
162
+ array([[1, 1, 1],
163
+ [1, 1, 1],
164
+ [1, 1, 1]])
165
+ """
166
+
167
+ return img[pad:-pad, pad:-pad]
168
+
169
+
170
+ def mask_edges(binary_mask, border_size):
171
+ """
172
+ Mask the edges of a binary mask.
173
+
174
+ This function sets the edges of a binary mask to False, effectively
175
+ masking out a border of the specified size.
176
+
177
+ Parameters
178
+ ----------
179
+ binary_mask : ndarray
180
+ A 2D binary mask array where the edges will be masked.
181
+ border_size : int
182
+ The size of the border to mask (set to False) on all sides.
183
+
184
+ Returns
185
+ -------
186
+ ndarray
187
+ The binary mask with the edges masked out.
188
+
189
+ Raises
190
+ ------
191
+ ValueError
192
+ If `border_size` is greater than or equal to half of the smallest
193
+ dimension of `binary_mask`.
194
+
195
+ Notes
196
+ -----
197
+ This function assumes that the input `binary_mask` is a 2D array. The
198
+ input mask is converted to a boolean array before masking the edges.
199
+
200
+ Examples
201
+ --------
202
+ >>> import numpy as np
203
+ >>> binary_mask = np.array([[1, 1, 1, 1, 1],
204
+ ... [1, 1, 1, 1, 1],
205
+ ... [1, 1, 1, 1, 1],
206
+ ... [1, 1, 1, 1, 1],
207
+ ... [1, 1, 1, 1, 1]])
208
+ >>> mask_edges(binary_mask, 1)
209
+ array([[False, False, False, False, False],
210
+ [False, True, True, True, False],
211
+ [False, True, True, True, False],
212
+ [False, True, True, True, False],
213
+ [False, False, False, False, False]])
214
+ """
215
+
216
+ binary_mask = binary_mask.astype(bool)
217
+ binary_mask[:border_size, :] = False
218
+ binary_mask[(binary_mask.shape[0] - border_size) :, :] = False
219
+ binary_mask[:, :border_size] = False
220
+ binary_mask[:, (binary_mask.shape[1] - border_size) :] = False
221
+
222
+ return binary_mask
223
+
224
+
225
+ def _estimate_scale_factor(spatial_calibration, required_spatial_calibration):
226
+ """
227
+ Estimates the scale factor needed to adjust spatial calibration to a required value.
228
+
229
+ This function calculates the scale factor by which spatial dimensions (e.g., in microscopy images)
230
+ should be adjusted to align with a specified calibration standard. This is particularly useful when
231
+ preparing data for analysis with models trained on data of a specific spatial calibration.
232
+
233
+ Parameters
234
+ ----------
235
+ spatial_calibration : float or None
236
+ The current spatial calibration factor of the data, expressed as units per pixel (e.g., micrometers per pixel).
237
+ If None, indicates that the current spatial calibration is unknown or unspecified.
238
+ required_spatial_calibration : float or None
239
+ The spatial calibration factor required for compatibility with the model or analysis standard, expressed
240
+ in the same units as `spatial_calibration`. If None, indicates no adjustment is required.
241
+
242
+ Returns
243
+ -------
244
+ float or None
245
+ The scale factor by which the current data should be rescaled to match the required spatial calibration,
246
+ or None if no scaling is necessary or if insufficient information is provided.
247
+
248
+ Notes
249
+ -----
250
+ - A scale factor close to 1 (within a tolerance defined by `epsilon`) indicates that no significant rescaling
251
+ is needed, and the function returns None.
252
+ - The function issues a warning if a significant rescaling is necessary, indicating the scale factor to be applied.
253
+
254
+ Examples
255
+ --------
256
+ >>> scale_factor = _estimate_scale_factor(spatial_calibration=0.5, required_spatial_calibration=0.25)
257
+ # Each frame will be rescaled by a factor 2.0 to match with the model training data...
258
+
259
+ >>> scale_factor = _estimate_scale_factor(spatial_calibration=None, required_spatial_calibration=0.25)
260
+ # Returns None due to insufficient information about current spatial calibration.
261
+ """
262
+
263
+ if (required_spatial_calibration is not None) * (spatial_calibration is not None):
264
+ scale = spatial_calibration / required_spatial_calibration
265
+ else:
266
+ scale = None
267
+
268
+ epsilon = 0.05
269
+ if scale is not None:
270
+ if not np.all([scale >= (1 - epsilon), scale <= (1 + epsilon)]):
271
+ print(
272
+ f"Each frame will be rescaled by a factor {scale} to match with the model training data..."
273
+ )
274
+ else:
275
+ scale = None
276
+ return scale
277
+
278
+
279
+ def threshold_image(
280
+ img,
281
+ min_threshold,
282
+ max_threshold,
283
+ foreground_value=255.0,
284
+ fill_holes=True,
285
+ edge_exclusion=None,
286
+ ):
287
+ """
288
+
289
+ Threshold the input image to create a binary mask.
290
+
291
+ Parameters
292
+ ----------
293
+ img : ndarray
294
+ The input image to be thresholded.
295
+ min_threshold : float
296
+ The minimum threshold value.
297
+ max_threshold : float
298
+ The maximum threshold value.
299
+ foreground_value : float, optional
300
+ The value assigned to foreground pixels in the binary mask. Default is 255.
301
+ fill_holes : bool, optional
302
+ Whether to fill holes in the binary mask. If True, the binary mask will be processed to fill any holes.
303
+ If False, the binary mask will not be modified. Default is True.
304
+
305
+ Returns
306
+ -------
307
+ ndarray
308
+ The binary mask after thresholding.
309
+
310
+ Notes
311
+ -----
312
+ This function applies a threshold to the input image to create a binary mask. Pixels with values within the specified
313
+ threshold range are considered as foreground and assigned the `foreground_value`, while pixels outside the range are
314
+ considered as background and assigned 0. If `fill_holes` is True, the binary mask will be processed to fill any holes
315
+ using morphological operations.
316
+
317
+ Examples
318
+ --------
319
+ >>> image = np.random.rand(256, 256)
320
+ >>> binary_mask = threshold_image(image, 0.2, 0.8, foreground_value=1., fill_holes=True)
321
+
322
+ """
323
+ from scipy import ndimage as ndi
324
+
325
+ binary = np.zeros_like(img).astype(bool)
326
+ binary[img == img] = (
327
+ (img[img == img] >= min_threshold)
328
+ * (img[img == img] <= max_threshold)
329
+ * foreground_value
330
+ )
331
+ if isinstance(edge_exclusion, (int, np.int_)):
332
+ binary = mask_edges(binary, edge_exclusion)
333
+ if fill_holes:
334
+ binary = ndi.binary_fill_holes(binary.astype(int))
335
+ return binary
@@ -0,0 +1,62 @@
1
+ import os
2
+ from pathlib import Path
3
+ from typing import Union
4
+ import numpy as np
5
+
6
+ from celldetective.utils.image_transforms import axes_check_and_normalize, move_image_axes
7
+
8
+ try:
9
+ from tifffile import imwrite as imsave
10
+ except ImportError:
11
+ from tifffile import imsave
12
+ import warnings
13
+
14
+
15
+ def remove_file_if_exists(file: Union[str, Path]):
16
+ if os.path.exists(file):
17
+ try:
18
+ os.remove(file)
19
+ except Exception as e:
20
+ print(e)
21
+
22
+
23
+ def save_tiff_imagej_compatible(file, img, axes, **imsave_kwargs):
24
+ """Save image in ImageJ-compatible TIFF format.
25
+ adapted from https://github.com/CSBDeep/CSBDeep/blob/main/csbdeep/utils/utils.py
26
+
27
+ Parameters
28
+ ----------
29
+ file : str
30
+ File name
31
+ img : numpy.ndarray
32
+ Image
33
+ axes: str
34
+ Axes of ``img``
35
+ imsave_kwargs : dict, optional
36
+ Keyword arguments for :func:`tifffile.imsave`
37
+
38
+ """
39
+ axes = axes_check_and_normalize(axes, img.ndim, disallowed="S")
40
+
41
+ # convert to imagej-compatible data type
42
+ t = img.dtype
43
+ if "float" in t.name:
44
+ t_new = np.float32
45
+ elif "uint" in t.name:
46
+ t_new = np.uint16 if t.itemsize >= 2 else np.uint8
47
+ elif "int" in t.name:
48
+ t_new = np.int16
49
+ else:
50
+ t_new = t
51
+ img = img.astype(t_new, copy=False)
52
+ if t != t_new:
53
+ warnings.warn(
54
+ "Converting data type from '%s' to ImageJ-compatible '%s'."
55
+ % (t, np.dtype(t_new))
56
+ )
57
+
58
+ # move axes to correct positions for imagej
59
+ img = move_image_axes(img, axes, "TZCYX", True)
60
+
61
+ imsave_kwargs["imagej"] = True
62
+ imsave(file, img, **imsave_kwargs)