Source code for node_morphology

# Copyright (c) 2017, System Engineering Software Society
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * 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.
#     * Neither the name of the System Engineering Software Society 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 SYSTEM ENGINEERING SOFTWARE SOCIETY 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.
"""
Some of the docstrings for this module have been
extracted from the `scikit-image <http://scikit-image.org/>`_ library
and are covered by their respective licenses.
"""

from __future__ import (print_function, division, unicode_literals,
                        absolute_import)
from sympathy.api import node
from sympathy.api.nodeconfig import Ports, Tag, Tags

import numpy as np
from skimage import morphology, filters
from sylib.imageprocessing.image import Image
from sylib.imageprocessing.algorithm_selector import ImageFiltering_abstract


[docs]class ImageMorphology(ImageFiltering_abstract, node.Node): name = 'Morphological Image Operations' author = 'Mathias Broxvall' copyright = '(C) 2017 System Engineering Software Society' version = '0.1' icon = 'image_morphology.svg' description = ( 'Performs one of a selection of morphological or rank operations on a ' 'target image (top) that uses a given structuring element (bottom). ' 'Other morphological operations can be found under the "filter" node') nodeid = 'syip.imagemorphology' tags = Tags(Tag.ImageProcessing.ImageManipulation) dilation = ( 'Morphological dilation sets a pixel at *(i,j)* to the maximum over ' 'all pixels in the neighborhood defined by\nthe structuring element ' 'centered at *(i,j)*. Dilation enlarges bright regions and shrinks ' 'dark regions.') erosion = ('Morphological erosion sets a pixel at *(i,j)* to the minimum ' 'over all pixels in the neighborhood centered at *(i,j)*. ' 'Erosion shrinks bright regions and enlarges dark regions.') closing = ('The morphological closing on an image is defined as a ' 'dilation followed by an erosion.\nClosing can remove small ' 'dark spots and connect small bright cracks.') opening = ('The morphological opening on an image is defined as a erosion ' 'followed by a dilation.\Opening can remove small white spots ' 'and connect small dark cracks.') def alg_median(target, se, par): if np.issubdtype(target.dtype, np.float): maxval = np.max(target) minval = np.min(target) target = (target - minval) * 256 / (maxval - minval) result = filters.median(target.astype('uint8'), se) return result * (maxval - minval) / 256.0 + minval else: return filters.median(target, se) api = 'http://scikit-image.org/docs/0.13.x/api/' algorithms = { 'binary, closing': { 'description': ( 'Perform morphological closing on image. Target and ' 'structuring element must be binary\n\n' + closing), 'multi_chromatic': False, 'algorithm': ( lambda target, se, par: morphology.binary_closing(target, se)) }, 'binary, dilation': { 'description': ( 'Perform morphological dilation on image. Target and ' 'structuring element must be binary\n\n' + dilation), 'multi_chromatic': False, 'algorithm': ( lambda target, se, par: morphology.binary_dilation(target, se)) }, 'binary, erosion': { 'description': ( 'Perform morphological erosion on image. Target and ' 'structuring element must be binary\n\n' + erosion), 'multi_chromatic': False, 'algorithm': ( lambda target, se, par: morphology.binary_erosion(target, se)) }, 'binary, opening': { 'description': ( 'Perform morphological opening on image. Target and ' 'structuring element must be binary\n\n'+opening), 'multi_chromatic': False, 'algorithm': ( lambda target, se, par: morphology.binary_opening(target, se)) }, 'grey, closing': { 'description': ( 'Perform greyscale morphological closing on image.\n\n' + closing), 'multi_chromatic': False, 'algorithm': lambda target, se, par: morphology.closing(target, se) }, 'grey, opening': { 'description': ( 'Perform greyscale morphological opening on image.\n\n' + opening), 'multi_chromatic': False, 'algorithm': lambda target, se, par: morphology.opening(target, se) }, 'grey, dilation': { 'description': ( 'Return greyscale morphological dilation of an image.\n\n' + dilation), 'multi_chromatic': False, 'algorithm': lambda target, se, par: morphology.dilation(target, se) }, 'grey, erosion': { 'description': ( 'Return greyscale morphological erosion of an image.\n\n' + erosion), 'multi_chromatic': False, 'algorithm': lambda target, se, par: morphology.erosion(target, se) }, 'tophat, black': { 'description': ( 'Return morphological black-tophat of image.\n\nThe black top ' 'hat of an image is defined as its morphological closing ' 'minus the original image.\nThis operation returns the dark ' 'spots of the image that are smaller than the structuring ' 'element.\nNote that dark spots in the original image are ' 'bright spots after the black top hat.'), 'multi_chromatic': False, 'algorithm': ( lambda target, se, par: morphology.black_tophat(target, se)) }, 'tophat, white': { 'description': ( 'Return morphological white-tophat of image.\n\n' 'The white top hat of an image is defined as the image minus ' 'its morphological opening.\nThis operation returns the white ' 'spots of the image that are smaller than the structuring ' 'element.'), 'multi_chromatic': False, 'algorithm': ( lambda target, se, par: morphology.white_tophat(target, se)) }, 'median': { 'description': ( 'Return the median of the neighborhood defined by sweeping ' 'the structuring element over the image.\nWhen used on ' 'non-integer datatypes they are first cast into uint8\nThis ' 'operation functions as a filter suitable to removing salt ' 'and pepper noise'), 'multi_chromatic': False, 'algorithm': alg_median }, 'autolevel': { 'description': ( 'Stretches the local histogram defined by structuring element ' 'to cover the full range'), 'p0': 'Defines the lower percentile (0..1) included in histogram', 'p1': 'Defines the upper percentile (0..1) included in histogram', 'multi_chromatic': False, 'url': ( api + 'skimage.filters.rank.html' + '#skimage.filters.rank.autolevel'), 'algorithm': (lambda target, se, par: filters.rank.autolevel_percentile( target, se, p0=par['p0'].value, p1=par['p1'].value) / 256.0) }, } options_list = ['p0', 'p1', ] options_types = { 'p0': float, 'p1': float, } options_default = { 'p0': 0.25, 'p1': 0.75, } parameters = node.parameters() parameters.set_string( 'algorithm', value=next(iter(algorithms)), description='', label='Algorithm') ImageFiltering_abstract.generate_parameters( parameters, options_types, options_default) inputs = Ports([ Image('Target image', name='target'), Image('Structuring element', name='structuring_element'), ]) outputs = Ports([ Image('Resulting image', name='output'), ]) __doc__ = ImageFiltering_abstract.generate_docstring( description, algorithms, options_list, inputs, outputs) def execute(self, node_context): target = node_context.input['target'].get_image() se = node_context.input['structuring_element'].get_image() params = node_context.parameters alg_name = params['algorithm'].value alg = self.algorithms[alg_name]['algorithm'] if len(target.shape) == 3 and target.shape[2] > 1: multichannel_image = True else: multichannel_image = False alg = self.algorithms[alg_name]['algorithm'] if len(se.shape) == 3 and se.shape[2] == 1: se = se.reshape(se.shape[:2]) if len(target.shape) == 3 and target.shape[2] == 1: target = target.reshape(target.shape[:2]) if (multichannel_image and not self.algorithms[alg_name]['multi_chromatic']): im1 = alg(target[:, :, 0], se, params) im = np.zeros(im1.shape[:2] + (target.shape[2],)) im[:, :, 0] = im1 for channel in range(1, target.shape[2]): im[:, :, channel] = alg(target[:, :, channel], se, params) else: im = alg(target, se, params) node_context.output['output'].set_image(im)