celldetective 1.3.8.post1__py3-none-any.whl → 1.3.9.post1__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/_version.py +1 -1
- celldetective/extra_properties.py +113 -17
- celldetective/filters.py +12 -12
- celldetective/gui/btrack_options.py +1 -1
- celldetective/gui/control_panel.py +1 -1
- celldetective/gui/gui_utils.py +4 -4
- celldetective/gui/measurement_options.py +1 -1
- celldetective/gui/plot_signals_ui.py +23 -6
- celldetective/gui/process_block.py +1 -1
- celldetective/gui/processes/measure_cells.py +4 -4
- celldetective/gui/processes/segment_cells.py +3 -3
- celldetective/gui/processes/track_cells.py +4 -4
- celldetective/gui/signal_annotator.py +26 -6
- celldetective/gui/signal_annotator2.py +1 -1
- celldetective/gui/signal_annotator_options.py +1 -1
- celldetective/gui/survival_ui.py +4 -1
- celldetective/gui/thresholds_gui.py +6 -5
- celldetective/io.py +1 -44
- celldetective/measure.py +22 -16
- celldetective/regionprops/__init__.py +1 -0
- celldetective/regionprops/_regionprops.py +310 -0
- celldetective/regionprops/props.json +63 -0
- celldetective/scripts/measure_relative.py +2 -20
- celldetective/segmentation.py +14 -4
- celldetective/utils.py +182 -171
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.post1.dist-info}/METADATA +1 -1
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.post1.dist-info}/RECORD +31 -28
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.post1.dist-info}/LICENSE +0 -0
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.post1.dist-info}/WHEEL +0 -0
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.post1.dist-info}/entry_points.txt +0 -0
- {celldetective-1.3.8.post1.dist-info → celldetective-1.3.9.post1.dist-info}/top_level.txt +0 -0
celldetective/measure.py
CHANGED
|
@@ -6,7 +6,7 @@ from sklearn.metrics import r2_score
|
|
|
6
6
|
from scipy.optimize import curve_fit
|
|
7
7
|
from scipy import ndimage
|
|
8
8
|
from tqdm import tqdm
|
|
9
|
-
from skimage.measure import regionprops_table
|
|
9
|
+
#from skimage.measure import regionprops_table
|
|
10
10
|
from functools import reduce
|
|
11
11
|
from mahotas.features import haralick
|
|
12
12
|
from scipy.ndimage import zoom
|
|
@@ -18,18 +18,20 @@ from skimage.draw import disk as dsk
|
|
|
18
18
|
from skimage.feature import blob_dog, blob_log
|
|
19
19
|
|
|
20
20
|
from celldetective.utils import rename_intensity_column, create_patch_mask, remove_redundant_features, \
|
|
21
|
-
remove_trajectory_measurements, contour_of_instance_segmentation, extract_cols_from_query, step_function, interpolate_nan
|
|
21
|
+
remove_trajectory_measurements, contour_of_instance_segmentation, extract_cols_from_query, step_function, interpolate_nan, _remove_invalid_cols
|
|
22
22
|
from celldetective.preprocessing import field_correction
|
|
23
|
-
import celldetective.extra_properties as extra_properties
|
|
24
23
|
from celldetective.extra_properties import *
|
|
25
24
|
from inspect import getmembers, isfunction
|
|
26
25
|
from skimage.morphology import disk
|
|
27
26
|
from scipy.signal import find_peaks, peak_widths
|
|
28
27
|
|
|
29
28
|
from celldetective.segmentation import filter_image
|
|
29
|
+
from celldetective.regionprops import regionprops_table
|
|
30
30
|
|
|
31
31
|
abs_path = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0], 'celldetective'])
|
|
32
32
|
|
|
33
|
+
|
|
34
|
+
|
|
33
35
|
def measure(stack=None, labels=None, trajectories=None, channel_names=None,
|
|
34
36
|
features=None, intensity_measurement_radii=None, isotropic_operations=['mean'], border_distances=None,
|
|
35
37
|
haralick_options=None, column_labels={'track': "TRACK_ID", 'time': 'FRAME', 'x': 'POSITION_X', 'y': 'POSITION_Y'}, clear_previous=False):
|
|
@@ -212,6 +214,7 @@ def measure(stack=None, labels=None, trajectories=None, channel_names=None,
|
|
|
212
214
|
measurements['ID'] = np.arange(len(df))
|
|
213
215
|
|
|
214
216
|
measurements = measurements.reset_index(drop=True)
|
|
217
|
+
measurements = _remove_invalid_cols(measurements)
|
|
215
218
|
|
|
216
219
|
return measurements
|
|
217
220
|
|
|
@@ -361,27 +364,29 @@ def measure_features(img, label, features=['area', 'intensity_mean'], channels=N
|
|
|
361
364
|
else:
|
|
362
365
|
corrected_image = field_correction(img[:,:,ind].copy(), threshold_on_std=norm['threshold_on_std'], operation=norm['operation'], model=norm['model'], clip=norm['clip'])
|
|
363
366
|
img[:, :, ind] = corrected_image
|
|
367
|
+
|
|
368
|
+
import celldetective.extra_properties as extra_props
|
|
364
369
|
|
|
365
|
-
|
|
366
|
-
|
|
370
|
+
extra = getmembers(extra_props, isfunction)
|
|
371
|
+
extra = [extra[i][0] for i in range(len(extra))]
|
|
367
372
|
|
|
368
373
|
extra_props_list = []
|
|
369
374
|
feats = features.copy()
|
|
370
375
|
for f in features:
|
|
371
|
-
if f in
|
|
376
|
+
if f in extra:
|
|
372
377
|
feats.remove(f)
|
|
373
|
-
extra_props_list.append(getattr(
|
|
378
|
+
extra_props_list.append(getattr(extra_props, f))
|
|
374
379
|
|
|
375
380
|
# Add intensity nan mean if need to measure mean intensities
|
|
376
381
|
if measure_mean_intensities:
|
|
377
|
-
extra_props_list.append(getattr(
|
|
382
|
+
extra_props_list.append(getattr(extra_props, 'intensity_nanmean'))
|
|
378
383
|
|
|
379
384
|
if len(extra_props_list) == 0:
|
|
380
385
|
extra_props_list = None
|
|
381
386
|
else:
|
|
382
387
|
extra_props_list = tuple(extra_props_list)
|
|
383
388
|
|
|
384
|
-
props = regionprops_table(label, intensity_image=img, properties=feats, extra_properties=extra_props_list)
|
|
389
|
+
props = regionprops_table(label, intensity_image=img, properties=feats, extra_properties=extra_props_list, channel_names=channels)
|
|
385
390
|
df_props = pd.DataFrame(props)
|
|
386
391
|
if spot_detection is not None:
|
|
387
392
|
if df_spots is not None:
|
|
@@ -395,8 +400,8 @@ def measure_features(img, label, features=['area', 'intensity_mean'], channels=N
|
|
|
395
400
|
intensity_features = list(np.array(features)[np.array(intensity_features_test)])
|
|
396
401
|
intensity_extra = []
|
|
397
402
|
for s in intensity_features:
|
|
398
|
-
if s in
|
|
399
|
-
intensity_extra.append(getattr(
|
|
403
|
+
if s in extra:
|
|
404
|
+
intensity_extra.append(getattr(extra_props, s))
|
|
400
405
|
intensity_features.remove(s)
|
|
401
406
|
|
|
402
407
|
if len(intensity_features) == 0:
|
|
@@ -407,13 +412,13 @@ def measure_features(img, label, features=['area', 'intensity_mean'], channels=N
|
|
|
407
412
|
|
|
408
413
|
new_intensity_features = intensity_features.copy()
|
|
409
414
|
for int_feat in intensity_features:
|
|
410
|
-
if int_feat in
|
|
415
|
+
if int_feat in extra:
|
|
411
416
|
new_intensity_features.remove(int_feat)
|
|
412
417
|
intensity_features = new_intensity_features
|
|
413
418
|
|
|
414
419
|
if (isinstance(border_dist, int) or isinstance(border_dist, float)):
|
|
415
420
|
border_label = contour_of_instance_segmentation(label, border_dist)
|
|
416
|
-
props_border = regionprops_table(border_label, intensity_image=img, properties=intensity_features)
|
|
421
|
+
props_border = regionprops_table(border_label, intensity_image=img, properties=intensity_features, channel_names=channels)
|
|
417
422
|
df_props_border = pd.DataFrame(props_border)
|
|
418
423
|
for c in df_props_border.columns:
|
|
419
424
|
if 'intensity' in c:
|
|
@@ -423,7 +428,7 @@ def measure_features(img, label, features=['area', 'intensity_mean'], channels=N
|
|
|
423
428
|
df_props_border_list = []
|
|
424
429
|
for d in border_dist:
|
|
425
430
|
border_label = contour_of_instance_segmentation(label, d)
|
|
426
|
-
props_border = regionprops_table(border_label, intensity_image=img, properties=intensity_features)
|
|
431
|
+
props_border = regionprops_table(border_label, intensity_image=img, properties=intensity_features, channel_names=channels)
|
|
427
432
|
df_props_border_d = pd.DataFrame(props_border)
|
|
428
433
|
for c in df_props_border_d.columns:
|
|
429
434
|
if 'intensity' in c:
|
|
@@ -832,16 +837,17 @@ def local_normalisation(image, labels, background_intensity, measurement='intens
|
|
|
832
837
|
|
|
833
838
|
def normalise_by_cell(image, labels, distance=5, model='median', operation='subtract', clip=False):
|
|
834
839
|
|
|
840
|
+
import celldetective.extra_properties as extra_props
|
|
835
841
|
|
|
836
842
|
border = contour_of_instance_segmentation(label=labels, distance=distance * (-1))
|
|
837
843
|
if model == 'mean':
|
|
838
844
|
measurement = 'intensity_nanmean'
|
|
839
|
-
extra_props = [getattr(
|
|
845
|
+
extra_props = [getattr(extra_props, measurement)]
|
|
840
846
|
background_intensity = regionprops_table(intensity_image=image, label_image=border,
|
|
841
847
|
extra_properties=extra_props)
|
|
842
848
|
elif model == 'median':
|
|
843
849
|
measurement = 'intensity_median'
|
|
844
|
-
extra_props = [getattr(
|
|
850
|
+
extra_props = [getattr(extra_props, measurement)]
|
|
845
851
|
background_intensity = regionprops_table(intensity_image=image, label_image=border,
|
|
846
852
|
extra_properties=extra_props)
|
|
847
853
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._regionprops import regionprops_table
|
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
from skimage.measure._regionprops import RegionProperties, regionprops, _cached, _props_to_dict, _infer_number_of_required_args
|
|
2
|
+
import numpy as np
|
|
3
|
+
import inspect
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
from scipy.ndimage import find_objects
|
|
7
|
+
|
|
8
|
+
abs_path = os.sep.join([os.path.split(os.path.dirname(os.path.realpath(__file__)))[0]])
|
|
9
|
+
|
|
10
|
+
with open(os.sep.join([abs_path, 'regionprops', 'props.json'])) as f:
|
|
11
|
+
PROPS = json.load(f)
|
|
12
|
+
|
|
13
|
+
COL_DTYPES = {
|
|
14
|
+
'area': float,
|
|
15
|
+
'area_bbox': float,
|
|
16
|
+
'area_convex': float,
|
|
17
|
+
'area_filled': float,
|
|
18
|
+
'axis_major_length': float,
|
|
19
|
+
'axis_minor_length': float,
|
|
20
|
+
'bbox': int,
|
|
21
|
+
'centroid': float,
|
|
22
|
+
'centroid_local': float,
|
|
23
|
+
'centroid_weighted': float,
|
|
24
|
+
'centroid_weighted_local': float,
|
|
25
|
+
'coords': object,
|
|
26
|
+
'coords_scaled': object,
|
|
27
|
+
'eccentricity': float,
|
|
28
|
+
'equivalent_diameter_area': float,
|
|
29
|
+
'euler_number': int,
|
|
30
|
+
'extent': float,
|
|
31
|
+
'feret_diameter_max': float,
|
|
32
|
+
'image': object,
|
|
33
|
+
'image_convex': object,
|
|
34
|
+
'image_filled': object,
|
|
35
|
+
'image_intensity': object,
|
|
36
|
+
'inertia_tensor': float,
|
|
37
|
+
'inertia_tensor_eigvals': float,
|
|
38
|
+
'intensity_max': float,
|
|
39
|
+
'intensity_mean': float,
|
|
40
|
+
'intensity_min': float,
|
|
41
|
+
'intensity_std': float,
|
|
42
|
+
'label': int,
|
|
43
|
+
'moments': float,
|
|
44
|
+
'moments_central': float,
|
|
45
|
+
'moments_hu': float,
|
|
46
|
+
'moments_normalized': float,
|
|
47
|
+
'moments_weighted': float,
|
|
48
|
+
'moments_weighted_central': float,
|
|
49
|
+
'moments_weighted_hu': float,
|
|
50
|
+
'moments_weighted_normalized': float,
|
|
51
|
+
'num_pixels': int,
|
|
52
|
+
'orientation': float,
|
|
53
|
+
'perimeter': float,
|
|
54
|
+
'perimeter_crofton': float,
|
|
55
|
+
'slice': object,
|
|
56
|
+
'solidity': float,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
OBJECT_COLUMNS = [col for col, dtype in COL_DTYPES.items() if dtype == object]
|
|
60
|
+
PROP_VALS = set(PROPS.values())
|
|
61
|
+
|
|
62
|
+
class CustomRegionProps(RegionProperties):
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
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
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
def __init__(self, channel_names, *args, **kwargs):
|
|
69
|
+
|
|
70
|
+
self.channel_names = channel_names
|
|
71
|
+
if isinstance(self.channel_names, np.ndarray):
|
|
72
|
+
self.channel_names = list(self.channel_names)
|
|
73
|
+
super().__init__(*args, **kwargs)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def __getattr__(self, attr):
|
|
77
|
+
|
|
78
|
+
if self.channel_names is not None and self._multichannel:
|
|
79
|
+
assert len(self.channel_names)==self._intensity_image.shape[-1],'Mismatch between provided channel names and the number of channels in the image...'
|
|
80
|
+
|
|
81
|
+
if attr == "__setstate__":
|
|
82
|
+
# When deserializing this object with pickle, `__setstate__`
|
|
83
|
+
# is accessed before any other attributes like `self._intensity_image`
|
|
84
|
+
# are available which leads to a RecursionError when trying to
|
|
85
|
+
# access them later on in this function. So guard against this by
|
|
86
|
+
# provoking the default AttributeError (gh-6465).
|
|
87
|
+
return self.__getattribute__(attr)
|
|
88
|
+
|
|
89
|
+
if self._intensity_image is None and attr in _require_intensity_image:
|
|
90
|
+
raise AttributeError(
|
|
91
|
+
f"Attribute '{attr}' unavailable when `intensity_image` "
|
|
92
|
+
f"has not been specified."
|
|
93
|
+
)
|
|
94
|
+
if attr in self._extra_properties:
|
|
95
|
+
func = self._extra_properties[attr]
|
|
96
|
+
n_args = _infer_number_of_required_args(func)
|
|
97
|
+
# determine whether func requires intensity image
|
|
98
|
+
if n_args == 2:
|
|
99
|
+
if self._intensity_image is not None:
|
|
100
|
+
if self._multichannel:
|
|
101
|
+
arg_dict = dict(inspect.signature(func).parameters)
|
|
102
|
+
if self.channel_names is not None and 'target_channel' in arg_dict:
|
|
103
|
+
multichannel_list = [np.nan for i in range(self.image_intensity.shape[-1])]
|
|
104
|
+
default_channel = arg_dict['target_channel']._default
|
|
105
|
+
if default_channel in self.channel_names:
|
|
106
|
+
idx = self.channel_names.index(default_channel)
|
|
107
|
+
multichannel_list[idx] = func(self.image, self.image_intensity[..., idx])
|
|
108
|
+
else:
|
|
109
|
+
print(f'Warning... Channel required by custom measurement ({default_channel}) could not be found in your data...')
|
|
110
|
+
return np.stack(multichannel_list, axis=-1)
|
|
111
|
+
else:
|
|
112
|
+
multichannel_list = [
|
|
113
|
+
func(self.image, self.image_intensity[..., i])
|
|
114
|
+
for i in range(self.image_intensity.shape[-1])
|
|
115
|
+
]
|
|
116
|
+
return np.stack(multichannel_list, axis=-1)
|
|
117
|
+
else:
|
|
118
|
+
return func(self.image, self.image_intensity)
|
|
119
|
+
else:
|
|
120
|
+
raise AttributeError(
|
|
121
|
+
f'intensity image required to calculate {attr}'
|
|
122
|
+
)
|
|
123
|
+
elif n_args == 1:
|
|
124
|
+
return func(self.image)
|
|
125
|
+
else:
|
|
126
|
+
raise AttributeError(
|
|
127
|
+
f'Custom regionprop function\'s number of arguments must '
|
|
128
|
+
f'be 1 or 2, but {attr} takes {n_args} arguments.'
|
|
129
|
+
)
|
|
130
|
+
elif attr in PROPS and attr.lower() == attr:
|
|
131
|
+
if (
|
|
132
|
+
self._intensity_image is None
|
|
133
|
+
and PROPS[attr] in _require_intensity_image
|
|
134
|
+
):
|
|
135
|
+
raise AttributeError(
|
|
136
|
+
f"Attribute '{attr}' unavailable when `intensity_image` "
|
|
137
|
+
f"has not been specified."
|
|
138
|
+
)
|
|
139
|
+
# retrieve deprecated property (excluding old CamelCase ones)
|
|
140
|
+
return getattr(self, PROPS[attr])
|
|
141
|
+
else:
|
|
142
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{attr}'")
|
|
143
|
+
|
|
144
|
+
@property
|
|
145
|
+
@_cached
|
|
146
|
+
def image_intensity(self):
|
|
147
|
+
if self._intensity_image is None:
|
|
148
|
+
raise AttributeError('No intensity image specified.')
|
|
149
|
+
image = (
|
|
150
|
+
self.image
|
|
151
|
+
if not self._multichannel
|
|
152
|
+
else np.expand_dims(self.image, self._ndim)
|
|
153
|
+
)
|
|
154
|
+
return self._intensity_image[self.slice]
|
|
155
|
+
|
|
156
|
+
def regionprops(label_image, intensity_image=None,cache=True,channel_names=None,*,extra_properties=None,spacing=None,offset=None):
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
From https://github.com/scikit-image/scikit-image/blob/main/skimage/measure/_regionprops.py with a modification to use CustomRegionProps
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
if label_image.ndim not in (2, 3):
|
|
163
|
+
raise TypeError('Only 2-D and 3-D images supported.')
|
|
164
|
+
|
|
165
|
+
if not np.issubdtype(label_image.dtype, np.integer):
|
|
166
|
+
if np.issubdtype(label_image.dtype, bool):
|
|
167
|
+
raise TypeError(
|
|
168
|
+
'Non-integer image types are ambiguous: '
|
|
169
|
+
'use skimage.measure.label to label the connected '
|
|
170
|
+
'components of label_image, '
|
|
171
|
+
'or label_image.astype(np.uint8) to interpret '
|
|
172
|
+
'the True values as a single label.'
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
raise TypeError('Non-integer label_image types are ambiguous')
|
|
176
|
+
|
|
177
|
+
if offset is None:
|
|
178
|
+
offset_arr = np.zeros((label_image.ndim,), dtype=int)
|
|
179
|
+
else:
|
|
180
|
+
offset_arr = np.asarray(offset)
|
|
181
|
+
if offset_arr.ndim != 1 or offset_arr.size != label_image.ndim:
|
|
182
|
+
raise ValueError(
|
|
183
|
+
'Offset should be an array-like of integers '
|
|
184
|
+
'of shape (label_image.ndim,); '
|
|
185
|
+
f'{offset} was provided.'
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
regions = []
|
|
189
|
+
|
|
190
|
+
objects = find_objects(label_image)
|
|
191
|
+
for i, sl in enumerate(objects):
|
|
192
|
+
if sl is None:
|
|
193
|
+
continue
|
|
194
|
+
|
|
195
|
+
label = i + 1
|
|
196
|
+
|
|
197
|
+
props = CustomRegionProps(
|
|
198
|
+
channel_names,
|
|
199
|
+
sl,
|
|
200
|
+
label,
|
|
201
|
+
label_image,
|
|
202
|
+
intensity_image,
|
|
203
|
+
cache,
|
|
204
|
+
spacing=spacing,
|
|
205
|
+
extra_properties=extra_properties,
|
|
206
|
+
offset=offset_arr,
|
|
207
|
+
)
|
|
208
|
+
regions.append(props)
|
|
209
|
+
|
|
210
|
+
return regions
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
def _props_to_dict(regions, properties=('label', 'bbox'), separator='-'):
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
out = {}
|
|
217
|
+
n = len(regions)
|
|
218
|
+
for prop in properties:
|
|
219
|
+
r = regions[0]
|
|
220
|
+
# Copy the original property name so the output will have the
|
|
221
|
+
# user-provided property name in the case of deprecated names.
|
|
222
|
+
orig_prop = prop
|
|
223
|
+
# determine the current property name for any deprecated property.
|
|
224
|
+
prop = PROPS.get(prop, prop)
|
|
225
|
+
rp = getattr(r, prop)
|
|
226
|
+
if prop in COL_DTYPES:
|
|
227
|
+
dtype = COL_DTYPES[prop]
|
|
228
|
+
else:
|
|
229
|
+
func = r._extra_properties[prop]
|
|
230
|
+
# dtype = _infer_regionprop_dtype(
|
|
231
|
+
# func,
|
|
232
|
+
# intensity=r._intensity_image is not None,
|
|
233
|
+
# ndim=r.image.ndim,
|
|
234
|
+
# )
|
|
235
|
+
dtype = np.float64
|
|
236
|
+
|
|
237
|
+
# scalars and objects are dedicated one column per prop
|
|
238
|
+
# array properties are raveled into multiple columns
|
|
239
|
+
# for more info, refer to notes 1
|
|
240
|
+
if np.isscalar(rp) or prop in OBJECT_COLUMNS or dtype is np.object_:
|
|
241
|
+
column_buffer = np.empty(n, dtype=dtype)
|
|
242
|
+
for i in range(n):
|
|
243
|
+
column_buffer[i] = regions[i][prop]
|
|
244
|
+
out[orig_prop] = np.copy(column_buffer)
|
|
245
|
+
else:
|
|
246
|
+
# precompute property column names and locations
|
|
247
|
+
modified_props = []
|
|
248
|
+
locs = []
|
|
249
|
+
for ind in np.ndindex(np.shape(rp)):
|
|
250
|
+
modified_props.append(separator.join(map(str, (orig_prop,) + ind)))
|
|
251
|
+
locs.append(ind if len(ind) > 1 else ind[0])
|
|
252
|
+
|
|
253
|
+
# fill temporary column data_array
|
|
254
|
+
n_columns = len(locs)
|
|
255
|
+
column_data = np.empty((n, n_columns), dtype=dtype)
|
|
256
|
+
for k in range(n):
|
|
257
|
+
# we coerce to a numpy array to ensure structures like
|
|
258
|
+
# tuple-of-arrays expand correctly into columns
|
|
259
|
+
rp = np.asarray(regions[k][prop])
|
|
260
|
+
for i, loc in enumerate(locs):
|
|
261
|
+
column_data[k, i] = rp[loc]
|
|
262
|
+
|
|
263
|
+
# add the columns to the output dictionary
|
|
264
|
+
for i, modified_prop in enumerate(modified_props):
|
|
265
|
+
out[modified_prop] = column_data[:, i]
|
|
266
|
+
return out
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def regionprops_table(label_image,intensity_image=None,properties=('label', 'bbox'),*,cache=True,separator='-',extra_properties=None,spacing=None,channel_names=None):
|
|
270
|
+
|
|
271
|
+
"""
|
|
272
|
+
From https://github.com/scikit-image/scikit-image/blob/main/skimage/measure/_regionprops.py
|
|
273
|
+
"""
|
|
274
|
+
regions = regionprops(
|
|
275
|
+
label_image,
|
|
276
|
+
intensity_image=intensity_image,
|
|
277
|
+
cache=cache,
|
|
278
|
+
extra_properties=extra_properties,
|
|
279
|
+
spacing=spacing,
|
|
280
|
+
channel_names=channel_names,
|
|
281
|
+
)
|
|
282
|
+
if extra_properties is not None:
|
|
283
|
+
properties = list(properties) + [prop.__name__ for prop in extra_properties]
|
|
284
|
+
if len(regions) == 0:
|
|
285
|
+
ndim = label_image.ndim
|
|
286
|
+
label_image = np.zeros((3,) * ndim, dtype=int)
|
|
287
|
+
label_image[(1,) * ndim] = 1
|
|
288
|
+
if intensity_image is not None:
|
|
289
|
+
intensity_image = np.zeros(
|
|
290
|
+
label_image.shape + intensity_image.shape[ndim:],
|
|
291
|
+
dtype=intensity_image.dtype,
|
|
292
|
+
)
|
|
293
|
+
regions = regionprops(
|
|
294
|
+
label_image,
|
|
295
|
+
intensity_image=intensity_image,
|
|
296
|
+
cache=cache,
|
|
297
|
+
extra_properties=extra_properties,
|
|
298
|
+
spacing=spacing,
|
|
299
|
+
channel_names=channel_names,
|
|
300
|
+
)
|
|
301
|
+
out_d = _props_to_dict(regions, properties=properties, separator=separator)
|
|
302
|
+
return {k: v[:0] for k, v in out_d.items()}
|
|
303
|
+
|
|
304
|
+
good_props = []
|
|
305
|
+
for prop in properties:
|
|
306
|
+
nan_test = [np.isnan(getattr(r,prop)) for r in regions]
|
|
307
|
+
if not np.all(nan_test):
|
|
308
|
+
good_props.append(prop)
|
|
309
|
+
|
|
310
|
+
return _props_to_dict(regions, properties=good_props, separator=separator)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Area": "area",
|
|
3
|
+
"BoundingBox": "bbox",
|
|
4
|
+
"BoundingBoxArea": "area_bbox",
|
|
5
|
+
"bbox_area": "area_bbox",
|
|
6
|
+
"CentralMoments": "moments_central",
|
|
7
|
+
"Centroid": "centroid",
|
|
8
|
+
"ConvexArea": "area_convex",
|
|
9
|
+
"convex_area": "area_convex",
|
|
10
|
+
"ConvexImage": "image_convex",
|
|
11
|
+
"convex_image": "image_convex",
|
|
12
|
+
"Coordinates": "coords",
|
|
13
|
+
"Eccentricity": "eccentricity",
|
|
14
|
+
"EquivDiameter": "equivalent_diameter_area",
|
|
15
|
+
"equivalent_diameter": "equivalent_diameter_area",
|
|
16
|
+
"EulerNumber": "euler_number",
|
|
17
|
+
"Extent": "extent",
|
|
18
|
+
"FeretDiameter": "feret_diameter_max",
|
|
19
|
+
"FeretDiameterMax": "feret_diameter_max",
|
|
20
|
+
"FilledArea": "area_filled",
|
|
21
|
+
"filled_area": "area_filled",
|
|
22
|
+
"FilledImage": "image_filled",
|
|
23
|
+
"filled_image": "image_filled",
|
|
24
|
+
"HuMoments": "moments_hu",
|
|
25
|
+
"Image": "image",
|
|
26
|
+
"InertiaTensor": "inertia_tensor",
|
|
27
|
+
"InertiaTensorEigvals": "inertia_tensor_eigvals",
|
|
28
|
+
"IntensityImage": "image_intensity",
|
|
29
|
+
"intensity_image": "image_intensity",
|
|
30
|
+
"Label": "label",
|
|
31
|
+
"LocalCentroid": "centroid_local",
|
|
32
|
+
"local_centroid": "centroid_local",
|
|
33
|
+
"MajorAxisLength": "axis_major_length",
|
|
34
|
+
"major_axis_length": "axis_major_length",
|
|
35
|
+
"MaxIntensity": "intensity_max",
|
|
36
|
+
"max_intensity": "intensity_max",
|
|
37
|
+
"MeanIntensity": "intensity_mean",
|
|
38
|
+
"mean_intensity": "intensity_mean",
|
|
39
|
+
"MinIntensity": "intensity_min",
|
|
40
|
+
"min_intensity": "intensity_min",
|
|
41
|
+
"std_intensity": "intensity_std",
|
|
42
|
+
"MinorAxisLength": "axis_minor_length",
|
|
43
|
+
"minor_axis_length": "axis_minor_length",
|
|
44
|
+
"Moments": "moments",
|
|
45
|
+
"NormalizedMoments": "moments_normalized",
|
|
46
|
+
"Orientation": "orientation",
|
|
47
|
+
"Perimeter": "perimeter",
|
|
48
|
+
"CroftonPerimeter": "perimeter_crofton",
|
|
49
|
+
"Slice": "slice",
|
|
50
|
+
"Solidity": "solidity",
|
|
51
|
+
"WeightedCentralMoments": "moments_weighted_central",
|
|
52
|
+
"weighted_moments_central": "moments_weighted_central",
|
|
53
|
+
"WeightedCentroid": "centroid_weighted",
|
|
54
|
+
"weighted_centroid": "centroid_weighted",
|
|
55
|
+
"WeightedHuMoments": "moments_weighted_hu",
|
|
56
|
+
"weighted_moments_hu": "moments_weighted_hu",
|
|
57
|
+
"WeightedLocalCentroid": "centroid_weighted_local",
|
|
58
|
+
"weighted_local_centroid": "centroid_weighted_local",
|
|
59
|
+
"WeightedMoments": "moments_weighted",
|
|
60
|
+
"weighted_moments": "moments_weighted",
|
|
61
|
+
"WeightedNormalizedMoments": "moments_weighted_normalized",
|
|
62
|
+
"weighted_moments_normalized": "moments_weighted_normalized"
|
|
63
|
+
}
|
|
@@ -34,36 +34,18 @@ movie_prefix = ConfigSectionMap(config, "MovieSettings")["movie_prefix"]
|
|
|
34
34
|
spatial_calibration = float(ConfigSectionMap(config, "MovieSettings")["pxtoum"])
|
|
35
35
|
time_calibration = float(ConfigSectionMap(config, "MovieSettings")["frametomin"])
|
|
36
36
|
len_movie = float(ConfigSectionMap(config, "MovieSettings")["len_movie"])
|
|
37
|
-
channel_names,
|
|
37
|
+
channel_names, channel_indices = extract_experiment_channels(expfolder)
|
|
38
38
|
nbr_channels = len(channel_names)
|
|
39
39
|
|
|
40
40
|
# from tracking instructions, fetch btrack config, features, haralick, clean_traj, idea: fetch custom timeline?
|
|
41
41
|
instr_path = PurePath(expfolder, Path(f"{instruction_file}"))
|
|
42
42
|
previous_pair_table_path = pos + os.sep.join(['output', 'tables', 'trajectories_pairs.csv'])
|
|
43
43
|
|
|
44
|
-
# if os.path.exists(instr_path):
|
|
45
|
-
# print(f"Neighborhood instructions has been successfully located.")
|
|
46
|
-
# with open(instr_path, 'r') as f:
|
|
47
|
-
# instructions = json.load(f)
|
|
48
|
-
# print("Reading the following instructions: ", instructions)
|
|
49
|
-
|
|
50
|
-
# if 'distance' in instructions:
|
|
51
|
-
# distance = instructions['distance'][0]
|
|
52
|
-
# else:
|
|
53
|
-
# distance = None
|
|
54
|
-
# else:
|
|
55
|
-
# print('No measurement instructions found')
|
|
56
|
-
# os.abort()
|
|
57
44
|
|
|
58
45
|
previous_neighborhoods = []
|
|
59
46
|
associated_reference_population = []
|
|
60
47
|
|
|
61
|
-
|
|
62
|
-
# print('No measurement could be performed. Check your inputs.')
|
|
63
|
-
# print('Done.')
|
|
64
|
-
# os.abort()
|
|
65
|
-
# #distance = 0
|
|
66
|
-
# else:
|
|
48
|
+
|
|
67
49
|
neighborhoods_to_measure = extract_neighborhoods_from_pickles(pos)
|
|
68
50
|
all_df_pairs = []
|
|
69
51
|
if os.path.exists(previous_pair_table_path):
|
celldetective/segmentation.py
CHANGED
|
@@ -229,7 +229,7 @@ def segment_from_thresholds(stack, target_channel=0, thresholds=None, view_on_na
|
|
|
229
229
|
return masks
|
|
230
230
|
|
|
231
231
|
def segment_frame_from_thresholds(frame, target_channel=0, thresholds=None, equalize_reference=None,
|
|
232
|
-
filters=None, marker_min_distance=30, marker_footprint_size=20, marker_footprint=None, feature_queries=None, channel_names=None, do_watershed=True):
|
|
232
|
+
filters=None, marker_min_distance=30, marker_footprint_size=20, marker_footprint=None, feature_queries=None, channel_names=None, do_watershed=True, edge_exclusion=True, fill_holes=True):
|
|
233
233
|
|
|
234
234
|
"""
|
|
235
235
|
Segments objects within a single frame based on intensity thresholds and optional image processing steps.
|
|
@@ -269,14 +269,24 @@ def segment_frame_from_thresholds(frame, target_channel=0, thresholds=None, equa
|
|
|
269
269
|
|
|
270
270
|
"""
|
|
271
271
|
|
|
272
|
+
if frame.ndim==2:
|
|
273
|
+
frame = frame[:,:,np.newaxis]
|
|
272
274
|
img = frame[:,:,target_channel]
|
|
273
|
-
|
|
275
|
+
|
|
276
|
+
if np.any(img!=img):
|
|
277
|
+
img = interpolate_nan(img)
|
|
278
|
+
|
|
274
279
|
if equalize_reference is not None:
|
|
275
280
|
img = match_histograms(img, equalize_reference)
|
|
281
|
+
|
|
276
282
|
img_mc = frame.copy()
|
|
277
283
|
img = filter_image(img, filters=filters)
|
|
278
|
-
|
|
279
|
-
|
|
284
|
+
if edge_exclusion:
|
|
285
|
+
edge = estimate_unreliable_edge(filters)
|
|
286
|
+
else:
|
|
287
|
+
edge = None
|
|
288
|
+
|
|
289
|
+
binary_image = threshold_image(img, thresholds[0], thresholds[1], fill_holes=fill_holes, edge_exclusion=edge)
|
|
280
290
|
|
|
281
291
|
if do_watershed:
|
|
282
292
|
coords,distance = identify_markers_from_binary(binary_image, marker_min_distance, footprint_size=marker_footprint_size, footprint=marker_footprint, return_edt=True)
|