<filename>genetic.py
from imager import *
from imager import Image as Img
from imager import array_to_img as ati
import time
import random

num_epochs = 10
total_inds = 10000
circle_size = 2 
k = 10
rectangle = 1
circle = 0

class Shape():
   def __init__(self):
       pass

   def rng(self):
       pass

   @staticmethod
   def draw_shape(shape_type, array, center=(0,0), radius=0, color=(255, 255, 255), thickness=-1, image_type=1, top=(0, 0), bottom=(0, 0)):
       if shape_type == circle:
           cv.circle(array, center, radius, color, thickness, image_type)
       elif shape_type == rectangle:
           cv.rectangle(array, top, bottom, color, thickness)


class Circle(Shape):
    def __init__(self, radius=0, x=0, y=0):
        self.r = radius
        self.center = (x, y)

    @staticmethod
    def rng(total, x_max, y_max, max_radius = circle_size):
        r = np.random.rand(1, total)
        r *= max_radius

        x = np.random.rand(1, total)
        x *= x_max

        y = np.random.rand(1, total)
        y *= y_max
        
        r = np.rint(r)
        x = np.rint(x)
        y = np.rint(y)

        r = r.astype(int)
        x = x.astype(int)
        y = y.astype(int)

        color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

        circles = [(circle, r[0][i], x[0][i], y[0][i], color) for i in range(total)]
        return circles

class Rectangle(Shape):
    def __init__(self, x_t, y_t, x_b, y_b):
        self.top = (x_t, y_t)
        self.bottom = (x_b, y_b)

    @staticmethod
    def rng(total, x_max, y_max):
        xt = np.random.rand(1, total)
        xt *= x_max
        xb = np.random.rand(1, total)
        xb *= x_max

        yt = np.random.rand(1, total)
        yt *= y_max
        yb = np.random.rand(1, total)
        yb *= y_max

        xt = np.rint(xt)
        xb = np.rint(xb)
        yt = np.rint(yt)
        yb = np.rint(yb)

        xt = xt.astype(int)
        xb = xb.astype(int)
        yt = yt.astype(int)
        yb = yb.astype(int)

        color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

        rectangles = [(rectangle, (xt[0][i], yt[0][i]), (xb[0][i], yb[0][i]), color) for i in range(total)]
        return rectangles

def get_img(img_name, img_type=1):
    return Img(img_name, img_type) 

def mse(imageA, imageB):
    sub = np.array(imageB) - np.array(imageA)
    chn_means = sub.mean(axis=0)
    rgb_err = chn_means.mean()

    err = np.sum((imageA.astype("float") - imageB.astype("float")) ** 2)
    err /= float(imageA.shape[0] * imageA.shape[1])

    return (err + rgb_err)/2

