mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-05 01:25:41 +08:00
Merge pull request #1063 from nlsn/3d-fallback
Add fallback decorator for 3D images When an image is 3D or higher, our morphology functions will now call out to scipy.
This commit is contained in:
@@ -1,9 +1,14 @@
|
||||
import warnings
|
||||
import numpy as np
|
||||
from scipy import ndimage
|
||||
from .selem import _default_selem
|
||||
from .misc import default_fallback
|
||||
|
||||
|
||||
# Our functions only work in 2D, so for 3D or higher input we should fall back
|
||||
# on `scipy.ndimage`. Additionally, we want to use a cross-shaped structuring
|
||||
# element of the appropriate dimension for each of these functions.
|
||||
# The `default_callback` provides all these.
|
||||
@default_fallback
|
||||
def binary_erosion(image, selem=None, out=None):
|
||||
"""Return fast binary morphological erosion of an image.
|
||||
|
||||
@@ -32,10 +37,6 @@ def binary_erosion(image, selem=None, out=None):
|
||||
|
||||
"""
|
||||
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
selem = (selem != 0)
|
||||
selem_sum = np.sum(selem)
|
||||
|
||||
@@ -48,10 +49,11 @@ def binary_erosion(image, selem=None, out=None):
|
||||
ndimage.convolve(binary, selem, mode='constant', cval=1, output=conv)
|
||||
|
||||
if out is None:
|
||||
out = conv
|
||||
out = np.empty_like(conv, dtype=np.bool)
|
||||
return np.equal(conv, selem_sum, out=out)
|
||||
|
||||
|
||||
@default_fallback
|
||||
def binary_dilation(image, selem=None, out=None):
|
||||
"""Return fast binary morphological dilation of an image.
|
||||
|
||||
@@ -81,10 +83,6 @@ def binary_dilation(image, selem=None, out=None):
|
||||
|
||||
"""
|
||||
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
selem = (selem != 0)
|
||||
|
||||
if np.sum(selem) <= 255:
|
||||
@@ -96,10 +94,11 @@ def binary_dilation(image, selem=None, out=None):
|
||||
ndimage.convolve(binary, selem, mode='constant', cval=0, output=conv)
|
||||
|
||||
if out is None:
|
||||
out = conv
|
||||
out = np.empty_like(conv, dtype=np.bool)
|
||||
return np.not_equal(conv, 0, out=out)
|
||||
|
||||
|
||||
@default_fallback
|
||||
def binary_opening(image, selem=None, out=None):
|
||||
"""Return fast binary morphological opening of an image.
|
||||
|
||||
@@ -133,6 +132,7 @@ def binary_opening(image, selem=None, out=None):
|
||||
return out
|
||||
|
||||
|
||||
@default_fallback
|
||||
def binary_closing(image, selem=None, out=None):
|
||||
"""Return fast binary morphological closing of an image.
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import warnings
|
||||
from skimage import img_as_ubyte
|
||||
from scipy import ndimage
|
||||
from .selem import _default_selem
|
||||
|
||||
from .misc import default_fallback
|
||||
|
||||
from . import cmorph
|
||||
|
||||
@@ -10,7 +8,7 @@ from . import cmorph
|
||||
__all__ = ['erosion', 'dilation', 'opening', 'closing', 'white_tophat',
|
||||
'black_tophat']
|
||||
|
||||
|
||||
@default_fallback
|
||||
def erosion(image, selem=None, out=None, shift_x=False, shift_y=False):
|
||||
"""Return greyscale morphological erosion of an image.
|
||||
|
||||
@@ -56,10 +54,6 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False):
|
||||
|
||||
"""
|
||||
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
if image is out:
|
||||
raise NotImplementedError("In-place erosion not supported!")
|
||||
image = img_as_ubyte(image)
|
||||
@@ -68,6 +62,7 @@ def erosion(image, selem=None, out=None, shift_x=False, shift_y=False):
|
||||
shift_x=shift_x, shift_y=shift_y)
|
||||
|
||||
|
||||
@default_fallback
|
||||
def dilation(image, selem=None, out=None, shift_x=False, shift_y=False):
|
||||
"""Return greyscale morphological dilation of an image.
|
||||
|
||||
@@ -114,18 +109,16 @@ def dilation(image, selem=None, out=None, shift_x=False, shift_y=False):
|
||||
|
||||
"""
|
||||
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
if image is out:
|
||||
raise NotImplementedError("In-place dilation not supported!")
|
||||
|
||||
image = img_as_ubyte(image)
|
||||
selem = img_as_ubyte(selem)
|
||||
return cmorph._dilate(image, selem, out=out,
|
||||
shift_x=shift_x, shift_y=shift_y)
|
||||
|
||||
|
||||
@default_fallback
|
||||
def opening(image, selem=None, out=None):
|
||||
"""Return greyscale morphological opening of an image.
|
||||
|
||||
@@ -169,10 +162,6 @@ def opening(image, selem=None, out=None):
|
||||
|
||||
"""
|
||||
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
h, w = selem.shape
|
||||
shift_x = True if (w % 2) == 0 else False
|
||||
shift_y = True if (h % 2) == 0 else False
|
||||
@@ -182,6 +171,7 @@ def opening(image, selem=None, out=None):
|
||||
return out
|
||||
|
||||
|
||||
@default_fallback
|
||||
def closing(image, selem=None, out=None):
|
||||
"""Return greyscale morphological closing of an image.
|
||||
|
||||
@@ -225,10 +215,6 @@ def closing(image, selem=None, out=None):
|
||||
|
||||
"""
|
||||
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
h, w = selem.shape
|
||||
shift_x = True if (w % 2) == 0 else False
|
||||
shift_y = True if (h % 2) == 0 else False
|
||||
@@ -238,6 +224,7 @@ def closing(image, selem=None, out=None):
|
||||
return out
|
||||
|
||||
|
||||
@default_fallback
|
||||
def white_tophat(image, selem=None, out=None):
|
||||
"""Return white top hat of an image.
|
||||
|
||||
@@ -280,10 +267,6 @@ def white_tophat(image, selem=None, out=None):
|
||||
|
||||
"""
|
||||
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
if image is out:
|
||||
raise NotImplementedError("Cannot perform white top hat in place.")
|
||||
|
||||
@@ -292,6 +275,7 @@ def white_tophat(image, selem=None, out=None):
|
||||
return out
|
||||
|
||||
|
||||
@default_fallback
|
||||
def black_tophat(image, selem=None, out=None):
|
||||
"""Return black top hat of an image.
|
||||
|
||||
@@ -335,10 +319,6 @@ def black_tophat(image, selem=None, out=None):
|
||||
|
||||
"""
|
||||
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
if image is out:
|
||||
raise NotImplementedError("Cannot perform white top hat in place.")
|
||||
|
||||
|
||||
@@ -1,5 +1,53 @@
|
||||
import numpy as np
|
||||
import scipy.ndimage as nd
|
||||
from .selem import _default_selem
|
||||
|
||||
# Our function names don't exactly correspond to ndimages.
|
||||
# This dictionary translates from our names to scipy's.
|
||||
funcs = ('erosion', 'dilation', 'opening', 'closing')
|
||||
skimage2ndimage = {x: 'grey_' + x for x in funcs}
|
||||
|
||||
# These function names are the same in ndimage.
|
||||
funcs = ('binary_erosion', 'binary_dilation', 'binary_opening',
|
||||
'binary_closing', 'black_tophat', 'white_tophat')
|
||||
skimage2ndimage.update({x: x for x in funcs})
|
||||
|
||||
def default_fallback(func):
|
||||
"""Decorator to fall back on ndimage for images with more than 2 dimensions
|
||||
|
||||
Decorator also provides a default structuring element, `selem`, with the
|
||||
appropriate dimensionality if none is specified.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
func : function
|
||||
A morphology function such as erosion, dilation, opening, closing,
|
||||
white_tophat, or black_tophat.
|
||||
|
||||
Returns
|
||||
-------
|
||||
func_out : function
|
||||
If the image dimentionality is greater than 2D, the ndimage
|
||||
function is returned, otherwise skimage function is used.
|
||||
"""
|
||||
|
||||
def func_out(image, selem=None, out=None, **kwargs):
|
||||
# Default structure element
|
||||
if selem is None:
|
||||
selem = _default_selem(image.ndim)
|
||||
|
||||
# If image has more than 2 dimensions, use scipy.ndimage
|
||||
if image.ndim > 2:
|
||||
function = getattr(nd, skimage2ndimage[func.__name__])
|
||||
try:
|
||||
return function(image, footprint=selem, output=out, **kwargs)
|
||||
except TypeError:
|
||||
# nd.binary_* take structure instead of footprint
|
||||
return function(image, structure=selem, output=out, **kwargs)
|
||||
else:
|
||||
return func(image, selem=selem, out=out, **kwargs)
|
||||
|
||||
return func_out
|
||||
|
||||
|
||||
def remove_small_objects(ar, min_size=64, connectivity=1, in_place=False):
|
||||
|
||||
@@ -311,10 +311,3 @@ def _default_selem(ndim):
|
||||
|
||||
"""
|
||||
return ndimage.morphology.generate_binary_structure(ndim, 1)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ from numpy import testing
|
||||
from skimage import data, color
|
||||
from skimage.util import img_as_bool
|
||||
from skimage.morphology import binary, grey, selem
|
||||
from scipy import ndimage
|
||||
|
||||
|
||||
lena = color.rgb2gray(data.lena())
|
||||
@@ -86,5 +87,84 @@ def test_default_selem():
|
||||
im_test = function(image)
|
||||
yield testing.assert_array_equal, im_expected, im_test
|
||||
|
||||
def test_3d_fallback_default_selem():
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), np.bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
opened = binary.binary_opening(image)
|
||||
|
||||
# expect a "hyper-cross" centered in the 5x5x5:
|
||||
image_expected = np.zeros((7, 7, 7), dtype=bool)
|
||||
image_expected[2:5, 2:5, 2:5] = ndimage.generate_binary_structure(3, 1)
|
||||
testing.assert_array_equal(opened, image_expected)
|
||||
|
||||
def test_3d_fallback_cube_selem():
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), np.bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
cube = np.ones((3, 3, 3), dtype=np.uint8)
|
||||
|
||||
for function in [binary.binary_closing, binary.binary_opening]:
|
||||
new_image = function(image, cube)
|
||||
yield testing.assert_array_equal, new_image, image
|
||||
|
||||
def test_2d_ndimage_equivalence():
|
||||
image = np.zeros((9, 9), np.uint16)
|
||||
image[2:-2, 2:-2] = 2**14
|
||||
image[3:-3, 3:-3] = 2**15
|
||||
image[4, 4] = 2**16-1
|
||||
|
||||
bin_opened = binary.binary_opening(image)
|
||||
bin_closed = binary.binary_closing(image)
|
||||
|
||||
selem = ndimage.generate_binary_structure(2, 1)
|
||||
ndimage_opened = ndimage.binary_opening(image, structure=selem)
|
||||
ndimage_closed = ndimage.binary_closing(image, structure=selem)
|
||||
|
||||
testing.assert_array_equal(bin_opened, ndimage_opened)
|
||||
testing.assert_array_equal(bin_closed, ndimage_closed)
|
||||
|
||||
def test_binary_output_2d():
|
||||
image = np.zeros((9, 9), np.uint16)
|
||||
image[2:-2, 2:-2] = 2**14
|
||||
image[3:-3, 3:-3] = 2**15
|
||||
image[4, 4] = 2**16-1
|
||||
|
||||
bin_opened = binary.binary_opening(image)
|
||||
bin_closed = binary.binary_closing(image)
|
||||
|
||||
int_opened = np.empty_like(image, dtype=np.uint8)
|
||||
int_closed = np.empty_like(image, dtype=np.uint8)
|
||||
binary.binary_opening(image, out=int_opened)
|
||||
binary.binary_closing(image, out=int_closed)
|
||||
|
||||
testing.assert_equal(bin_opened.dtype, np.bool)
|
||||
testing.assert_equal(bin_closed.dtype, np.bool)
|
||||
|
||||
testing.assert_equal(int_opened.dtype, np.uint8)
|
||||
testing.assert_equal(int_closed.dtype, np.uint8)
|
||||
|
||||
def test_binary_output_3d():
|
||||
image = np.zeros((9, 9, 9), np.uint16)
|
||||
image[2:-2, 2:-2, 2:-2] = 2**14
|
||||
image[3:-3, 3:-3, 3:-3] = 2**15
|
||||
image[4, 4, 4] = 2**16-1
|
||||
|
||||
bin_opened = binary.binary_opening(image)
|
||||
bin_closed = binary.binary_closing(image)
|
||||
|
||||
int_opened = np.empty_like(image, dtype=np.uint8)
|
||||
int_closed = np.empty_like(image, dtype=np.uint8)
|
||||
binary.binary_opening(image, out=int_opened)
|
||||
binary.binary_closing(image, out=int_closed)
|
||||
|
||||
testing.assert_equal(bin_opened.dtype, np.bool)
|
||||
testing.assert_equal(bin_closed.dtype, np.bool)
|
||||
|
||||
testing.assert_equal(int_opened.dtype, np.uint8)
|
||||
testing.assert_equal(int_closed.dtype, np.uint8)
|
||||
|
||||
if __name__ == '__main__':
|
||||
testing.run_module_suite()
|
||||
|
||||
@@ -2,6 +2,7 @@ import os.path
|
||||
|
||||
import numpy as np
|
||||
from numpy import testing
|
||||
from scipy import ndimage
|
||||
|
||||
import skimage
|
||||
from skimage import data_dir
|
||||
@@ -142,6 +143,65 @@ def test_default_selem():
|
||||
im_test = function(image)
|
||||
yield testing.assert_array_equal, im_expected, im_test
|
||||
|
||||
def test_3d_fallback_default_selem():
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), np.bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
opened = grey.opening(image)
|
||||
|
||||
# expect a "hyper-cross" centered in the 5x5x5:
|
||||
image_expected = np.zeros((7, 7, 7), dtype=bool)
|
||||
image_expected[2:5, 2:5, 2:5] = ndimage.generate_binary_structure(3, 1)
|
||||
testing.assert_array_equal(opened, image_expected)
|
||||
|
||||
def test_3d_fallback_cube_selem():
|
||||
# 3x3x3 cube inside a 7x7x7 image:
|
||||
image = np.zeros((7, 7, 7), np.bool)
|
||||
image[2:-2, 2:-2, 2:-2] = 1
|
||||
|
||||
cube = np.ones((3, 3, 3), dtype=np.uint8)
|
||||
|
||||
for function in [grey.closing, grey.opening]:
|
||||
new_image = function(image, cube)
|
||||
yield testing.assert_array_equal, new_image, image
|
||||
|
||||
def test_3d_fallback_white_tophat():
|
||||
image = np.zeros((7, 7, 7), dtype=bool)
|
||||
image[2, 2:4, 2:4] = 1
|
||||
image[3, 2:5, 2:5] = 1
|
||||
image[4, 3:5, 3:5] = 1
|
||||
new_image = grey.white_tophat(image)
|
||||
footprint = ndimage.generate_binary_structure(3,1)
|
||||
image_expected = ndimage.white_tophat(image,footprint=footprint)
|
||||
testing.assert_array_equal(new_image, image_expected)
|
||||
|
||||
def test_3d_fallback_black_tophat():
|
||||
image = np.ones((7, 7, 7), dtype=bool)
|
||||
image[2, 2:4, 2:4] = 0
|
||||
image[3, 2:5, 2:5] = 0
|
||||
image[4, 3:5, 3:5] = 0
|
||||
new_image = grey.black_tophat(image)
|
||||
footprint = ndimage.generate_binary_structure(3,1)
|
||||
image_expected = ndimage.black_tophat(image,footprint=footprint)
|
||||
testing.assert_array_equal(new_image, image_expected)
|
||||
|
||||
def test_2d_ndimage_equivalence():
|
||||
image = np.zeros((9, 9), np.uint8)
|
||||
image[2:-2, 2:-2] = 128
|
||||
image[3:-3, 3:-3] = 196
|
||||
image[4, 4] = 255
|
||||
|
||||
opened = grey.opening(image)
|
||||
closed = grey.closing(image)
|
||||
|
||||
selem = ndimage.generate_binary_structure(2, 1)
|
||||
ndimage_opened = ndimage.grey_opening(image, footprint=selem)
|
||||
ndimage_closed = ndimage.grey_closing(image, footprint=selem)
|
||||
|
||||
testing.assert_array_equal(opened, ndimage_opened)
|
||||
testing.assert_array_equal(closed, ndimage_closed)
|
||||
|
||||
class TestDTypes():
|
||||
|
||||
def setUp(self):
|
||||
|
||||
Reference in New Issue
Block a user