cellprofiler-library-nightly 5.0.0.dev643__tar.gz → 5.0.0.dev649__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 (118) hide show
  1. {cellprofiler_library_nightly-5.0.0.dev643/cellprofiler_library_nightly.egg-info → cellprofiler_library_nightly-5.0.0.dev649}/PKG-INFO +1 -1
  2. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/_version.py +3 -3
  3. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/image_processing.py +309 -2
  4. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/measurement.py +27 -0
  5. cellprofiler_library_nightly-5.0.0.dev649/cellprofiler_library/modules/_align.py +266 -0
  6. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectintensity.py +8 -9
  7. cellprofiler_library_nightly-5.0.0.dev649/cellprofiler_library/opts/align.py +20 -0
  8. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649/cellprofiler_library_nightly.egg-info}/PKG-INFO +1 -1
  9. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library_nightly.egg-info/SOURCES.txt +2 -0
  10. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/LICENSE +0 -0
  11. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/README.md +0 -0
  12. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/__init__.py +0 -0
  13. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/__init__.py +0 -0
  14. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/file_processing.py +0 -0
  15. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/object_processing.py +0 -0
  16. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/segmentation.py +0 -0
  17. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/measurement_model.py +0 -0
  18. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/__init__.py +0 -0
  19. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_closing.py +0 -0
  20. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_colortogray.py +0 -0
  21. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_combineobjects.py +0 -0
  22. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_convertimagetoobjects.py +0 -0
  23. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_convertobjectstoimage.py +0 -0
  24. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_correctilluminationapply.py +0 -0
  25. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_crop.py +0 -0
  26. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_dilateimage.py +0 -0
  27. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_dilateobjects.py +0 -0
  28. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_enhanceedges.py +0 -0
  29. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_enhanceorsuppressfeatures.py +0 -0
  30. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_erodeimage.py +0 -0
  31. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_erodeobjects.py +0 -0
  32. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_expandorshrinkobjects.py +0 -0
  33. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_fillobjects.py +0 -0
  34. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_flipandrotate.py +0 -0
  35. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_gaussianfilter.py +0 -0
  36. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_graytocolor.py +0 -0
  37. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_identifydeadworms.py +0 -0
  38. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_identifyprimaryobjects.py +0 -0
  39. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_identifysecondaryobjects.py +0 -0
  40. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_identifytertiaryobjects.py +0 -0
  41. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_imagemath.py +0 -0
  42. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measurecolocalization.py +0 -0
  43. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measuregranularity.py +0 -0
  44. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimageareaoccupied.py +0 -0
  45. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimageintensity.py +0 -0
  46. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimageoverlap.py +0 -0
  47. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimagequality.py +0 -0
  48. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimageskeleton.py +0 -0
  49. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectintensitydistribution.py +0 -0
  50. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectneighbors.py +0 -0
  51. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectoverlap.py +0 -0
  52. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectsizeshape.py +0 -0
  53. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectskeleton.py +0 -0
  54. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measuretexture.py +0 -0
  55. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_medialaxis.py +0 -0
  56. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_medianfilter.py +0 -0
  57. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_morph.py +0 -0
  58. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_morphologicalskeleton.py +0 -0
  59. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_opening.py +0 -0
  60. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_overlayobjects.py +0 -0
  61. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_overlayoutlines.py +0 -0
  62. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_reducenoise.py +0 -0
  63. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_removeholes.py +0 -0
  64. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_resize.py +0 -0
  65. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_savecroppedobjects.py +0 -0
  66. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_shrinktoobjectcenters.py +0 -0
  67. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_smooth.py +0 -0
  68. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_threshold.py +0 -0
  69. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_watershed.py +0 -0
  70. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/__init__.py +0 -0
  71. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/colortogray.py +0 -0
  72. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/convertimagetoobjects.py +0 -0
  73. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/convertobjectstoimage.py +0 -0
  74. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/correctilluminationapply.py +0 -0
  75. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/crop.py +0 -0
  76. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/dilateimage.py +0 -0
  77. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/dilateobjects.py +0 -0
  78. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/enhanceedges.py +0 -0
  79. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/enhanceorsuppressfeatures.py +0 -0
  80. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/erodeimage.py +0 -0
  81. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/erodeobjects.py +0 -0
  82. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/flipandrotate.py +0 -0
  83. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/graytocolor.py +0 -0
  84. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/identifydeadworms.py +0 -0
  85. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/identifyprimaryobjects.py +0 -0
  86. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/identifysecondaryobjects.py +0 -0
  87. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/identifytertiaryobjects.py +0 -0
  88. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/imagemath.py +0 -0
  89. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measurecolocalization.py +0 -0
  90. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measuregranularity.py +0 -0
  91. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimageareaoccupied.py +0 -0
  92. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimageintensity.py +0 -0
  93. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimageoverlap.py +0 -0
  94. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimagequality.py +0 -0
  95. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimageskeleton.py +0 -0
  96. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectintensity.py +0 -0
  97. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectintensitydistribution.py +0 -0
  98. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectneighbors.py +0 -0
  99. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectoverlap.py +0 -0
  100. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectskeleton.py +0 -0
  101. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measuretexture.py +0 -0
  102. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/morph.py +0 -0
  103. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/objectsizeshapefeatures.py +0 -0
  104. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/overlayoutlines.py +0 -0
  105. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/removeholes.py +0 -0
  106. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/resize.py +0 -0
  107. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/shrinktoobjectcenters.py +0 -0
  108. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/smooth.py +0 -0
  109. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/structuring_elements.py +0 -0
  110. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/threshold.py +0 -0
  111. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/py.typed +0 -0
  112. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/types.py +0 -0
  113. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library_nightly.egg-info/dependency_links.txt +0 -0
  114. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library_nightly.egg-info/requires.txt +0 -0
  115. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library_nightly.egg-info/top_level.txt +0 -0
  116. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/environment.yml +0 -0
  117. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/pyproject.toml +0 -0
  118. {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/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.dev643
3
+ Version: 5.0.0.dev649
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.dev643'
32
- __version_tuple__ = version_tuple = (5, 0, 0, 'dev643')
31
+ __version__ = version = '5.0.0.dev649'
32
+ __version_tuple__ = version_tuple = (5, 0, 0, 'dev649')
33
33
 
34
- __commit_id__ = commit_id = 'g229e6671e'
34
+ __commit_id__ = commit_id = 'g6fe7b0942'
@@ -13,6 +13,7 @@ import centrosome.threshold
13
13
  import centrosome.filter
14
14
  import scipy
15
15
  import scipy.interpolate
16
+ import scipy.sparse
16
17
  import matplotlib
17
18
  import math
18
19
  from numpy.typing import NDArray
@@ -22,6 +23,7 @@ from skimage.restoration import denoise_bilateral
22
23
  from centrosome.cpmorphology import get_line_pts, all_connected_components
23
24
  from scipy.ndimage import binary_erosion, binary_fill_holes
24
25
  from scipy.ndimage import mean as mean_of_labels
26
+ from scipy.fftpack import fft2, ifft2
25
27
  from cellprofiler_library.types import (
26
28
  ImageGrayscale, ImageGrayscaleMask,
27
29
  Image2DColor,
@@ -29,8 +31,7 @@ from cellprofiler_library.types import (
29
31
  ImageAny, ImageAnyMask,
30
32
  ObjectSegmentation,
31
33
  Image2D, Image2DMask,
32
- StructuringElement, ObjectLabelSet, ImageColor, ImageBinaryMask,
33
- Image2DBinary, ObjectLabel
34
+ StructuringElement, ObjectLabelSet, ImageColor, ImageBinaryMask, Image2DBinary, ObjectLabel, ImageBinary, Pixel
34
35
  )
35
36
  from cellprofiler_library.opts import threshold as Threshold
36
37
  from cellprofiler_library.opts.enhanceorsuppressfeatures import SpeckleAccuracy, NeuriteMethod
@@ -2563,3 +2564,309 @@ def find_adjacent_by_distance(
2563
2564
  | (angular_orientation[second] + numpy.pi - angular_orientation[first] <= angle_distance)
2564
2565
  )
2565
2566
  return order[first[mask]], order[second[mask]]
2567
+
2568
+ ################################################################################
2569
+ # Align
2570
+ ################################################################################
2571
+
2572
+
2573
+ def align_cross_correlation(
2574
+ pixels1: Image2D,
2575
+ pixels2: Image2D
2576
+ ) -> Tuple[
2577
+ int,
2578
+ int
2579
+ ]:
2580
+ """Align the second image with the first using max cross-correlation
2581
+
2582
+ returns the x,y offsets to add to image1's indexes to align it with
2583
+ image2
2584
+
2585
+ Many of the ideas here are based on the paper, "Fast Normalized
2586
+ Cross-Correlation" by J.P. Lewis
2587
+ (http://www.idiom.com/~zilla/Papers/nvisionInterface/nip.html)
2588
+ which is frequently cited when addressing this problem.
2589
+ """
2590
+ #
2591
+ # TODO: Possibly use all 3 dimensions for color some day
2592
+ #
2593
+ if pixels1.ndim == 3:
2594
+ pixels1 = numpy.mean(pixels1, 2)
2595
+ if pixels2.ndim == 3:
2596
+ pixels2 = numpy.mean(pixels2, 2)
2597
+ #
2598
+ # We double the size of the image to get a field of zeros
2599
+ # for the parts of one image that don't overlap the displaced
2600
+ # second image.
2601
+ #
2602
+ # Since we're going into the frequency domain, if the images are of
2603
+ # different sizes, we can make the FFT shape large enough to capture
2604
+ # the period of the largest image - the smaller just will have zero
2605
+ # amplitude at that frequency.
2606
+ #
2607
+ s = numpy.maximum(pixels1.shape, pixels2.shape)
2608
+ fshape = s * 2
2609
+ #
2610
+ # Calculate the # of pixels at a particular point
2611
+ #
2612
+ i, j = numpy.mgrid[-s[0]: s[0], -s[1]: s[1]]
2613
+ unit = numpy.abs(i * j).astype(float)
2614
+ unit[unit < 1] = 1 # keeps from dividing by zero in some places
2615
+ #
2616
+ # Normalize the pixel values around zero which does not affect the
2617
+ # correlation, keeps some of the sums of multiplications from
2618
+ # losing precision and precomputes t(x-u,y-v) - t_mean
2619
+ #
2620
+ pixels1 = pixels1 - numpy.mean(pixels1)
2621
+ pixels2 = pixels2 - numpy.mean(pixels2)
2622
+ #
2623
+ # Lewis uses an image, f and a template t. He derives a normalized
2624
+ # cross correlation, ncc(u,v) =
2625
+ # sum((f(x,y)-f_mean(u,v))*(t(x-u,y-v)-t_mean),x,y) /
2626
+ # sqrt(sum((f(x,y)-f_mean(u,v))**2,x,y) * (sum((t(x-u,y-v)-t_mean)**2,x,y)
2627
+ #
2628
+ # From here, he finds that the numerator term, f_mean(u,v)*(t...) is zero
2629
+ # leaving f(x,y)*(t(x-u,y-v)-t_mean) which is a convolution of f
2630
+ # by t-t_mean.
2631
+ #
2632
+ fp1 = fft2(pixels1, fshape.tolist())
2633
+ fp2 = fft2(pixels2, fshape.tolist())
2634
+ corr12 = ifft2(fp1 * fp2.conj()).real
2635
+
2636
+ #
2637
+ # Use the trick of Lewis here - compute the cumulative sums
2638
+ # in a fashion that accounts for the parts that are off the
2639
+ # edge of the template.
2640
+ #
2641
+ # We do this in quadrants:
2642
+ # q0 q1
2643
+ # q2 q3
2644
+ # For the first,
2645
+ # q0 is the sum over pixels1[i:,j:] - sum i,j backwards
2646
+ # q1 is the sum over pixels1[i:,:j] - sum i backwards, j forwards
2647
+ # q2 is the sum over pixels1[:i,j:] - sum i forwards, j backwards
2648
+ # q3 is the sum over pixels1[:i,:j] - sum i,j forwards
2649
+ #
2650
+ # The second is done as above but reflected lr and ud
2651
+ #
2652
+ p1_si = pixels1.shape[0]
2653
+ p1_sj = pixels1.shape[1]
2654
+ p1_sum = numpy.zeros(fshape)
2655
+ p1_sum[:p1_si, :p1_sj] = cumsum_quadrant(pixels1, False, False)
2656
+ p1_sum[:p1_si, -p1_sj:] = cumsum_quadrant(pixels1, False, True)
2657
+ p1_sum[-p1_si:, :p1_sj] = cumsum_quadrant(pixels1, True, False)
2658
+ p1_sum[-p1_si:, -p1_sj:] = cumsum_quadrant(pixels1, True, True)
2659
+ #
2660
+ # Divide the sum over the # of elements summed-over
2661
+ #
2662
+ p1_mean = p1_sum / unit
2663
+
2664
+ p2_si = pixels2.shape[0]
2665
+ p2_sj = pixels2.shape[1]
2666
+ p2_sum = numpy.zeros(fshape)
2667
+ p2_sum[:p2_si, :p2_sj] = cumsum_quadrant(pixels2, False, False)
2668
+ p2_sum[:p2_si, -p2_sj:] = cumsum_quadrant(pixels2, False, True)
2669
+ p2_sum[-p2_si:, :p2_sj] = cumsum_quadrant(pixels2, True, False)
2670
+ p2_sum[-p2_si:, -p2_sj:] = cumsum_quadrant(pixels2, True, True)
2671
+ p2_sum = numpy.fliplr(numpy.flipud(p2_sum))
2672
+ p2_mean = p2_sum / unit
2673
+ #
2674
+ # Once we have the means for u,v, we can calculate the
2675
+ # variance-like parts of the equation. We have to multiply
2676
+ # the mean^2 by the # of elements being summed-over
2677
+ # to account for the mean being summed that many times.
2678
+ #
2679
+ p1sd = numpy.sum(pixels1 ** 2) - p1_mean ** 2 * numpy.product(s)
2680
+ p2sd = numpy.sum(pixels2 ** 2) - p2_mean ** 2 * numpy.product(s)
2681
+ #
2682
+ # There's always chance of roundoff error for a zero value
2683
+ # resulting in a negative sd, so limit the sds here
2684
+ #
2685
+ sd = numpy.sqrt(numpy.maximum(p1sd * p2sd, 0))
2686
+ corrnorm = corr12 / sd
2687
+ #
2688
+ # There's not much information for points where the standard
2689
+ # deviation is less than 1/100 of the maximum. We exclude these
2690
+ # from consideration.
2691
+ #
2692
+ corrnorm[(unit < numpy.product(s) / 2) & (sd < numpy.mean(sd) / 100)] = 0
2693
+ i, j = numpy.unravel_index(numpy.argmax(corrnorm), fshape)
2694
+ #
2695
+ # Reflect values that fall into the second half
2696
+ #
2697
+ if i > pixels1.shape[0]:
2698
+ i = i - fshape[0]
2699
+ if j > pixels1.shape[1]:
2700
+ j = j - fshape[1]
2701
+ return int(j), int(i)
2702
+
2703
+ def align_mutual_information(
2704
+ pixels1: Image2D,
2705
+ pixels2: Image2D,
2706
+ mask1: Image2DMask,
2707
+ mask2: Image2DMask
2708
+ ) -> Tuple[
2709
+ int,
2710
+ int
2711
+ ]:
2712
+ """Align the second image with the first using mutual information
2713
+
2714
+ returns the x,y offsets to add to image1's indexes to align it with
2715
+ image2
2716
+
2717
+ The algorithm computes the mutual information content of the two
2718
+ images, offset by one in each direction (including diagonal) and
2719
+ then picks the direction in which there is the most mutual information.
2720
+ From there, it tries all offsets again and so on until it reaches
2721
+ a local maximum.
2722
+ """
2723
+ #
2724
+ # TODO: Possibly use all 3 dimensions for color some day
2725
+ #
2726
+ if pixels1.ndim == 3:
2727
+ _pixels1: Image2DGrayscale = numpy.mean(pixels1, 2)
2728
+ else:
2729
+ _pixels1: Image2DGrayscale = pixels1
2730
+ if pixels2.ndim == 3:
2731
+ _pixels2: Image2DGrayscale = numpy.mean(pixels2, 2)
2732
+ else:
2733
+ _pixels2: Image2DGrayscale = pixels2
2734
+
2735
+ def mutualinf(x: Image2DGrayscale, y: Image2DGrayscale, maskx: Image2DMask, masky: Image2DMask) -> float:
2736
+ _x = x[maskx & masky]
2737
+ _y = y[maskx & masky]
2738
+ return entropy(_x) + entropy(_y) - entropy2(_x, _y)
2739
+ maxshape = tuple(numpy.maximum(_pixels1.shape, _pixels2.shape))
2740
+ _pixels1 = reshape_image(_pixels1, maxshape)
2741
+ _pixels2 = reshape_image(_pixels2, maxshape)
2742
+ mask1 = reshape_image(mask1, maxshape)
2743
+ mask2 = reshape_image(mask2, maxshape)
2744
+
2745
+ best = mutualinf(_pixels1, _pixels2, mask1, mask2)
2746
+ i = 0
2747
+ j = 0
2748
+ while True:
2749
+ last_i = i
2750
+ last_j = j
2751
+ for new_i in range(last_i - 1, last_i + 2):
2752
+ for new_j in range(last_j - 1, last_j + 2):
2753
+ if new_i == 0 and new_j == 0:
2754
+ continue
2755
+ p2, p1 = offset_slice(_pixels2, _pixels1, new_i, new_j)
2756
+ m2, m1 = offset_slice(mask2, mask1, new_i, new_j)
2757
+ info = mutualinf(p1, p2, m1, m2)
2758
+ if info > best:
2759
+ best = info
2760
+ i = new_i
2761
+ j = new_j
2762
+ if i == last_i and j == last_j:
2763
+ return j, i
2764
+
2765
+ def offset_slice(
2766
+ pixels1: Union[Image2D, ImageBinary], # Union to support crop_mask (3 channel binary NDArray)
2767
+ pixels2: Image2D,
2768
+ i: int,
2769
+ j: int
2770
+ ) -> Tuple[
2771
+ Union[Image2D, ImageBinary],
2772
+ Image2D
2773
+ ]:
2774
+ """Return two sliced arrays where the first slice is offset by i,j
2775
+ relative to the second slice.
2776
+
2777
+ """
2778
+ if i < 0:
2779
+ height = min(pixels1.shape[0] + i, pixels2.shape[0])
2780
+ p1_imin = -i
2781
+ p2_imin = 0
2782
+ else:
2783
+ height = min(pixels1.shape[0], pixels2.shape[0] - i)
2784
+ p1_imin = 0
2785
+ p2_imin = i
2786
+ p1_imax = p1_imin + height
2787
+ p2_imax = p2_imin + height
2788
+ if j < 0:
2789
+ width = min(pixels1.shape[1] + j, pixels2.shape[1])
2790
+ p1_jmin = -j
2791
+ p2_jmin = 0
2792
+ else:
2793
+ width = min(pixels1.shape[1], pixels2.shape[1] - j)
2794
+ p1_jmin = 0
2795
+ p2_jmin = j
2796
+ p1_jmax = p1_jmin + width
2797
+ p2_jmax = p2_jmin + width
2798
+
2799
+ p1 = pixels1[p1_imin:p1_imax, p1_jmin:p1_jmax]
2800
+ p2 = pixels2[p2_imin:p2_imax, p2_jmin:p2_jmax]
2801
+ return p1, p2
2802
+
2803
+
2804
+ def cumsum_quadrant(
2805
+ x: Image2D,
2806
+ i_forwards: bool,
2807
+ j_forwards: bool
2808
+ ) -> NDArray[numpy.float64]:
2809
+ """Return the cumulative sum going in the i, then j direction
2810
+
2811
+ x - the matrix to be summed
2812
+ i_forwards - sum from 0 to end in the i direction if true
2813
+ j_forwards - sum from 0 to end in the j direction if true
2814
+ """
2815
+ if i_forwards:
2816
+ x = x.cumsum(0)
2817
+ else:
2818
+ x = numpy.flipud(numpy.flipud(x).cumsum(0))
2819
+ if j_forwards:
2820
+ return x.cumsum(1)
2821
+ else:
2822
+ return numpy.fliplr(numpy.fliplr(x).cumsum(1))
2823
+
2824
+
2825
+ def entropy(x: NDArray[Pixel]) -> float:
2826
+ """The entropy of x as if x is a probability distribution"""
2827
+ histogram = scipy.ndimage.histogram(x.astype(float), numpy.min(x), numpy.max(x), 256)
2828
+ n = numpy.sum(histogram)
2829
+ if n > 0 and numpy.max(histogram) > 0:
2830
+ histogram = histogram[histogram != 0]
2831
+ return numpy.log2(n) - numpy.sum(histogram * numpy.log2(histogram)) / n
2832
+ else:
2833
+ return 0
2834
+
2835
+
2836
+ def entropy2(x: NDArray[Pixel], y: NDArray[Pixel]) -> float:
2837
+ """Joint entropy of paired samples X and Y"""
2838
+ #
2839
+ # Bin each image into 256 gray levels
2840
+ #
2841
+ x = (centrosome.filter.stretch(x) * 255).astype(int)
2842
+ y = (centrosome.filter.stretch(y) * 255).astype(int)
2843
+ #
2844
+ # create an image where each pixel with the same X & Y gets
2845
+ # the same value
2846
+ #
2847
+ xy = 256 * x + y
2848
+ xy = xy.flatten()
2849
+ sparse = scipy.sparse.coo_matrix(
2850
+ (numpy.ones(xy.shape, dtype=numpy.int32), (xy, numpy.zeros(xy.shape, dtype=numpy.int32)))
2851
+ )
2852
+ histogram = sparse.toarray()
2853
+ n = numpy.sum(histogram)
2854
+ if n > 0 and numpy.max(histogram) > 0:
2855
+ histogram = histogram[histogram > 0]
2856
+ return numpy.log2(n) - numpy.sum(histogram * numpy.log2(histogram)) / n
2857
+ else:
2858
+ return 0
2859
+
2860
+ ReshapeImageInput = TypeVar("ReshapeImageInput", bound=Union[Image2D, Image2DMask])
2861
+ def reshape_image(
2862
+ source: ReshapeImageInput, # Union to support crop_mask (3 channel binary NDArray)
2863
+ new_shape: Tuple[int, int]
2864
+ ) -> ReshapeImageInput:
2865
+ """Reshape an image to a larger shape, padding with zeros"""
2866
+ if tuple(source.shape) == tuple(new_shape):
2867
+ return source
2868
+
2869
+ result = numpy.zeros(new_shape, source.dtype)
2870
+ result[: source.shape[0], : source.shape[1]] = source
2871
+ return result
2872
+
@@ -1692,6 +1692,33 @@ def measure_quartile_intensity(indices:NDArray[numpy.int_], areas: NDArray[numpy
1692
1692
  dest_no_upper = limg[order[qindex[qmask_no_upper]]]
1693
1693
  return qmask, _dest, qmask_no_upper, dest_no_upper
1694
1694
 
1695
+ def measure_mad_intensity(
1696
+ limg: NDArray[Pixel],
1697
+ llabels: NDArray[ObjectLabel],
1698
+ lindexes: NDArray[numpy.int_],
1699
+ median_intensity: NDArray[numpy.float_],
1700
+ ) -> NDArray[numpy.float64]:
1701
+ """Compute the median absolute deviation (MAD) of intensity per object.
1702
+
1703
+ MAD is defined as median(|x_i - median(x)|). Unlike the quartile measurements,
1704
+ this is computed as a true per-object median of the absolute deviations rather
1705
+ than via interpolated quartile indexing, which would otherwise interpolate
1706
+ across value boundaries for odd-sized objects and yield an incorrect result.
1707
+
1708
+ Args:
1709
+ limg: 1D array of object pixel intensities
1710
+ llabels: 1D array of object labels, aligned with ``limg``
1711
+ lindexes: object label indices to compute over
1712
+ median_intensity: per-object median intensity, indexed by label - 1
1713
+
1714
+ Returns:
1715
+ Array of MAD values, one per entry in ``lindexes``.
1716
+ """
1717
+ madimg = numpy.abs(limg - median_intensity[llabels - 1])
1718
+ return centrosome.cpmorphology.fixup_scipy_ndimage_result(
1719
+ scipy.ndimage.median(madimg, llabels, lindexes)
1720
+ )
1721
+
1695
1722
  ###############################################################################
1696
1723
  # MeasureObjectOverlap
1697
1724
  ###############################################################################
@@ -0,0 +1,266 @@
1
+ import numpy as np
2
+
3
+ from typing import Tuple, Optional, List, Union, Annotated
4
+ from pydantic import Field, validate_call, ConfigDict, BaseModel
5
+
6
+ from cellprofiler_library.opts.align import CropMode, AlignmentMethod, AdditionalAlignmentChoice, MEASUREMENT_FORMAT
7
+ from cellprofiler_library.types import Image2D, Image2DMask, ImageBinary
8
+ from cellprofiler_library.measurement_model import LibraryMeasurements
9
+ from cellprofiler_library.functions.image_processing import (
10
+ align_cross_correlation,
11
+ align_mutual_information,
12
+ offset_slice,
13
+ )
14
+
15
+ ImageInfo = List[Tuple[
16
+ str, # input image name
17
+ Image2D, # input image pixels
18
+ str, # output image name
19
+ Image2D, # output image pixels
20
+ int, # offset X
21
+ int, # offset Y
22
+ Tuple[int, int], # image shape
23
+ ]]
24
+
25
+ class AlignDisplayData(BaseModel):
26
+ model_config = ConfigDict(
27
+ arbitrary_types_allowed=True,
28
+ populate_by_name=True
29
+ )
30
+
31
+ image_info: ImageInfo
32
+
33
+ AlignReturnData = Tuple[
34
+ List[Image2D],
35
+ List[Image2DMask],
36
+ List[Optional[Union[Image2DMask, ImageBinary]]],
37
+ LibraryMeasurements
38
+ ]
39
+
40
+ @validate_call(config=ConfigDict(arbitrary_types_allowed=True))
41
+ @validate_call(config=ConfigDict(arbitrary_types_allowed=True))
42
+ def align_images(
43
+ primary_image: Annotated[Image2D, Field(description="Primary image")],
44
+ primary_image_mask: Annotated[Image2DMask, Field(description="Primary image mask")],
45
+ secondary_image: Annotated[Image2D, Field(description="Secondary image")],
46
+ secondary_image_mask: Annotated[Image2DMask, Field(description="Secondary image mask")],
47
+ alignment_method: Annotated[AlignmentMethod, Field(description="Alignment method")],
48
+ crop_mode: Annotated[CropMode, Field(description="Crop mode")],
49
+ additional_images: Annotated[List[Image2D], Field(description="List of additonal images to align")] = [],
50
+ additional_image_masks: Annotated[List[Image2DMask], Field(description="List of image masks for additonal images")] = [],
51
+ additional_image_alignments: Annotated[List[AdditionalAlignmentChoice], Field(description="List of alignment types for additonal images")] = [],
52
+ input_image_names: Annotated[Optional[List[str]], Field(description="List of input image names for visualization (if None name will be generated)")] = None,
53
+ output_image_names: Annotated[Optional[List[str]], Field(description="List of output image names for visualization (if None name will be generated)")] = None,
54
+ return_visualization_data: Annotated[bool, Field(description="Return GT_pixels and ID_pixels for visualization")] = False,
55
+ ) -> Union[
56
+ Tuple[AlignReturnData, AlignDisplayData],
57
+ AlignReturnData
58
+ ]:
59
+ assert len(additional_images) == len(additional_image_masks), "Must have same number of image masks as images"
60
+ assert len(additional_images) == len(additional_image_alignments), "Must have same number of image alignments as images"
61
+ assert input_image_names is None or len(input_image_names) == (len(additional_images) + 2), "Must have same number of input image names as images"
62
+ assert output_image_names is None or len(output_image_names) == (len(additional_images) + 2), "Must have same number of output image names as images"
63
+
64
+ off_x, off_y = align_pair(
65
+ primary_image,
66
+ secondary_image,
67
+ primary_image_mask,
68
+ secondary_image_mask,
69
+ alignment_method
70
+ )
71
+ offsets = [(0,0), (off_y, off_x)]
72
+
73
+ for i in range(len(additional_images)):
74
+ if additional_image_alignments[i] == AdditionalAlignmentChoice.SIMILARLY.value:
75
+ offsets.append((off_y, off_x))
76
+ elif additional_image_alignments[i] == AdditionalAlignmentChoice.SEPARATELY.value:
77
+ a_off_x, a_off_y = align_pair(
78
+ primary_image,
79
+ additional_images[i],
80
+ primary_image_mask,
81
+ additional_image_masks[i],
82
+ alignment_method
83
+ )
84
+ offsets.append((a_off_y, a_off_x))
85
+
86
+ shapes = [primary_image.shape[:2], secondary_image.shape[:2]] + [img.shape[:2] for img in additional_images]
87
+ offsets, shapes = adjust_offsets(offsets, shapes, crop_mode)
88
+
89
+ output_images: List[Image2D] = []
90
+ output_image_masks: List[Image2DMask] = []
91
+ crop_masks: List[Optional[Union[Image2DMask, ImageBinary]]] = []
92
+
93
+ measurements = LibraryMeasurements()
94
+ image_info: ImageInfo = []
95
+
96
+ for i in range(len(offsets)):
97
+ if i == 0:
98
+ input_image = primary_image
99
+ input_image_mask = primary_image_mask
100
+ if return_visualization_data:
101
+ input_image_name = input_image_names[i] if input_image_names else "Primary Image"
102
+ output_image_name = output_image_names[i] if output_image_names else "Primary Image Aligned"
103
+ elif i == 1:
104
+ input_image = secondary_image
105
+ input_image_mask = secondary_image_mask
106
+ if return_visualization_data:
107
+ input_image_name = input_image_names[i] if input_image_names else "Secondary Image"
108
+ output_image_name = output_image_names[i] if output_image_names else "Secondary Image Aligned"
109
+ else:
110
+ input_image = additional_images[i-2]
111
+ input_image_mask = additional_image_masks[i-2]
112
+ if return_visualization_data:
113
+ input_image_name = input_image_names[i] if input_image_names else f"Additional Image {i+1}"
114
+ output_image_name = output_image_names[i] if output_image_names else f"Additional Image {i+1} Aligned"
115
+
116
+ output_image, output_mask, crop_mask = apply_alignment(
117
+ input_image,
118
+ input_image_mask,
119
+ offsets[i][1],
120
+ offsets[i][0],
121
+ shapes[i],
122
+ )
123
+
124
+ output_images.append(output_image)
125
+ output_image_masks.append(output_mask)
126
+ crop_masks.append(crop_mask)
127
+ output_image_name = output_image_names[i] if output_image_names is not None else f"Image{i+1}"
128
+
129
+ for axis, value in (("X", -offsets[i][1]), ("Y", -offsets[i][0])):
130
+ measurements.add_image_measurement(MEASUREMENT_FORMAT % (axis, output_image_name), value)
131
+
132
+ if return_visualization_data:
133
+ image_info.append((
134
+ input_image_name,
135
+ input_image,
136
+ output_image_name,
137
+ output_image,
138
+ offsets[i][1],
139
+ offsets[i][0],
140
+ shapes[i]
141
+ ))
142
+
143
+ if return_visualization_data:
144
+ return (output_images, output_image_masks, crop_masks, measurements), AlignDisplayData(image_info=image_info)
145
+ return output_images, output_image_masks, crop_masks, measurements
146
+
147
+ @validate_call(config=ConfigDict(arbitrary_types_allowed=True))
148
+ def align_pair(
149
+ image1_pixels: Image2D,
150
+ image2_pixels: Image2D,
151
+ image1_mask: Image2DMask,
152
+ image2_mask: Image2DMask,
153
+ alignment_method: AlignmentMethod
154
+ ) -> Tuple[
155
+ int,
156
+ int
157
+ ]:
158
+ """Align the second image with the first
159
+ Calculate the alignment offset that must be added to indexes in the
160
+ first image to arrive at indexes in the second image.
161
+
162
+ Returns the x,y (not i,j) offsets.
163
+ """
164
+ if alignment_method == AlignmentMethod.CROSS_CORRELATION.value:
165
+ return align_cross_correlation(image1_pixels, image2_pixels)
166
+ else: # alignment_method == AlignmentMethod.MUTUAL_INFORMATION.value:
167
+ return align_mutual_information(
168
+ image1_pixels, image2_pixels, image1_mask, image2_mask
169
+ )
170
+
171
+
172
+ @validate_call(config=ConfigDict(arbitrary_types_allowed=True))
173
+ def adjust_offsets(
174
+ offsets: Annotated[List[Tuple[int, int]], Field(description="Offsets to be adjusted")],
175
+ shapes: Annotated[List[Tuple[int, int]], Field(description="Shapes of images")],
176
+ crop_mode: Annotated[CropMode, Field(description="The crop mode determines how the output images are either cropped or padded after alignment")]
177
+ ) -> Tuple[
178
+ List[Tuple[int, int]],
179
+ List[Tuple[int, int]]
180
+ ]:
181
+ """Adjust the offsets and shapes for output
182
+
183
+ workspace - workspace passed to "run"
184
+
185
+ offsets - i,j offsets for each image
186
+
187
+ shapes - shapes of the input images
188
+
189
+ names - pairs of input / output names
190
+
191
+ Based on the crop mode, adjust the offsets and shapes to optimize
192
+ the cropping.
193
+ """
194
+ offsets = np.array(offsets)
195
+ shapes = np.array(shapes)
196
+ if crop_mode == CropMode.CROP.value:
197
+ # modify the offsets so that all are negative
198
+ max_offset = np.max(offsets, 0)
199
+ offsets = offsets - max_offset[np.newaxis, :]
200
+ #
201
+ # Reduce each shape by the amount chopped off
202
+ #
203
+ shapes += offsets
204
+ #
205
+ # Pick the smallest in each of the dimensions and repeat for all
206
+ #
207
+ shape = np.min(shapes, 0)
208
+ shapes = np.tile(shape, len(shapes))
209
+ shapes.shape = offsets.shape
210
+ elif crop_mode == CropMode.PAD.value:
211
+ #
212
+ # modify the offsets so that they are all positive
213
+ #
214
+ min_offset = np.min(offsets, 0)
215
+ offsets = offsets - min_offset[np.newaxis, :]
216
+ #
217
+ # Expand each shape by the top-left padding
218
+ #
219
+ shapes += offsets
220
+ #
221
+ # Pick the largest in each of the dimensions and repeat for all
222
+ #
223
+ shape = np.max(shapes, 0)
224
+ shapes = np.tile(shape, len(shapes))
225
+ shapes.shape = offsets.shape
226
+ return offsets.tolist(), shapes.tolist()
227
+
228
+
229
+ @validate_call(config=ConfigDict(arbitrary_types_allowed=True))
230
+ def apply_alignment(
231
+ pixel_data: Annotated[Image2D, Field(description="Pixel data to be aligned")],
232
+ image_mask: Annotated[Image2DMask, Field(description="Mask of the image to be aligned")],
233
+ off_x: Annotated[int, Field(description="Offset of the resultant image relative to the original")],
234
+ off_y: Annotated[int, Field(description="Offset of the resultant image relative to the original")],
235
+ shape: Annotated[Tuple[int, int], Field(description="Shape of the resultant image")],
236
+ ) -> Tuple[
237
+ Image2D,
238
+ Image2DMask,
239
+ Optional[Union[Image2DMask, ImageBinary]],
240
+ ]:
241
+ if pixel_data.ndim == 2:
242
+ output_shape = (shape[0], shape[1], 1)
243
+ planes = [pixel_data]
244
+ else:
245
+ output_shape = (shape[0], shape[1], pixel_data.shape[2])
246
+ planes = [pixel_data[:, :, i] for i in range(pixel_data.shape[2])]
247
+ output_pixels = np.zeros(output_shape, pixel_data.dtype)
248
+ for i, plane in enumerate(planes):
249
+ #
250
+ # Copy the input to the output
251
+ #
252
+ p1, p2 = offset_slice(plane, output_pixels[:, :, i], off_y, off_x)
253
+ p2[:, :] = p1[:, :]
254
+ if pixel_data.ndim == 2:
255
+ output_pixels.shape = output_pixels.shape[:2]
256
+ output_mask = np.zeros(shape, bool)
257
+ p1, p2 = offset_slice(image_mask, output_mask, off_y, off_x)
258
+ p2[:, :] = p1[:, :]
259
+ if np.all(output_mask):
260
+ output_mask = None
261
+ crop_mask = np.zeros(pixel_data.shape, bool)
262
+ p1, p2 = offset_slice(crop_mask, output_pixels, off_y, off_x)
263
+ p1[:, :] = True
264
+ if np.all(crop_mask):
265
+ crop_mask = None
266
+ return output_pixels, output_mask, crop_mask
@@ -7,7 +7,7 @@ from pydantic import Field, validate_call, ConfigDict, BaseModel
7
7
  from cellprofiler_core.utilities.core.object import crop_labels_and_image
8
8
  from cellprofiler_library.types import ImageGrayscale, ImageGrayscaleMask, ObjectLabelSet, Pixel, ObjectLabel
9
9
  from cellprofiler_library.measurement_model import LibraryMeasurements
10
- from cellprofiler_library.functions.measurement import measure_object_area_occupied, measure_integrated_intensity, measure_mean_intensity, measure_std_intensity, measure_min_intensity, measure_max_intensity, measure_max_position, measure_center_of_mass_binary, measure_center_of_mass_intensity, measure_mass_displacement, measure_quartile_intensity
10
+ from cellprofiler_library.functions.measurement import measure_object_area_occupied, measure_integrated_intensity, measure_mean_intensity, measure_std_intensity, measure_min_intensity, measure_max_intensity, measure_max_position, measure_center_of_mass_binary, measure_center_of_mass_intensity, measure_mass_displacement, measure_quartile_intensity, measure_mad_intensity
11
11
  from cellprofiler_library.opts.measureobjectintensity import TemplateMeasurementFormat, IntensityFeature
12
12
 
13
13
 
@@ -216,15 +216,14 @@ def measure_object_intensity(
216
216
  dest[lindexes[qmask_no_upper] - 1] = _dest_no_upper
217
217
 
218
218
  #
219
- # Once again, for the MAD
219
+ # The MAD = median(|x_i - median(x)|). Compute it as a true
220
+ # per-object median of the absolute deviations. Reusing the
221
+ # quartile machinery here would interpolate across value
222
+ # boundaries for odd-sized objects and give a wrong result.
220
223
  #
221
- fraction = 1.0/ image_dimensions
222
- madimg = numpy.abs(limg - median_intensity[llabels - 1])
223
- order = numpy.lexsort((madimg, llabels))
224
-
225
- qmask, _mad_intensity, qmask_no_upper, _mad_intensity_no_upper = measure_quartile_intensity(indices, areas, fraction, madimg, order)
226
- mad_intensity[lindexes[qmask] - 1] = _mad_intensity
227
- mad_intensity[lindexes[qmask_no_upper] - 1] = _mad_intensity_no_upper
224
+ mad_intensity[lindexes - 1] = measure_mad_intensity(
225
+ limg, llabels, lindexes, median_intensity
226
+ )
228
227
 
229
228
  emask = masked_outlines > 0
230
229
  eimg = img[emask]
@@ -0,0 +1,20 @@
1
+ from enum import Enum
2
+
3
+ class AlignmentMethod(str, Enum):
4
+ MUTUAL_INFORMATION = "Mutual Information"
5
+ CROSS_CORRELATION = "Normalized Cross Correlation"
6
+
7
+ class CropMode(str, Enum):
8
+ SAME_SIZE = "Keep size"
9
+ CROP = "Crop to aligned region"
10
+ PAD = "Pad images"
11
+
12
+ class AdditionalAlignmentChoice(str, Enum):
13
+ SIMILARLY = "Similarly"
14
+ SEPARATELY = "Separately"
15
+
16
+ M_ALL = (AlignmentMethod.MUTUAL_INFORMATION, AlignmentMethod.CROSS_CORRELATION)
17
+
18
+ C_ALIGN = "Align"
19
+
20
+ MEASUREMENT_FORMAT = C_ALIGN + "_%sshift_%s"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cellprofiler-library-nightly
3
- Version: 5.0.0.dev643
3
+ Version: 5.0.0.dev649
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>
@@ -14,6 +14,7 @@ cellprofiler_library/functions/measurement.py
14
14
  cellprofiler_library/functions/object_processing.py
15
15
  cellprofiler_library/functions/segmentation.py
16
16
  cellprofiler_library/modules/__init__.py
17
+ cellprofiler_library/modules/_align.py
17
18
  cellprofiler_library/modules/_closing.py
18
19
  cellprofiler_library/modules/_colortogray.py
19
20
  cellprofiler_library/modules/_combineobjects.py
@@ -67,6 +68,7 @@ cellprofiler_library/modules/_smooth.py
67
68
  cellprofiler_library/modules/_threshold.py
68
69
  cellprofiler_library/modules/_watershed.py
69
70
  cellprofiler_library/opts/__init__.py
71
+ cellprofiler_library/opts/align.py
70
72
  cellprofiler_library/opts/colortogray.py
71
73
  cellprofiler_library/opts/convertimagetoobjects.py
72
74
  cellprofiler_library/opts/convertobjectstoimage.py