Skip to content

Instantly share code, notes, and snippets.

@nonchris
Last active June 22, 2021 09:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nonchris/9a4f1e32e1c801d4779fa2462fcf327f to your computer and use it in GitHub Desktop.
Save nonchris/9a4f1e32e1c801d4779fa2462fcf327f to your computer and use it in GitHub Desktop.
A task for the lecture 'computational itelligence' at the univeristy of bonn. It places three images from the MNIST dataset 'randomized' on a 64x64 image.
import random
from typing import Tuple, List
import numpy as np
import numpy.typing as npt
import torch
"""
This is a sub-task for the lecture 'computational itelligence' at the univeristy of bonn.
It places three images from the MNIST dataset 'randomized' on a 64x64 image.
"""
class ImageGenerator:
"""
Class to handle the positioning of three sub images and the generation of those label-cards\n
\n
The the values in __init__() are hardcoded to a base-image of 64x64 and sub-images of 28x28.\n
Some functions assume that y and x dimensions are the same and that the shapes are even.\n
\n
Internal processes are based on the mutability of np.array and torch.tensor\n
-> only references to an array will be passed into a function, the "outer" array will also be changed
"""
def __init__(self):
# we need to place the images without colliding.
# so imagine we split the whole field in four equal sectors.
# the anchorpoint (upper left corner) of our sub-image can only be located in a small field
# so that the whole 28x28 image fits into the 32x32 sector it can be placed in.
# those two lists represent the coordinates for those corners (see names)
self.left_or_upper_offset = [i for i in range(0, 5)] # upper for y axis, left offsite for x axis
self.right_or_lower_offset = [i for i in range(32, 37)] # lower for y axis, right for x-axis
# tuples that represent the y and x coordinates that describe one of those anchorpoint fields
# ([y values], [x values])
self.upper_left = (self.left_or_upper_offset, self.left_or_upper_offset)
self.lower_left = (self.right_or_lower_offset, self.left_or_upper_offset)
self.upper_right = (self.left_or_upper_offset, self.right_or_lower_offset)
self.lower_right = (self.right_or_lower_offset, self.right_or_lower_offset)
# the random field selection is done by getting a random integer
# using enumeration from cartesian sectors
# fields that have been used will be set to None
# using integers has also the advantage that we're able to detect whether two left fields are adjacent
self.field_map = {
0: self.upper_right,
1: self.upper_left,
2: self.lower_left,
3: self.lower_right
}
def put_sub_images(self, sub_images, base_image=None, shape=(64, 64)):
"""
Main function to be called by the user.\n
Takes up to three sub-images and places them randomized on a - possibly new generated - tensor
:param sub_images: List[Tuple[torch.tensor[float, float], int] - the tensors must be 2 dimensional!\n
List of three or less Tuples.
First entry of the Tuple is sub-image that shall be placed onto the base-image
Second entry is the actual value (int) shown on the image that is placed
:param base_image: optional base image (2dim torch.tensor) as input, create new (zeroes) if None is given
:param shape: shape of the base image (y_axis, x_axis), used to generate a new array if none is given
:returns: base_image (2dim torch.tensor) of given shape, label cards as torch.tensor([10, shape[0], shape[1]])
Label cards are in "natural" order - cards[0] label card for value 0, ..., cards[9] card for value 9
-------
"""
# check if base image was given - generate new one if not
if base_image is None:
base_image = torch.zeros(shape[0], shape[1])
# generate list of label cards
label_cards = self.generate_label_cards(base_image.shape[0], 10)
# print(label_cards)
# iterate over sub-images to place them random in the base image
for sub_image in sub_images:
content = sub_image[1] # extract value shown on image, just for readability :)
# print(f"{len(sub_image)=}")
# print(sub_image[1])
# print(f"{label_cards[content]=}")
# place sub-image in base-image, mark on label-card that belongs to this value
self.__put_sub_image(base_image, sub_image[0], label_cards[content])
# print(base_image)
# resetting fields for next round, since we've set some to the value of None
# not the best solution, but it does the job and this is the only function that shall be called by the user
self._reset_fields()
# print(torch.from_numpy(np.array(label_cards)).shape)
return base_image, torch.from_numpy(np.array(label_cards))
def _select_field(self) -> Tuple[int, int]:
"""
Selects a random field that's still free.\n
Chooses a random value from the set of possible anchor points for that field.\n
If only tow fields left: concatenate those fields if adjacent
:return: y and x anchorpoint to place the sub-image
"""
# get fields that are still available - fields that have the value None will be ignored
fields = [x for x in self.field_map.keys() if self.field_map[x]]
# only relevant when only two entries are left
# needed to decide whether those fields are adjacent
mx: int = max(fields)
mn: int = min(fields)
# check if only two fields are left - concat and return if possible
# subtracting the greater from the smaller
# if the delta value is 1 or 3 this means the fields are adjacent (2 means that they are diagonal)
if len(fields) == 2 and mx - mn in [1, 3]:
y_options, x_options = self.__concat_fields(mn, mx)
return random.choice(y_options), random.choice(x_options)
# merge not possible select single available field
choice = random.choice(fields)
# print(f"{choice=}")
# choose random coordinate from from possible anchor points
y_val = random.choice(self.field_map[choice][0])
x_val = random.choice(self.field_map[choice][1])
# set fields value to None so it won't be selected again
self.field_map[choice] = None
return y_val, x_val
def __concat_fields(self, key1: int, key2: int) -> Tuple[List[int], List[int]]:
"""
Used to concatenate two fields.\n
This is used when only tow (adjacent) fields are left in the dict.\n
Enables a more random pattern if we merge the last two empty sectors
:param key1: first field left
:param key2: second field to merge
:return: Tuple: all possible y-anchorpoints, possible x-anchorpoints
"""
# get anchor fields coordinates
y_1, x_1 = self.field_map[key1]
y_2, x_2 = self.field_map[key2]
# merge field coords to set
y_joined = {*y_1, *y_2}
x_joined = {*x_1, *x_2}
# print(f"{y_joined=}")
# print(f"{x_joined=}")
# generate all possible coords by generating all values between lowest and greatest possible coordinate
# plus one since end of range is an open interval and we want to include the last coordinate as well
y_options = [i for i in range(min(y_joined), max(y_joined) + 1)]
x_options = [i for i in range(min(x_joined), max(x_joined) + 1)]
# print(f"{y_options=}")
# print(f"{x_options=}")
return y_options, x_options
def __put_sub_image(self, base_image: npt.ArrayLike, sub_image: npt.ArrayLike, label_card: np.ndarray):
"""
Internal function to place a single sub-image on a yet to determine place in the base-image.\n
Marks position of the sub_image on the label card
:param base_image: ArrayLike with 2 dims - Image to place the sub_image in
:param sub_image: ArrayLike with 2 dims - Image that shall be placed onto base_image
:param label_card: NumpyArray with 2 dims - Label card of the number that is shown on the sub_image
"""
# get y and x positions to place the array at
y_pos, x_pos = self._select_field()
# print(sub_image)
# print(type(sub_image))
# get size of sub_image - needed to determine end of slice to place sub-image in
y_size_img = sub_image.shape[0]
x_size_img = sub_image.shape[1]
# print(f"{y_pos=}, {x_pos=}")
# print(f"{y_size_img} {x_size_img}")
# place sub-image based on y and x anchor-points
base_image[y_pos:y_pos + y_size_img, x_pos:x_pos + x_size_img] = sub_image
# mark position on label card
self._mark_position(label_card, y_pos, x_pos, placed_image_size=sub_image.shape[0])
@staticmethod
def _mark_position(label_card: np.ndarray, y_pos: int, x_pos: int, placed_image_size=28, label_size=4, smb_number=1):
"""
Mark center of placed sum image with centered filed of ones\ņ
function can fail if placed_image_size minus label_size results in an odd value!
:param label_card: card to mark the position on
:param y_pos: anchor-point of placed image on y axis
:param x_pos: anchor-point of placed image on x axis
:param placed_image_size: size of the sub-image that was placed, needed to determine center of image
:param label_size: size of the square that shall mark the middle of the sub-image
:param smb_number: symbol number that shall be placed in the middle as mark of the center
"""
# generate array to position
label = np.full((label_size, label_size), smb_number)
# print()
# print(f"{placed_image_size=}")
# print(f"{y_pos=}")
# determine value to shift y_pos and x_pos to get the anchor point of the sub-images center
# the place odd values can cause some uncool errors, but we've only got even numbers in this task - ignored
shift = (placed_image_size - label_size) // 2
# print(f"{shift=}")
# determine anchor-points of the center field
y_anchor_point = y_pos + shift
x_anchor_point = x_pos + shift
# print(f"{y_anchor_point=}")
# print(f"{x_anchor_point=}")
# place label array in at the determined position
label_card[y_anchor_point:y_anchor_point+label_size, x_anchor_point:x_anchor_point+label_size] = label
# print(label_card)
@staticmethod
def generate_label_cards(size: int, amount: int):
"""
Generate given amount of label cards of wanted size
:return: List[np.ndarray, ...] list of as many numpy arrays as wished - typehints for numpy suck
"""
# generate zeroed arrays of given size
return [np.zeros((size, size)) for i in range(0, amount)]
def _reset_fields(self):
"""
Just resetting the internal dict, to be called when we're done with placing all sub-images
"""
self.field_map = {
0: self.upper_right,
1: self.upper_left,
2: self.lower_left,
3: self.lower_right
}
# if __name__ == '__main__':
# image = np.arange(0, 100).reshape((10, 10))
# # image = np.zeros((64, 64))
#
# sub_img1 = np.array([
# [1, 1, 1, 0],
# [0, 0, 1, 0],
# [0, 1, 1, 1],
# [0, 0, 1, 0],
#
# ], dtype=np.float64)
#
# print(image)
# print(sub_img1)
#
# # place_image(image, sub_img1)
#
# generator = ImageGenerator()
#
# subimages = [(sub_img1, 1), (sub_img1, 2), (sub_img1, 3)]
# base_image, labels = generator.put_sub_images(subimages, base_image=image)
#
# print()
# print(base_image)
# print(labels)
# generator.mark_position()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment