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.
- celldetective/__init__.py +25 -0
- celldetective/__main__.py +62 -43
- celldetective/_version.py +1 -1
- celldetective/extra_properties.py +477 -399
- celldetective/filters.py +192 -97
- celldetective/gui/InitWindow.py +541 -411
- celldetective/gui/__init__.py +0 -15
- celldetective/gui/about.py +44 -39
- celldetective/gui/analyze_block.py +120 -84
- celldetective/gui/base/__init__.py +0 -0
- celldetective/gui/base/channel_norm_generator.py +335 -0
- celldetective/gui/base/components.py +249 -0
- celldetective/gui/base/feature_choice.py +92 -0
- celldetective/gui/base/figure_canvas.py +52 -0
- celldetective/gui/base/list_widget.py +133 -0
- celldetective/gui/{styles.py → base/styles.py} +92 -36
- celldetective/gui/base/utils.py +33 -0
- celldetective/gui/base_annotator.py +900 -767
- celldetective/gui/classifier_widget.py +6 -22
- celldetective/gui/configure_new_exp.py +777 -671
- celldetective/gui/control_panel.py +635 -524
- celldetective/gui/dynamic_progress.py +449 -0
- celldetective/gui/event_annotator.py +2023 -1662
- celldetective/gui/generic_signal_plot.py +1292 -944
- celldetective/gui/gui_utils.py +899 -1289
- celldetective/gui/interactions_block.py +658 -0
- celldetective/gui/interactive_timeseries_viewer.py +447 -0
- celldetective/gui/json_readers.py +48 -15
- celldetective/gui/layouts/__init__.py +5 -0
- celldetective/gui/layouts/background_model_free_layout.py +537 -0
- celldetective/gui/layouts/channel_offset_layout.py +134 -0
- celldetective/gui/layouts/local_correction_layout.py +91 -0
- celldetective/gui/layouts/model_fit_layout.py +372 -0
- celldetective/gui/layouts/operation_layout.py +68 -0
- celldetective/gui/layouts/protocol_designer_layout.py +96 -0
- celldetective/gui/pair_event_annotator.py +3130 -2435
- celldetective/gui/plot_measurements.py +586 -267
- celldetective/gui/plot_signals_ui.py +724 -506
- celldetective/gui/preprocessing_block.py +395 -0
- celldetective/gui/process_block.py +1678 -1831
- celldetective/gui/seg_model_loader.py +580 -473
- celldetective/gui/settings/__init__.py +0 -7
- celldetective/gui/settings/_cellpose_model_params.py +181 -0
- celldetective/gui/settings/_event_detection_model_params.py +95 -0
- celldetective/gui/settings/_segmentation_model_params.py +159 -0
- celldetective/gui/settings/_settings_base.py +77 -65
- celldetective/gui/settings/_settings_event_model_training.py +752 -526
- celldetective/gui/settings/_settings_measurements.py +1133 -964
- celldetective/gui/settings/_settings_neighborhood.py +574 -488
- celldetective/gui/settings/_settings_segmentation_model_training.py +779 -564
- celldetective/gui/settings/_settings_signal_annotator.py +329 -305
- celldetective/gui/settings/_settings_tracking.py +1304 -1094
- celldetective/gui/settings/_stardist_model_params.py +98 -0
- celldetective/gui/survival_ui.py +422 -312
- celldetective/gui/tableUI.py +1665 -1701
- celldetective/gui/table_ops/_maths.py +295 -0
- celldetective/gui/table_ops/_merge_groups.py +140 -0
- celldetective/gui/table_ops/_merge_one_hot.py +95 -0
- celldetective/gui/table_ops/_query_table.py +43 -0
- celldetective/gui/table_ops/_rename_col.py +44 -0
- celldetective/gui/thresholds_gui.py +382 -179
- celldetective/gui/viewers/__init__.py +0 -0
- celldetective/gui/viewers/base_viewer.py +700 -0
- celldetective/gui/viewers/channel_offset_viewer.py +331 -0
- celldetective/gui/viewers/contour_viewer.py +394 -0
- celldetective/gui/viewers/size_viewer.py +153 -0
- celldetective/gui/viewers/spot_detection_viewer.py +341 -0
- celldetective/gui/viewers/threshold_viewer.py +309 -0
- celldetective/gui/workers.py +304 -126
- celldetective/log_manager.py +92 -0
- celldetective/measure.py +1895 -1478
- celldetective/napari/__init__.py +0 -0
- celldetective/napari/utils.py +1025 -0
- celldetective/neighborhood.py +1914 -1448
- celldetective/preprocessing.py +1620 -1220
- celldetective/processes/__init__.py +0 -0
- celldetective/processes/background_correction.py +271 -0
- celldetective/processes/compute_neighborhood.py +894 -0
- celldetective/processes/detect_events.py +246 -0
- celldetective/processes/measure_cells.py +565 -0
- celldetective/processes/segment_cells.py +760 -0
- celldetective/processes/track_cells.py +435 -0
- celldetective/processes/train_segmentation_model.py +694 -0
- celldetective/processes/train_signal_model.py +265 -0
- celldetective/processes/unified_process.py +292 -0
- celldetective/regionprops/_regionprops.py +358 -317
- celldetective/relative_measurements.py +987 -710
- celldetective/scripts/measure_cells.py +313 -212
- celldetective/scripts/measure_relative.py +90 -46
- celldetective/scripts/segment_cells.py +165 -104
- celldetective/scripts/segment_cells_thresholds.py +96 -68
- celldetective/scripts/track_cells.py +198 -149
- celldetective/scripts/train_segmentation_model.py +324 -201
- celldetective/scripts/train_signal_model.py +87 -45
- celldetective/segmentation.py +844 -749
- celldetective/signals.py +3514 -2861
- celldetective/tracking.py +30 -15
- celldetective/utils/__init__.py +0 -0
- celldetective/utils/cellpose_utils/__init__.py +133 -0
- celldetective/utils/color_mappings.py +42 -0
- celldetective/utils/data_cleaning.py +630 -0
- celldetective/utils/data_loaders.py +450 -0
- celldetective/utils/dataset_helpers.py +207 -0
- celldetective/utils/downloaders.py +197 -0
- celldetective/utils/event_detection/__init__.py +8 -0
- celldetective/utils/experiment.py +1782 -0
- celldetective/utils/image_augmenters.py +308 -0
- celldetective/utils/image_cleaning.py +74 -0
- celldetective/utils/image_loaders.py +926 -0
- celldetective/utils/image_transforms.py +335 -0
- celldetective/utils/io.py +62 -0
- celldetective/utils/mask_cleaning.py +348 -0
- celldetective/utils/mask_transforms.py +5 -0
- celldetective/utils/masks.py +184 -0
- celldetective/utils/maths.py +351 -0
- celldetective/utils/model_getters.py +325 -0
- celldetective/utils/model_loaders.py +296 -0
- celldetective/utils/normalization.py +380 -0
- celldetective/utils/parsing.py +465 -0
- celldetective/utils/plots/__init__.py +0 -0
- celldetective/utils/plots/regression.py +53 -0
- celldetective/utils/resources.py +34 -0
- celldetective/utils/stardist_utils/__init__.py +104 -0
- celldetective/utils/stats.py +90 -0
- celldetective/utils/types.py +21 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/METADATA +1 -1
- celldetective-1.5.0b0.dist-info/RECORD +187 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/WHEEL +1 -1
- tests/gui/test_new_project.py +129 -117
- tests/gui/test_project.py +127 -79
- tests/test_filters.py +39 -15
- tests/test_notebooks.py +8 -0
- tests/test_tracking.py +232 -13
- tests/test_utils.py +123 -77
- celldetective/gui/base_components.py +0 -23
- celldetective/gui/layouts.py +0 -1602
- celldetective/gui/processes/compute_neighborhood.py +0 -594
- celldetective/gui/processes/measure_cells.py +0 -360
- celldetective/gui/processes/segment_cells.py +0 -499
- celldetective/gui/processes/track_cells.py +0 -303
- celldetective/gui/processes/train_segmentation_model.py +0 -270
- celldetective/gui/processes/train_signal_model.py +0 -108
- celldetective/gui/table_ops/merge_groups.py +0 -118
- celldetective/gui/viewers.py +0 -1354
- celldetective/io.py +0 -3663
- celldetective/utils.py +0 -3108
- celldetective-1.4.2.dist-info/RECORD +0 -123
- /celldetective/{gui/processes → processes}/downloader.py +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/entry_points.txt +0 -0
- {celldetective-1.4.2.dist-info → celldetective-1.5.0b0.dist-info}/licenses/LICENSE +0 -0
- {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)
|