#!/usr/bin/python3 import pathlib import pygubu import glob from tkinter import * from PIL import ImageTk, Image import numpy as np import cv2 import time import matplotlib.pyplot as plt import json from helpers.statistics import imgStats import datetime import os import copy ## UI config load PROJECT_PATH = pathlib.Path(__file__).parent PROJECT_UI = "./src/helpers/gui/main.ui" ## Config file load CONFIG_PATH = "./src/config/config.json" config_file = open(CONFIG_PATH) config_json = json.load(config_file) ## UI class setup class MainApp: def __init__(self, master=None): self.builder = builder = pygubu.Builder() builder.add_resource_path(PROJECT_PATH) builder.add_from_file(PROJECT_UI) # Main widget self.mainwindow = builder.get_object("main", master) # Canvas for output images self.canvas = builder.get_object("output_canvas") self.tk_imgs = [] # Required or python will forget self.output = [[] for x in range(2)] self.meta = builder.get_object("dataset") # Keep track of images in dataset self.img_current = 0 self.img_old = -1 self.img_max = 0 # Plots self.axs = self.createPlot(2, 2) # UI Variables self.canny_thr1 = None self.canny_thr2 = None self.img_path = None self.contrast = None self.img_size = None self.img_size_old = 0 ## Check if the rendering size has changed, if it has the analysis has to be run self.sobel_select = None self.export_id = None self.brightness = None builder.import_variables(self,['canny_thr1','canny_thr2','img_path','contrast','img_size','sobel_select','export_id','brightness']) builder.connect_callbacks(self) # Load values from config after UI has been initialised self.img_path.set(config_json["path"]) self.img_size.set(config_json["size"]) def on_quit(self, event=None): ''' Close PLT windows on main app quit ''' plt.close() self.mainwindow.quit(); def run(self): ''' Run loop ''' self.mainwindow.mainloop() def img_prev(self, event=None): ''' Open previous image from path ''' if self.img_current == 0: self.img_current = self.img_max - 1 else: self.img_current = self.img_current - 1 self.update(self) def img_next(self, event=None): ''' Open next image from path ''' if self.img_current == (self.img_max - 1): self.img_current = 0 else: self.img_current = self.img_current + 1 self.update(self) def apply(self, event=None, path=None): ''' Export current dataset ''' # Get export settings img_arr = self.tk_imgs img_id = self.export_id.get() if path == None: path = config_json["out"] else: print(F"Using path: {path}") if (img_id >= 0 and img_id < len(img_arr)): # Create file now = datetime.datetime.now() new_file_name = F"{self.img_current}-{self.output[1][img_id]}-{now.strftime('%Y-%m-%dT%H.%M.%S')}.png" # Put data file_path = pathlib.Path(path, new_file_name) # print(file_path) imgpil = ImageTk.getimage(self.tk_imgs[img_id]) imgpil.save(file_path, "PNG" ) imgpil.close() print(f"Exported Image ID {img_id} to {os.path.join(path, new_file_name)}") else: print("Nothing to export!") def apply_all(self, event=None): img_id = self.export_id.get() img_current = copy.deepcopy(self.img_current) now = datetime.datetime.now() path = pathlib.Path(config_json["out"], F"{self.output[1][img_id]}-all-{now.strftime('%Y-%m-%dT%H.%M.%S')}/") os.mkdir(path) while True: self.img_next() self.update(part_update=True) # Enforce partial update since we don't need the histograms etc. self.apply(path=path) if (self.img_current == img_current): break ## Ensure display is always correct with image self.update() def add_output(self, data, name: str): ''' Add CV2 image to canvas output ''' self.output[0].append(data) self.output[1].append(name) def draw_output(self, size): # Check if size of canvas has updated drawW = self.canvas.winfo_width() # Reset drawing position drawX = 0 drawY = 0 # Clear previously printed images self.tk_imgs = [] self.meta.config(state=NORMAL) self.meta.delete(1.0, END) # Draw all output images for idx, data in enumerate(self.output[0]): # Create ui image tk_img = cv2.cvtColor(data, cv2.COLOR_BGR2RGB) tk_img = ImageTk.PhotoImage(image=Image.fromarray(tk_img)) self.tk_imgs.append(tk_img) ## Check if next item will be out of range if (drawX + size >= drawW): drawY += size drawX = 0 self.canvas.configure(height=(drawY+size)) self.canvas.create_image(drawX,drawY,anchor=NW,image=self.tk_imgs[idx],tags="og") drawX += size # Add name to text box self.meta.insert(END, F"{idx}: {self.output[1][idx]}\n") # Clear output self.meta.config(state=DISABLED) # Draw canvas # TODO IDK volgens mij moet je deze wel callen maar het programma doet het nog (geen vragen stellen) # self.canvas.draw() def createPlot(self, columns, rows): fig, axs = plt.subplots(columns, rows) return axs def drawHist(self, image, labels, column, row): self.axs[column, row].clear() for i,lab in enumerate(labels): hist = cv2.calcHist( [image], [i], None, [256], [0, 256], ) self.axs[column, row].plot(hist, label=lab) self.axs[column, row].grid() self.axs[column, row].legend() def drawCannyHM(self, img, column, row): self.axs[column, row].clear() canny_max = 500 canny_step = 20 results = [[] for x in range((int)(canny_max / canny_step))] for th1 in range(0, canny_max, canny_step): for th2 in range(0, canny_max, canny_step): # Canny Edge Detection edges = cv2.Canny(image=img, threshold1=th1, threshold2=th2) w_res = cv2.countNonZero(edges) y_ind = (int)(th1 / canny_step) x_ind = (int)(th2 / canny_step) results[y_ind].append(w_res) # print(f"Result at thres {th1}, {th2}; \tIndex {y_ind}, {x_ind} \t= {w_res}") # print(results[y_ind]) self.axs[column, row].title.set_text(F"Mean: {np.matrix(results).mean()}") self.axs[column, row].imshow(results) self.axs[column, row].xaxis.set_major_formatter(lambda x, pos: str(x*canny_step)) self.axs[column, row].yaxis.set_major_formatter(lambda x, pos: str(x*canny_step)) def writeStats(self, img, labels, column, row): mean, std = imgStats(img) self.axs[column, row].title.set_text( "mean: %c:%d %c:%d %c:%d \n std: %c:%d %c:%d %c:%d" %(labels[0], mean[0], labels[1], mean[1], labels[2], mean[2], labels[0], std[0], labels[1], std[1], labels[2], std[2])) def update(self, event=None, part_update=False): path = self.img_path.get() ## Check if hist and canny hm have to be rerendered if not part_update: ## If partial update has not been forced, check if full update is required if (self.img_current != self.img_old or self.img_size != self.img_size_old): part_update = False self.img_old = self.img_current self.img_size_old = self.img_size else: part_update = True else: print("Partial update forced!") if path != None and path != "": # Get all images at current path images = [] for file in glob.glob(path + "/*.png"): images.append(file) self.img_max = len(images) # Get all user vars ct1 = self.canny_thr1.get() ct2 = self.canny_thr2.get() sxy = self.sobel_select.get() size = self.img_size.get() contrast = self.contrast.get() bright = self.brightness.get() # Clear output self.output = [[] for x in range(2)] # Import and resize image img = cv2.imread(images[self.img_current]) img = cv2.resize(img, (size, size), interpolation = cv2.INTER_AREA) self.add_output(img, "Original") # Set grayscale img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) self.add_output(img_gray, "Grayscale") # Contrast / brightness boost contrast_val = contrast / 100 bright_val = bright / 100 img_contrast = np.clip(contrast_val * (img_gray + bright_val), 0, 255).astype(np.uint8) # self.add_output(img_contrast, F"Contrast / Brightness\n c+{contrast_val} b+{bright_val}") self.add_output(img_contrast, F"BCG") # Blurred edition img_blur = cv2.GaussianBlur(img_gray, (3, 3), 0) self.add_output(img_blur, "Blurred_k3") # Sobel edge if sxy in ['x', 'y', 'both']: if sxy == 'x': dx = 1 dy = 0 elif sxy == 'y': dx = 0 dy = 1 elif sxy == 'both': dx = 1 dy = 1 img_sobel = cv2.Sobel(src=img_blur, ddepth=cv2.CV_8U, dx=dx, dy=dy, ksize=5) else: img_sobel = img_gray self.add_output(img_sobel, "Sobel_edge") # self.add_output(img_sobel, F"Sobel Edge\n nz={cv2.countNonZero(img_sobel)}") # Canny edge img_canny = cv2.Canny(image=img_blur,threshold1=ct1,threshold2=ct2) self.add_output(img_canny, "Canny_edge") self.writeStats(img, ('B', 'G', 'R'), 0, 0) # BGR self.add_output(img[:, :, 0], "BGR_B") self.add_output(img[:, :, 1], "BGR_G") self.add_output(img[:, :, 2], "BGR_R") if img is not None: self.drawHist(img, ('B', 'G', 'R'), 0, 0) # HSV img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) self.add_output(img_hsv, "HSV") self.add_output(img_hsv[:, :, 0], "HSV_H") # H self.add_output(img_hsv[:, :, 1], "HSV_S") # S self.add_output(img_hsv[:, :, 2], "HSV_V") # V if not part_update: if img_hsv is not None: self.drawHist(img_hsv, ('H', 'S', 'V'), 0, 1) self.writeStats(img_hsv, ('H', 'S', 'V'), 0, 1) # Canny Heatmap if not part_update: self.drawCannyHM(img, 1, 1) # Show all data plt.show(block=False) ## Graphs self.draw_output(size) ## Images if __name__ == "__main__": app = MainApp() app.run()