From 8f0a238ae8566a974ee983715006ab9d1cac29d2 Mon Sep 17 00:00:00 2001 From: Arne van Iterson Date: Fri, 13 Oct 2023 16:51:40 +0200 Subject: [PATCH] Canvas logic split from main suite --- .pylintrc | Bin 0 -> 43958 bytes requirements.txt | Bin 1006 -> 1266 bytes src/config/config.template.json | 7 +- src/helpers/canvas.py | 103 ++++++++++++++ src/helpers/gui/main.ui | 44 ++---- src/helpers/logger.py | 21 ++- src/helpers/{treenum.py => tags.py} | 0 src/suite.py | 213 ++++++++++------------------ 8 files changed, 205 insertions(+), 183 deletions(-) create mode 100644 .pylintrc create mode 100644 src/helpers/canvas.py rename src/helpers/{treenum.py => tags.py} (100%) diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000000000000000000000000000000000000..9b6b176d162a3290cee176d34b91ae2b8df566e6 GIT binary patch literal 43958 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 literal 0 HcmV?d00001 diff --git a/requirements.txt b/requirements.txt index 8601087ae74fae61f9c08847dfc84a44476560ae..f92b3d8ebd6feec656236e36d291226e79c1e9c5 100644 GIT binary patch delta 245 zcmaFI{)uyfiexcE2}2P>K0_u$3WF^W8Z+oI7=Z9ZS#8N2phOWvB10|^gCs!`CO~X9 z(cV)u1*j(n2w@T+o#qppwIwqdioyCyz*43_VM7KZAZajh<$B3nhGZa2WJqF2h3hm2 zNlp%C+^(1dlqq3I18dI(x+oK9eKA-a$aF&n!^!^{8%1+~;(0)I$nG`VT+S59D4h>< WYYsy;Pys|5= drawW: + drawY += size + drawX = 0 + self.canvas.configure(height=(drawY + size)) + + self.canvas.create_image( + drawX, drawY, anchor=tkinter.NW, image=self.tk_imgs[idx], tags="og" + ) + + # Add name to text box + tag = self.canvas.create_text(drawX+5, drawY+5, anchor=tkinter.NW, text=F"{idx} {self.tags[idx]}", fill="black", font=('Helvetica 12 bold')) + tag_bg = self.canvas.create_rectangle(self.canvas.bbox(tag), fill="white") + self.canvas.tag_lower(tag_bg,tag) + # self.meta.insert(tkinter.END, f"{idx}: {self.output[1][idx]}\n") + + drawX += size + + # Clear output + # self.meta.config(state=tkinter.DISABLED) + + def export(self, id, name, path): + """ + Export id to file + """ + # Get export settings + img_arr = self.tk_imgs + + print(C_INFO, f"Using path: {path}") + + if id >= 0 and id < len(img_arr): + # Create file + now = datetime.datetime.now() + new_file_name = f"{name}-{self.tags[id]}-{now.strftime('%Y-%m-%dT%H.%M.%S')}.png" + + # Put data + file_path = pathlib.Path(path, new_file_name) + # print(file_path) + + img_pil = ImageTk.getimage(self.tk_imgs[id]) + img_pil.save(file_path, "PNG") + img_pil.close() + + print(C_DONE, f"Exported Image ID {id} ({self.tags[id]}) to {os.path.join(path, new_file_name)}") + else: + print(C_ERR, "Nothing to export!") \ No newline at end of file diff --git a/src/helpers/gui/main.ui b/src/helpers/gui/main.ui index abd321f..0f899d7 100644 --- a/src/helpers/gui/main.ui +++ b/src/helpers/gui/main.ui @@ -194,8 +194,8 @@ + imgExport Export PNG - 1 1 @@ -225,25 +225,6 @@ - - - 15 - 0 - 4 - false - normal - true - Image IDs should appear here - 25 - word - - 4 - 1 - 1 - 7 - - - bottom @@ -281,7 +262,7 @@ - applyAll + imgCycle Export ID for entire dataset 2 @@ -297,14 +278,14 @@ Powered by ARNweb.nl & TomSelier.com 4 - 2 + 1 8 - applyAll + imgCycle Run analysis for entire dataset (!) 2 @@ -318,7 +299,7 @@ imgCtl - < Prev tree + < Prev tag 2 10 @@ -330,7 +311,7 @@ imgCtl - Next tree > + Next tag > 3 10 @@ -347,27 +328,18 @@ 25 word - 5 + 4 1 1 7 - - - Metadata - - 4 - 0 - - - Test results - 5 + 4 0 diff --git a/src/helpers/logger.py b/src/helpers/logger.py index fd8c63f..521873b 100644 --- a/src/helpers/logger.py +++ b/src/helpers/logger.py @@ -1,15 +1,22 @@ import pathlib import datetime +# Some feedback with colours to make console output easier to read +C_INFO = "\u001b[96m*INFO*\u001b[0m\t" # Info - Cyan +C_WARN = "\u001b[33m*WARN*\u001b[0m\t" # Warning - Orange +C_ERR = "\u001b[31m*ERR*\u001b[0m\t" # Error - Red +C_USER = "\u001b[35m*USER*\u001b[0m\t" # User input - Purple +C_DONE = "\u001b[32m*DONE*\u001b[0m\t" # Done - Green +C_DBUG = "\u001b[94m*DBUG*\u001b[0m\t" # Debug - Blue + now = datetime.datetime.now() - -class Logger: +class CVSuiteLogger: def __init__(self, path): self.fileName = pathlib.Path( path, f"result-{now.strftime('%Y-%m-%dT%H.%M.%S')}.csv" ) - self.file = open(self.fileName, "x") + self.file = open(self.fileName, encoding="utf-8", mode="x") self.first = True self.index = [] @@ -19,15 +26,15 @@ class Logger: self.index.append(name) self.data.append(value) - def csv(self, input): + def csv(self, data): result = "" - for idx, item in enumerate(input): + for idx, item in enumerate(data): result += str(item) - if idx != (len(input) - 1): + if idx != (len(data) - 1): result += ", " result += "\n" - print(result) + # print(result) return result diff --git a/src/helpers/treenum.py b/src/helpers/tags.py similarity index 100% rename from src/helpers/treenum.py rename to src/helpers/tags.py diff --git a/src/suite.py b/src/suite.py index 8555170..8effa38 100644 --- a/src/suite.py +++ b/src/suite.py @@ -1,48 +1,48 @@ #!/usr/bin/python3 import pathlib -import pygubu import glob -import tkinter -from PIL import ImageTk, Image -import numpy as np -import cv2 -import time -import matplotlib.pyplot as plt import json import datetime import os import copy +from io import open +import numpy as np +import cv2 +# GUI +import pygubu +import matplotlib.pyplot as plt + +# Helpers from helpers.statistics import imgStats -from helpers.logger import Logger +from helpers.logger import CVSuiteLogger, C_DBUG +from helpers.canvas import CVSuiteCanvas ## UI config load PROJECT_PATH = pathlib.Path(__file__).parent PROJECT_UI = "./src/helpers/gui/main.ui" +TITLE = "Tree Recogniser 7000" ## Config file load CONFIG_PATH = "./src/config/config.json" -config_file = open(CONFIG_PATH) +config_file = open(CONFIG_PATH, encoding="utf-8") config_json = json.load(config_file) -log = Logger(config_json["out"]) - - ## UI class setup -class MainApp: +class CVSuite: def __init__(self, master=None): self.builder = builder = pygubu.Builder() builder.add_resource_path(PROJECT_PATH) builder.add_from_file(PROJECT_UI) - # Main widget + # Main window 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.meta = builder.get_object("dataset") - self.output = [[] for x in range(2)] + self.canvas = CVSuiteCanvas(builder.get_object("output_canvas")) + + # Log file + self.log = CVSuiteLogger(config_json["out"]["log"]) # Keep track of images in dataset self.img_current = 0 @@ -90,7 +90,7 @@ class MainApp: """ if plt is not None: plt.close() # Close graph vies - log.file.close() # Close log files + self.log.file.close() # Close log files self.mainwindow.quit() # Close main @@ -108,19 +108,19 @@ class MainApp: # Determine detection based on widget id if cmd[0] == "next": - dir = 1 + cdir = 1 elif cmd[0] == "prev": - dir = -1 + cdir = -1 # Get name of current img start = copy.deepcopy( self.img_name.split("_")[0] ) # deepcopy cus snaky boi language likes to create pointers - next = start + inext = start - while start == next: + while start == inext: # Check for limits - self.img_current += dir + self.img_current += cdir if self.img_current == self.img_max: self.img_current = 0 elif self.img_current == -1: @@ -130,43 +130,22 @@ class MainApp: break # Stop if only one image should be skipped elif cmd[1] == "tree": self.updatePath() - next = copy.deepcopy(self.img_name.split("_")[0]) + inext = copy.deepcopy(self.img_name.split("_")[0]) # Update UI self.update(self) - def apply(self, event=None, path=None): + def imgExport(self, event=None, path=config_json["out"]["img"]): """ - Export current dataset + Export given preprocess id to file """ - # 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}") + iid = self.export_id.get() + self.canvas.export(iid, self.img_name.split("_")[0], 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 applyAll(self, widget_id): + def imgCycle(self, widget_id): """ - Export given preprocess id for every image in the dataset folder + GUI Button callback + Cycle through all images in the data set """ if widget_id == "export": export = True @@ -179,14 +158,14 @@ class MainApp: if export: 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')}/", + config_json["out"]["img"], + f"{self.canvas.tags[img_id]}-all-{now.strftime('%Y-%m-%dT%H.%M.%S')}/", ) os.mkdir(path) while True: if export: - self.apply(path=path) + self.imgExport(path=path) self.imgCtl("next_img") if self.img_current == img_current: @@ -195,52 +174,6 @@ class MainApp: ## Ensure display is always correct with image self.update() - def addOutput(self, data, name: str): - """ - Add CV2 image to canvas output - """ - self.output[0].append(data) - self.output[1].append(name) - - def drawOutput(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=tkinter.NORMAL) - self.meta.delete(1.0, tkinter.END) - self.meta.insert(tkinter.END, f"{self.img_name}\n") - - # 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=tkinter.NW, image=self.tk_imgs[idx], tags="og" - ) - drawX += size - - # Add name to text box - self.meta.insert(tkinter.END, f"{idx}: {self.output[1][idx]}\n") - - # Clear output - self.meta.config(state=tkinter.DISABLED) - def createPlot(self, columns, rows): fig, axs = plt.subplots(columns, rows) return axs @@ -282,9 +215,9 @@ class MainApp: func = np.diag(results) diff = np.diff(func) area = sum(func) - + self.axs[column, row - 1].clear() - self.axs[column, row - 1].title.set_text(F"Area: {area}") + self.axs[column, row - 1].title.set_text(f"Area: {area}") self.axs[column, row - 1].plot(func) self.axs[column, row - 1].plot(diff) @@ -299,15 +232,15 @@ class MainApp: lambda x, pos: str(x * canny_step) ) - log.add("Canny Mean", func.mean()) - log.add("Canny Std", func.std()) - log.add("Canny Min", func.min()) - log.add("Canny Max x", np.where(func==func.max())[0][0]) - log.add("Canny Max y", func.max()) - log.add("Canny Diff max y", diff.max()) - log.add("Canny Diff min x",np.where(diff==diff.min())[0][0]) - log.add("Canny Diff min y", diff.min()) - log.add("Canny Area", area) + self.log.add("Canny Mean", func.mean()) + self.log.add("Canny Std", func.std()) + self.log.add("Canny Min", func.min()) + self.log.add("Canny Max x", np.where(func == func.max())[0][0]) + self.log.add("Canny Max y", func.max()) + self.log.add("Canny Diff max y", diff.max()) + self.log.add("Canny Diff min x", np.where(diff == diff.min())[0][0]) + self.log.add("Canny Diff min y", diff.min()) + self.log.add("Canny Area", area) def writeStats(self, img, labels, column, row): mean, std = imgStats(img) @@ -330,8 +263,8 @@ class MainApp: ) for idx, label in enumerate(labels): - log.add(f"Mean {label}", mean[idx]) - log.add(f"Std {label}", std[idx]) + self.log.add(f"Mean {label}", mean[idx]) + self.log.add(f"Std {label}", std[idx]) def updatePath(self): """ @@ -357,7 +290,7 @@ class MainApp: def update(self, event=None, part_update=None): ## Check if hist and canny hm have to be rerendered if ( - part_update == None + part_update is None ): ## 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 @@ -372,7 +305,10 @@ class MainApp: print("Full update forced!") if self.updatePath(): - log.add("Tree", self.img_name.split("_")[0]) + print(C_DBUG, F"Processing {self.img_name}") + + self.mainwindow.title(F"{TITLE} - {self.img_name}") + self.log.add("Tree", self.img_name.split("_")[0]) # Get all user vars ct1 = self.canny_thr1.get() @@ -383,17 +319,17 @@ class MainApp: bright = self.brightness.get() # Clear output - self.output = [[] for x in range(2)] + self.canvas.clear() # Import and resize image # img = cv2.imread(images[self.img_current]) img = cv2.imread(os.path.join(self.img_path.get(), self.img_name)) - # img = cv2.resize(img, (size, size), interpolation=cv2.INTER_AREA) - self.addOutput(img, "Original") + img = cv2.resize(img, (size, size), interpolation=cv2.INTER_AREA) + self.canvas.add(img, "Original") # Set grayscale img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) - self.addOutput(img_gray, "Grayscale") + self.canvas.add(img_gray, "Grayscale") # Contrast / brightness boost contrast_val = contrast / 100 @@ -401,12 +337,12 @@ class MainApp: img_contrast = np.clip( contrast_val * (img_gray + bright_val), 0, 255 ).astype(np.uint8) - # self.addOutput(img_contrast, F"Contrast / Brightness\n c+{contrast_val} b+{bright_val}") - self.addOutput(img_contrast, f"BCG") + # self.canvas.add(img_contrast, F"Contrast / Brightness\n c+{contrast_val} b+{bright_val}") + self.canvas.add(img_contrast, f"BCG") # Blurred edition img_blur = cv2.GaussianBlur(img_gray, (3, 3), 0) - self.addOutput(img_blur, "Blurred_k3") + self.canvas.add(img_blur, "Blurred_k3") # Sobel edge if sxy in ["x", "y", "both"]: @@ -426,17 +362,17 @@ class MainApp: else: img_sobel = img_gray - self.addOutput(img_sobel, "Sobel_edge") - log.add("Sobel nonzero", cv2.countNonZero(img_sobel)) + self.canvas.add(img_sobel, "Sobel_edge") + self.log.add("Sobel nonzero", cv2.countNonZero(img_sobel)) # Canny edge img_canny = cv2.Canny(image=img_blur, threshold1=ct1, threshold2=ct2) - self.addOutput(img_canny, "Canny_edge") + self.canvas.add(img_canny, "Canny_edge") # BGR - self.addOutput(img[:, :, 0], "BGR_B") - self.addOutput(img[:, :, 1], "BGR_G") - self.addOutput(img[:, :, 2], "BGR_R") + self.canvas.add(img[:, :, 0], "BGR_B") + self.canvas.add(img[:, :, 1], "BGR_G") + self.canvas.add(img[:, :, 2], "BGR_R") if img is not None: self.drawHist(img, ("B", "G", "R"), 0, 0) @@ -444,10 +380,10 @@ class MainApp: # HSV img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) - self.addOutput(img_hsv, "HSV") - self.addOutput(img_hsv[:, :, 0], "HSV_H") # H - self.addOutput(img_hsv[:, :, 1], "HSV_S") # S - self.addOutput(img_hsv[:, :, 2], "HSV_V") # V + self.canvas.add(img_hsv, "HSV") + self.canvas.add(img_hsv[:, :, 0], "HSV_H") # H + self.canvas.add(img_hsv[:, :, 1], "HSV_S") # S + self.canvas.add(img_hsv[:, :, 2], "HSV_V") # V if not part_update: if img_hsv is not None: @@ -460,14 +396,15 @@ class MainApp: # Write results to CSV file if not part_update: - log.update() + self.log.update() else: - log.clear() # Prevent partial updates from breaking log + self.log.clear() # Prevent partial updates from breaking log # Show all data plt.show(block=False) ## Graphs - self.drawOutput(size) ## Images + self.canvas.draw(size) ## Images + if __name__ == "__main__": - app = MainApp() + app = CVSuite() app.run()