Source code for node_filtering

# 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 filters, feature, exposure, color, morphology, transform
from skimage import io
from sylib.imageprocessing.image import Image
from sylib.imageprocessing.algorithm_selector import ImageFiltering_abstract
from sylib.imageprocessing.algorithm_selector import AlgorithmParameterWidget
from sylib.imageprocessing.color import grayscale_transform


def alg_prewitt(im, params):
    method = params['horizontal/vertical'].value
    if method == 'horizontal':
        return filters.prewitt_h(im)
    elif method == 'vertical':
        return filters.prewitt_v(im)
    else:
        return filters.prewitt(im)


def alg_scharr(im, params):
    method = params['horizontal/vertical'].value
    if method == 'horizontal':
        return filters.scharr_h(im)
    elif method == 'vertical':
        return filters.scharr_v(im)
    else:
        return filters.scharr(im)


def alg_sobel(im, params):
    method = params['horizontal/vertical'].value
    if method == 'horizontal':
        return filters.sobel_h(im)
    elif method == 'vertical':
        return filters.sobel_v(im)
    else:
        return filters.sobel(im)


def alg_roberts(im, params):
    method = params['positive/negative diagonal'].value
    if method == 'positive':
        return filters.roberts_pos_diag(im)
    elif method == 'negative':
        return filters.roberts_neg_diag(im)
    else:
        return filters.roberts(im)


def alg_greyscale(im, params):
    if len(im.shape) == 2:
        result = im
    elif im.shape[2] == 3 and params['luminance preserving'].value:
        result = im.dot(grayscale_transform)
    elif (im.shape[2] == 4 and
          params['luminance preserving'].value and
          params['preserve alpha'].value):
        result = np.zeros(im.shape[:2]+(2,))
        result[:,:,0] = im[:,:,:3].dot(grayscale_transform)
        result[:,:,1] = im[:,:,3]
    elif im.shape[2] == 4 and not params['preserve alpha'].value:
        result = im[:,:,:3].dot(grayscale_transform)
    else:
        result = im.mean(axis=2).reshape(im.shape[:2]+(1,))
    return result


def alg_center_image(im, params):
    x_weights = np.ones(im.shape[:2]) * np.arange(im.shape[1])
    y_weights = (
        np.ones((im.shape[1], im.shape[0])) * np.arange(im.shape[0])
    ).transpose()
    if len(im.shape) < 3: im = im.reshape(im.shape+(1,))
    channels = im.shape[2]
    x_w_sum, y_w_sum = 0, 0
    x_sum, y_sum = 0, 0
    for channel in range(channels):
        x_w_sum += np.sum(im[:,:,channel] * x_weights)
        y_w_sum += np.sum(im[:,:,channel] * y_weights)
        x_sum += np.sum(im[:,:,channel])
        y_sum += np.sum(im[:,:,channel])
    xpos = x_w_sum / x_sum
    ypos = y_w_sum / y_sum
    dx = int(xpos - im.shape[1]/2)
    dy = int(ypos - im.shape[0]/2)
    out = np.zeros(im.shape)
    if dx < 0 and dy < 0:
        out[-dy:, -dx:, :] = im[:dy, :dx, :]
    elif dx < 0 and dy >= 0:
        out[:-dy, -dx:, :] = im[dy:, :dx, :]
    elif dx >= 0 and dy < 0:
        out[-dy:, :-dx, :] = im[:dy, dx:, :]
    elif dx >= 0 and dy >= 0:
        out[:-dy, :-dx, :] = im[dy:, dx:, :]
    return out


def alg_padding(im, params):
    if len(im.shape) < 3: im = im.reshape(im.shape+(1,))
    add_alpha = params['add alpha'].value
    px, py = params['x'].value, params['y'].value
    px, py = int(px), int(py)
    k = params['k'].value
    max_x = im.shape[1]+abs(px)
    max_y = im.shape[0]+abs(py)
    result = np.full((max_y, max_x, im.shape[2]+add_alpha), k)
    if add_alpha:
        result[max(0,py):max(0,py)+im.shape[0],
               max(0,px):max(0,px)+im.shape[1], :-1] = im
        result[max(0,py):max(0,py)+im.shape[0],
               max(0,px):max(0,px)+im.shape[1], -1] = np.ones(im.shape[:2])
    else:
        result[max(0,py):max(0,py)+im.shape[0],
               max(0,px):max(0,px)+im.shape[1]] = im
    return result


