Esempio n. 1
0
    def run_function(self, function, pixel_data, mask):
        '''Apply the function once to the image, returning the result'''
        count = function.repeat_count
        function_name = function.function.value
        scale = function.scale.value
        custom_repeats = function.custom_repeats.value

        is_binary = pixel_data.dtype.kind == 'b'
        if function.structuring_element == SE_ARBITRARY:
            strel = np.array(function.strel.get_matrix())
        elif function.structuring_element == SE_DISK:
            strel = morph.strel_disk(scale / 2.0)
        elif function.structuring_element == SE_DIAMOND:
            strel = morph.strel_diamond(scale / 2.0)
        elif function.structuring_element == SE_LINE:
            strel = morph.strel_line(scale, function.angle.value)
        elif function.structuring_element == SE_OCTAGON:
            strel = morph.strel_octagon(scale / 2.0)
        elif function.structuring_element == SE_PAIR:
            strel = morph.strel_pair(function.x_offset.value,
                                     function.y_offset.value)
        elif function.structuring_element == SE_PERIODIC_LINE:
            xoff = function.x_offset.value
            yoff = function.y_offset.value
            n = max(scale / 2.0 / np.sqrt(float(xoff * xoff + yoff * yoff)), 1)
            strel = morph.strel_periodicline(xoff, yoff, n)
        elif function.structuring_element == SE_RECTANGLE:
            strel = morph.strel_rectangle(function.width.value,
                                          function.height.value)
        else:
            strel = morph.strel_square(scale)

        if (function_name
                in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL,
                    F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK,
                    F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE,
                    F_SPUR, F_THICKEN, F_THIN, F_VBREAK) and not is_binary):
            # Apply a very crude threshold to the image for binary algorithms
            logger.warning("Warning: converting image to binary for %s\n" %
                           function_name)
            pixel_data = pixel_data != 0

        if (function_name
                in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG, F_CONVEX_HULL,
                    F_DISTANCE, F_ENDPOINTS, F_FILL, F_FILL_SMALL, F_HBREAK,
                    F_INVERT, F_LIFE, F_MAJORITY, F_REMOVE, F_SHRINK, F_SKEL,
                    F_SKELPE, F_SPUR, F_THICKEN, F_THIN, F_VBREAK, F_OPENLINES)
                or
            (is_binary
             and function_name in (F_CLOSE, F_DILATE, F_ERODE, F_OPEN))):
            # All of these have an iterations argument or it makes no
            # sense to iterate
            if function_name == F_BRANCHPOINTS:
                return morph.branchpoints(pixel_data, mask)
            elif function_name == F_BRIDGE:
                return morph.bridge(pixel_data, mask, count)
            elif function_name == F_CLEAN:
                return morph.clean(pixel_data, mask, count)
            elif function_name == F_CLOSE:
                if mask is None:
                    return scind.binary_closing(pixel_data,
                                                strel,
                                                iterations=count)
                else:
                    return (scind.binary_closing(
                        pixel_data & mask, strel, iterations=count) |
                            (pixel_data & ~mask))
            elif function_name == F_CONVEX_HULL:
                if mask is None:
                    return morph.convex_hull_image(pixel_data)
                else:
                    return morph.convex_hull_image(pixel_data & mask)
            elif function_name == F_DIAG:
                return morph.diag(pixel_data, mask, count)
            elif function_name == F_DILATE:
                return scind.binary_dilation(pixel_data,
                                             strel,
                                             iterations=count,
                                             mask=mask)
            elif function_name == F_DISTANCE:
                image = scind.distance_transform_edt(pixel_data)
                if function.rescale_values.value:
                    image = image / np.max(image)
                return image
            elif function_name == F_ENDPOINTS:
                return morph.endpoints(pixel_data, mask)
            elif function_name == F_ERODE:
                return scind.binary_erosion(pixel_data,
                                            strel,
                                            iterations=count,
                                            mask=mask)
            elif function_name == F_FILL:
                return morph.fill(pixel_data, mask, count)
            elif function_name == F_FILL_SMALL:

                def small_fn(area, foreground):
                    return (not foreground) and (area <= custom_repeats)

                return morph.fill_labeled_holes(pixel_data, mask, small_fn)
            elif function_name == F_HBREAK:
                return morph.hbreak(pixel_data, mask, count)
            elif function_name == F_INVERT:
                if is_binary:
                    if mask is None:
                        return ~pixel_data
                    result = pixel_data.copy()
                    result[mask] = ~result[mask]
                    return result
                elif mask is None:
                    return 1 - pixel_data
                else:
                    result = pixel_data.copy()
                    result[mask] = 1 - result[mask]
                    return result
            elif function_name == F_LIFE:
                return morph.life(pixel_data, count)
            elif function_name == F_MAJORITY:
                return morph.majority(pixel_data, mask, count)
            elif function_name == F_OPEN:
                if mask is None:
                    return scind.binary_opening(pixel_data,
                                                strel,
                                                iterations=count)
                else:
                    return (scind.binary_opening(
                        pixel_data & mask, strel, iterations=count) |
                            (pixel_data & ~mask))
            elif function_name == F_OPENLINES:
                return morph.openlines(pixel_data,
                                       linelength=custom_repeats,
                                       mask=mask)
            elif function_name == F_REMOVE:
                return morph.remove(pixel_data, mask, count)
            elif function_name == F_SHRINK:
                return morph.binary_shrink(pixel_data, count)
            elif function_name == F_SKEL:
                return morph.skeletonize(pixel_data, mask)
            elif function_name == F_SKELPE:
                return morph.skeletonize(
                    pixel_data, mask,
                    scind.distance_transform_edt(pixel_data) *
                    poisson_equation(pixel_data))
            elif function_name == F_SPUR:
                return morph.spur(pixel_data, mask, count)
            elif function_name == F_THICKEN:
                return morph.thicken(pixel_data, mask, count)
            elif function_name == F_THIN:
                return morph.thin(pixel_data, mask, count)
            elif function_name == F_VBREAK:
                return morph.vbreak(pixel_data, mask)
            else:
                raise NotImplementedError(
                    "Unimplemented morphological function: %s" % function_name)
        else:
            for i in range(count):
                if function_name == F_BOTHAT:
                    new_pixel_data = morph.black_tophat(pixel_data,
                                                        mask=mask,
                                                        footprint=strel)
                elif function_name == F_CLOSE:

                    new_pixel_data = morph.closing(pixel_data,
                                                   mask=mask,
                                                   footprint=strel)
                elif function_name == F_DILATE:
                    new_pixel_data = morph.grey_dilation(pixel_data,
                                                         mask=mask,
                                                         footprint=strel)
                elif function_name == F_ERODE:
                    new_pixel_data = morph.grey_erosion(pixel_data,
                                                        mask=mask,
                                                        footprint=strel)
                elif function_name == F_OPEN:
                    new_pixel_data = morph.opening(pixel_data,
                                                   mask=mask,
                                                   footprint=strel)
                elif function_name == F_TOPHAT:
                    new_pixel_data = morph.white_tophat(pixel_data,
                                                        mask=mask,
                                                        footprint=strel)
                else:
                    raise NotImplementedError(
                        "Unimplemented morphological function: %s" %
                        function_name)
                if np.all(new_pixel_data == pixel_data):
                    break
                pixel_data = new_pixel_data
            return pixel_data
