コード例 #1
0
def binary_dilation(image, selem=None, out=None):
    """Return fast binary morphological dilation of an image.

    This function returns the same result as greyscale dilation but performs
    faster for binary images.

    Morphological dilation sets a pixel at ``(i,j)`` to the maximum over all
    pixels in the neighborhood centered at ``(i,j)``. Dilation enlarges bright
    regions and shrinks dark regions.

    Parameters
    ----------

    image : ndarray
        Binary input image.
    selem : ndarray, optional
        The neighborhood expressed as a 2-D array of 1's and 0's.
        If None, use a cross-shaped structuring element (connectivity=1).
    out : ndarray of bool, optional
        The array to store the result of the morphology. If None is
        passed, a new array will be allocated.

    Returns
    -------
    dilated : ndarray of bool or uint
        The result of the morphological dilation with values in
        ``[False, True]``.
    """
    if out is None:
        out = cp.empty(image.shape, dtype=cp.bool)
    ndi.binary_dilation(image, structure=selem, output=out)
    return out
コード例 #2
0
ファイル: test_canny.py プロジェクト: grlee77/cucim
 def test_01_01_circle(self):
     """Test that the Canny filter finds the outlines of a circle"""
     i, j = cp.mgrid[-200:200, -200:200].astype(float) / 200
     c = cp.abs(cp.sqrt(i * i + j * j) - 0.5) < 0.02
     result = feature.canny(c.astype(float), 4, 0, 0,
                            cp.ones(c.shape, bool))
     #
     # erode and dilate the circle to get rings that should contain the
     # outlines
     #
     # TODO: grlee77: only implemented brute_force=True, so added that to
     #                these tests
     cd = binary_dilation(c, iterations=3, brute_force=True)
     ce = binary_erosion(c, iterations=3, brute_force=True)
     cde = cp.logical_and(cd, cp.logical_not(ce))
     self.assertTrue(cp.all(cde[result]))
     #
     # The circle has a radius of 100. There are two rings here, one
     # for the inside edge and one for the outside. So that's
     # 100 * 2 * 2 * 3 for those places where pi is still 3.
     # The edge contains both pixels if there's a tie, so we
     # bump the count a little.
     point_count = cp.sum(result)
     self.assertTrue(point_count > 1200)
     self.assertTrue(point_count < 1600)
コード例 #3
0
def inf_sup(u):
    """IS operator."""

    if _misc.ndim(u) == 2:
        P = _P2
    elif _misc.ndim(u) == 3:
        P = _P3
    else:
        raise ValueError("u has an invalid number of dimensions "
                         "(should be 2 or 3)")

    dilations = []
    for P_i in P:
        d = ndi.binary_dilation(u, cp.asarray(P_i)).astype(np.int8, copy=False)
        dilations.append(d)

    return cp.stack(dilations, axis=0).min(0)
コード例 #4
0
ファイル: test_canny.py プロジェクト: grlee77/cucim
 def test_01_02_circle_with_noise(self):
     """Test that the Canny filter finds the circle outlines
     in a noisy image"""
     cp.random.seed(0)
     i, j = cp.mgrid[-200:200, -200:200].astype(float) / 200
     c = cp.abs(cp.sqrt(i * i + j * j) - 0.5) < 0.02
     cf = c.astype(float) * 0.5 + cp.random.uniform(size=c.shape) * 0.5
     result = feature.canny(cf, 4, 0.1, 0.2, cp.ones(c.shape, bool))
     #
     # erode and dilate the circle to get rings that should contain the
     # outlines
     #
     cd = binary_dilation(c, iterations=4, brute_force=True)
     ce = binary_erosion(c, iterations=4, brute_force=True)
     cde = cp.logical_and(cd, cp.logical_not(ce))
     self.assertTrue(cp.all(cde[result]))
     point_count = cp.sum(result)
     self.assertTrue(point_count > 1200)
     self.assertTrue(point_count < 1600)