def alg_crop_image(im, params):
    x, y = params['x'].value, params['y'].value
    w, h = params['width'].value, params['height'].value
    shape = im.shape
    x, y = min(x, shape[1]), min(y, shape[0])
    w, h = min(w, shape[1]-x), min(h, shape[0]-y)
    return im[y:y+h, x:x+w]


def alg_resize(im, params):
    h = params['height'].value
    w = params['width'].value
    if params['aspect'].value:
        if im.shape[0] > im.shape[1]:
            w = int((h * im.shape[1]) / im.shape[0])
        else:
            h = int((w * im.shape[0]) / im.shape[1])
    if len(im.shape) == 2:
        shape = (h, w)
    else:
        shape = (h, w, im.shape[2])
    return transform.resize(
        im, shape, order=params['interpolation degree'].value)


[docs]class ImageFiltering(ImageFiltering_abstract, node.Node): name = 'Filter Image' author = 'Mathias Broxvall' copyright = "(C) 2017 System Engineering Software Society" version = '0.1' icon = 'image_filtering.svg' description = ( 'Filters one image into another using one of a number of predefined' 'filtering operations. The implemented filters are to a large extent' 'based on `scikit-image`, but some filters are not from this package.') nodeid = 'syip.imagefiltering' tags = Tags(Tag.ImageProcessing.ImageManipulation) convex_hull_desc=( 'The convex hull is the set of pixels included in the smallest convex' 'polygon that surround all white pixels in the input image.' ) interpolation_degree_desc=( 'Degree of polynomial (0 - 5) used for interpolation.\n' '0 - no interpolation, 1 - bi-linear interpolation, ' '3 - bi-cubic interpolation' ) api = 'http://scikit-image.org/docs/0.13.x/api/' algorithms = { 'canny': { 'description': 'Canny edge detection.', 'sigma': 'Standard deviation of gaussian kernel (default 1.0)', 'multi_chromatic': False, 'url': api+'skimage.feature.html#skimage.feature.canny', 'algorithm': ( lambda im, par: feature.canny(im, sigma=par['sigma'].value)) }, 'corner, FAST': { 'description': ( 'Corner detection using the FAST ' '(Feature from Accelerated Segment Test) method.'), 'n': ( 'Number of points out of 16 that should be all brighter or' 'darker than test point. (default 12)'), 'threshold': ( 'Threshold used in determining wheter the pixels are darker or' 'brighter (default 0.15).\nDecrease threshold when more ' 'corners are desired'), 'multi_chromatic': False, 'url': api+'skimage.feature.html#skimage.feature.corner_fast', 'algorithm': ( lambda im, par: feature.corner_fast( im, n=par['n'].value, threshold=par['threshold'].value)) }, 'corner, harris': { 'description': 'Compute corner harris response image.', 'harris method': ( 'Method to compute response image from auto-correlation' 'matrix'), 'k': ( 'Sensitivity factor to separate corners from edges, typically ' 'in range [0, 0.2]. Small values of k result in detection of ' 'sharp corners.'), 'eps': 'Normalisation factor (Nobles corner measure)', 'sigma': ( 'Standard deviation used for the Gaussian kernel, which is ' 'used as weighting function for the auto-correlation matrix.'), 'multi_chromatic': False, 'url': api+'skimage.feature.html#skimage.feature.corner_harris', 'algorithm': ( lambda im, par: feature.corner_harris( im, k=par['k'].value, eps=par['eps'].value, sigma=par['sigma'].value, method=par['harris method'].value)) }, 'corner, KR': { 'description': ( 'Compute Kitchen-Rosenfeld corner measure response image'), 'border mode': 'Method for handling values outside the borders', 'k': 'Value outside image borders when method constant is used.', 'multi_chromatic': False, 'url': ( api + 'skimage.feature.html' + '#skimage.feature.corner_kitchen_rosenfeld'), 'algorithm': ( lambda im, par: feature.corner_kitchen_rosenfeld( im, cval=par['k'].value, mode=par['border mode'].value)) }, 'corner, moravec': { 'description': ( 'Compute Moravec corner measure response image.\n\n ' 'This is one of the simplest corner detectors and is ' 'comparatively fast but has several limitations ' '(e.g. not rotation invariant).'), 'window size': 'Size of window used during calculations', 'multi_chromatic': False, 'url': api+'skimage.feature.html#skimage.feature.corner_moravec', 'algorithm': ( lambda im, par: feature.corner_moravec( im, window_size=par['window size'].value)) }, 'corner, ST': { 'description': ( 'Compute Shi-Tomasi (Kanade-Tomasi) corner measure response' 'image. Uses information from auto-correlation matrix'), 'sigma': ( 'Standard deviation used for the Gaussian kernel, which is ' 'used as weighting function for the auto-correlation matrix.'), 'multi_chromatic': False, 'url': ( api+'skimage.feature.html#skimage.feature.corner_shi_tomasi'), 'algorithm': ( lambda im, par: feature.corner_shi_tomasi( im, sigma=par['sigma'].value)) }, 'gaussian': { 'description': 'Two-dimensional Gaussian filter', 'sigma-x': 'Standard deviation of gaussian filter along X-axis', 'sigma-y': 'Standard deviation of gaussian filter along Y-axis', 'border mode': 'Determines how the array borders are handled', 'k': 'Value outside image borders when method constant is used.', 'multi_chromatic': False, 'url': api+'skimage.filters.html#skimage.filters.gaussian', 'algorithm': ( lambda im, par: filters.gaussian( im, cval=par['k'].value, mode=par['border mode'].value, sigma=(par['sigma-x'].value, par['sigma-y'].value))) }, 'edges, prewitt': { 'description': ( 'Find edges using the Prewitt transform as one of, or ' 'combination of horizontal and vertical prewitt convolutions'), 'horizontal/vertical': ( 'Select orientation for transform, ' 'if both then mean square of both will be used'), 'multi_chromatic': False, 'url': api+'skimage.filters.html#skimage.filters.prewitt_h', 'algorithm': alg_prewitt }, 'edges, scharr': { 'description': ( 'Find edges using the Scharr transform as one of, or ' 'combination of horizontal and vertical prewitt convolutions.' '\nThe Scharr operator has a better rotation invariance than ' 'other edge filters such as the Sobel or the Prewitt ' 'operators'), 'horizontal/vertical': ( 'Select orientation for transform, ' 'if both then mean square of both will be used'), 'multi_chromatic': False, 'url': api+'skimage.filters.html#skimage.filters.scharr_h', 'algorithm': alg_scharr }, 'edges, sobel': { 'description': ( 'Find edges using the Sobel transform as one of, or ' 'combination of horizontal and vertical prewitt ' 'convolutions.'), 'horizontal/vertical': ( 'Select orientation for transform, ' 'if both then mean square of both will be used'), 'multi_chromatic': False, 'url': api+'skimage.filters.html#skimage.filters.sobel_h', 'algorithm': alg_sobel }, 'edges, roberts': { 'description': "Find edges using Robert's cross operator.", 'positive/negative diagonal': 'Select orientation for transform', 'multi_chromatic': False, 'url': api+'skimage.filters.html#skimage.filters.roberts_pos_diag', 'algorithm': alg_roberts }, 'edges, laplace': { 'description': "Find edges using the Laplace operator.", 'kernel size': 'Kernel size of the discrete Laplacian operator', 'multi_chromatic': False, 'url': api+'skimage.filters.html#skimage.filters.laplace', 'algorithm': ( lambda im, par: filters.laplace( im, ksize=par['kernel size'].value)) }, 'scale/offset': { 'description': ( 'Adds a scale and/or an offset to each channel equally'), 'scale': 'Scale factor applied to image before offset', 'offset': 'Offset applied to image after scale', 'multi_chromatic': True, 'algorithm': ( lambda im, par: im*par['scale'].value + par['offset'].value) }, 'normalize': { 'description': ( 'Adds a (positive) scale and offset so that smallest/highest ' 'value in image becomes 0 and 1 respectively.\n ' 'Operates on each channel separately'), 'multi_chromatic': False, 'algorithm': ( lambda im, par: im/(np.max(im)-np.min(im)) - np.min(im)) }, 'adjust, gamma correction': { 'description': ( 'Applies the correction: ' 'Vout = scale Vin^gamma\nProcesses each channel separately' ), 'scale': 'Constant scale factor applied after gamma correction', 'gamma': ( 'Gamma factor applied to image.\n<1 increases intensities of ' 'mid-tones,\n>1 decreases intensities of mid-tones' ), 'multi_chromatic': False, 'url': api+'skimage.exposure.html#skimage.exposure.adjust_gamma', 'algorithm': ( lambda im, par: exposure.adjust_gamma( im, gamma=par['gamma'].value, gain=par['scale'].value)) }, 'adjust, log correction': { 'description': ( 'Applies the correction: ' 'Vout = scale log(1 + Vin)\n' 'Processes each channel separately'), 'scale': 'Constant scale factor applied after gamma correction', 'inverse': ( 'Perform inverse log-correction instead (default false):\n' 'Vout = scale (2^Vin - 1)'), 'multi_chromatic': False, 'url': api+'skimage.exposure.html#skimage.exposure.adjust_log', 'algorithm': ( lambda im, par: exposure.adjust_log( im, gain=par['scale'].value, inv=par['inverse'].value)) }, 'adjust, sigmoid': { 'description': ( 'Performs Sigmoid correction on input image. ' 'Also known as contrast adjustment.\n' 'Vout = 1/(1+exp(gain*(cutoff-Vin)))\n' 'Processes each channel separately'), 'cutoff': ( 'Shifts the characteristic curve for the sigmoid horizontally' '(default 0.5)'), 'gain': ( 'Gain of sigmoid, affects rise time of curve (default 10.0)'), 'inverse': ( 'Perform negative sigmoid correction instead (default false)'), 'multi_chromatic': False, 'url': api+'skimage.exposure.html#skimage.exposure.adjust_sigmoid', 'algorithm': ( lambda im, par: exposure.adjust_sigmoid( im, gain=par['gain'].value, cutoff=par['cutoff'].value, inv=par['inverse'].value)) }, 'greyscale': { 'description': 'Transforms RGB images into greyscale', 'luminance preserving': ( 'Use weighted average based on separate luminosity of ' 'red-green-blue receptors in human eye.\nOnly works for three ' 'channel images'), 'preserve alpha': ( 'Passes through channel 4 (alpha), ' 'otherwise it is treated as another channel affecting output'), 'multi_chromatic': True, 'algorithm': alg_greyscale }, 'clamp': { 'description': ( 'Restricts the output values to a given maximum/minimum'), 'maximum': 'The maximum output value that can be passed through', 'minimum': 'The minimum output value that can be passed through', 'multi_chromatic': True, 'algorithm': ( lambda im,par: np.maximum( np.minimum(im, par['maximum'].value), par['minimum'].value)) }, 'adjust, histogram equalization': { 'description': ( 'Improves contrast by stretching and equalizing the histogram' ), 'bins': 'Number of bins in computed histogram (default 256)', 'multi_chromatic': True, 'url': api+'skimage.exposure.html#skimage.exposure.equalize_hist', 'algorithm': ( lambda im,par: exposure.equalize_hist( im, nbins=par['bins'].value)) }, 'adjust, adaptive histogram': { 'description': ( 'Improves contrast by stretching and equalizing the histogram' 'in a sliding window over the image'), 'adaptive kernel size': ( 'Size of the sliding window. ' 'Must evenly divide both image width and height.'), 'sigma': ('Clipping limit (normalized between 0 and 1). ' 'Higher values give more contrast. (default 1.0)'), 'bins': 'Number of bins in computed histogram (default 256)', 'multi_chromatic': True, 'url': ( api+'skimage.exposure.html'+ '#skimage.exposure.equalize_adapthist'), 'algorithm': ( lambda im,par: exposure.equalize_adapthist( im, kernel_size=par['adaptive kernel size'].value, clip_limit=par['sigma'].value, nbins=par['bins'].value)) }, 'color, hsv2rgb': { 'description': ( 'Interprets input channels as Hue-Saturation-Value (HSV) ' 'and outputs Red-Green-Blue (RGB) channels.'), 'multi_chromatic': True, 'url': api+'skimage.color.html#skimage.color.hsv2rgb', 'algorithm': lambda im,par: color.hsv2rgb(im) }, 'color, rgb2hsv': { 'description': ( 'Interprets input channels as Red-Green-Blue (RGB) ' 'and outputs Hue-Saturation-Value (HSV) channels.'), 'multi_chromatic': True, 'url': api+'skimage.color.html#skimage.color.rgb2hsv', 'algorithm': lambda im,par: color.rgb2hsv(im) }, 'color, rgb2xyz': { 'description': ( 'Interprets input channels as sRGB and outputs ' 'CIE XYZ channels.'), 'multi_chromatic': True, 'url': api+'skimage.color.html#skimage.color.rgb2xyz', 'algorithm': lambda im,par: color.rgb2xyz(im) }, 'color, xyz2rgb': { 'description': ('Interprets input channels as CIE XYZ ' 'and outputs sRGB channels.'), 'multi_chromatic': True, 'url': api+'skimage.color.html#skimage.color.xyz2rgb', 'algorithm': lambda im,par: color.xyz2rgb(im) }, 'threshold, basic': { 'description': 'Compares each channel with a threshold', 'threshold': 'Threshold value to compare with', 'multi_chromatic': False, 'algorithm': lambda im,par: im >= par['threshold'].value }, 'threshold, adaptive': { 'description': ( 'Applies an adaptive threshold to an array.\n\n' 'Also known as local or dynamic thresholding where the ' 'threshold value is the weighted mean for the local ' 'neighborhood of a pixel subtracted by a constant.'), 'kernel size': ( 'Size of blocks used during threshold check.\n' 'Must be an odd number. (default 3)'), 'threshold method': ( 'Method used for calculating adaptive threshold'), 'offset': ( 'Constant subtracted from weighted mean of neighborhood ' 'to calculate the local threshold value. (default 0.0)'), 'sigma': ( 'Standard deviation of gaussian kernel when method ' 'gaussian is used.'), 'multi_chromatic': False, 'url': ( api+'skimage.filters.html#skimage.filters.threshold_adaptive'), 'algorithm': lambda im,par: filters.threshold_adaptive( im, par['kernel size'].value, method=par['threshold method'].value, offset=par['offset'].value, param=par['sigma'].value) }, 'convex hull, image': { 'description': ( 'Computes the convex hull of a binary image.\n'+ convex_hull_desc), 'multi_chromatic': False, 'url': ( api+'skimage.morphology.html' '#skimage.morphology.convex_hull_image'), 'algorithm': lambda im,par: morphology.convex_hull_image(im) }, 'convex hull, objects': { 'description': ( 'Computes the convex hull of each object in a binary image.\n'+ convex_hull_desc+ '\nThis function uses labeling to define unique objects, finds' 'the convex hull of each using convex_hull_image,\nand ' 'combines these regions with logical OR. Be aware the convex' 'hulls of unconnected objects may overlap in the result'), 'multi_chromatic': False, 'url': ( api+'skimage.morphology.html' '#skimage.morphology.convex_hull_object'), 'algorithm': lambda im,par: morphology.convex_hull_object(im) }, 'morphology, skeletonize': { 'description': ( 'Returns the skeleton of a binary image. ' 'Thinning is used to reduce each connected component in a ' 'binary image to a single-pixel wide skeleton.'), 'multi_chromatic': False, 'url': ( api+'skimage.morphology.html#skimage.morphology.skeletonize'), 'algorithm': lambda im,par: morphology.skeletonize(im) }, 'morphology, labeling': { 'description': ( 'Creates a unique integer label for each connected component ' 'in an integer valued or binary image.'), 'diagonal neighborhood': ( 'If true then also consider diagonals for connectivity'), 'multi_chromatic': False, 'url': api+'skimage.morphology.html#skimage.morphology.label', 'algorithm': lambda im,par: morphology.label( im, connectivity=2 if par['diagonal neighborhood'].value else 1) }, 'morphology, remove small holes': { 'description': ( 'Removes small holes from an integer or boolean image.'), 'diagonal neighborhood': ( 'If true then also consider diagonals for connectivity'), 'n': 'Maximum size in pixels of areas to remove (default 64)', 'multi_chromatic': False, 'url': ( api+'skimage.morphology.html' '#skimage.morphology.remove_small_holes'), 'algorithm': lambda im,par: morphology.remove_small_holes( im, min_size=par['n'].value, connectivity=2 if par['diagonal neighborhood'].value else 1) }, 'morphology, remove small objects': { 'description': ( 'Removes connected components smaller than the given size.'), 'diagonal neighborhood': ( 'If true then also consider diagonals for connectivity'), 'n': 'Maximum size in pixels of areas to remove (default 64)', 'multi_chromatic': False, 'url': ( api+'skimage.morphology.html' '#skimage.morphology.remove_small_objects'), 'algorithm': lambda im,par: morphology.remove_small_objects( im, min_size=par['n'].value, connectivity=2 if par['diagonal neighborhood'].value else 1) }, 'transform, to integer': { 'description': 'Converts all channels into integer data', 'multi_chromatic': True, 'algorithm': lambda im,par: im.astype('int64') }, 'transform, resize': { 'description': 'Resizes an image to match the given dimensions', 'width': 'The new width of the image', 'height': 'The new height of the image', 'interpolation degree': interpolation_degree_desc, 'multi_chromatic': True, 'aspect': 'Preserve aspect ratio (gives smaller size on one axis)', 'url': api+'skimage.transform.html#skimage.transform.resize', 'algorithm': alg_resize }, 'transform, rescale': { 'description': 'Rescales an image by a given factor', 'scale x': 'Scale factor along X direction (horizontal)', 'scale y': 'Scale factor along Y direction (vertical)', 'interpolation degree': interpolation_degree_desc, 'multi_chromatic': True, 'url': api+'skimage.transform.html#skimage.transform.rescale', 'algorithm': lambda im,par: transform.rescale( im, (par['scale y'].value, par['scale x'].value), order=par['interpolation degree'].value) }, 'transform, rotate': { 'description': 'Rotates an image', 'angle': 'Angular degrees to rotate clockwise', 'resize': ( 'If true new image dimensions are calculated ' 'to exactly fit the image'), 'multi_chromatic': True, 'url': api+'skimage.transform.html#skimage.transform.rotate', 'algorithm': lambda im,par: transform.rotate( im, par['angle'].value, resize=par['resize'].value) }, 'transform, padding': { 'description': 'Adds a padding to an image', 'x': ( 'If positive, amount of padding added on the left side. ' 'If negative the amount of padding added on the right side.'), 'y': ('If positive, amount of padding added on the top.' 'If negative the amount of padding added on the bottom.'), 'k': 'Constant value used in padded areas', 'add alpha': ( 'Adds an alpha with value 1.0 inside image, 0.0 outside'), 'multi_chromatic': True, 'algorithm': alg_padding, }, 'transform, crop': { 'description': 'Crops the image to the given rectanglular area', 'x': 'Left edge of image', 'y': 'Top edge of image', 'width': 'Width of image', 'height': 'Height of image', 'multi_chromatic': False, 'algorithm': alg_crop_image, }, 'integral image': { 'description': ( 'Creates the integral image of the input.\n' 'An integral image contains at coordinate (m,n) the sum of ' 'all values above and to the left of it.\n ' ' S(m,n) = sum(im[0:m, 0:n])'), 'multi_chromatic': False, 'url': ( api+'skimage.feature.html' '#skimage.feature.hessian_matrix_det'), 'algorithm': lambda im,par: transform.integral_image(im) }, 'hessian determinant': { 'description': ( 'Computes an approximation of the determinant of the ' 'hessian matrix for each pixel.'), 'multi_chromatic': False, 'sigma': ( 'Standard deviation of gaussian kernel (default 3.0) used ' 'for calculating Hessian.\nApproximation is not reliable ' 'for sigma < 3.0'), 'url': ( api+'skimage.feature.html' '#skimage.feature.hessian_matrix_det'), 'algorithm': lambda im,par: feature.hessian_matrix_det( im, sigma=par['sigma'].value) }, 'center image': { 'description': ( 'Shifts image so that center of mass lies in center of image'), 'multi_chromatic': True, 'algorithm': alg_center_image }, # 'threshold, histogram min': { # 'description': ('The histogram of the image is computed and ' # 'smoothed until there are only two maxima. Then ' # 'the minimum in between is the threshold value'), # 'bins': 'Number of bins in computed histogram (default 256)', # 'histogram selection': 'Selected threshold from histogram', # 'multi_chromatic': False, # 'url': ( # api+'skimage.filters.html' # '#threshold-minimum'), # 'algorithm': lambda im,par: im > filters.threshold_minimum ( # im, nbins=par['bins'].value, # bias=par['histogram selection'].value) # }, # 'thin': { # 'description': ( # 'Performs morphological thinning of binary image by making ' # 'multiple passes over the image,\nremoving pixels matching a' # 'set of criteria designed to thin connected regions while ' # 'preserving connectivity'), # 'url': ( # api+'skimage.morphology' # '#thin'), # 'multi_chromatic': False, # 'algorithm': lambda im,par: morphology.thin(im) # }, } options_list = [ 'x', 'y', 'width', 'height', 'n', 'sigma', 'sigma-x', 'sigma-y', 'threshold', 'eps', 'window size', 'border mode', 'k', 'harris method', 'horizontal/vertical', 'scale', 'offset', 'gamma', 'positive/negative diagonal', 'kernel size', 'adaptive kernel size', 'luminance preserving', 'preserve alpha', 'maximum', 'minimum', 'inverse', 'cutoff', 'gain', 'bins', 'threshold method', 'histogram selection', 'diagonal neighborhood', 'interpolation degree', 'scale x', 'scale y', 'angle', 'resize', 'add alpha', 'aspect', ] options_types = { 'n': int, 'width': int, 'height': int, 'x': int, 'y': int, 'aspect': bool, 'interpolation degree': int, 'scale x': float, 'scale y': float, 'sigma': float, 'sigma-x': float, 'sigma-y': float, 'scale': float, 'offset': float, 'gamma': float, 'threshold': float, 'eps': float, 'k': float, 'angle': float, 'resize': bool, 'window size': int, 'kernel size': int, 'adaptive kernel size': int, 'luminance preserving': bool, 'preserve alpha': bool, 'maximum' : float, 'minimum' : float, 'inverse' : bool, 'cutoff' : float, 'gain' : float, 'bins' : int, 'diagonal neighborhood' : bool, 'border mode': ['constant', 'reflect', 'wrap', 'nearest', 'mirror'], 'harris method': ['k', 'eps'], 'horizontal/vertical': ['horizontal', 'vertical', 'both'], 'positive/negative diagonal': ['default', 'positive', 'negative'], 'threshold method': ['gaussian', 'mean', 'median'], 'histogram selection': ['min', 'mid', 'max'], 'add alpha': bool, } options_default = { 'n': 12, 'width': 512, 'height': 512, 'x': 0, 'y': 0, 'sigma': 1.0, 'sigma-x': 1.0, 'sigma-y': 1.0, 'threshold': 0.15, 'eps': 1e-6, 'k': 0.05, 'window size': 1, 'scale': 1.0, 'offset': 0.0, 'gamma': 1.0, 'kernel size': 3, 'adaptive kernel size': 4, 'luminance preserving': True, 'preserve alpha': True, 'maximum': 1.0, 'minimum': 0.0, 'inverse': False, 'cutoff': 0.5, 'gain': 10, 'bins': 256, 'diagonal neighborhood': False, 'scale x': 1.0, 'scale y': 1.0, 'interpolation degree': 3, 'angle': 0.0, 'resize': True, 'add alpha': False, 'aspect': False, } 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('source image to filter', name='source'), ]) outputs = Ports([ Image('result after filtering', name='result'), ]) __doc__ = ImageFiltering_abstract.generate_docstring( description, algorithms, options_list, inputs, outputs) def execute(self, node_context): source_obj = node_context.input['source'] source = source_obj.get_image() params = node_context.parameters alg_name = params['algorithm'].value if len(source.shape) == 3 and source.shape[2] > 1: multichannel_image = True else: multichannel_image = False alg = self.algorithms[alg_name]['algorithm'] if (multichannel_image and not self.algorithms[alg_name]['multi_chromatic']): # Process each channel separately im1 = alg(source[:,:,0], params) im = np.zeros(im1.shape[:2]+(source.shape[2],)) im[:,:,0] = im1 for channel in range(1,source.shape[2]): im[:,:,channel] = alg(source[:,:,channel], params) else: # Process all channels at once if len(source.shape) == 3 and source.shape[2] == 1: source = source.reshape(source.shape[:2]) im = alg(source, params) node_context.output['result'].set_image(im)
[docs]class ImageFiltering2(ImageFiltering_abstract, node.Node): name = 'Filter Image, Dual Output' author = 'Mathias Broxvall' copyright = '(C) 2017 System Engineering Software Society' version = '0.1' icon = 'image_filtering_dual.svg' description = 'Filters one image using algorithms with two images as output' nodeid = 'syip.imagefiltering2' tags = Tags(Tag.ImageProcessing.ImageManipulation) def alg_hessian_eigenval(im, par): hrr, hrc, hcc = feature.hessian_matrix(im, sigma=par['sigma'].value) return feature.hessian_matrix_eigvals(hrr, hrc, hcc) algorithms = { 'corner_foerstner': { 'description': ( 'Computes Foerstner corner measure response images. Outputs ' 'error eclipse sizes (top) and roundness or error eclipse ' '(bottom).'), 'sigma': 'Standard deviation of gaussian kernel (default 1.0)', 'multi_chromatic': False, 'algorithm': lambda im, par: feature.corner_foerstner( im, sigma=par['sigma'].value) }, 'hessian eigenvalues': { 'description': ( 'Computes the eigenvalues of the hessian matrix for each ' 'pixel. Returns larger eigenvalue in first output image and ' 'smaller in second'), 'multi_chromatic': False, 'sigma': ( 'Standard deviation of gaussian kernel (default 3.0) used for ' 'calculating Hessian.\nApproximation is not reliable for ' 'sigma < 3.0'), 'algorithm': alg_hessian_eigenval }, } options_list = ['n', 'sigma', 'threshold'] options_types = {'n': int, 'sigma': float, 'threshold': float } options_default = {'n': 12, 'sigma': 1.0, 'threshold': 0.15} 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('source image to filter', name='source'), ]) outputs = Ports([ Image('result after filtering', name='resultA'), Image('result after filtering', name='resultB'), ]) __doc__ = ImageFiltering_abstract.generate_docstring( description, algorithms, options_list, inputs, outputs) def execute(self, node_context): source_obj = node_context.input['source'] source = source_obj.get_image() params = node_context.parameters alg_name = params['algorithm'].value if len(source.shape) == 3 and source.shape[2] > 1: multichannel_image = True else: multichannel_image = False alg = self.algorithms[alg_name]['algorithm'] if (multichannel_image and not self.algorithms[alg_name]['multi_chromatic']): # Process each channel separately imA1, imB1 = alg(source[:,:,0], params) imA = np.zeros(imA1.shape[:2]+(source.shape[2],)) imB = np.zeros(imB1.shape[:2]+(source.shape[2],)) imA[:,:,0] = imA1 imB[:,:,0] = imA1 for channel in range(1,source.shape[2]): result = alg(source[:,:,channel], params) imA[:,:,channel], imB[:,:,channel] = result else: # Process all channels at once if len(source.shape) == 3 and source.shape[2] == 1: source = source.reshape(source.shape[:2]) imA, imB = alg(source, params) node_context.output['resultA'].set_image(imA) node_context.output['resultB'].set_image(imB)