Esempio n. 2
0
    def run_on_image_setting(self, workspace, image):
        assert isinstance(workspace, cpw.Workspace)
        image_set = workspace.image_set
        measurements = workspace.measurements
        im = image_set.get_image(image.image_name.value,
                                 must_be_grayscale=True)
        #
        # Downsample the image and mask
        #
        new_shape = np.array(im.pixel_data.shape)
        if image.subsample_size.value < 1:
            new_shape = new_shape * image.subsample_size.value
            i, j = (np.mgrid[0:new_shape[0], 0:new_shape[1]].astype(float) /
                    image.subsample_size.value)
            pixels = scind.map_coordinates(im.pixel_data, (i, j), order=1)
            mask = scind.map_coordinates(im.mask.astype(float), (i, j)) > .9
        else:
            pixels = im.pixel_data
            mask = im.mask
        #
        # Remove background pixels using a greyscale tophat filter
        #
        if image.image_sample_size.value < 1:
            back_shape = new_shape * image.image_sample_size.value
            i, j = (np.mgrid[0:back_shape[0], 0:back_shape[1]].astype(float) /
                    image.image_sample_size.value)
            back_pixels = scind.map_coordinates(pixels, (i, j), order=1)
            back_mask = scind.map_coordinates(mask.astype(float), (i, j)) > .9
        else:
            back_pixels = pixels
            back_mask = mask
        radius = image.element_size.value
        back_pixels = morph.grey_erosion(back_pixels, radius, back_mask)
        back_pixels = morph.grey_dilation(back_pixels, radius, back_mask)
        if image.image_sample_size.value < 1:
            i, j = np.mgrid[0:new_shape[0], 0:new_shape[1]].astype(float)
            #
            # Make sure the mapping only references the index range of
            # back_pixels.
            #
            i *= float(back_shape[0] - 1) / float(new_shape[0] - 1)
            j *= float(back_shape[1] - 1) / float(new_shape[1] - 1)
            back_pixels = scind.map_coordinates(back_pixels, (i, j), order=1)
        pixels -= back_pixels
        pixels[pixels < 0] = 0

        #
        # For each object, build a little record
        #
        class ObjectRecord(object):
            def __init__(self, name):
                self.name = name
                self.labels = workspace.object_set.get_objects(name).segmented
                self.nobjects = np.max(self.labels)
                if self.nobjects != 0:
                    self.range = np.arange(1, np.max(self.labels) + 1)
                    self.labels = self.labels.copy()
                    self.labels[~im.mask] = 0
                    self.current_mean = fix(
                        scind.mean(im.pixel_data, self.labels, self.range))
                    self.start_mean = np.maximum(self.current_mean,
                                                 np.finfo(float).eps)

        object_records = [
            ObjectRecord(ob.objects_name.value) for ob in image.objects
        ]
        #
        # Transcribed from the Matlab module: granspectr function
        #
        # CALCULATES GRANULAR SPECTRUM, ALSO KNOWN AS SIZE DISTRIBUTION,
        # GRANULOMETRY, AND PATTERN SPECTRUM, SEE REF.:
        # J.Serra, Image Analysis and Mathematical Morphology, Vol. 1. Academic Press, London, 1989
        # Maragos,P. "Pattern spectrum and multiscale shape representation", IEEE Transactions on Pattern Analysis and Machine Intelligence, 11, N 7, pp. 701-716, 1989
        # L.Vincent "Granulometries and Opening Trees", Fundamenta Informaticae, 41, No. 1-2, pp. 57-90, IOS Press, 2000.
        # L.Vincent "Morphological Area Opening and Closing for Grayscale Images", Proc. NATO Shape in Picture Workshop, Driebergen, The Netherlands, pp. 197-208, 1992.
        # I.Ravkin, V.Temov "Bit representation techniques and image processing", Applied Informatics, v.14, pp. 41-90, Finances and Statistics, Moskow, 1988 (in Russian)
        # THIS IMPLEMENTATION INSTEAD OF OPENING USES EROSION FOLLOWED BY RECONSTRUCTION
        #
        ng = image.granular_spectrum_length.value
        startmean = np.mean(pixels[mask])
        ero = pixels.copy()
        # Mask the test image so that masked pixels will have no effect
        # during reconstruction
        #
        ero[~mask] = 0
        currentmean = startmean
        startmean = max(startmean, np.finfo(float).eps)

        footprint = np.array([[False, True, False], [True, True, True],
                              [False, True, False]])
        statistics = [image.image_name.value]
        for i in range(1, ng + 1):
            prevmean = currentmean
            ero = morph.grey_erosion(ero, mask=mask, footprint=footprint)
            rec = morph.grey_reconstruction(ero, pixels, footprint)
            currentmean = np.mean(rec[mask])
            gs = (prevmean - currentmean) * 100 / startmean
            statistics += ["%.2f" % gs]
            feature = image.granularity_feature(i)
            measurements.add_image_measurement(feature, gs)
            #
            # Restore the reconstructed image to the shape of the
            # original image so we can match against object labels
            #
            orig_shape = im.pixel_data.shape
            i, j = np.mgrid[0:orig_shape[0], 0:orig_shape[1]].astype(float)
            #
            # Make sure the mapping only references the index range of
            # back_pixels.
            #
            i *= float(new_shape[0] - 1) / float(orig_shape[0] - 1)
            j *= float(new_shape[1] - 1) / float(orig_shape[1] - 1)
            rec = scind.map_coordinates(rec, (i, j), order=1)

            #
            # Calculate the means for the objects
            #
            for object_record in object_records:
                assert isinstance(object_record, ObjectRecord)
                if object_record.nobjects > 0:
                    new_mean = fix(
                        scind.mean(rec, object_record.labels,
                                   object_record.range))
                    gss = ((object_record.current_mean - new_mean) * 100 /
                           object_record.start_mean)
                    object_record.current_mean = new_mean
                else:
                    gss = np.zeros((0, ))
                measurements.add_measurement(object_record.name, feature, gss)
        return statistics
