diff --git a/A2/GUI.py b/A2/GUI.py new file mode 100644 index 0000000000000000000000000000000000000000..fb3dc7402e68fe9ffe7a768375a069cebd8fb31c --- /dev/null +++ b/A2/GUI.py @@ -0,0 +1,155 @@ +from tkinter import StringVar, Label, OptionMenu, Tk, Button, Entry, IntVar +from tkinter.filedialog import askopenfilename +from tokenize import String + +from PIL import ImageTk, Image + +from histogram import Histogram +from histogram_enum import HISTOGRAMS + + +class GUI(object): + + def __init__(self, master): + """ + This class defines the GUI as one. + :param master: The Tk root. + """ + self.master = master + self.master.title('Filter') + + self.image_label_width = 500 + self.image_label_height = 500 + + self.filter_choices = {HISTOGRAMS.h1.value, + HISTOGRAMS.h2.value} + + self.filter_name = StringVar(root) + self.filter_name.set(HISTOGRAMS.empty.value) + self.filter_name.trace('w', self.change_filter) + + # Images + self.image_path = './images/original.jpeg' + self.edit_path = './images/histogram.jpeg' + self.image = self.read_image(self.image_path) + + # Labels + self.original_image_label = Label(self.master, image=self.image) + self.original_image_label.pack() + self.filtered_image_label = Label(self.master, image=self.image) + self.filtered_image_label.pack() + + # Buttons + self.option_menu = OptionMenu(self.master, self.filter_name, *self.filter_choices) + self.file_dialog = Button(master, text='Choose', command=lambda: self.set_new_file()) + + # Spinner + self.b_value = IntVar(self.master) + self.b_value.set(1) + self.b_menu = Entry(self.master, textvariable=self.b_value) + + # Layout + self.file_dialog.grid(row=0, column=0) + self.option_menu.grid(row=0, column=1) + self.b_menu.grid(row=0, column=2) + self.original_image_label.grid(row=1, column=0) + self.filtered_image_label.grid(row=1, column=2) + + def read_image(self, path: String) -> ImageTk.PhotoImage: + """ + This reads an image from a given path and resize it. + + :param path: The path to the image. + :return: A image to be used in the frontend + """ + return ImageTk.PhotoImage( + Image.open(path).resize( + (self.image_label_width, self.image_label_height), + Image.ANTIALIAS + ) + ) + + def _update_label_image(self, label: Label, new_image, row: int, column: int) -> None: + """ + This method updates to label with a new image. + + :param label: The reference to the label which should be changes. + :param new_image: The reference to the new image. + :param row: Row in the GUI. + :param column: Column in the GUI. + :return: None + """ + label.destroy() + label = Label(self.master, image=new_image) + label.image = new_image + label.pack() + label.grid(row=row, column=column) + + def set_new_file(self, *args) -> None: + """ + This sets the new file and refreshed the paths as well with the lables. + + :return: None + """ + + self.image_path: String = askopenfilename(initialdir='/', title='Select file') + + new_image = self.read_image(self.image_path) + + self._update_label_image(self.original_image_label, new_image, 1, 0) + + self.filter_name.set(HISTOGRAMS.empty.value) + self.b_value.set(1) + self._update_label_image(self.filtered_image_label, new_image, 1, 2) + + def change_filter(self, *args) -> None: + """ + This changes the filter and updates the label for the edited images. + + :param args: Arguments of the listener. + :return: None + """ + self.edit_image() + edited_image = self.read_image(self.edit_path) + self._update_label_image(self.filtered_image_label, edited_image, 1, 2) + + def edit_image(self) -> None: + """ + This method edits a given image and saves it as a grey-scale image. + + :return: None + """ + print('From: ' + self.image_path + + ' to ' + self.edit_path + + ' with ' + self.filter_name.get() + + ' and sigma: ' + + str(self.b_value.get())) + + Histogram( + self.image_path, self.edit_path, self.b_value.get() + ).get_histogram(self.filter_name.get()) + # print('Done...') + + +if __name__ == '__main__': + """ + This program is for editing images with a given filter. + The user can choose between all filters defined in the FILTER enum. + The user can choose between all images defined in the IMAGE enum. + + Preparation: + Make sure you have already installed all packages by running: + $ pip3 install -r requirements.txt + + To use the program run: + $ python3 GUI.py + + Notice: The edited image will be saved in ./images/edited.py. Each new filter will overwrite this image. This may + take a while. + + This version uses a padding-method with mean. It makes the usage of the Kirsch-operator much faster. + """ + root = Tk() + root.resizable(False, False) + my_gui = GUI(root) + root.mainloop() diff --git a/A2/__init__.py b/A2/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/A2/histogram.py b/A2/histogram.py new file mode 100644 index 0000000000000000000000000000000000000000..1887bddfd762826a0cf09a81f38e914e7b09e2bc --- /dev/null +++ b/A2/histogram.py @@ -0,0 +1,123 @@ +from tokenize import String +from typing import Dict, List + +import imageio +import matplotlib.pyplot as plt +import numpy as np +from skimage import color + +from histogram_enum import HISTOGRAMS + + +class Histogram(object): + + def __init__(self, origin_path: String, edit_path: String, b: int = 0): + """ + This class is the editor which edits a given image and stores a new one. + + :param origin_path: The original image which should be edited. + :param edit_path: The edited image which should be stored. + :param b: Is only used if the LoG-Operator is used. + """ + self.image_path = origin_path + self.edit_path = edit_path + self.b = b + + @staticmethod + def __get_bins(image: imageio.core.util.Array) -> Dict: + """ + This method counts each value in the image. + h(i) = card{(u,v) | I(u,v) = i} + 0 <= i <= K - 1 + + :param image: the greyscale image + :return: Dict where the keys are the values and the key-value is the count + """ + K = 256 + count = {} + for k in range(K): + count[str(k)] = 0 + height, width = image.shape + for v in range(height): + for u in range(width): + count[str(image[v, u])] += 1 + return count + + @staticmethod + def __get_intervals(B: int): + intervals = {} + splits = np.arange(0, 256, 255 / B) + for b in range(B): + intervals[str(b)] = list(splits[b:b + 2]) + + return intervals + + def __get_bins_in_interval(self, image: imageio.core.util.Array, B: int) -> Dict: + """ + This method counts each value in the image. + h(i) = card{(u,v) | a_j < I(u,v) < a_j+1} + 0 <= j <= B + + :param image: the greyscale image + :return: Dict where the keys are the values and the key-value is the count + """ + intervals = self.__get_intervals(B) + count = {} + for b in range(B): + count[str(b)] = 0 + height, width = image.shape + for v in range(height): + for u in range(width): + for b in range(B): + if intervals[str(b)][0] < image[v, u] < intervals[str(b)][1]: + count[str(b)] += 1 + return count + + @staticmethod + def __bins_to_list(bins: Dict) -> List: + """ + This method converts a dict into a sorted list of tuples. + + :param bins: Bins of the histogram + :return: Sorted list of tuples containing all key and key-value pairs. + """ + bins_list = [] + for key in bins.keys(): + bins_list += [(int(key), bins[str(key)])] + return bins_list + + def __plot_h1(self, keys: List, values: List, option: String): + ticks = 6 + indices = np.arange(len(keys)) + plt.bar(indices, values, color='black') + + if option == HISTOGRAMS.h1.value: + plt.xticks(np.arange(min(keys), 256, step=255 / (ticks - 1))) + elif option == HISTOGRAMS.h2.value: + plt.xticks(np.arange(0, self.b)) + plt.savefig('images/histogram.jpeg', dpi=100) + + def get_histogram(self, option: String) -> None: + """ + This method iterates over the image and changes each pixel by a given method. + Notice, that the image writes the result as a grey-scale image. + + :param option: The options defined in HISTOGRAMS. + :return: None + """ + image = imageio.imread(uri=self.image_path) + plt.imshow(image) + plt.show() + grey_scale_image = color.rgb2gray(image) + + # convert greyscale to interval + result = np.multiply(255, grey_scale_image).astype(int) + bins = {'4': '2'} + if option == HISTOGRAMS.h1.value: + bins = self.__get_bins(result) + elif option == HISTOGRAMS.h2.value: + bins = self.__get_bins_in_interval(result, self.b) + + bins = self.__bins_to_list(bins) + keys, values = zip(*bins) + self.__plot_h1(keys, values, option) diff --git a/A2/histogram_enum.py b/A2/histogram_enum.py new file mode 100644 index 0000000000000000000000000000000000000000..f669869bb3307d15b0ca6ccb9ebd9fa348402453 --- /dev/null +++ b/A2/histogram_enum.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class HISTOGRAMS(Enum): + """ + This enum defines all filters. + """ + empty = '---' + h1 = 'H1' + h2 = 'H2' diff --git a/A2/images/chalsea/Kadse.jpeg b/A2/images/chalsea/Kadse.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..aa6964c4b7a6dc0dd0629fab3a768a292bee8cf8 Binary files /dev/null and b/A2/images/chalsea/Kadse.jpeg differ diff --git a/A2/images/histogram.jpeg b/A2/images/histogram.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..0a2a569281288c76f24682066c75ad38e1e32d96 Binary files /dev/null and b/A2/images/histogram.jpeg differ diff --git a/A2/images/martian/martian.jpg b/A2/images/martian/martian.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc71f5f2f030729f05583c8a7ca09d8d0f1e980f Binary files /dev/null and b/A2/images/martian/martian.jpg differ diff --git a/A2/images/original.jpeg b/A2/images/original.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..9716f6562697103a3a5086ae98493eb8e5fbb46b Binary files /dev/null and b/A2/images/original.jpeg differ