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, ParameterView
from sympathy.api.nodeconfig import Port, Ports, Tag, Tags

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

[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 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 an 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) range = maxval - minval 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) 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\nThe 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 }, } options_list = [ ] options_types = { } options_default = { } parameters = node.parameters() parameters.set_string('algorithm', value=next(iter(algorithms)), description='', label='') 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)