Add csv logging component

This commit is contained in:
Arne van Iterson 2023-10-01 18:09:26 +02:00
parent 6162f5a3c1
commit a41d57f451
2 changed files with 237 additions and 135 deletions

47
src/helpers/logger.py Normal file
View File

@ -0,0 +1,47 @@
import pathlib
import datetime
now = datetime.datetime.now()
class Logger:
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.first = True
self.index = []
self.data = []
def add(self, name: str, value):
self.index.append(name)
self.data.append(value)
def csv(self, input):
result = ""
for idx, item in enumerate(input):
result += str(item)
if idx != (len(input) - 1):
result += ", "
result += "\n"
print(result)
return result
def update(self):
if self.first:
self.first = False
self.file.write(self.csv(self.index))
self.file.write(self.csv(self.data))
# Clear data
self.index = []
self.data = []
def __del__(self):
print("Log file closed!")
self.file.close()

View File

@ -9,11 +9,13 @@ import cv2
import time
import matplotlib.pyplot as plt
import json
from helpers.statistics import imgStats
import datetime
import datetime
import os
import copy
from helpers.statistics import imgStats
from helpers.logger import Logger
## UI config load
PROJECT_PATH = pathlib.Path(__file__).parent
PROJECT_UI = "./src/helpers/gui/main.ui"
@ -23,26 +25,29 @@ CONFIG_PATH = "./src/config/config.json"
config_file = open(CONFIG_PATH)
config_json = json.load(config_file)
log = Logger(config_json["out"])
## 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.tk_imgs = [] # Required or python will forget
self.meta = builder.get_object("dataset")
self.output = [[] for x in range(2)]
# Keep track of images in dataset
self.img_current = 0
self.img_name = ""
self.img_old = -1 ## minus 1 to enforce full update on start
self.img_old = -1 ## minus 1 to enforce full update on start
self.img_max = 0
# Plots
@ -53,42 +58,54 @@ class MainApp:
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.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.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"])
now = datetime.datetime.now()
self.log = open(pathlib.Path(config_json["out"], F"result-{now.strftime('%Y-%m-%dT%H.%M.%S')}.csv"), "x")
self.log.write("test")
def on_quit(self, event=None):
'''
Close PLT windows on main app quit
'''
plt.close()
self.log.close()
self.mainwindow.quit();
"""
Close PLT windows on main app quit
"""
# TODO: This function runs multiple times for some reason
if plt is not None:
plt.close() # Close graph vies
log.file.close() # Close log files
self.mainwindow.quit() # Close main
def run(self):
'''
Run loop
'''
"""
Run loop
"""
self.mainwindow.mainloop()
def img_prev(self, event=None):
'''
Open previous image from path
'''
"""
Open previous image from path
"""
if self.img_current == 0:
self.img_current = self.img_max - 1
else:
@ -96,9 +113,9 @@ class MainApp:
self.update(self)
def img_next(self, event=None):
'''
Open next image from path
'''
"""
Open next image from path
"""
if self.img_current == (self.img_max - 1):
self.img_current = 0
else:
@ -106,111 +123,114 @@ class MainApp:
self.update(self)
def apply(self, event=None, path=None):
'''
Export current dataset
'''
"""
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)):
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"
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.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):
'''
Export given preprocess id for every image in the dataset folder
'''
"""
Export given preprocess id for every image in the dataset folder
"""
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')}/")
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.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):
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
'''
"""
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)
self.meta.insert(END, f"{self.img_name[1]}\n")
self.meta.insert(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):
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")
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")
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):
for i, lab in enumerate(labels):
hist = cv2.calcHist(
[image],
[i],
@ -221,7 +241,7 @@ class MainApp:
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
@ -233,41 +253,71 @@ class MainApp:
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])
func = np.diag(results)
self.axs[column, row-1].clear()
self.axs[column, row-1].title.set_text("Canny F U N C")
self.axs[column, row-1].plot(func)
self.axs[column, row-1].plot(np.diff(func))
self.axs[column, row].title.set_text(F"Mean: {np.matrix(results).mean()}\nStd: {np.matrix(results).std()}")
self.axs[column, row - 1].clear()
self.axs[column, row - 1].title.set_text("Canny F U N C")
self.axs[column, row - 1].plot(func)
self.axs[column, row - 1].plot(np.diff(func))
self.axs[column, row].title.set_text(
f"Mean: {np.matrix(results).mean()}\nStd: {np.matrix(results).std()}"
)
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))
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)
)
log.add("Canny Mean", np.matrix(results).mean())
log.add("Canny Std", np.matrix(results).std())
log.add("Canny Min", np.matrix(results).min())
log.add("Canny Max", np.matrix(results).max())
# log.add("Canny Diff max", np.diff(func))
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 \nStd: %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]))
% (
labels[0],
mean[0],
labels[1],
mean[1],
labels[2],
mean[2],
labels[0],
std[0],
labels[1],
std[1],
labels[2],
std[2],
)
)
for idx, label in enumerate(labels):
log.add(f"Mean {label}", mean[idx])
log.add(f"Std {label}", std[idx])
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):
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
@ -275,7 +325,7 @@ class MainApp:
part_update = True
else:
print("Partial update forced!")
if path != None and path != "":
# Get all images at current path
images = []
@ -284,61 +334,67 @@ class MainApp:
for file in glob.glob(path + "/*.png"):
images.append(file)
self.img_max = len(images)
self.img_name = os.path.split(images[self.img_current])
self.img_name = os.path.split(images[self.img_current])[1]
log.add("Img", self.img_name)
# Get all user vars
ct1 = self.canny_thr1.get()
ct2 = self.canny_thr2.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)
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)
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")
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':
if sxy in ["x", "y", "both"]:
if sxy == "x":
dx = 1
dy = 0
elif sxy == 'y':
elif sxy == "y":
dx = 0
dy = 1
elif sxy == 'both':
elif sxy == "both":
dx = 1
dy = 1
img_sobel = cv2.Sobel(src=img_blur, ddepth=cv2.CV_8U, dx=dx, dy=dy, ksize=5)
img_sobel = cv2.Sobel(
src=img_blur, ddepth=cv2.CV_8U, dx=dx, dy=dy, ksize=5
)
else:
img_sobel = img_gray
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)}")
log.add("Sobel nonzero", cv2.countNonZero(img_sobel))
# Canny edge
img_canny = cv2.Canny(image=img_blur,threshold1=ct1,threshold2=ct2)
img_canny = cv2.Canny(image=img_blur, threshold1=ct1, threshold2=ct2)
self.add_output(img_canny, "Canny_edge")
# BGR
@ -347,35 +403,34 @@ class MainApp:
self.add_output(img[:, :, 2], "BGR_R")
if img is not None:
self.drawHist(img, ('B', 'G', 'R'), 0, 0)
self.writeStats(img, ('B', 'G', 'R'), 0, 0)
self.drawHist(img, ("B", "G", "R"), 0, 0)
self.writeStats(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
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)
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)
# Write results to CSV file
if not part_update:
# if self.log.
# Only do this if the image has changed
self.log.write(F"{self.img_name[1]}, test\n")
log.update()
# Show all data
plt.show(block=False) ## Graphs
plt.show(block=False) ## Graphs
self.draw_output(size) ## Images
if __name__ == "__main__":
app = MainApp()
app.run()