Esempio n. 3
0
 def run_on_image_setting(self, workspace, image):
     assert isinstance(workspace, cpw.Workspace)
     image_set = workspace.image_set
     measurements = workspace.measurements
     im = image_set.get_image(image.image_name.value,
                                 must_be_grayscale=True)
     #
     # Downsample the image and mask
     #
     new_shape = np.array(im.pixel_data.shape)
     if image.subsample_size.value < 1:
         new_shape = new_shape * image.subsample_size.value
         i,j = (np.mgrid[0:new_shape[0],0:new_shape[1]].astype(float) /
                image.subsample_size.value)
         pixels = scind.map_coordinates(im.pixel_data,(i,j),order=1)
         mask = scind.map_coordinates(im.mask.astype(float), (i,j)) > .9
     else:
         pixels = im.pixel_data
         mask = im.mask
     #
     # Remove background pixels using a greyscale tophat filter
     #
     if image.image_sample_size.value < 1:
         back_shape = new_shape * image.image_sample_size.value
         i,j = (np.mgrid[0:back_shape[0],0:back_shape[1]].astype(float) /
                image.image_sample_size.value)
         back_pixels = scind.map_coordinates(pixels,(i,j), order=1)
         back_mask = scind.map_coordinates(mask.astype(float), (i,j)) > .9
     else:
         back_pixels = pixels
         back_mask = mask
     radius = image.element_size.value
     back_pixels = morph.grey_erosion(back_pixels, radius, back_mask)
     back_pixels = morph.grey_dilation(back_pixels, radius, back_mask)
     if image.image_sample_size.value < 1:
         i,j = np.mgrid[0:new_shape[0],0:new_shape[1]].astype(float)
         #
         # Make sure the mapping only references the index range of
         # back_pixels.
         #
         i *= float(back_shape[0]-1)/float(new_shape[0]-1)
         j *= float(back_shape[1]-1)/float(new_shape[1]-1)
         back_pixels = scind.map_coordinates(back_pixels,(i,j), order=1)
     pixels -= back_pixels
     pixels[pixels < 0] = 0
     #
     # For each object, build a little record
     #
     class ObjectRecord(object):
         def __init__(self, name):
             self.name = name
             self.labels = workspace.object_set.get_objects(name).segmented
             self.nobjects = np.max(self.labels)
             if self.nobjects != 0:
                 self.range = np.arange(1, np.max(self.labels)+1)
                 self.labels = self.labels.copy()
                 self.labels[~ im.mask] = 0
                 self.current_mean = fix(
                     scind.mean(im.pixel_data,
                                self.labels,
                                self.range))
                 self.start_mean = np.maximum(
                     self.current_mean, np.finfo(float).eps)
     object_records = [ObjectRecord(ob.objects_name.value)
                       for ob in image.objects ]
     #
     # Transcribed from the Matlab module: granspectr function
     #
     # CALCULATES GRANULAR SPECTRUM, ALSO KNOWN AS SIZE DISTRIBUTION,
     # GRANULOMETRY, AND PATTERN SPECTRUM, SEE REF.:
     # J.Serra, Image Analysis and Mathematical Morphology, Vol. 1. Academic Press, London, 1989
     # Maragos,P. "Pattern spectrum and multiscale shape representation", IEEE Transactions on Pattern Analysis and Machine Intelligence, 11, N 7, pp. 701-716, 1989
     # L.Vincent "Granulometries and Opening Trees", Fundamenta Informaticae, 41, No. 1-2, pp. 57-90, IOS Press, 2000.
     # L.Vincent "Morphological Area Opening and Closing for Grayscale Images", Proc. NATO Shape in Picture Workshop, Driebergen, The Netherlands, pp. 197-208, 1992.
     # I.Ravkin, V.Temov "Bit representation techniques and image processing", Applied Informatics, v.14, pp. 41-90, Finances and Statistics, Moskow, 1988 (in Russian)
     # THIS IMPLEMENTATION INSTEAD OF OPENING USES EROSION FOLLOWED BY RECONSTRUCTION
     #
     ng = image.granular_spectrum_length.value
     startmean = np.mean(pixels[mask])
     ero = pixels.copy()
     # Mask the test image so that masked pixels will have no effect
     # during reconstruction
     #
     ero[~mask] = 0
     currentmean = startmean
     startmean = max(startmean, np.finfo(float).eps)
     
     footprint = np.array([[False,True,False],
                           [True ,True,True],
                           [False,True,False]])
     statistics = [ image.image_name.value]
     for i in range(1,ng+1):
         prevmean = currentmean
         ero = morph.grey_erosion(ero, mask = mask, footprint=footprint)
         rec = morph.grey_reconstruction(ero, pixels, footprint)
         currentmean = np.mean(rec[mask])
         gs = (prevmean - currentmean) * 100 / startmean
         statistics += [ "%.2f"%gs]
         feature = image.granularity_feature(i)
         measurements.add_image_measurement(feature, gs)
         #
         # Restore the reconstructed image to the shape of the
         # original image so we can match against object labels
         #
         orig_shape = im.pixel_data.shape
         i,j = np.mgrid[0:orig_shape[0],0:orig_shape[1]].astype(float)
         #
         # Make sure the mapping only references the index range of
         # back_pixels.
         #
         i *= float(new_shape[0]-1)/float(orig_shape[0]-1)
         j *= float(new_shape[1]-1)/float(orig_shape[1]-1)
         rec = scind.map_coordinates(rec,(i,j), order=1)
         
         #
         # Calculate the means for the objects
         #
         for object_record in object_records:
             assert isinstance(object_record, ObjectRecord)
             if object_record.nobjects > 0:
                 new_mean = fix(scind.mean(rec, object_record.labels, 
                                           object_record.range))
                 gss = ((object_record.current_mean - new_mean) * 100 / 
                        object_record.start_mean)
                 object_record.current_mean = new_mean
             else:
                 gss = np.zeros((0,))
             measurements.add_measurement(object_record.name, feature, gss)
     return statistics
