Source code for dippykit.windows

"""Module of window-generating functions

This module contains an assortment of functions that generate various windows.

"""

# This library was developed for the Georgia Tech graduate course ECE 6258:
# Digital Image Processing with Professor Ghassan AlRegib.
# For comments and feedback, please email dippykit[at]gmail.com

# Internal imports
from . import _utils

# Functional imports
import numpy as np

# General imports
import warnings
from typing import Union, Tuple

__author__ = 'Brighton Ancelin, Motaz Alfarraj, Ghassan AlRegib'

__all__ = ['window_2d']


[docs]def window_2d( support_size: Union[int, Tuple[int, int]], window_type: str='gaussian', **kwargs ) -> np.ndarray: """Generates a specified 2-dimensional window array. Returns a window with the specified parameters. The returned window is normalized such that the sum of all its elements is 1. When the window cannot be centered, the window will prefer top-left placement of pixels. :type support_size: ``int`` or ``Tuple[int, int]`` :param support_size: Height and width of the window array in pixels. :type window_type: ``str`` :param window_type: (default='gaussian') Type of window desired. Must be one of the following: *gaussian, rectangle, ellipse, circle*. :param kwargs: See below. :rtype: ``numpy.ndarray`` :return: The desired, normalized 2-dimensional window. :Keyword Arguments: * *Gaussian windows* * **variance** (``float``) -- (default=1.0) The sigma squared variance of the gaussian window. * *Rectangle windows* * **dimensions** (``int`` or ``Tuple[int, int]``) -- (default= **support_size** ) The dimensions of the rectangle window. If a list of two integers is provided, the first element is the window height and the second element is the window width. If a single integer is provided, a square is generated. * *Ellipse windows* * **radii** (``int`` or ``Tuple[int, int]``) -- (default= **support_size** /2) The radii of the ellipse window. If a list of two integers is provided, the first element is the window height-radius and the second element is the window width-radius. If a single integer is provided, a circle is generated. * *Circle windows* * **radius** (``int`` or ``Tuple[int, int]``) -- (default= **support_size** /2) The radius of the circle window. Examples: >>> window_2d(5, 'rect', dim=(2,3)) array([[0. , 0. , 0. , 0. , 0. ], [0. , 0.16666667, 0.16666667, 0.16666667, 0. ], [0. , 0.16666667, 0.16666667, 0.16666667, 0. ], [0. , 0. , 0. , 0. , 0. ], [0. , 0. , 0. , 0. , 0. ]]) >>> window_2d(5) array([[0.00296902, 0.01330621, 0.02193823, 0.01330621, 0.00296902], [0.01330621, 0.0596343 , 0.09832033, 0.0596343 , 0.01330621], [0.02193823, 0.09832033, 0.16210282, 0.09832033, 0.02193823], [0.01330621, 0.0596343 , 0.09832033, 0.0596343 , 0.01330621], [0.00296902, 0.01330621, 0.02193823, 0.01330621, 0.00296902]]) >>> window_2d(8, 'e', radii=(3,2)) array([[0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ], [0. , 0. , 0. , 0.05, 0.05, 0. , 0. , 0. ], [0. , 0. , 0.05, 0.05, 0.05, 0.05, 0. , 0. ], [0. , 0. , 0.05, 0.05, 0.05, 0.05, 0. , 0. ], [0. , 0. , 0.05, 0.05, 0.05, 0.05, 0. , 0. ], [0. , 0. , 0.05, 0.05, 0.05, 0.05, 0. , 0. ], [0. , 0. , 0. , 0.05, 0.05, 0. , 0. , 0. ], [0. , 0. , 0. , 0. , 0. , 0. , 0. , 0. ]]) """ def gaussian(): variance = _utils.get_arg_with_default(arg_dict, 'variance', 1) # Shift centers by -0.5 to accommodate the grid h_center = (support_size[0] / 2) - 0.5 w_center = (support_size[1] / 2) - 0.5 x, y = np.ogrid[(-h_center):(support_size[0] - h_center), (-w_center):(support_size[1] - w_center)] window_array = np.exp(-0.5 * (x**2 + y**2) / variance) # Normalize the window window_array /= np.sum(window_array) return window_array def rectangle(): dimensions = _utils.get_arg_with_default(arg_dict, 'dimensions', support_size) dimensions = _utils.resolve_shape_arg(dimensions, support_size, 'dimensions', False) (height, width) = dimensions # Should never happen thanks to resolve_shape_arg, but added as a # final precaution assert height <= support_size[0] and width <= support_size[1], \ "Rectangle dimensions must be less than support_size" window_array = np.zeros(support_size) h_begin = int((support_size[0] - height) / 2) h_end = h_begin + height w_begin = int((support_size[1] - width) / 2) w_end = w_begin + width weight = 1/(height * width) window_array[h_begin:h_end, w_begin:w_end] = weight return window_array def ellipse(): radii = _utils.get_arg_with_default(arg_dict, 'radii', [int(x/2) for x in support_size]) radii = _utils.resolve_shape_arg(radii, [int(x/2) for x in support_size], 'radii', True) (h_radius, w_radius) = radii window_array = np.zeros(support_size) h_center = (support_size[0] / 2) - 0.5 w_center = (support_size[1] / 2) - 0.5 if ((h_center + 0.5) - h_radius) < 0 or \ ((w_center + 0.5) - w_radius) < 0: warnings.warn("Figure extends beyond the support size and will " "be clipped.") x, y = np.ogrid[(-h_center):(support_size[0]-h_center), (-w_center):(support_size[1]-w_center)] # Create a mask in the shape of the ellipse mask = (x/h_radius)**2 + (y/w_radius)**2 < 1 weight = 1/np.sum(mask) window_array[mask] = weight return window_array def circle(): radius = _utils.get_arg_with_default(arg_dict, 'radius', int(min(support_size)/2)) # Assert that the kwargs are valid assert isinstance(radius, int) or isinstance(radius, float) or \ 1 == len(radius), "Keyword argument '{}' has a max size of " \ "1".format('radius') # Add an appropriately-named entry in the arg_dict and let ellipse() # handle the rest arg_dict['radii'] = radius return ellipse() # Dictionary of window_type names and their associated functions and # function parameter names func_dict = { 'gaussian': (gaussian, ['variance']), 'rectangle': (rectangle, ['dimensions']), 'ellipse': (ellipse, ['radii']), 'circle': (circle, ['radius']), } # Ensure that the support_size is valid support_size = _utils.resolve_shape_arg_no_max(support_size, 'support_size') window_type = _utils.resolve_arg_from_list(window_type, list(func_dict.keys())) arg_dict = _utils.resolve_arg_dict_from_list(kwargs, func_dict[ window_type][1]) return func_dict[window_type][0]()