コード例 #5
0
def test_mask():
    from cudipy.segment.mask import otsu
    vol = cp.zeros((30, 30, 30))
    vol[15, 15, 15] = 1
    struct = generate_binary_structure(3, 1)
    # TODO: remove brute_force=True once non-brute force implemented for CuPy
    voln = binary_dilation(vol,
                           structure=struct,
                           iterations=4,
                           brute_force=True).astype("f4")
    initial = cp.sum(voln > 0)
    mask = voln.copy()
    thresh = otsu(mask)
    mask = mask > thresh
    initial_otsu = cp.sum(mask > 0)
    assert_array_equal(initial_otsu, initial)

    mins, maxs = bounding_box(mask)
    voln_crop = crop(mask, mins, maxs)
    initial_crop = cp.sum(voln_crop > 0)
    assert_array_equal(initial_crop, initial)

    applymask(voln, mask)
    final = cp.sum(voln > 0)
    assert_array_equal(final, initial)

    # Test multi_median.
    img = cp.arange(25).reshape(5, 5)
    img_copy = img.copy()
    medianradius = 2
    median_test = multi_median(img, medianradius, 3)
    assert_array_equal(img, img_copy)

    medarr = ((medianradius * 2) + 1, ) * img.ndim
    median_control = median_filter(img, medarr)
    median_control = median_filter(median_control, medarr)
    median_control = median_filter(median_control, medarr)
    assert_array_equal(median_test, median_control)
コード例 #6
0
def morphological_geodesic_active_contour(gimage,
                                          iterations,
                                          init_level_set='circle',
                                          smoothing=1,
                                          threshold='auto',
                                          balloon=0,
                                          iter_callback=lambda x: None):
    """Morphological Geodesic Active Contours (MorphGAC).

    Geodesic active contours implemented with morphological operators. It can
    be used to segment objects with visible but noisy, cluttered, broken
    borders.

    Parameters
    ----------
    gimage : (M, N) or (L, M, N) array
        Preprocessed image or volume to be segmented. This is very rarely the
        original image. Instead, this is usually a preprocessed version of the
        original image that enhances and highlights the borders (or other
        structures) of the object to segment.
        `morphological_geodesic_active_contour` will try to stop the contour
        evolution in areas where `gimage` is small. See
        `morphsnakes.inverse_gaussian_gradient` as an example function to
        perform this preprocessing. Note that the quality of
        `morphological_geodesic_active_contour` might greatly depend on this
        preprocessing.
    iterations : uint
        Number of iterations to run.
    init_level_set : str, (M, N) array, or (L, M, N) array
        Initial level set. If an array is given, it will be binarized and used
        as the initial level set. If a string is given, it defines the method
        to generate a reasonable initial level set with the shape of the
        `image`. Accepted values are 'checkerboard' and 'circle'. See the
        documentation of `checkerboard_level_set` and `circle_level_set`
        respectively for details about how these level sets are created.
    smoothing : uint, optional
        Number of times the smoothing operator is applied per iteration.
        Reasonable values are around 1-4. Larger values lead to smoother
        segmentations.
    threshold : float, optional
        Areas of the image with a value smaller than this threshold will be
        considered borders. The evolution of the contour will stop in this
        areas.
    balloon : float, optional
        Balloon force to guide the contour in non-informative areas of the
        image, i.e., areas where the gradient of the image is too small to push
        the contour towards a border. A negative value will shrink the contour,
        while a positive value will expand the contour in these areas. Setting
        this to zero will disable the balloon force.
    iter_callback : function, optional
        If given, this function is called once per iteration with the current
        level set as the only argument. This is useful for debugging or for
        plotting intermediate results during the evolution.

    Returns
    -------
    out : (M, N) or (L, M, N) array
        Final segmentation (i.e., the final level set)

    See Also
    --------
    inverse_gaussian_gradient, circle_level_set, checkerboard_level_set

    Notes
    -----

    This is a version of the Geodesic Active Contours (GAC) algorithm that uses
    morphological operators instead of solving partial differential equations
    (PDEs) for the evolution of the contour. The set of morphological operators
    used in this algorithm are proved to be infinitesimally equivalent to the
    GAC PDEs (see [1]_). However, morphological operators are do not suffer
    from the numerical stability issues typically found in PDEs (e.g., it is
    not necessary to find the right time step for the evolution), and are
    computationally faster.

    The algorithm and its theoretical derivation are described in [1]_.

    References
    ----------
    .. [1] A Morphological Approach to Curvature-based Evolution of Curves and
           Surfaces, Pablo Márquez-Neila, Luis Baumela, Luis Álvarez. In IEEE
           Transactions on Pattern Analysis and Machine Intelligence (PAMI),
           2014, :DOI:`10.1109/TPAMI.2013.106`
    """

    image = gimage
    init_level_set = _init_level_set(init_level_set, image.shape)

    _check_input(image, init_level_set)

    if threshold == 'auto':
        threshold = cp.percentile(image, 40)

    structure = cp.ones((3, ) * len(image.shape), dtype=cp.int8)
    dimage = cp.gradient(image)
    # threshold_mask = image > threshold
    if balloon != 0:
        threshold_mask_balloon = image > threshold / cp.abs(balloon)

    u = (init_level_set > 0).astype(cp.int8)

    iter_callback(u)

    for _ in range(iterations):

        # Balloon
        if balloon > 0:
            aux = ndi.binary_dilation(u, structure)
        elif balloon < 0:
            aux = ndi.binary_erosion(u, structure)
        if balloon != 0:
            u[threshold_mask_balloon] = aux[threshold_mask_balloon]

        # Image attachment
        aux = cp.zeros_like(image)
        du = cp.gradient(u)
        for el1, el2 in zip(dimage, du):
            aux += el1 * el2
        u[aux > 0] = 1
        u[aux < 0] = 0

        # Smoothing
        for _ in range(smoothing):
            u = _curvop(u)

        iter_callback(u)

    return u
