# 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.
import numpy as np
from sympathy.api import node
from sympathy.api.nodeconfig import Port, Ports, Tag, Tags
from sylib.imageprocessing.image import ImagePort
class ArrangeImagesAbstract:
author = 'Mathias Broxvall'
icon = 'image_arrange.svg'
description = ('Arranges a sequence of images '
'horizontally, vertically, or in a grid ')
tags = Tags(Tag.ImageProcessing.Layers)
parameters = node.parameters()
parameters.set_integer(
'horizontally',
value=0,
description=('Number of images horizontally. Use 0 for all horizontal '
'or -1 for grid layout'),
label='Horizontally')
parameters.set_integer(
'hspacing',
value=0,
description='Spacing in pixels between images horizontally',
label='Horizontal spacing')
parameters.set_integer(
'vspacing',
value=0,
description='Spacing in pixels between images vertically',
label='Vertical spacing')
parameters.set_boolean(
'add_alpha',
value=True,
description=('If true then output will have one more output channel '
'than the inputs'),
label='Add alpha')
parameters.set_boolean(
'use_alpha',
value=True,
description=('If true then last output channel will be set to 0 '
'in-between images, 1 otherwise'),
label='Use alpha')
parameters.set_boolean(
'center',
value=True,
description=('Centers the images inside each grid cell where they are'
' placed'),
label='Center')
parameters.set_string(
'background',
value="0.0",
description='Background colour as float values separated by comma',
label='Background')
def calc_size(self, horizontally, hspacing, vspacing, images):
im_width = np.max([im.shape[1] for im in images])
im_height = np.max([im.shape[0] for im in images])
im_channels = np.min(
[im.shape[2] if len(im.shape) > 2 else 1 for im in images])
width = (horizontally - 1) * hspacing + horizontally * im_width
rows = int(np.ceil(len(images) / float(horizontally)))
height = (rows - 1) * vspacing + rows * im_height
return im_height, im_width, height, width, im_channels, rows
def execute(self, node_context):
inputs = self.get_input_images(node_context)
output = node_context.output['result']
images = [input_.get_image() for input_ in inputs]
hspacing = node_context.parameters['hspacing'].value
vspacing = node_context.parameters['vspacing'].value
add_alpha = node_context.parameters['add_alpha'].value
use_alpha = node_context.parameters['use_alpha'].value
horizontally = node_context.parameters['horizontally'].value
center = node_context.parameters['center'].value
background = node_context.parameters['background'].value
background = [float(v) for v in background.split(',')]
if horizontally == -1:
horizontally = int(np.ceil(np.sqrt(len(images))))
if horizontally == 0:
horizontally = len(images)
im_height, im_width, height, width, channels, rows = (
self.calc_size(horizontally, hspacing, vspacing, images))
if add_alpha:
channels += 1
if len(background) < channels:
background += background[-1:] * (channels - len(background))
if use_alpha:
background[-1] = 0
result = np.full((height, width, channels), background[:channels])
for row in range(rows):
posy = row * vspacing + row * im_height
for col in range(horizontally):
if col + row * horizontally >= len(images):
break
im = images[col + row * horizontally]
posx = col * hspacing + col * im_width
h, w = im.shape[:2]
if center:
dx = int((im_width - w) / 2)
dy = int((im_height - h) / 2)
else:
dx, dy = 0, 0
for ch in range(channels - add_alpha * 1):
if len(im.shape) == 2:
result[posy+dy:posy+dy+h, posx+dx:posx+dx+w, ch] = (
im[:, :])
else:
result[posy+dy:posy+dy+h, posx+dx:posx+dx+w, ch] = (
im[:, :, ch])
if use_alpha:
result[posy+dy:posy+dy+h, posx+dx:posx+dx+w, -1] = (
np.ones(im.shape[:2]))
output.set_image(result)
[docs]
class ArrangeImages(ArrangeImagesAbstract, node.Node):
"""
Arranges all inputs images in a horizontal, vertical, or grid
layout with optional spacing between each image. The number of
output channels are limited by the image with the least number of
channels. Outputs optional alpha channel with 1's or 0's on pixels
covered by an image or by the spacing.
"""
name = 'Arrange Images'
nodeid = 'com.sympathyfordata.imageanalysis.arrange_images'
inputs = Ports([
Port.Custom('image', 'Input images', name='images', n=(1, 6, 2))
])
outputs = Ports([
ImagePort('result after filtering', name='result'),
])
def get_input_images(self, node_context):
return node_context.input.group('images')
[docs]
class ArrangeImageList(ArrangeImagesAbstract, node.Node):
"""
Arranges all inputs images in a horizontal, vertical, or grid
layout with optional spacing between each image. The number of
output channels are limited by the image with the least number of
channels. Outputs optional alpha channel with 1's or 0's on pixels
covered by an image or by the spacing.
"""
name = 'Arrange Image List'
nodeid = 'com.sympathyfordata.imageanalysis.arrange_image_list'
inputs = Ports([
Port.Custom('[image]', 'Input images', name='images')
])
outputs = Ports([
ImagePort('result after filtering', name='result'),
])
def get_input_images(self, node_context):
return node_context.input['images']