Source code for node_draw

# This file is part of Sympathy for Data.
# Copyright (c) 2017, Combine Control Systems AB
#
# SYMPATHY FOR DATA COMMERCIAL LICENSE
# You should have received a link to the License with Sympathy for Data.
from sympathy.api import node
from sympathy.api.nodeconfig import Port, Ports, Tag, Tags

import numpy as np
import skimage
from skimage import draw
from sylib.imageprocessing.image import ImagePort
from sylib.imageprocessing.algorithm_selector import ImageFiltering_abstract


[docs] class ImageDraw(ImageFiltering_abstract, node.Node): name = 'Draw on Image' author = 'Mathias Broxvall' icon = 'image_draw.svg' description = 'Annotates an image with drawings based on tabular data.' nodeid = 'syip.imagedraw' tags = Tags(Tag.ImageProcessing.ImageManipulation) @staticmethod def column_value(s, table, N): """Get the intended parameter value from a string. Convert the given parameter string s to a number if possible, or return the column with that name from the table """ try: return np.ones(N) * float(s) except ValueError: return table.get_column_to_array(s) @staticmethod def get_colors(params, table, N): """Draw coordinates with color given by parameters""" try: red = np.ones(N) * float(params['red color'].value) except ValueError: red = table.get_column_to_array(params['red color'].value) try: green = np.ones(N) * float(params['green color'].value) except ValueError: green = table.get_column_to_array(params['green color'].value) try: blue = np.ones(N) * float(params['blue color'].value) except ValueError: blue = table.get_column_to_array(params['blue color'].value) try: alpha = np.ones(N) * float(params['alpha value'].value) except ValueError: alpha = table.get_column_to_array(params['alpha value'].value) colors = [red, green, blue, alpha] return colors @staticmethod def set_colors(image, coords, row, colors): if len(image.shape) < 3 or image.shape[2] == 1: draw.set_color(image, coords, [colors[0][row]], alpha=colors[3][row]) elif image.shape[2] == 2: draw.set_color( image, coords, [colors[0][row], colors[1][row]], alpha=colors[3][row] ) elif image.shape[2] == 3: draw.set_color( image, coords, [colors[0][row], colors[1][row], colors[2][row]], alpha=colors[3][row] ) elif image.shape[2] == 4: draw.set_color( image, coords, [colors[0][row], colors[1][row], colors[2][row], colors[3][row]], alpha=colors[3][row] ) else: pass def alg_circles(image, table, params): xs = table.get_column_to_array(params['x'].value) ys = table.get_column_to_array(params['y'].value) radii = ImageDraw.column_value( params['radius'].value, table, xs.shape[0] ) colors = ImageDraw.get_colors(params, table, ys.shape[0]) image = np.copy(image) for row in range(ys.shape[0]): x, y = draw.circle_perimeter( int(ys[row]), int(xs[row]), int(radii[row]), shape=image.shape ) ImageDraw.set_colors(image, (x, y), row, colors) return image def alg_filled_circles(image, table, params): xs = table.get_column_to_array(params['x'].value) ys = table.get_column_to_array(params['y'].value) radii = ImageDraw.column_value( params['radius'].value, table, xs.shape[0] ) colors = ImageDraw.get_colors(params, table, ys.shape[0]) image = np.copy(image) for row in range(ys.shape[0]): try: draw.disk # noqa: B018 except AttributeError: # Deprecated in 0.17. x, y = draw.circle( int(ys[row]), int(xs[row]), int(radii[row]), shape=image.shape ) else: x, y = draw.disk( (int(ys[row]), int(xs[row])), int(radii[row]), shape=image.shape ) ImageDraw.set_colors(image, (x, y), row, colors) return image def alg_lines(image, table, params): x1s = table.get_column_to_array(params['x'].value) y1s = table.get_column_to_array(params['y'].value) x2s = table.get_column_to_array(params['x2'].value) y2s = table.get_column_to_array(params['y2'].value) colors = ImageDraw.get_colors(params, table, x1s.shape[0]) image = np.copy(image) for row in range(y1s.shape[0]): x, y = draw.line( int(y1s[row]), int(x1s[row]), int(y2s[row]), int(x2s[row]) ) ImageDraw.set_colors(image, (x, y), row, colors) return image def alg_symbols(image, table, params): x0s = table.get_column_to_array(params['x'].value) y0s = table.get_column_to_array(params['y'].value) Ns = ImageDraw.column_value( params['N'].value, table, x0s.shape[0] ).astype(int) radii = ImageDraw.column_value( params['radius'].value, table, x0s.shape[0] ) colors = ImageDraw.get_colors(params, table, x0s.shape[0]) image = np.copy(image) for row in range(y0s.shape[0]): N = Ns[row] r = radii[row] x0 = int(x0s[row]) y0 = int(y0s[row]) convex = N < 0 N = np.abs(N) poly_x = np.array([]) poly_y = np.array([]) for n in range(N+1): alpha1 = (2*np.pi*(n+0.5))/N x1, y1 = int(x0+r*np.sin(alpha1)), int(y0+r*np.cos(alpha1)) poly_x = np.append(poly_x, x1) poly_y = np.append(poly_y, y1) if convex: alpha2 = alpha1 + np.pi/N x2 = int(x0+r*np.sin(alpha2)/4.) y2 = int(y0+r*np.cos(alpha2)/4.) poly_x = np.append(poly_x, x2) poly_y = np.append(poly_y, y2) ImageDraw.set_colors( image, draw.polygon(poly_y, poly_x), row, colors ) return image default_parameters = { 'red color': ( 'A number (0-1) or column name containing colors for first image' 'channel.' ), 'green color': ( 'A number (0-1) or column name containing colors for second image' 'channel, ignored for non RGB images' ), 'blue color': ( 'A number (0-1) or column name containing colors for third image' 'channel, ignored for non RGB images' ), 'alpha value': ( 'A number (0-1) or column name containing alpha values used for' 'blending the drawings over the image.\n' ' 1.0 is opaque, 0.0 transparent' ), 'force colors': ( 'If true then forces output to be RGB' ), } algorithms = { 'circle': dict({ 'description': ( 'Draws (non-filled) circles from given X,Y points with given' 'radii' ), 'x': 'Column name containing X-coordinates', 'y': 'Column name containing Y-coordinates', 'radius': 'Column name containing radii', 'algorithm': alg_circles, }, **default_parameters), 'symbols': dict({ 'description': ( 'Draws a symbol defined by number of vertices. Negative values' 'are drawn as convex objects. Eg, triangles have N=3,' 'squares N=4, hexagons N=6, stars N=-6' ), 'x': 'Column name containing X-coordinates', 'y': 'Column name containing Y-coordinates', 'N': 'Column name containing number of vertices', 'radius': 'Column name containing radii', 'algorithm': alg_symbols, }, **default_parameters), 'filled circle': dict({ 'description': ( 'Draws filled circles from given X,Y points with given radii' ), 'x': 'Column name containing X-coordinates', 'y': 'Column name containing Y-coordinates', 'radius': 'Column name containing radii', 'algorithm': alg_filled_circles, }, **default_parameters), 'lines': dict({ 'description': ( 'Draws lines from coordinates (X,Y) to coordinates (X2,Y2)' ), 'x': 'Column name containing X-coordinates', 'y': 'Column name containing Y-coordinates', 'x2': 'Column name containing X2-coordinates', 'y2': 'Column name containing Y2-coordinates', 'algorithm': alg_lines, }, **default_parameters), } options_list = [ 'x', 'y', 'x2', 'y2', 'radius', 'red color', 'green color', 'blue color', 'alpha value', 'N', 'force colors', ] options_types = { 'x': str, 'y': str, 'x2': str, 'y2': str, 'N': str, 'radius': str, 'red color': str, 'green color': str, 'blue color': str, 'alpha value': str, 'force colors': bool, } options_default = { 'x': 'X', 'y': 'Y', 'x2': 'X2', 'y2': 'Y2', 'N': 'N', 'radius': 'radius', 'red color': '1.0', 'green color': '1.0', 'blue color': '0.5', 'alpha value': '1.0', 'force colors': False, } 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([ ImagePort('Image to draw on', name='image'), Port.Table('Table used for drawing', name='table'), ]) outputs = Ports([ ImagePort('Resulting image', name='output'), ]) __doc__ = ImageFiltering_abstract.generate_docstring( description, algorithms, options_list, inputs, outputs ) def execute(self, node_context): image = node_context.input['image'].get_image() table = node_context.input['table'] params = node_context.parameters alg_name = params['algorithm'].value alg = self.algorithms[alg_name]['algorithm'] if len(image.shape) < 3: image = image.reshape(image.shape+(1,)) if params['force colors'].value: if image.shape[2] == 1: image = skimage.color.gray2rgb(image[:, :, 0]) elif image.shape[2] >= 3: image = image[:, :, :3] result = alg(image, table, params) node_context.output['output'].set_image(result)