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
|
@@ -1,337 +1,378 @@
|
|
|
1
|
-
from skimage.measure._regionprops import
|
|
1
|
+
from skimage.measure._regionprops import (
|
|
2
|
+
RegionProperties,
|
|
3
|
+
regionprops,
|
|
4
|
+
_cached,
|
|
5
|
+
_props_to_dict,
|
|
6
|
+
_infer_number_of_required_args,
|
|
7
|
+
)
|
|
2
8
|
import numpy as np
|
|
3
9
|
import inspect
|
|
4
10
|
import json
|
|
5
11
|
import os
|
|
6
12
|
from scipy.ndimage import find_objects
|
|
13
|
+
from celldetective.log_manager import get_logger
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
7
16
|
|
|
8
17
|
abs_path = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]])
|
|
9
18
|
|
|
10
|
-
with open(os.sep.join([abs_path,
|
|
11
|
-
|
|
19
|
+
with open(os.sep.join([abs_path, "regionprops", "props.json"])) as f:
|
|
20
|
+
PROPS = json.load(f)
|
|
12
21
|
|
|
13
22
|
COL_DTYPES = {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
23
|
+
"area": float,
|
|
24
|
+
"area_bbox": float,
|
|
25
|
+
"area_convex": float,
|
|
26
|
+
"area_filled": float,
|
|
27
|
+
"axis_major_length": float,
|
|
28
|
+
"axis_minor_length": float,
|
|
29
|
+
"bbox": int,
|
|
30
|
+
"centroid": float,
|
|
31
|
+
"centroid_local": float,
|
|
32
|
+
"centroid_weighted": float,
|
|
33
|
+
"centroid_weighted_local": float,
|
|
34
|
+
"coords": object,
|
|
35
|
+
"coords_scaled": object,
|
|
36
|
+
"eccentricity": float,
|
|
37
|
+
"equivalent_diameter_area": float,
|
|
38
|
+
"euler_number": int,
|
|
39
|
+
"extent": float,
|
|
40
|
+
"feret_diameter_max": float,
|
|
41
|
+
"image": object,
|
|
42
|
+
"image_convex": object,
|
|
43
|
+
"image_filled": object,
|
|
44
|
+
"image_intensity": object,
|
|
45
|
+
"inertia_tensor": float,
|
|
46
|
+
"inertia_tensor_eigvals": float,
|
|
47
|
+
"intensity_max": float,
|
|
48
|
+
"intensity_mean": float,
|
|
49
|
+
"intensity_min": float,
|
|
50
|
+
"intensity_std": float,
|
|
51
|
+
"label": int,
|
|
52
|
+
"moments": float,
|
|
53
|
+
"moments_central": float,
|
|
54
|
+
"moments_hu": float,
|
|
55
|
+
"moments_normalized": float,
|
|
56
|
+
"moments_weighted": float,
|
|
57
|
+
"moments_weighted_central": float,
|
|
58
|
+
"moments_weighted_hu": float,
|
|
59
|
+
"moments_weighted_normalized": float,
|
|
60
|
+
"num_pixels": int,
|
|
61
|
+
"orientation": float,
|
|
62
|
+
"perimeter": float,
|
|
63
|
+
"perimeter_crofton": float,
|
|
64
|
+
"slice": object,
|
|
65
|
+
"solidity": float,
|
|
57
66
|
}
|
|
58
67
|
|
|
59
68
|
OBJECT_COLUMNS = [col for col, dtype in COL_DTYPES.items() if dtype == object]
|
|
60
69
|
PROP_VALS = set(PROPS.values())
|
|
61
70
|
|
|
62
71
|
_require_intensity_image = (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
"image_intensity",
|
|
73
|
+
"intensity_max",
|
|
74
|
+
"intensity_mean",
|
|
75
|
+
"intensity_median",
|
|
76
|
+
"intensity_min",
|
|
77
|
+
"intensity_std",
|
|
78
|
+
"moments_weighted",
|
|
79
|
+
"moments_weighted_central",
|
|
80
|
+
"centroid_weighted",
|
|
81
|
+
"centroid_weighted_local",
|
|
82
|
+
"moments_weighted_hu",
|
|
83
|
+
"moments_weighted_normalized",
|
|
75
84
|
)
|
|
76
85
|
|
|
77
86
|
|
|
78
87
|
class CustomRegionProps(RegionProperties):
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
88
|
+
"""
|
|
89
|
+
From https://github.com/scikit-image/scikit-image/blob/main/skimage/measure/_regionprops.py with a modification to not mask the intensity image itself before measurements
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
def __init__(self, channel_names, *args, **kwargs):
|
|
93
|
+
|
|
94
|
+
self.channel_names = channel_names
|
|
95
|
+
if isinstance(self.channel_names, np.ndarray):
|
|
96
|
+
self.channel_names = list(self.channel_names)
|
|
97
|
+
super().__init__(*args, **kwargs)
|
|
98
|
+
|
|
99
|
+
def __getattr__(self, attr):
|
|
100
|
+
|
|
101
|
+
if self.channel_names is not None and self._multichannel:
|
|
102
|
+
assert (
|
|
103
|
+
len(self.channel_names) == self._intensity_image.shape[-1]
|
|
104
|
+
), "Mismatch between provided channel names and the number of channels in the image..."
|
|
105
|
+
|
|
106
|
+
if attr == "__setstate__":
|
|
107
|
+
return self.__getattribute__(attr)
|
|
108
|
+
|
|
109
|
+
if self._intensity_image is None and attr in _require_intensity_image:
|
|
110
|
+
raise AttributeError(
|
|
111
|
+
f"Attribute '{attr}' unavailable when `intensity_image` "
|
|
112
|
+
f"has not been specified."
|
|
113
|
+
)
|
|
114
|
+
if attr in self._extra_properties:
|
|
115
|
+
func = self._extra_properties[attr]
|
|
116
|
+
n_args = _infer_number_of_required_args(func)
|
|
117
|
+
# determine whether func requires intensity image
|
|
118
|
+
if n_args == 2:
|
|
119
|
+
if self._intensity_image is not None:
|
|
120
|
+
if self._multichannel:
|
|
121
|
+
arg_dict = dict(inspect.signature(func).parameters)
|
|
122
|
+
if (
|
|
123
|
+
self.channel_names is not None
|
|
124
|
+
and "target_channel" in arg_dict
|
|
125
|
+
):
|
|
126
|
+
multichannel_list = [
|
|
127
|
+
np.nan for i in range(self.image_intensity.shape[-1])
|
|
128
|
+
]
|
|
129
|
+
len_output = 1
|
|
130
|
+
default_channel = arg_dict["target_channel"]._default
|
|
131
|
+
|
|
132
|
+
if default_channel in self.channel_names:
|
|
133
|
+
|
|
134
|
+
idx = self.channel_names.index(default_channel)
|
|
135
|
+
res = func(self.image, self.image_intensity[..., idx])
|
|
136
|
+
if isinstance(res, tuple):
|
|
137
|
+
len_output = len(res)
|
|
138
|
+
else:
|
|
139
|
+
len_output = 1
|
|
140
|
+
|
|
141
|
+
if len_output > 1:
|
|
142
|
+
multichannel_list = [
|
|
143
|
+
[np.nan] * len_output
|
|
144
|
+
for c in range(len(self.channel_names))
|
|
145
|
+
]
|
|
146
|
+
multichannel_list[idx] = res
|
|
147
|
+
else:
|
|
148
|
+
multichannel_list = [
|
|
149
|
+
np.nan for c in range(len(self.channel_names))
|
|
150
|
+
]
|
|
151
|
+
multichannel_list[idx] = res
|
|
152
|
+
|
|
153
|
+
else:
|
|
154
|
+
print(
|
|
155
|
+
f"Warning... Channel required by custom measurement ({default_channel}) could not be found in your data..."
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
return np.stack(multichannel_list, axis=-1)
|
|
159
|
+
else:
|
|
160
|
+
multichannel_list = [
|
|
161
|
+
func(self.image, self.image_intensity[..., i])
|
|
162
|
+
for i in range(self.image_intensity.shape[-1])
|
|
163
|
+
]
|
|
164
|
+
return np.stack(multichannel_list, axis=-1)
|
|
165
|
+
else:
|
|
166
|
+
return func(self.image, self.image_intensity)
|
|
167
|
+
else:
|
|
168
|
+
raise AttributeError(
|
|
169
|
+
f"intensity image required to calculate {attr}"
|
|
170
|
+
)
|
|
171
|
+
elif n_args == 1:
|
|
172
|
+
return func(self.image)
|
|
173
|
+
else:
|
|
174
|
+
raise AttributeError(
|
|
175
|
+
f"Custom regionprop function's number of arguments must "
|
|
176
|
+
f"be 1 or 2, but {attr} takes {n_args} arguments."
|
|
177
|
+
)
|
|
178
|
+
elif attr in PROPS and attr.lower() == attr:
|
|
179
|
+
if (
|
|
180
|
+
self._intensity_image is None
|
|
181
|
+
and PROPS[attr] in _require_intensity_image
|
|
182
|
+
):
|
|
183
|
+
raise AttributeError(
|
|
184
|
+
f"Attribute '{attr}' unavailable when `intensity_image` "
|
|
185
|
+
f"has not been specified."
|
|
186
|
+
)
|
|
187
|
+
# retrieve deprecated property (excluding old CamelCase ones)
|
|
188
|
+
return getattr(self, PROPS[attr])
|
|
189
|
+
else:
|
|
190
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{attr}'")
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
@_cached
|
|
194
|
+
def image_intensity(self):
|
|
195
|
+
if self._intensity_image is None:
|
|
196
|
+
raise AttributeError("No intensity image specified.")
|
|
197
|
+
image = (
|
|
198
|
+
self.image
|
|
199
|
+
if not self._multichannel
|
|
200
|
+
else np.expand_dims(self.image, self._ndim)
|
|
201
|
+
)
|
|
202
|
+
return self._intensity_image[self.slice]
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def regionprops(
|
|
206
|
+
label_image,
|
|
207
|
+
intensity_image=None,
|
|
208
|
+
cache=True,
|
|
209
|
+
channel_names=None,
|
|
210
|
+
*,
|
|
211
|
+
extra_properties=None,
|
|
212
|
+
spacing=None,
|
|
213
|
+
offset=None,
|
|
214
|
+
):
|
|
215
|
+
"""
|
|
216
|
+
From https://github.com/scikit-image/scikit-image/blob/main/skimage/measure/_regionprops.py with a modification to use CustomRegionProps
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
if label_image.ndim not in (2, 3):
|
|
220
|
+
raise TypeError("Only 2-D and 3-D images supported.")
|
|
221
|
+
|
|
222
|
+
if not np.issubdtype(label_image.dtype, np.integer):
|
|
223
|
+
if np.issubdtype(label_image.dtype, bool):
|
|
224
|
+
raise TypeError(
|
|
225
|
+
"Non-integer image types are ambiguous: "
|
|
226
|
+
"use skimage.measure.label to label the connected "
|
|
227
|
+
"components of label_image, "
|
|
228
|
+
"or label_image.astype(np.uint8) to interpret "
|
|
229
|
+
"the True values as a single label."
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
raise TypeError("Non-integer label_image types are ambiguous")
|
|
233
|
+
|
|
234
|
+
if offset is None:
|
|
235
|
+
offset_arr = np.zeros((label_image.ndim,), dtype=int)
|
|
236
|
+
else:
|
|
237
|
+
offset_arr = np.asarray(offset)
|
|
238
|
+
if offset_arr.ndim != 1 or offset_arr.size != label_image.ndim:
|
|
239
|
+
raise ValueError(
|
|
240
|
+
"Offset should be an array-like of integers "
|
|
241
|
+
"of shape (label_image.ndim,); "
|
|
242
|
+
f"{offset} was provided."
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
regions = []
|
|
246
|
+
|
|
247
|
+
objects = find_objects(label_image)
|
|
248
|
+
for i, sl in enumerate(objects):
|
|
249
|
+
if sl is None:
|
|
250
|
+
continue
|
|
251
|
+
|
|
252
|
+
label = i + 1
|
|
253
|
+
|
|
254
|
+
props = CustomRegionProps(
|
|
255
|
+
channel_names,
|
|
256
|
+
sl,
|
|
257
|
+
label,
|
|
258
|
+
label_image,
|
|
259
|
+
intensity_image,
|
|
260
|
+
cache,
|
|
261
|
+
spacing=spacing,
|
|
262
|
+
extra_properties=extra_properties,
|
|
263
|
+
offset=offset_arr,
|
|
264
|
+
)
|
|
265
|
+
regions.append(props)
|
|
266
|
+
|
|
267
|
+
return regions
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _props_to_dict(regions, properties=("label", "bbox"), separator="-"):
|
|
271
|
+
|
|
272
|
+
out = {}
|
|
273
|
+
n = len(regions)
|
|
274
|
+
for prop in properties:
|
|
275
|
+
r = regions[0]
|
|
276
|
+
# Copy the original property name so the output will have the
|
|
277
|
+
# user-provided property name in the case of deprecated names.
|
|
278
|
+
orig_prop = prop
|
|
279
|
+
# determine the current property name for any deprecated property.
|
|
280
|
+
prop = PROPS.get(prop, prop)
|
|
281
|
+
rp = getattr(r, prop)
|
|
282
|
+
if prop in COL_DTYPES:
|
|
283
|
+
dtype = COL_DTYPES[prop]
|
|
284
|
+
else:
|
|
285
|
+
func = r._extra_properties[prop]
|
|
286
|
+
# dtype = _infer_regionprop_dtype(
|
|
287
|
+
# func,
|
|
288
|
+
# intensity=r._intensity_image is not None,
|
|
289
|
+
# ndim=r.image.ndim,
|
|
290
|
+
# )
|
|
291
|
+
dtype = np.float64
|
|
292
|
+
|
|
293
|
+
# scalars and objects are dedicated one column per prop
|
|
294
|
+
# array properties are raveled into multiple columns
|
|
295
|
+
# for more info, refer to notes 1
|
|
296
|
+
if np.isscalar(rp) or prop in OBJECT_COLUMNS or dtype is np.object_:
|
|
297
|
+
column_buffer = np.empty(n, dtype=dtype)
|
|
298
|
+
for i in range(n):
|
|
299
|
+
column_buffer[i] = regions[i][prop]
|
|
300
|
+
out[orig_prop] = np.copy(column_buffer)
|
|
301
|
+
else:
|
|
302
|
+
# precompute property column names and locations
|
|
303
|
+
modified_props = []
|
|
304
|
+
locs = []
|
|
305
|
+
for ind in np.ndindex(np.shape(rp)):
|
|
306
|
+
modified_props.append(separator.join(map(str, (orig_prop,) + ind)))
|
|
307
|
+
locs.append(ind if len(ind) > 1 else ind[0])
|
|
308
|
+
|
|
309
|
+
# fill temporary column data_array
|
|
310
|
+
n_columns = len(locs)
|
|
311
|
+
column_data = np.empty((n, n_columns), dtype=dtype)
|
|
312
|
+
for k in range(n):
|
|
313
|
+
# we coerce to a numpy array to ensure structures like
|
|
314
|
+
# tuple-of-arrays expand correctly into columns
|
|
315
|
+
rp = np.asarray(regions[k][prop])
|
|
316
|
+
for i, loc in enumerate(locs):
|
|
317
|
+
column_data[k, i] = rp[loc]
|
|
318
|
+
|
|
319
|
+
# add the columns to the output dictionary
|
|
320
|
+
for i, modified_prop in enumerate(modified_props):
|
|
321
|
+
out[modified_prop] = column_data[:, i]
|
|
322
|
+
return out
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def regionprops_table(
|
|
326
|
+
label_image,
|
|
327
|
+
intensity_image=None,
|
|
328
|
+
properties=("label", "bbox"),
|
|
329
|
+
*,
|
|
330
|
+
cache=True,
|
|
331
|
+
separator="-",
|
|
332
|
+
extra_properties=None,
|
|
333
|
+
spacing=None,
|
|
334
|
+
channel_names=None,
|
|
335
|
+
):
|
|
336
|
+
"""
|
|
337
|
+
From https://github.com/scikit-image/scikit-image/blob/main/skimage/measure/_regionprops.py
|
|
338
|
+
"""
|
|
339
|
+
regions = regionprops(
|
|
340
|
+
label_image,
|
|
341
|
+
intensity_image=intensity_image,
|
|
342
|
+
cache=cache,
|
|
343
|
+
extra_properties=extra_properties,
|
|
344
|
+
spacing=spacing,
|
|
345
|
+
channel_names=channel_names,
|
|
346
|
+
)
|
|
347
|
+
if extra_properties is not None:
|
|
348
|
+
properties = list(properties) + [prop.__name__ for prop in extra_properties]
|
|
349
|
+
if len(regions) == 0:
|
|
350
|
+
ndim = label_image.ndim
|
|
351
|
+
label_image = np.zeros((3,) * ndim, dtype=int)
|
|
352
|
+
label_image[(1,) * ndim] = 1
|
|
353
|
+
if intensity_image is not None:
|
|
354
|
+
intensity_image = np.zeros(
|
|
355
|
+
label_image.shape + intensity_image.shape[ndim:],
|
|
356
|
+
dtype=intensity_image.dtype,
|
|
357
|
+
)
|
|
358
|
+
regions = regionprops(
|
|
359
|
+
label_image,
|
|
360
|
+
intensity_image=intensity_image,
|
|
361
|
+
cache=cache,
|
|
362
|
+
extra_properties=extra_properties,
|
|
363
|
+
spacing=spacing,
|
|
364
|
+
channel_names=channel_names,
|
|
365
|
+
)
|
|
366
|
+
out_d = _props_to_dict(regions, properties=properties, separator=separator)
|
|
367
|
+
return {k: v[:0] for k, v in out_d.items()}
|
|
368
|
+
|
|
369
|
+
good_props = []
|
|
370
|
+
for prop in properties:
|
|
371
|
+
try:
|
|
372
|
+
nan_test = [np.isnan(getattr(r, prop)) for r in regions]
|
|
373
|
+
if not np.all(nan_test):
|
|
374
|
+
good_props.append(prop)
|
|
375
|
+
except AttributeError:
|
|
376
|
+
logger.warning(f"Could not measure {prop}... Skip...")
|
|
377
|
+
|
|
378
|
+
return _props_to_dict(regions, properties=good_props, separator=separator)
|