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.
Files changed (80) hide show
  1. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/PKG-INFO +1 -1
  2. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/_version.py +3 -3
  3. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/image_processing.py +1 -1
  4. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/object_processing.py +520 -26
  5. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/__init__.py +3 -0
  6. cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/modules/_identifyprimaryobjects.py +223 -0
  7. cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/modules/_identifysecondaryobjects.py +264 -0
  8. cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/modules/_identifytertiaryobjects.py +66 -0
  9. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_threshold.py +1 -2
  10. cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/opts/identifyprimaryobjects.py +22 -0
  11. cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/opts/identifysecondaryobjects.py +8 -0
  12. cellprofiler_library_nightly-5.0.0.dev454/cellprofiler_library/opts/identifytertiaryobjects.py +5 -0
  13. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/PKG-INFO +1 -1
  14. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/SOURCES.txt +6 -0
  15. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/LICENSE +0 -0
  16. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/README.md +0 -0
  17. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/__init__.py +0 -0
  18. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/__init__.py +0 -0
  19. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/file_processing.py +0 -0
  20. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/measurement.py +0 -0
  21. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/functions/segmentation.py +0 -0
  22. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_closing.py +0 -0
  23. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_colortogray.py +0 -0
  24. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_combineobjects.py +0 -0
  25. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_convertimagetoobjects.py +0 -0
  26. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_convertobjectstoimage.py +0 -0
  27. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_correctilluminationapply.py +0 -0
  28. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_crop.py +0 -0
  29. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_dilateimage.py +0 -0
  30. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_dilateobjects.py +0 -0
  31. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_enhanceedges.py +0 -0
  32. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_enhanceorsuppressfeatures.py +0 -0
  33. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_erodeimage.py +0 -0
  34. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_erodeobjects.py +0 -0
  35. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_expandorshrinkobjects.py +0 -0
  36. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_fillobjects.py +0 -0
  37. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_gaussianfilter.py +0 -0
  38. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_measureimageoverlap.py +0 -0
  39. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_measureobjectsizeshape.py +0 -0
  40. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_medialaxis.py +0 -0
  41. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_medianfilter.py +0 -0
  42. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_morph.py +0 -0
  43. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_morphologicalskeleton.py +0 -0
  44. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_opening.py +0 -0
  45. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_overlayobjects.py +0 -0
  46. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_overlayoutlines.py +0 -0
  47. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_reducenoise.py +0 -0
  48. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_removeholes.py +0 -0
  49. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_resize.py +0 -0
  50. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_savecroppedobjects.py +0 -0
  51. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_shrinktoobjectcenters.py +0 -0
  52. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/modules/_watershed.py +0 -0
  53. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/__init__.py +0 -0
  54. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/colortogray.py +0 -0
  55. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/convertimagetoobjects.py +0 -0
  56. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/convertobjectstoimage.py +0 -0
  57. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/correctilluminationapply.py +0 -0
  58. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/crop.py +0 -0
  59. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/dilateimage.py +0 -0
  60. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/dilateobjects.py +0 -0
  61. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/enhanceorsuppressfeatures.py +0 -0
  62. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/erodeimage.py +0 -0
  63. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/erodeobjects.py +0 -0
  64. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/measureimageoverlap.py +0 -0
  65. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/morph.py +0 -0
  66. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/objectsizeshapefeatures.py +0 -0
  67. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/overlayoutlines.py +0 -0
  68. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/removeholes.py +0 -0
  69. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/resize.py +0 -0
  70. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/shrinktoobjectcenters.py +0 -0
  71. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/structuring_elements.py +0 -0
  72. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/opts/threshold.py +0 -0
  73. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/py.typed +0 -0
  74. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library/types.py +0 -0
  75. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/dependency_links.txt +0 -0
  76. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/requires.txt +0 -0
  77. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/cellprofiler_library_nightly.egg-info/top_level.txt +0 -0
  78. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/environment.yml +0 -0
  79. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/pyproject.toml +0 -0
  80. {cellprofiler_library_nightly-5.0.0.dev416 → cellprofiler_library_nightly-5.0.0.dev454}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cellprofiler-library-nightly
3
- Version: 5.0.0.dev416
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.dev416'
32
- __version_tuple__ = version_tuple = (5, 0, 0, 'dev416')
31
+ __version__ = version = '5.0.0.dev454'
32
+ __version_tuple__ = version_tuple = (5, 0, 0, 'dev454')
33
33
 
34
- __commit_id__ = commit_id = 'g16aa642b5'
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: int = 2,
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
- from numpy.typing import NDArray
10
- from typing import Optional, Literal, Tuple
11
- from cellprofiler_library.types import ImageAnyMask, ObjectLabel, ImageColor, ImageGrayscale, ImageBinary, ObjectSegmentation, StructuringElement
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
- labels, iterations=iterations
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
- labels, iterations=iterations
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