Esempio n. 4
0
    def run_function(self, function, pixel_data, mask):
        '''Apply the function once to the image, returning the result'''
        count = function.repeat_count
        function_name = function.function.value
        scale = function.scale.value
        custom_repeats = function.custom_repeats.value

        is_binary = pixel_data.dtype.kind == 'b'
        if function.structuring_element == SE_ARBITRARY:
            strel = np.array(function.strel.get_matrix())
        elif function.structuring_element == SE_DISK:
            strel = morph.strel_disk(scale / 2.0)
        elif function.structuring_element == SE_DIAMOND:
            strel = morph.strel_diamond(scale / 2.0)
        elif function.structuring_element == SE_LINE:
            strel = morph.strel_line(scale, function.angle.value)
        elif function.structuring_element == SE_OCTAGON:
            strel = morph.strel_octagon(scale / 2.0)
        elif function.structuring_element == SE_PAIR:
            strel = morph.strel_pair(function.x_offset.value,
                                     function.y_offset.value)
        elif function.structuring_element == SE_PERIODIC_LINE:
            xoff = function.x_offset.value
            yoff = function.y_offset.value
            n = max(scale / 2.0 / np.sqrt(float(xoff * xoff + yoff * yoff)), 1)
            strel = morph.strel_periodicline(
                    xoff, yoff, n)
        elif function.structuring_element == SE_RECTANGLE:
            strel = morph.strel_rectangle(
                    function.width.value, function.height.value)
        else:
            strel = morph.strel_square(scale)

        if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG,
                              F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL,
                              F_FILL_SMALL, F_HBREAK, F_LIFE, F_MAJORITY,
                              F_REMOVE, F_SHRINK, F_SKEL, F_SKELPE, F_SPUR,
                              F_THICKEN, F_THIN, F_VBREAK)
            and not is_binary):
            # Apply a very crude threshold to the image for binary algorithms
            logger.warning("Warning: converting image to binary for %s\n" %
                           function_name)
            pixel_data = pixel_data != 0

        if (function_name in (F_BRANCHPOINTS, F_BRIDGE, F_CLEAN, F_DIAG,
                              F_CONVEX_HULL, F_DISTANCE, F_ENDPOINTS, F_FILL,
                              F_FILL_SMALL,
                              F_HBREAK, F_INVERT, F_LIFE, F_MAJORITY, F_REMOVE,
                              F_SHRINK,
                              F_SKEL, F_SKELPE, F_SPUR, F_THICKEN, F_THIN,
                              F_VBREAK, F_OPENLINES) or
                (is_binary and
                         function_name in (F_CLOSE, F_DILATE, F_ERODE, F_OPEN))):
            # All of these have an iterations argument or it makes no
            # sense to iterate
            if function_name == F_BRANCHPOINTS:
                return morph.branchpoints(pixel_data, mask)
            elif function_name == F_BRIDGE:
                return morph.bridge(pixel_data, mask, count)
            elif function_name == F_CLEAN:
                return morph.clean(pixel_data, mask, count)
            elif function_name == F_CLOSE:
                if mask is None:
                    return scind.binary_closing(pixel_data,
                                                strel,
                                                iterations=count)
                else:
                    return (scind.binary_closing(pixel_data & mask,
                                                 strel,
                                                 iterations=count) |
                            (pixel_data & ~ mask))
            elif function_name == F_CONVEX_HULL:
                if mask is None:
                    return morph.convex_hull_image(pixel_data)
                else:
                    return morph.convex_hull_image(pixel_data & mask)
            elif function_name == F_DIAG:
                return morph.diag(pixel_data, mask, count)
            elif function_name == F_DILATE:
                return scind.binary_dilation(pixel_data,
                                             strel,
                                             iterations=count,
                                             mask=mask)
            elif function_name == F_DISTANCE:
                image = scind.distance_transform_edt(pixel_data)
                if function.rescale_values.value:
                    image = image / np.max(image)
                return image
            elif function_name == F_ENDPOINTS:
                return morph.endpoints(pixel_data, mask)
            elif function_name == F_ERODE:
                return scind.binary_erosion(pixel_data, strel,
                                            iterations=count,
                                            mask=mask)
            elif function_name == F_FILL:
                return morph.fill(pixel_data, mask, count)
            elif function_name == F_FILL_SMALL:
                def small_fn(area, foreground):
                    return (not foreground) and (area <= custom_repeats)

                return morph.fill_labeled_holes(pixel_data, mask, small_fn)
            elif function_name == F_HBREAK:
                return morph.hbreak(pixel_data, mask, count)
            elif function_name == F_INVERT:
                if is_binary:
                    if mask is None:
                        return ~ pixel_data
                    result = pixel_data.copy()
                    result[mask] = ~result[mask]
                    return result
                elif mask is None:
                    return 1 - pixel_data
                else:
                    result = pixel_data.copy()
                    result[mask] = 1 - result[mask]
                    return result
            elif function_name == F_LIFE:
                return morph.life(pixel_data, count)
            elif function_name == F_MAJORITY:
                return morph.majority(pixel_data, mask, count)
            elif function_name == F_OPEN:
                if mask is None:
                    return scind.binary_opening(pixel_data,
                                                strel,
                                                iterations=count)
                else:
                    return (scind.binary_opening(pixel_data & mask,
                                                 strel,
                                                 iterations=count) |
                            (pixel_data & ~ mask))
            elif function_name == F_OPENLINES:
                return morph.openlines(pixel_data, linelength=custom_repeats, mask=mask)
            elif function_name == F_REMOVE:
                return morph.remove(pixel_data, mask, count)
            elif function_name == F_SHRINK:
                return morph.binary_shrink(pixel_data, count)
            elif function_name == F_SKEL:
                return morph.skeletonize(pixel_data, mask)
            elif function_name == F_SKELPE:
                return morph.skeletonize(
                        pixel_data, mask,
                        scind.distance_transform_edt(pixel_data) *
                        poisson_equation(pixel_data))
            elif function_name == F_SPUR:
                return morph.spur(pixel_data, mask, count)
            elif function_name == F_THICKEN:
                return morph.thicken(pixel_data, mask, count)
            elif function_name == F_THIN:
                return morph.thin(pixel_data, mask, count)
            elif function_name == F_VBREAK:
                return morph.vbreak(pixel_data, mask)
            else:
                raise NotImplementedError("Unimplemented morphological function: %s" %
                                          function_name)
        else:
            for i in range(count):
                if function_name == F_BOTHAT:
                    new_pixel_data = morph.black_tophat(pixel_data, mask=mask,
                                                        footprint=strel)
                elif function_name == F_CLOSE:

                    new_pixel_data = morph.closing(pixel_data, mask=mask,
                                                   footprint=strel)
                elif function_name == F_DILATE:
                    new_pixel_data = morph.grey_dilation(pixel_data, mask=mask,
                                                         footprint=strel)
                elif function_name == F_ERODE:
                    new_pixel_data = morph.grey_erosion(pixel_data, mask=mask,
                                                        footprint=strel)
                elif function_name == F_OPEN:
                    new_pixel_data = morph.opening(pixel_data, mask=mask,
                                                   footprint=strel)
                elif function_name == F_TOPHAT:
                    new_pixel_data = morph.white_tophat(pixel_data, mask=mask,
                                                        footprint=strel)
                else:
                    raise NotImplementedError("Unimplemented morphological function: %s" %
                                              function_name)
                if np.all(new_pixel_data == pixel_data):
                    break
                pixel_data = new_pixel_data
            return pixel_data