cellprofiler-library-nightly 5.0.0.dev416__tar.gz → 5.0.0.dev454__tar.gz
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.
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/PKG-INFO +1 -1
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/_version.py +3 -3
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/image_processing.py +1 -1
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/object_processing.py +520 -26
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/__init__.py +3 -0
- cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/modules/_identifyprimaryobjects.py +223 -0
- cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/modules/_identifysecondaryobjects.py +264 -0
- cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/modules/_identifytertiaryobjects.py +66 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_threshold.py +1 -2
- cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/opts/identifyprimaryobjects.py +22 -0
- cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/opts/identifysecondaryobjects.py +8 -0
- cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/opts/identifytertiaryobjects.py +5 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/PKG-INFO +1 -1
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/SOURCES.txt +6 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/LICENSE +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/README.md +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/__init__.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/__init__.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/file_processing.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/measurement.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/segmentation.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_closing.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_colortogray.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_combineobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_convertimagetoobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_convertobjectstoimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_correctilluminationapply.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_crop.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_dilateimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_dilateobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_enhanceedges.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_enhanceorsuppressfeatures.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_erodeimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_erodeobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_expandorshrinkobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_fillobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_gaussianfilter.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_measureimageoverlap.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_measureobjectsizeshape.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_medialaxis.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_medianfilter.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_morph.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_morphologicalskeleton.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_opening.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_overlayobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_overlayoutlines.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_reducenoise.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_removeholes.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_resize.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_savecroppedobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_shrinktoobjectcenters.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_watershed.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/__init__.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/colortogray.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/convertimagetoobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/convertobjectstoimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/correctilluminationapply.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/crop.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/dilateimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/dilateobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/enhanceorsuppressfeatures.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/erodeimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/erodeobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/measureimageoverlap.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/morph.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/objectsizeshapefeatures.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/overlayoutlines.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/removeholes.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/resize.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/shrinktoobjectcenters.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/structuring_elements.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/threshold.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/py.typed +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/types.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/dependency_links.txt +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/requires.txt +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/top_level.txt +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/environment.yml +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/pyproject.toml +0 -0
- {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/setup.cfg +0 -0
{cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cellprofiler-library-nightly
|
|
3
|
-
Version: 5.0.0.
|
|
3
|
+
Version: 5.0.0.dev454
|
|
4
4
|
Summary: cellprofiler-library implements CellProfiler's image processing and mathematical code, and is usable as a standalone library
|
|
5
5
|
Author: Anne Carpenter, Thouis (Ray) Jones, Lee Kamentsky, Vebjorn Ljosa, David Logan, Mark Bray, Madison Swain-Bowden, Allen Goodman, Claire McQuinn, Alice Lucas, Callum Tromans-Coia
|
|
6
6
|
Author-email: Beth Cimini <bcimini@broadinstitute.org>, David Stirling <dstirling@glencoesoftware.com>, Nodar Gogoberidze <ngogober@broadinstitute.org>
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '5.0.0.
|
|
32
|
-
__version_tuple__ = version_tuple = (5, 0, 0, '
|
|
31
|
+
__version__ = version = '5.0.0.dev454'
|
|
32
|
+
__version_tuple__ = version_tuple = (5, 0, 0, 'dev454')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g957fa6e04'
|
|
@@ -382,7 +382,7 @@ def get_threshold_robust_background(
|
|
|
382
382
|
upper_outlier_fraction: float = 0.05,
|
|
383
383
|
averaging_method: Threshold.AveragingMethod = Threshold.AveragingMethod.MEAN,
|
|
384
384
|
variance_method: Threshold.VarianceMethod = Threshold.VarianceMethod.STANDARD_DEVIATION,
|
|
385
|
-
number_of_deviations:
|
|
385
|
+
number_of_deviations: float = 2.0,
|
|
386
386
|
) -> float:
|
|
387
387
|
"""Calculate threshold based on mean & standard deviation.
|
|
388
388
|
The threshold is calculated by trimming the top and bottom 5% of
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
import centrosome.cpmorphology
|
|
3
1
|
import numpy
|
|
2
|
+
from numpy.typing import NDArray
|
|
4
3
|
import scipy.ndimage
|
|
5
4
|
import skimage.morphology
|
|
6
|
-
import cellprofiler.utilities.morphology
|
|
7
|
-
import mahotas
|
|
8
5
|
import matplotlib.cm
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
6
|
+
import mahotas
|
|
7
|
+
import centrosome.cpmorphology
|
|
8
|
+
import cellprofiler.utilities.morphology
|
|
9
|
+
from typing import Optional, Literal, Tuple, Union
|
|
10
|
+
from cellprofiler_library.types import ImageAnyMask, ObjectLabel, ImageColor, ImageGrayscale, Image2DGrayscale, Image2DGrayscaleMask, ImageBinary, ObjectSegmentation, StructuringElement
|
|
11
|
+
from cellprofiler_library.types import Image2DBinary
|
|
12
|
+
from cellprofiler_library.opts.identifyprimaryobjects import UnclumpMethod, WatershedMethod, FillHolesMethod
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
def shrink_to_point(labels, fill):
|
|
14
16
|
"""
|
|
@@ -17,22 +19,22 @@ def shrink_to_point(labels, fill):
|
|
|
17
19
|
"""
|
|
18
20
|
|
|
19
21
|
if fill:
|
|
20
|
-
labels=centrosome.cpmorphology.fill_labeled_holes(labels)
|
|
22
|
+
labels = centrosome.cpmorphology.fill_labeled_holes(labels)
|
|
21
23
|
return centrosome.cpmorphology.binary_shrink(labels)
|
|
22
24
|
|
|
25
|
+
|
|
23
26
|
def shrink_defined_pixels(labels, fill, iterations):
|
|
24
27
|
"""
|
|
25
28
|
Remove pixels around the perimeter of an object unless
|
|
26
|
-
doing so would change the object’s Euler number `iterations` times.
|
|
29
|
+
doing so would change the object’s Euler number `iterations` times.
|
|
27
30
|
Processing stops automatically when there are no more pixels to
|
|
28
31
|
remove.
|
|
29
32
|
"""
|
|
30
33
|
|
|
31
34
|
if fill:
|
|
32
|
-
labels=centrosome.cpmorphology.fill_labeled_holes(labels)
|
|
33
|
-
return centrosome.cpmorphology.binary_shrink(
|
|
34
|
-
|
|
35
|
-
)
|
|
35
|
+
labels = centrosome.cpmorphology.fill_labeled_holes(labels)
|
|
36
|
+
return centrosome.cpmorphology.binary_shrink(labels, iterations=iterations)
|
|
37
|
+
|
|
36
38
|
|
|
37
39
|
def add_dividing_lines(labels):
|
|
38
40
|
"""
|
|
@@ -51,20 +53,21 @@ def add_dividing_lines(labels):
|
|
|
51
53
|
|
|
52
54
|
return out_labels
|
|
53
55
|
|
|
56
|
+
|
|
54
57
|
def skeletonize(labels):
|
|
55
58
|
"""
|
|
56
59
|
Erode each object to its skeleton.
|
|
57
60
|
"""
|
|
58
61
|
return centrosome.cpmorphology.skeletonize_labels(labels)
|
|
59
62
|
|
|
63
|
+
|
|
60
64
|
def despur(labels, iterations):
|
|
61
65
|
"""
|
|
62
66
|
Remove or reduce the length of spurs in a skeletonized
|
|
63
67
|
image. The algorithm reduces spur size by `iterations` pixels.
|
|
64
68
|
"""
|
|
65
|
-
return centrosome.cpmorphology.spur(
|
|
66
|
-
|
|
67
|
-
)
|
|
69
|
+
return centrosome.cpmorphology.spur(labels, iterations=iterations)
|
|
70
|
+
|
|
68
71
|
|
|
69
72
|
def expand(labels, distance):
|
|
70
73
|
"""
|
|
@@ -85,6 +88,7 @@ def expand(labels, distance):
|
|
|
85
88
|
|
|
86
89
|
return out_labels
|
|
87
90
|
|
|
91
|
+
|
|
88
92
|
def expand_until_touching(labels):
|
|
89
93
|
"""
|
|
90
94
|
Expand objects, assigning every pixel in the
|
|
@@ -94,22 +98,23 @@ def expand_until_touching(labels):
|
|
|
94
98
|
distance = numpy.max(labels.shape)
|
|
95
99
|
return expand(labels, distance)
|
|
96
100
|
|
|
101
|
+
|
|
97
102
|
def expand_defined_pixels(labels, iterations):
|
|
98
103
|
"""
|
|
99
104
|
Expand each object by adding background pixels
|
|
100
|
-
adjacent to the image `iterations` times. Processing stops
|
|
105
|
+
adjacent to the image `iterations` times. Processing stops
|
|
101
106
|
automatically if there are no more background pixels.
|
|
102
107
|
"""
|
|
103
108
|
return expand(labels, iterations)
|
|
104
109
|
|
|
105
110
|
def merge_objects(labels_x, labels_y, dimensions):
|
|
106
111
|
"""
|
|
107
|
-
Make overlapping objects combine into a single object, taking
|
|
112
|
+
Make overlapping objects combine into a single object, taking
|
|
108
113
|
on the label of the object from the initial set.
|
|
109
114
|
|
|
110
|
-
If an object overlaps multiple objects, each pixel of the added
|
|
111
|
-
object will be assigned to the closest object from the initial
|
|
112
|
-
set. This is primarily useful when the same objects appear in
|
|
115
|
+
If an object overlaps multiple objects, each pixel of the added
|
|
116
|
+
object will be assigned to the closest object from the initial
|
|
117
|
+
set. This is primarily useful when the same objects appear in
|
|
113
118
|
both sets.
|
|
114
119
|
"""
|
|
115
120
|
output = numpy.zeros_like(labels_x)
|
|
@@ -129,7 +134,7 @@ def merge_objects(labels_x, labels_y, dimensions):
|
|
|
129
134
|
output = numpy.where(mask, labels_y, output)
|
|
130
135
|
labels_y[mask] = 0
|
|
131
136
|
to_segment = numpy.logical_or(labels_x > 0, labels_y > 0)
|
|
132
|
-
if dimensions == 2:
|
|
137
|
+
if dimensions == 2:
|
|
133
138
|
distances, (i, j) = scipy.ndimage.distance_transform_edt(
|
|
134
139
|
labels_x == 0, return_indices=True
|
|
135
140
|
)
|
|
@@ -139,18 +144,20 @@ def merge_objects(labels_x, labels_y, dimensions):
|
|
|
139
144
|
labels_x == 0, return_indices=True
|
|
140
145
|
)
|
|
141
146
|
output[to_segment] = labels_x[i[to_segment], j[to_segment], v[to_segment]]
|
|
142
|
-
|
|
147
|
+
|
|
143
148
|
return output
|
|
144
149
|
|
|
150
|
+
|
|
145
151
|
def preserve_objects(labels_x, labels_y):
|
|
146
152
|
"""
|
|
147
|
-
Preserve the initial object set. Any overlapping regions from
|
|
148
|
-
the second set will be ignored in favour of the object from
|
|
149
|
-
the initial set.
|
|
153
|
+
Preserve the initial object set. Any overlapping regions from
|
|
154
|
+
the second set will be ignored in favour of the object from
|
|
155
|
+
the initial set.
|
|
150
156
|
"""
|
|
151
157
|
labels_y[labels_y > 0] += labels_x.max()
|
|
152
158
|
return numpy.where(labels_x > 0, labels_x, labels_y)
|
|
153
159
|
|
|
160
|
+
|
|
154
161
|
def discard_objects(labels_x, labels_y):
|
|
155
162
|
"""
|
|
156
163
|
Discard objects that overlap with objects in the initial set
|
|
@@ -173,6 +180,7 @@ def discard_objects(labels_x, labels_y):
|
|
|
173
180
|
|
|
174
181
|
return numpy.where(labels_x > 0, labels_x, output)
|
|
175
182
|
|
|
183
|
+
|
|
176
184
|
def segment_objects(labels_x, labels_y, dimensions):
|
|
177
185
|
"""
|
|
178
186
|
Combine object sets and re-draw segmentation for overlapping
|
|
@@ -440,6 +448,7 @@ def fill_object_holes(labels, diameter, planewise=False):
|
|
|
440
448
|
array[filled_mask] = obj
|
|
441
449
|
return array
|
|
442
450
|
|
|
451
|
+
|
|
443
452
|
def fill_convex_hulls(labels):
|
|
444
453
|
data = skimage.measure.regionprops(labels)
|
|
445
454
|
output = numpy.zeros_like(labels)
|
|
@@ -455,6 +464,491 @@ def fill_convex_hulls(labels):
|
|
|
455
464
|
] = label
|
|
456
465
|
return output
|
|
457
466
|
|
|
467
|
+
################################################################################
|
|
468
|
+
# IdentifyPrimaryObjects IdentifySecondaryObjects IdentifyTertiaryObjects
|
|
469
|
+
################################################################################
|
|
470
|
+
|
|
471
|
+
# Rename to get_maxima_from_foreground?
|
|
472
|
+
def get_maxima(
|
|
473
|
+
image: Image2DGrayscale,
|
|
474
|
+
labeled_image: Optional[ObjectSegmentation]=None,
|
|
475
|
+
footprint: Optional[StructuringElement]=None,
|
|
476
|
+
image_resize_factor:float=1.0,
|
|
477
|
+
) -> Image2DBinary:
|
|
478
|
+
"""_summary_
|
|
479
|
+
|
|
480
|
+
Parameters
|
|
481
|
+
----------
|
|
482
|
+
image : _type_
|
|
483
|
+
_description_
|
|
484
|
+
labeled_image : _type_
|
|
485
|
+
Binary threshold of the input image
|
|
486
|
+
"""
|
|
487
|
+
if image_resize_factor < 1.0:
|
|
488
|
+
shape = numpy.array(image.shape) * image_resize_factor
|
|
489
|
+
i_j = (
|
|
490
|
+
numpy.mgrid[0 : shape[0], 0 : shape[1]].astype(float) / image_resize_factor
|
|
491
|
+
)
|
|
492
|
+
resized_image = scipy.ndimage.map_coordinates(image, i_j)
|
|
493
|
+
resized_labels = scipy.ndimage.map_coordinates(
|
|
494
|
+
labeled_image, i_j, order=0
|
|
495
|
+
).astype(labeled_image.dtype)
|
|
496
|
+
else:
|
|
497
|
+
resized_image = image
|
|
498
|
+
resized_labels = labeled_image
|
|
499
|
+
#
|
|
500
|
+
# find local maxima
|
|
501
|
+
#
|
|
502
|
+
if footprint is not None:
|
|
503
|
+
binary_maxima_image = centrosome.cpmorphology.is_local_maximum(
|
|
504
|
+
resized_image, resized_labels, footprint
|
|
505
|
+
)
|
|
506
|
+
binary_maxima_image[resized_image <= 0] = 0
|
|
507
|
+
else:
|
|
508
|
+
binary_maxima_image = (resized_image > 0) & (labeled_image > 0)
|
|
509
|
+
if image_resize_factor < 1.0:
|
|
510
|
+
inverse_resize_factor = float(image.shape[0]) / float(
|
|
511
|
+
binary_maxima_image.shape[0]
|
|
512
|
+
)
|
|
513
|
+
i_j = (
|
|
514
|
+
numpy.mgrid[0 : image.shape[0], 0 : image.shape[1]].astype(float)
|
|
515
|
+
/ inverse_resize_factor
|
|
516
|
+
)
|
|
517
|
+
binary_maxima_image = (
|
|
518
|
+
scipy.ndimage.map_coordinates(binary_maxima_image.astype(float), i_j) > 0.5
|
|
519
|
+
)
|
|
520
|
+
assert binary_maxima_image.shape[0] == image.shape[0]
|
|
521
|
+
assert binary_maxima_image.shape[1] == image.shape[1]
|
|
522
|
+
|
|
523
|
+
# Erode blobs of touching maxima to a single point
|
|
524
|
+
shrunk_image = centrosome.cpmorphology.binary_shrink(binary_maxima_image)
|
|
525
|
+
return shrunk_image
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def smooth_image(
|
|
529
|
+
image: Image2DGrayscale,
|
|
530
|
+
mask: Optional[Image2DGrayscaleMask]=None,
|
|
531
|
+
filter_size: Optional[float]=None,
|
|
532
|
+
min_obj_size: Optional[int]=10
|
|
533
|
+
) -> Image2DGrayscale:
|
|
534
|
+
"""Apply the smoothing filter to the image"""
|
|
535
|
+
|
|
536
|
+
if mask is None:
|
|
537
|
+
mask = numpy.ones_like(image, dtype=bool)
|
|
538
|
+
|
|
539
|
+
if filter_size is None:
|
|
540
|
+
filter_size = 2.35 * min_obj_size / 3.5
|
|
541
|
+
|
|
542
|
+
if filter_size == 0:
|
|
543
|
+
return image
|
|
544
|
+
sigma = filter_size / 2.35
|
|
545
|
+
#
|
|
546
|
+
# We not only want to smooth using a Gaussian, but we want to limit
|
|
547
|
+
# the spread of the smoothing to 2 SD, partly to make things happen
|
|
548
|
+
# locally, partly to make things run faster, partly to try to match
|
|
549
|
+
# the Matlab behavior.
|
|
550
|
+
#
|
|
551
|
+
filter_size = max(int(float(filter_size) / 2.0), 1)
|
|
552
|
+
f = (
|
|
553
|
+
1
|
|
554
|
+
/ numpy.sqrt(2.0 * numpy.pi)
|
|
555
|
+
/ sigma
|
|
556
|
+
* numpy.exp(
|
|
557
|
+
-0.5 * numpy.arange(-filter_size, filter_size + 1) ** 2 / sigma ** 2
|
|
558
|
+
)
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
def fgaussian(image):
|
|
562
|
+
output = scipy.ndimage.convolve1d(image, f, axis=0, mode="constant")
|
|
563
|
+
return scipy.ndimage.convolve1d(output, f, axis=1, mode="constant")
|
|
564
|
+
|
|
565
|
+
#
|
|
566
|
+
# Use the trick where you similarly convolve an array of ones to find
|
|
567
|
+
# out the edge effects, then divide to correct the edge effects
|
|
568
|
+
#
|
|
569
|
+
edge_array = fgaussian(mask.astype(float))
|
|
570
|
+
masked_image = image.copy()
|
|
571
|
+
masked_image[~mask] = 0
|
|
572
|
+
smoothed_image = fgaussian(masked_image)
|
|
573
|
+
masked_image[mask] = smoothed_image[mask] / edge_array[mask]
|
|
574
|
+
return masked_image
|
|
575
|
+
|
|
576
|
+
|
|
577
|
+
def filter_on_size(
|
|
578
|
+
labeled_image: ObjectSegmentation,
|
|
579
|
+
min_size: int,
|
|
580
|
+
max_size: int,
|
|
581
|
+
return_only_small: bool=False
|
|
582
|
+
) -> ObjectSegmentation:
|
|
583
|
+
"""Filter the labeled image based on the size range
|
|
584
|
+
|
|
585
|
+
labeled_image - pixel image labels
|
|
586
|
+
object_count - # of objects in the labeled image
|
|
587
|
+
returns the labeled image, and the labeled image with the
|
|
588
|
+
small objects removed
|
|
589
|
+
"""
|
|
590
|
+
labeled_image = labeled_image.copy()
|
|
591
|
+
|
|
592
|
+
# Take the max since objects may have been removed, but their label number
|
|
593
|
+
# has not been adjusted accordingly. eg. array [2, 1, 0, 3] has label 2
|
|
594
|
+
# removed due to being on the border, so the array is [0, 1, 0, 3].
|
|
595
|
+
# Object numbers/indices will be used for slicing in areas[labeled_image]
|
|
596
|
+
object_count = numpy.max(labeled_image)
|
|
597
|
+
# Check if there are no labelled objects
|
|
598
|
+
if object_count > 0:
|
|
599
|
+
areas = scipy.ndimage.measurements.sum(
|
|
600
|
+
numpy.ones(labeled_image.shape),
|
|
601
|
+
labeled_image,
|
|
602
|
+
numpy.array(list(range(0, object_count + 1)), dtype=numpy.int32),
|
|
603
|
+
)
|
|
604
|
+
areas = numpy.array(areas, dtype=int)
|
|
605
|
+
min_allowed_area = numpy.pi * (min_size * min_size) / 4
|
|
606
|
+
max_allowed_area = numpy.pi * (max_size * max_size) / 4
|
|
607
|
+
# area_image has the area of the object at every pixel within the object
|
|
608
|
+
area_image = areas[labeled_image]
|
|
609
|
+
labeled_image[area_image < min_allowed_area] = 0
|
|
610
|
+
if return_only_small:
|
|
611
|
+
small_removed_labels = labeled_image.copy()
|
|
612
|
+
labeled_image[area_image > max_allowed_area] = 0
|
|
613
|
+
return labeled_image, small_removed_labels
|
|
614
|
+
else:
|
|
615
|
+
labeled_image[area_image > max_allowed_area] = 0
|
|
616
|
+
return labeled_image
|
|
617
|
+
else:
|
|
618
|
+
if return_only_small:
|
|
619
|
+
small_removed_labels = labeled_image.copy()
|
|
620
|
+
return labeled_image, small_removed_labels
|
|
621
|
+
else:
|
|
622
|
+
return labeled_image
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def filter_on_border(
|
|
626
|
+
labeled_image: ObjectSegmentation,
|
|
627
|
+
mask: Optional[Image2DGrayscaleMask]=None
|
|
628
|
+
) -> ObjectSegmentation:
|
|
629
|
+
"""Filter out objects touching the border
|
|
630
|
+
|
|
631
|
+
In addition, if the image has a mask, filter out objects
|
|
632
|
+
touching the border of the mask.
|
|
633
|
+
"""
|
|
634
|
+
labeled_image = labeled_image.copy()
|
|
635
|
+
|
|
636
|
+
border_labels = list(labeled_image[0, :])
|
|
637
|
+
border_labels.extend(labeled_image[:, 0])
|
|
638
|
+
border_labels.extend(labeled_image[labeled_image.shape[0] - 1, :])
|
|
639
|
+
border_labels.extend(labeled_image[:, labeled_image.shape[1] - 1])
|
|
640
|
+
border_labels = numpy.array(border_labels)
|
|
641
|
+
#
|
|
642
|
+
# the following histogram has a value > 0 for any object
|
|
643
|
+
# with a border pixel
|
|
644
|
+
#
|
|
645
|
+
histogram = scipy.sparse.coo_matrix(
|
|
646
|
+
(
|
|
647
|
+
numpy.ones(border_labels.shape),
|
|
648
|
+
(border_labels, numpy.zeros(border_labels.shape)),
|
|
649
|
+
),
|
|
650
|
+
shape=(numpy.max(labeled_image) + 1, 1),
|
|
651
|
+
).todense()
|
|
652
|
+
histogram = numpy.array(histogram).flatten()
|
|
653
|
+
if any(histogram[1:] > 0):
|
|
654
|
+
histogram_image = histogram[labeled_image]
|
|
655
|
+
labeled_image[histogram_image > 0] = 0
|
|
656
|
+
elif mask is not None:
|
|
657
|
+
# The assumption here is that, if nothing touches the border,
|
|
658
|
+
# the mask is a large, elliptical mask that tells you where the
|
|
659
|
+
# well is. That's the way the old Matlab code works and it's duplicated here
|
|
660
|
+
#
|
|
661
|
+
# The operation below gets the mask pixels that are on the border of the mask
|
|
662
|
+
# The erosion turns all pixels touching an edge to zero. The not of this
|
|
663
|
+
# is the border + formerly masked-out pixels.
|
|
664
|
+
mask_border = numpy.logical_not(scipy.ndimage.binary_erosion(mask))
|
|
665
|
+
mask_border = numpy.logical_and(mask_border, mask)
|
|
666
|
+
border_labels = labeled_image[mask_border]
|
|
667
|
+
border_labels = border_labels.flatten()
|
|
668
|
+
histogram = scipy.sparse.coo_matrix(
|
|
669
|
+
(
|
|
670
|
+
numpy.ones(border_labels.shape),
|
|
671
|
+
(border_labels, numpy.zeros(border_labels.shape)),
|
|
672
|
+
),
|
|
673
|
+
shape=(numpy.max(labeled_image) + 1, 1),
|
|
674
|
+
).todense()
|
|
675
|
+
histogram = numpy.array(histogram).flatten()
|
|
676
|
+
if any(histogram[1:] > 0):
|
|
677
|
+
histogram_image = histogram[labeled_image]
|
|
678
|
+
labeled_image[histogram_image > 0] = 0
|
|
679
|
+
return labeled_image
|
|
680
|
+
|
|
681
|
+
|
|
682
|
+
def separate_neighboring_objects(
|
|
683
|
+
image: Image2DGrayscale,
|
|
684
|
+
labeled_image: ObjectSegmentation,
|
|
685
|
+
mask: Optional[Image2DGrayscaleMask] = None,
|
|
686
|
+
unclump_method: UnclumpMethod = UnclumpMethod.INTENSITY,
|
|
687
|
+
watershed_method: WatershedMethod = WatershedMethod.INTENSITY,
|
|
688
|
+
fill_holes_method: FillHolesMethod = FillHolesMethod.THRESHOLDING,
|
|
689
|
+
filter_size: Optional[float] = None,
|
|
690
|
+
min_size: int = 10,
|
|
691
|
+
max_size: int = 40,
|
|
692
|
+
low_res_maxima: bool = False,
|
|
693
|
+
maxima_suppression_size: float = 7,
|
|
694
|
+
automatic_suppression: bool = False,
|
|
695
|
+
return_cp_output: bool = False,
|
|
696
|
+
) -> Union[
|
|
697
|
+
ObjectSegmentation,
|
|
698
|
+
Tuple[
|
|
699
|
+
ObjectSegmentation,
|
|
700
|
+
ObjectSegmentation,
|
|
701
|
+
float
|
|
702
|
+
]
|
|
703
|
+
]:
|
|
704
|
+
|
|
705
|
+
if unclump_method == UnclumpMethod.NONE or watershed_method == WatershedMethod.NONE:
|
|
706
|
+
if return_cp_output:
|
|
707
|
+
return labeled_image, numpy.zeros_like(labeled_image), 7
|
|
708
|
+
else:
|
|
709
|
+
return labeled_image
|
|
710
|
+
|
|
711
|
+
blurred_image = smooth_image(image, mask, filter_size, min_size)
|
|
712
|
+
|
|
713
|
+
# For image resizing, the min_size must be larger than 10
|
|
714
|
+
if min_size > 10 and low_res_maxima:
|
|
715
|
+
image_resize_factor = 10.0 / float(min_size)
|
|
716
|
+
if automatic_suppression:
|
|
717
|
+
maxima_suppression_size = 7
|
|
718
|
+
else:
|
|
719
|
+
maxima_suppression_size = (
|
|
720
|
+
maxima_suppression_size * image_resize_factor + 0.5
|
|
721
|
+
)
|
|
722
|
+
else:
|
|
723
|
+
image_resize_factor = 1.0
|
|
724
|
+
if automatic_suppression:
|
|
725
|
+
maxima_suppression_size = min_size / 1.5
|
|
726
|
+
|
|
727
|
+
footprint = centrosome.cpmorphology.strel_disk(
|
|
728
|
+
max(1, maxima_suppression_size - 0.5)
|
|
729
|
+
)
|
|
730
|
+
|
|
731
|
+
distance_transformed_image = None
|
|
732
|
+
|
|
733
|
+
if unclump_method == UnclumpMethod.INTENSITY:
|
|
734
|
+
# Remove dim maxima
|
|
735
|
+
maxima_image = get_maxima(
|
|
736
|
+
blurred_image, labeled_image, footprint, image_resize_factor
|
|
737
|
+
)
|
|
738
|
+
elif unclump_method == UnclumpMethod.SHAPE:
|
|
739
|
+
if fill_holes_method == FillHolesMethod.NEVER:
|
|
740
|
+
# For shape, even if the user doesn't want to fill holes,
|
|
741
|
+
# a point far away from the edge might be near a hole.
|
|
742
|
+
# So we fill just for this part.
|
|
743
|
+
foreground = centrosome.cpmorphology.fill_labeled_holes(labeled_image) > 0
|
|
744
|
+
else:
|
|
745
|
+
foreground = labeled_image > 0
|
|
746
|
+
distance_transformed_image = scipy.ndimage.distance_transform_edt(foreground)
|
|
747
|
+
# randomize the distance slightly to get unique maxima
|
|
748
|
+
numpy.random.seed(0)
|
|
749
|
+
distance_transformed_image += numpy.random.uniform(
|
|
750
|
+
0, 0.001, distance_transformed_image.shape
|
|
751
|
+
)
|
|
752
|
+
maxima_image = get_maxima(
|
|
753
|
+
distance_transformed_image,
|
|
754
|
+
labeled_image,
|
|
755
|
+
footprint,
|
|
756
|
+
image_resize_factor,
|
|
757
|
+
)
|
|
758
|
+
else:
|
|
759
|
+
raise ValueError(f"Unsupported unclump method: {unclump_method}")
|
|
760
|
+
|
|
761
|
+
# Create the image for watershed
|
|
762
|
+
if watershed_method == WatershedMethod.INTENSITY:
|
|
763
|
+
# use the reverse of the image to get valleys at peaks
|
|
764
|
+
watershed_image = 1 - image
|
|
765
|
+
elif watershed_method == WatershedMethod.SHAPE:
|
|
766
|
+
if distance_transformed_image is None:
|
|
767
|
+
distance_transformed_image = scipy.ndimage.distance_transform_edt(
|
|
768
|
+
labeled_image > 0
|
|
769
|
+
)
|
|
770
|
+
watershed_image = -distance_transformed_image
|
|
771
|
+
watershed_image = watershed_image - numpy.min(watershed_image)
|
|
772
|
+
elif watershed_method == WatershedMethod.PROPAGATE:
|
|
773
|
+
# No image used
|
|
774
|
+
pass
|
|
775
|
+
else:
|
|
776
|
+
raise ValueError(f"Unsupported watershed method: {watershed_method}")
|
|
777
|
+
#
|
|
778
|
+
# Create a marker array where the unlabeled image has a label of
|
|
779
|
+
# -(nobjects+1)
|
|
780
|
+
# and every local maximum has a unique label which will become
|
|
781
|
+
# the object's label. The labels are negative because that
|
|
782
|
+
# makes the watershed algorithm use FIFO for the pixels which
|
|
783
|
+
# yields fair boundaries when markers compete for pixels.
|
|
784
|
+
#
|
|
785
|
+
labeled_maxima, object_count = scipy.ndimage.label(
|
|
786
|
+
maxima_image, numpy.ones((3, 3), bool)
|
|
787
|
+
)
|
|
788
|
+
if watershed_method == WatershedMethod.PROPAGATE:
|
|
789
|
+
watershed_boundaries, distance = centrosome.propagate.propagate(
|
|
790
|
+
numpy.zeros(labeled_maxima.shape),
|
|
791
|
+
labeled_maxima,
|
|
792
|
+
labeled_image != 0,
|
|
793
|
+
1.0,
|
|
794
|
+
)
|
|
795
|
+
else:
|
|
796
|
+
markers_dtype = (
|
|
797
|
+
numpy.int16 if object_count < numpy.iinfo(numpy.int16).max else numpy.int32
|
|
798
|
+
)
|
|
799
|
+
markers = numpy.zeros(watershed_image.shape, markers_dtype)
|
|
800
|
+
markers[labeled_maxima > 0] = -labeled_maxima[labeled_maxima > 0]
|
|
801
|
+
|
|
802
|
+
#
|
|
803
|
+
# Some labels have only one maker in them, some have multiple and
|
|
804
|
+
# will be split up.
|
|
805
|
+
#
|
|
806
|
+
|
|
807
|
+
watershed_boundaries = skimage.segmentation.watershed(
|
|
808
|
+
connectivity=numpy.ones((3, 3), bool),
|
|
809
|
+
image=watershed_image,
|
|
810
|
+
markers=markers,
|
|
811
|
+
mask=labeled_image != 0,
|
|
812
|
+
)
|
|
813
|
+
|
|
814
|
+
watershed_boundaries = -watershed_boundaries
|
|
815
|
+
|
|
816
|
+
if return_cp_output:
|
|
817
|
+
return watershed_boundaries, labeled_maxima, maxima_suppression_size
|
|
818
|
+
else:
|
|
819
|
+
return watershed_boundaries
|
|
820
|
+
|
|
821
|
+
|
|
822
|
+
def filter_labels(
|
|
823
|
+
labels_out: ObjectSegmentation,
|
|
824
|
+
objects: ObjectSegmentation,
|
|
825
|
+
mask: Optional[Image2DGrayscaleMask]=None,
|
|
826
|
+
discard_edge: bool=False
|
|
827
|
+
) -> ObjectSegmentation:
|
|
828
|
+
"""Filter labels out of the output
|
|
829
|
+
|
|
830
|
+
Filter labels that are not in the segmented input labels. Optionally
|
|
831
|
+
filter labels that are touching the edge.
|
|
832
|
+
|
|
833
|
+
labels_out - the unfiltered output labels
|
|
834
|
+
objects - the objects thing, containing both segmented and
|
|
835
|
+
small_removed labels
|
|
836
|
+
"""
|
|
837
|
+
segmented_labels = objects.copy()
|
|
838
|
+
max_out = numpy.max(labels_out)
|
|
839
|
+
if max_out > 0:
|
|
840
|
+
segmented_labels, m1 = size_similarly(labels_out, segmented_labels)
|
|
841
|
+
segmented_labels[~m1] = 0
|
|
842
|
+
lookup = scipy.ndimage.maximum(
|
|
843
|
+
segmented_labels, labels_out, list(range(max_out + 1))
|
|
844
|
+
)
|
|
845
|
+
lookup = numpy.array(lookup, int)
|
|
846
|
+
lookup[0] = 0
|
|
847
|
+
segmented_labels_out = lookup[labels_out]
|
|
848
|
+
else:
|
|
849
|
+
segmented_labels_out = labels_out.copy()
|
|
850
|
+
if discard_edge:
|
|
851
|
+
if mask is not None:
|
|
852
|
+
mask_border = mask & ~scipy.ndimage.binary_erosion(mask)
|
|
853
|
+
edge_labels = segmented_labels_out[mask_border]
|
|
854
|
+
else:
|
|
855
|
+
edge_labels = numpy.hstack(
|
|
856
|
+
(
|
|
857
|
+
segmented_labels_out[0, :],
|
|
858
|
+
segmented_labels_out[-1, :],
|
|
859
|
+
segmented_labels_out[:, 0],
|
|
860
|
+
segmented_labels_out[:, -1],
|
|
861
|
+
)
|
|
862
|
+
)
|
|
863
|
+
edge_labels = numpy.unique(edge_labels)
|
|
864
|
+
#
|
|
865
|
+
# Make a lookup table that translates edge labels to zero
|
|
866
|
+
# but translates everything else to itself
|
|
867
|
+
#
|
|
868
|
+
lookup = numpy.arange(max(max_out, numpy.max(segmented_labels)) + 1)
|
|
869
|
+
lookup[edge_labels] = 0
|
|
870
|
+
#
|
|
871
|
+
# Run the segmented labels through this to filter out edge
|
|
872
|
+
# labels
|
|
873
|
+
segmented_labels_out = lookup[segmented_labels_out]
|
|
874
|
+
|
|
875
|
+
return segmented_labels_out
|
|
876
|
+
|
|
877
|
+
|
|
878
|
+
def size_similarly(labels, secondary):
|
|
879
|
+
"""Size the secondary matrix similarly to the labels matrix
|
|
880
|
+
|
|
881
|
+
labels - labels matrix
|
|
882
|
+
secondary - a secondary image or labels matrix which might be of
|
|
883
|
+
different size.
|
|
884
|
+
Return the resized secondary matrix and a mask indicating what portion
|
|
885
|
+
of the secondary matrix is bogus (manufactured values).
|
|
886
|
+
|
|
887
|
+
Either the mask is all ones or the result is a copy, so you can
|
|
888
|
+
modify the output within the unmasked region w/o destroying the original.
|
|
889
|
+
"""
|
|
890
|
+
if labels.shape[:2] == secondary.shape[:2]:
|
|
891
|
+
return secondary, numpy.ones(secondary.shape, bool)
|
|
892
|
+
if labels.shape[0] <= secondary.shape[0] and labels.shape[1] <= secondary.shape[1]:
|
|
893
|
+
if secondary.ndim == 2:
|
|
894
|
+
return (
|
|
895
|
+
secondary[: labels.shape[0], : labels.shape[1]],
|
|
896
|
+
numpy.ones(labels.shape, bool),
|
|
897
|
+
)
|
|
898
|
+
else:
|
|
899
|
+
return (
|
|
900
|
+
secondary[: labels.shape[0], : labels.shape[1], :],
|
|
901
|
+
numpy.ones(labels.shape, bool),
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
#
|
|
905
|
+
# Some portion of the secondary matrix does not cover the labels
|
|
906
|
+
#
|
|
907
|
+
result = numpy.zeros(
|
|
908
|
+
list(labels.shape) + list(secondary.shape[2:]), secondary.dtype
|
|
909
|
+
)
|
|
910
|
+
i_max = min(secondary.shape[0], labels.shape[0])
|
|
911
|
+
j_max = min(secondary.shape[1], labels.shape[1])
|
|
912
|
+
if secondary.ndim == 2:
|
|
913
|
+
result[:i_max, :j_max] = secondary[:i_max, :j_max]
|
|
914
|
+
else:
|
|
915
|
+
result[:i_max, :j_max, :] = secondary[:i_max, :j_max, :]
|
|
916
|
+
mask = numpy.zeros(labels.shape, bool)
|
|
917
|
+
mask[:i_max, :j_max] = 1
|
|
918
|
+
return result, mask
|
|
919
|
+
|
|
920
|
+
def outline(labels: ObjectSegmentation) -> ObjectSegmentation:
|
|
921
|
+
"""Given a label matrix, return a matrix of the outlines of the labeled objects
|
|
922
|
+
|
|
923
|
+
If a pixel is not zero and has at least one neighbor with a different
|
|
924
|
+
value, then it is part of the outline.
|
|
925
|
+
"""
|
|
926
|
+
|
|
927
|
+
output = numpy.zeros(labels.shape, labels.dtype)
|
|
928
|
+
lr_different = labels[1:, :] != labels[:-1, :]
|
|
929
|
+
ud_different = labels[:, 1:] != labels[:, :-1]
|
|
930
|
+
d1_different = labels[1:, 1:] != labels[:-1, :-1]
|
|
931
|
+
d2_different = labels[1:, :-1] != labels[:-1, 1:]
|
|
932
|
+
different = numpy.zeros(labels.shape, bool)
|
|
933
|
+
different[1:, :][lr_different] = True
|
|
934
|
+
different[:-1, :][lr_different] = True
|
|
935
|
+
different[:, 1:][ud_different] = True
|
|
936
|
+
different[:, :-1][ud_different] = True
|
|
937
|
+
different[1:, 1:][d1_different] = True
|
|
938
|
+
different[:-1, :-1][d1_different] = True
|
|
939
|
+
different[1:, :-1][d2_different] = True
|
|
940
|
+
different[:-1, 1:][d2_different] = True
|
|
941
|
+
#
|
|
942
|
+
# Labels on edges need outlines
|
|
943
|
+
#
|
|
944
|
+
different[0, :] = True
|
|
945
|
+
different[:, 0] = True
|
|
946
|
+
different[-1, :] = True
|
|
947
|
+
different[:, -1] = True
|
|
948
|
+
|
|
949
|
+
output[different] = labels[different]
|
|
950
|
+
return output
|
|
951
|
+
|
|
458
952
|
#############################################################
|
|
459
953
|
# ConvertObjectsToImage
|
|
460
954
|
#############################################################
|
|
@@ -16,3 +16,6 @@ from ._watershed import watershed
|
|
|
16
16
|
from ._measureimageoverlap import measureimageoverlap
|
|
17
17
|
from ._gaussianfilter import gaussianfilter
|
|
18
18
|
from ._measureobjectsizeshape import measureobjectsizeshape
|
|
19
|
+
from ._identifyprimaryobjects import identifyprimaryobjects
|
|
20
|
+
from ._identifysecondaryobjects import identifysecondaryobjects
|
|
21
|
+
from ._identifytertiaryobjects import identifytertiaryobjects
|