# This file is part of Sympathy for Data.
# Copyright (c) 2017, Combine Control Systems AB
#
# Sympathy for Data is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, version 3 of the License.
#
# Sympathy for Data is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Sympathy for Data. If not, see <http://www.gnu.org/licenses/>.
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 Image
from sylib.imageprocessing.algorithm_selector import ImageFiltering_abstract
[docs]class ImageDraw(ImageFiltering_abstract, node.Node):
name = 'Draw on Image'
author = 'Mathias Broxvall'
version = '0.1'
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])
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])
for row in range(ys.shape[0]):
try:
draw.disk
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])
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(np.int)
radii = ImageDraw.column_value(
params['radius'].value, table, x0s.shape[0]
)
colors = ImageDraw.get_colors(params, table, x0s.shape[0])
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([
Image('Image to draw on', name='image'),
Port.Table('Table used for drawing', name='table'),
])
outputs = Ports([
Image('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)