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.
- {cellprofiler_library_nightly-5.0.0.dev643/cellprofiler_library_nightly.egg-info → cellprofiler_library_nightly-5.0.0.dev649}/PKG-INFO +1 -1
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/_version.py +3 -3
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/image_processing.py +309 -2
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/measurement.py +27 -0
- cellprofiler_library_nightly-5.0.0.dev649/cellprofiler_library/modules/_align.py +266 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectintensity.py +8 -9
- cellprofiler_library_nightly-5.0.0.dev649/cellprofiler_library/opts/align.py +20 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649/cellprofiler_library_nightly.egg-info}/PKG-INFO +1 -1
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library_nightly.egg-info/SOURCES.txt +2 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/LICENSE +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/README.md +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/__init__.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/__init__.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/file_processing.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/object_processing.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/functions/segmentation.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/measurement_model.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/__init__.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_closing.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_colortogray.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_combineobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_convertimagetoobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_convertobjectstoimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_correctilluminationapply.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_crop.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_dilateimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_dilateobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_enhanceedges.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_enhanceorsuppressfeatures.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_erodeimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_erodeobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_expandorshrinkobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_fillobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_flipandrotate.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_gaussianfilter.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_graytocolor.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_identifydeadworms.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_identifyprimaryobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_identifysecondaryobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_identifytertiaryobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_imagemath.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measurecolocalization.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measuregranularity.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimageareaoccupied.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimageintensity.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimageoverlap.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimagequality.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureimageskeleton.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectintensitydistribution.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectneighbors.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectoverlap.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectsizeshape.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measureobjectskeleton.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_measuretexture.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_medialaxis.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_medianfilter.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_morph.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_morphologicalskeleton.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_opening.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_overlayobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_overlayoutlines.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_reducenoise.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_removeholes.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_resize.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_savecroppedobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_shrinktoobjectcenters.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_smooth.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_threshold.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/modules/_watershed.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/__init__.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/colortogray.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/convertimagetoobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/convertobjectstoimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/correctilluminationapply.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/crop.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/dilateimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/dilateobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/enhanceedges.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/enhanceorsuppressfeatures.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/erodeimage.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/erodeobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/flipandrotate.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/graytocolor.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/identifydeadworms.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/identifyprimaryobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/identifysecondaryobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/identifytertiaryobjects.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/imagemath.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measurecolocalization.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measuregranularity.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimageareaoccupied.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimageintensity.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimageoverlap.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimagequality.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureimageskeleton.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectintensity.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectintensitydistribution.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectneighbors.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectoverlap.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measureobjectskeleton.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/measuretexture.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/morph.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/objectsizeshapefeatures.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/overlayoutlines.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/removeholes.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/resize.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/shrinktoobjectcenters.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/smooth.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/structuring_elements.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/opts/threshold.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/py.typed +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library/types.py +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library_nightly.egg-info/dependency_links.txt +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library_nightly.egg-info/requires.txt +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/cellprofiler_library_nightly.egg-info/top_level.txt +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/environment.yml +0 -0
- {cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/pyproject.toml +0 -0
- {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.
|
|
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.
|
|
32
|
-
__version_tuple__ = version_tuple = (5, 0, 0, '
|
|
31
|
+
__version__ = version = '5.0.0.dev649'
|
|
32
|
+
__version_tuple__ = version_tuple = (5, 0, 0, 'dev649')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
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
|
-
#
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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.
|
|
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
|
{cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/LICENSE
RENAMED
|
File without changes
|
{cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/README.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{cellprofiler_library_nightly-5.0.0.dev643 → cellprofiler_library_nightly-5.0.0.dev649}/setup.cfg
RENAMED
|
File without changes
|