def get_individuals(img, total, shape='c'):
    if shape == 'c':
        inds = Circle.rng(total, img.shape[1], img.shape[0])
    elif shape == 'r':
        inds = Rectangle.rng(total, img.shape[1], img.shape[0])
    else:
        inds = Circle.rng(total//2, img.shape[1], img.shape[0])
        inds2 = Rectangle.rng(total-total//2, img.shape[1], img.shape[0])
        inds = inds + inds2

    return inds

def create_new_img(img):
    return np.zeros(img.shape)

def get_fitnesses(img, new, inds):
    errors = []
    mse_new = mse(new, img)
    for i in range(len(inds)): 
        updt = np.copy(new)
        if(inds[i][0] == circle):
            Shape.draw_shape(circle, updt, center=(inds[i][2], inds[i][3]), radius=inds[i][1], color=inds[i][4])
        elif(inds[i][0] == rectangle):
            Shape.draw_shape(rectangle, updt, top=inds[i][1], bottom=inds[i][2], color=inds[i][3])

        mse_updt = mse(updt, img)
        errors.append((mse_updt - mse_new, i, inds[i][0]))
    
    errors.sort(key = lambda s: s[0])
    return errors

def draw_k_best(shapes, n, new, inds):
    best_ret = -1
    best_circ = -1

    for i in range(n):
        index = shapes[i][1]
        shape_type = shapes[i][2]

        if shape_type == circle and best_circ == -1:
            best_circ = (circle, (inds[index][2], inds[index][3]), inds[i][1], inds[index][4])
        if shape_type == rectangle and best_ret == -1:
            best_ret = (rectangle, (inds[index][1], inds[index][2]), inds[index][3]) 

        if(shape_type == circle):
            Shape.draw_shape(circle, new, center=(inds[index][2], inds[index][3]), radius=inds[i][1], color=inds[index][4])
        elif(shape_type == rectangle):
            Shape.draw_shape(rectangle, new, top=inds[index][1], bottom=inds[index][2], color=inds[index][3])

    return [best_circ, best_ret]

if __name__ == '__main__':

    print("Choose image type: 1 for RGB, 0 for grayscale")
    flag = int(input())

    print("Choose the number of epochs")
    num_epochs = int(input())

    print("Choose the population size")
    total_inds = int(input())

    print("Choose your type of shape: Circle(c), Rectangle(r), Mixed(m)")
    s_type = input()

    if s_type != 'r':
        print("Choose the maximum circle size")
        circle_size = int(input())

    k = total_inds//10 

    print("What is the name of the input image?")
    image_name = input()
    image_name = image_name.strip()

    print("What is the name of the output image?")
    otp_name = input()
    otp_name = otp_name.strip()

    img_obj = get_img(image_name, flag)
    image = img_obj.img

    new_img = create_new_img(image)

    first = time.time()
    epoch_time = 0

    best_c, best_r = -1, -1 

    for epoch in range(num_epochs):
        print(f'Generation: {epoch}')
        print(f'Estimated time left: {(num_epochs-epoch)*(time.time() - epoch_time)/60.0} minutes')
        epoch_time = time.time()
        inds = get_individuals(image, total_inds, s_type)
        fits = get_fitnesses(image, new_img, inds)

        draw_k_best(fits, k, new_img, inds)

    elapsed = time.time() - first
    hours = elapsed // 3600
    minutes = elapsed % 3600
    seconds = 60*minutes/60%60
    minutes//=60

    print(f"It took me {num_epochs} epochs, {k * num_epochs} shapes to draw your image. A total of {hours} hours, {minutes} minutes and {seconds} seconds")

    cv.imwrite(otp_name, new_img)
    get_img(otp_name, flag).display() 

<filename>genetic.py
from imager import *
from imager import Image as Img
from imager import array_to_img as ati
import time
import random

num_epochs = 10
total_inds = 10000
circle_size = 2 
k = 10
rectangle = 1
circle = 0

class Shape():
   def __init__(self):
       pass

   def rng(self):
       pass

   @staticmethod
   def draw_shape(shape_type, array, center=(0,0), radius=0, color=(255, 255, 255), thickness=-1, image_type=1, top=(0, 0), bottom=(0, 0)):
       if shape_type == circle:
           cv.circle(array, center, radius, color, thickness, image_type)
       elif shape_type == rectangle:
           cv.rectangle(array, top, bottom, color, thickness)


class Circle(Shape):
    def __init__(self, radius=0, x=0, y=0):
        self.r = radius
        self.center = (x, y)

    @staticmethod
    def rng(total, x_max, y_max, max_radius = circle_size):
        r = np.random.rand(1, total)
        r *= max_radius

        x = np.random.rand(1, total)
        x *= x_max

        y = np.random.rand(1, total)
        y *= y_max
        
        r = np.rint(r)
        x = np.rint(x)
        y = np.rint(y)

        r = r.astype(int)
        x = x.astype(int)
        y = y.astype(int)

        color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

        circles = [(circle, r[0][i], x[0][i], y[0][i], color) for i in range(total)]
        return circles

class Rectangle(Shape):
    def __init__(self, x_t, y_t, x_b, y_b):
        self.top = (x_t, y_t)
        self.bottom = (x_b, y_b)

    @staticmethod
    def rng(total, x_max, y_max):
        xt = np.random.rand(1, total)
        xt *= x_max
        xb = np.random.rand(1, total)
        xb *= x_max

        yt = np.random.rand(1, total)
        yt *= y_max
        yb = np.random.rand(1, total)
        yb *= y_max

        xt = np.rint(xt)
        xb = np.rint(xb)
        yt = np.rint(yt)
        yb = np.rint(yb)

        xt = xt.astype(int)
        xb = xb.astype(int)
        yt = yt.astype(int)
        yb = yb.astype(int)

        color = (random.randint(0, 255), random.randint(0, 255), random.randint(0, 255))

        rectangles = [(rectangle, (xt[0][i], yt[0][i]), (xb[0][i], yb[0][i]), color) for i in range(total)]
        return rectangles

def get_img(img_name, img_type=1):
    return Img(img_name, img_type) 

def mse(imageA, imageB):
    sub = np.array(imageB) - np.array(imageA)
    chn_means = sub.mean(axis=0)
    rgb_err = chn_means.mean()

    err = np.sum((imageA.astype("float") - imageB.astype("float")) ** 2)
    err /= float(imageA.shape[0] * imageA.shape[1])

    return (err + rgb_err)/2

def get_individuals(img, total, shape='c'):
    if shape == 'c':
        inds = Circle.rng(total, img.shape[1], img.shape[0])
    elif shape == 'r':
        inds = Rectangle.rng(total, img.shape[1], img.shape[0])
    else:
        inds = Circle.rng(total//2, img.shape[1], img.shape[0])
        inds2 = Rectangle.rng(total-total//2, img.shape[1], img.shape[0])
        inds = inds + inds2

    return inds

def create_new_img(img):
    return np.zeros(img.shape)

def get_fitnesses(img, new, inds):
    errors = []
    mse_new = mse(new, img)
    for i in range(len(inds)): 
        updt = np.copy(new)
        if(inds[i][0] == circle):
            Shape.draw_shape(circle, updt, center=(inds[i][2], inds[i][3]), radius=inds[i][1], color=inds[i][4])
        elif(inds[i][0] == rectangle):
            Shape.draw_shape(rectangle, updt, top=inds[i][1], bottom=inds[i][2], color=inds[i][3])

        mse_updt = mse(updt, img)
        errors.append((mse_updt - mse_new, i, inds[i][0]))
    
    errors.sort(key = lambda s: s[0])
    return errors

def draw_k_best(shapes, n, new, inds):
    best_ret = -1
    best_circ = -1

    for i in range(n):
        index = shapes[i][1]
        shape_type = shapes[i][2]

        if shape_type == circle and best_circ == -1:
            best_circ = (circle, (inds[index][2], inds[index][3]), inds[i][1], inds[index][4])
        if shape_type == rectangle and best_ret == -1:
            best_ret = (rectangle, (inds[index][1], inds[index][2]), inds[index][3]) 

        if(shape_type == circle):
            Shape.draw_shape(circle, new, center=(inds[index][2], inds[index][3]), radius=inds[i][1], color=inds[index][4])
        elif(shape_type == rectangle):
            Shape.draw_shape(rectangle, new, top=inds[index][1], bottom=inds[index][2], color=inds[index][3])

    return [best_circ, best_ret]

if __name__ == '__main__':

    print("Choose image type: 1 for RGB, 0 for grayscale")
    flag = int(input())

    print("Choose the number of epochs")
    num_epochs = int(input())

    print("Choose the population size")
    total_inds = int(input())

    print("Choose your type of shape: Circle(c), Rectangle(r), Mixed(m)")
    s_type = input()

    if s_type != 'r':
        print("Choose the maximum circle size")
        circle_size = int(input())

    k = total_inds//10 

    print("What is the name of the input image?")
    image_name = input()
    image_name = image_name.strip()

    print("What is the name of the output image?")
    otp_name = input()
    otp_name = otp_name.strip()

    img_obj = get_img(image_name, flag)
    image = img_obj.img

    new_img = create_new_img(image)

    first = time.time()
    epoch_time = 0

    best_c, best_r = -1, -1 

    for epoch in range(num_epochs):
        print(f'Generation: {epoch}')
        print(f'Estimated time left: {(num_epochs-epoch)*(time.time() - epoch_time)/60.0} minutes')
        epoch_time = time.time()
        inds = get_individuals(image, total_inds, s_type)
        fits = get_fitnesses(image, new_img, inds)

        draw_k_best(fits, k, new_img, inds)

    elapsed = time.time() - first
    hours = elapsed // 3600
    minutes = elapsed % 3600
    seconds = 60*minutes/60%60
    minutes//=60

    print(f"It took me {num_epochs} epochs, {k * num_epochs} shapes to draw your image. A total of {hours} hours, {minutes} minutes and {seconds} seconds")

    cv.imwrite(otp_name, new_img)
    get_img(otp_name, flag).display() 