コード例 #7
0
ファイル: mask.py プロジェクト: dipy/cudipy
def median_otsu(input_volume,
                vol_idx=None,
                median_radius=4,
                numpass=4,
                autocrop=False,
                dilate=None):
    """Simple brain extraction tool method for images from DWI data.

    It uses a median filter smoothing of the input_volumes `vol_idx` and an
    automatic histogram Otsu thresholding technique, hence the name
    *median_otsu*.

    This function is inspired from Mrtrix's bet which has default values
    ``median_radius=3``, ``numpass=2``. However, from tests on multiple 1.5T
    and 3T data     from GE, Philips, Siemens, the most robust choice is
    ``median_radius=4``, ``numpass=4``.

    Parameters
    ----------
    input_volume : ndarray
        3D or 4D array of the brain volume.
    vol_idx : None or array, optional.
        1D array representing indices of ``axis=3`` of a 4D `input_volume`.
        None is only an acceptable input if ``input_volume`` is 3D.
    median_radius : int
        Radius (in voxels) of the applied median filter (default: 4).
    numpass: int
        Number of pass of the median filter (default: 4).
    autocrop: bool, optional
        if True, the masked input_volume will also be cropped using the
        bounding box defined by the masked data. Should be on if DWI is
        upsampled to 1x1x1 resolution. (default: False).
    dilate : None or int, optional
        number of iterations for binary dilation

    Returns
    -------
    maskedvolume : ndarray
        Masked input_volume
    mask : 3D ndarray
        The binary brain mask

    Notes
    -----
    Copyright (C) 2011, the scikit-image team
    All rights reserved.

    Redistribution and use in source and binary forms, with or without
    modification, are permitted provided that the following conditions are
    met:

     1. Redistributions of source code must retain the above copyright
        notice, this list of conditions and the following disclaimer.
     2. Redistributions in binary form must reproduce the above copyright
        notice, this list of conditions and the following disclaimer in
        the documentation and/or other materials provided with the
        distribution.
     3. Neither the name of skimage nor the names of its contributors may be
        used to endorse or promote products derived from this software without
        specific prior written permission.

    THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
    IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
    WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
    DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
    INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
    (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
    SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
    HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
    IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
    POSSIBILITY OF SUCH DAMAGE.
    """
    if not _otsu_available:
        raise ImportError("cupyimg is required to use median_otsu")
    xp = get_array_module(input_volume, vol_idx)
    if len(input_volume.shape) == 4:
        if vol_idx is not None:
            b0vol = xp.mean(input_volume[..., xp.asarray(vol_idx)], axis=3)
        else:
            raise ValueError("For 4D images, must provide vol_idx input")
    else:
        b0vol = input_volume
    # Make a mask using a multiple pass median filter and histogram
    # thresholding.
    mask = multi_median(b0vol, median_radius, numpass)
    thresh = otsu(mask)
    mask = mask > thresh

    if dilate is not None:
        cross = generate_binary_structure(3, 1)
        # only brute_force iterations have been implemented in cupy
        kwargs = dict(brute_force=True) if xp == cp else {}
        mask = binary_dilation(mask, cross, iterations=dilate, **kwargs)

    # Auto crop the volumes using the mask as input_volume for bounding box
    # computing.
    if autocrop:
        mins, maxs = bounding_box(mask)
        mask = crop(mask, mins, maxs)
        croppedvolume = crop(input_volume, mins, maxs)
        maskedvolume = applymask(croppedvolume, mask)
    else:
        maskedvolume = applymask(input_volume, mask)
    return maskedvolume, mask