This commit is contained in:
Tom Selier 2023-10-13 20:10:22 +02:00
commit 311070d1a6
8 changed files with 205 additions and 183 deletions

BIN
.pylintrc Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,5 +1,8 @@
{ {
"path": "", "path": "",
"out": "", "out": {
"size": 750 "img": "",
"log": ""
},
"size": 100
} }

103
src/helpers/canvas.py Normal file
View File

@ -0,0 +1,103 @@
import cv2
import tkinter
from PIL import ImageTk, Image
import datetime
import pathlib
import os
from helpers.logger import C_INFO, C_DONE, C_ERR
class CVSuiteCanvas:
def __init__(self, canvas):
self.canvas = canvas
self.name = "" # Name of current image
self.imgs = [] # OpenCV Image data
self.tk_imgs = [] # Tkinter Image data
self.tags = [] # Tag data
def add(self, data, name: str):
"""
Add CV2 image to canvas output
"""
self.imgs.append(data)
self.tags.append(name)
def clear(self):
"""
Clear canvas
"""
self.imgs = []
self.tags = []
def draw(self, size):
"""
Update canvas
"""
# 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.imgs):
# 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"
)
# 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!")

View File

@ -194,8 +194,8 @@
</child> </child>
<child> <child>
<object class="ttk.Button" id="button3"> <object class="ttk.Button" id="button3">
<property name="command" type="command" cbtype="simple">imgExport</property>
<property name="text" translatable="yes">Export PNG</property> <property name="text" translatable="yes">Export PNG</property>
<bind sequence="&lt;ButtonPress&gt;" handler="apply" add="" />
<layout manager="grid"> <layout manager="grid">
<property name="column">1</property> <property name="column">1</property>
<property name="columnspan">1</property> <property name="columnspan">1</property>
@ -225,25 +225,6 @@
</layout> </layout>
</object> </object>
</child> </child>
<child>
<object class="tk.Text" id="dataset" named="True">
<property name="height">15</property>
<property name="pady">0</property>
<property name="selectborderwidth">4</property>
<property name="setgrid">false</property>
<property name="state">normal</property>
<property name="takefocus">true</property>
<property name="text" translatable="yes">Image IDs should appear here</property>
<property name="width">25</property>
<property name="wrap">word</property>
<layout manager="grid">
<property name="column">4</property>
<property name="columnspan">1</property>
<property name="row">1</property>
<property name="rowspan">7</property>
</layout>
</object>
</child>
<child> <child>
<object class="ttk.LabeledScale" id="s_bright" named="True"> <object class="ttk.LabeledScale" id="s_bright" named="True">
<property name="compound">bottom</property> <property name="compound">bottom</property>
@ -281,7 +262,7 @@
</child> </child>
<child> <child>
<object class="ttk.Button" id="export" named="True"> <object class="ttk.Button" id="export" named="True">
<property name="command" type="command" cbtype="with_wid">applyAll</property> <property name="command" type="command" cbtype="with_wid">imgCycle</property>
<property name="text" translatable="yes">Export ID for entire dataset</property> <property name="text" translatable="yes">Export ID for entire dataset</property>
<layout manager="grid"> <layout manager="grid">
<property name="column">2</property> <property name="column">2</property>
@ -297,14 +278,14 @@
<property name="text" translatable="yes">Powered by ARNweb.nl &amp; TomSelier.com</property> <property name="text" translatable="yes">Powered by ARNweb.nl &amp; TomSelier.com</property>
<layout manager="grid"> <layout manager="grid">
<property name="column">4</property> <property name="column">4</property>
<property name="columnspan">2</property> <property name="columnspan">1</property>
<property name="row">8</property> <property name="row">8</property>
</layout> </layout>
</object> </object>
</child> </child>
<child> <child>
<object class="ttk.Button" id="analyse" named="True"> <object class="ttk.Button" id="analyse" named="True">
<property name="command" type="command" cbtype="with_wid">applyAll</property> <property name="command" type="command" cbtype="with_wid">imgCycle</property>
<property name="text" translatable="yes">Run analysis for entire dataset (!)</property> <property name="text" translatable="yes">Run analysis for entire dataset (!)</property>
<layout manager="grid"> <layout manager="grid">
<property name="column">2</property> <property name="column">2</property>
@ -318,7 +299,7 @@
<child> <child>
<object class="ttk.Button" id="prev_tree" named="True"> <object class="ttk.Button" id="prev_tree" named="True">
<property name="command" type="command" cbtype="with_wid">imgCtl</property> <property name="command" type="command" cbtype="with_wid">imgCtl</property>
<property name="text" translatable="yes">&lt; Prev tree</property> <property name="text" translatable="yes">&lt; Prev tag</property>
<layout manager="grid"> <layout manager="grid">
<property name="column">2</property> <property name="column">2</property>
<property name="ipadx">10</property> <property name="ipadx">10</property>
@ -330,7 +311,7 @@
<child> <child>
<object class="ttk.Button" id="next_tree" named="True"> <object class="ttk.Button" id="next_tree" named="True">
<property name="command" type="command" cbtype="with_wid">imgCtl</property> <property name="command" type="command" cbtype="with_wid">imgCtl</property>
<property name="text" translatable="yes">Next tree &gt;</property> <property name="text" translatable="yes">Next tag &gt;</property>
<layout manager="grid"> <layout manager="grid">
<property name="column">3</property> <property name="column">3</property>
<property name="ipadx">10</property> <property name="ipadx">10</property>
@ -347,27 +328,18 @@
<property name="width">25</property> <property name="width">25</property>
<property name="wrap">word</property> <property name="wrap">word</property>
<layout manager="grid"> <layout manager="grid">
<property name="column">5</property> <property name="column">4</property>
<property name="columnspan">1</property> <property name="columnspan">1</property>
<property name="row">1</property> <property name="row">1</property>
<property name="rowspan">7</property> <property name="rowspan">7</property>
</layout> </layout>
</object> </object>
</child> </child>
<child>
<object class="ttk.Label" id="l_meta" named="True">
<property name="text" translatable="yes">Metadata</property>
<layout manager="grid">
<property name="column">4</property>
<property name="row">0</property>
</layout>
</object>
</child>
<child> <child>
<object class="ttk.Label" id="l_tests" named="True"> <object class="ttk.Label" id="l_tests" named="True">
<property name="text" translatable="yes">Test results</property> <property name="text" translatable="yes">Test results</property>
<layout manager="grid"> <layout manager="grid">
<property name="column">5</property> <property name="column">4</property>
<property name="row">0</property> <property name="row">0</property>
</layout> </layout>
</object> </object>

View File

@ -1,15 +1,22 @@
import pathlib import pathlib
import datetime 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() now = datetime.datetime.now()
class CVSuiteLogger:
class Logger:
def __init__(self, path): def __init__(self, path):
self.fileName = pathlib.Path( self.fileName = pathlib.Path(
path, f"result-{now.strftime('%Y-%m-%dT%H.%M.%S')}.csv" 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.first = True
self.index = [] self.index = []
@ -19,15 +26,15 @@ class Logger:
self.index.append(name) self.index.append(name)
self.data.append(value) self.data.append(value)
def csv(self, input): def csv(self, data):
result = "" result = ""
for idx, item in enumerate(input): for idx, item in enumerate(data):
result += str(item) result += str(item)
if idx != (len(input) - 1): if idx != (len(data) - 1):
result += ", " result += ", "
result += "\n" result += "\n"
print(result) # print(result)
return result return result

View File

@ -1,48 +1,48 @@
#!/usr/bin/python3 #!/usr/bin/python3
import pathlib import pathlib
import pygubu
import glob 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 json
import datetime import datetime
import os import os
import copy 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.statistics import imgStats
from helpers.logger import Logger from helpers.logger import CVSuiteLogger, C_DBUG
from helpers.canvas import CVSuiteCanvas
## UI config load ## UI config load
PROJECT_PATH = pathlib.Path(__file__).parent PROJECT_PATH = pathlib.Path(__file__).parent
PROJECT_UI = "./src/helpers/gui/main.ui" PROJECT_UI = "./src/helpers/gui/main.ui"
TITLE = "Tree Recogniser 7000"
## Config file load ## Config file load
CONFIG_PATH = "./src/config/config.json" 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) config_json = json.load(config_file)
log = Logger(config_json["out"])
## UI class setup ## UI class setup
class MainApp: class CVSuite:
def __init__(self, master=None): def __init__(self, master=None):
self.builder = builder = pygubu.Builder() self.builder = builder = pygubu.Builder()
builder.add_resource_path(PROJECT_PATH) builder.add_resource_path(PROJECT_PATH)
builder.add_from_file(PROJECT_UI) builder.add_from_file(PROJECT_UI)
# Main widget # Main window
self.mainwindow = builder.get_object("main", master) self.mainwindow = builder.get_object("main", master)
# Canvas for output images # Canvas for output images
self.canvas = builder.get_object("output_canvas") self.canvas = CVSuiteCanvas(builder.get_object("output_canvas"))
self.tk_imgs = [] # Required or python will forget
self.meta = builder.get_object("dataset") # Log file
self.output = [[] for x in range(2)] self.log = CVSuiteLogger(config_json["out"]["log"])
# Keep track of images in dataset # Keep track of images in dataset
self.img_current = 0 self.img_current = 0
@ -90,7 +90,7 @@ class MainApp:
""" """
if plt is not None: if plt is not None:
plt.close() # Close graph vies plt.close() # Close graph vies
log.file.close() # Close log files self.log.file.close() # Close log files
self.mainwindow.quit() # Close main self.mainwindow.quit() # Close main
@ -108,19 +108,19 @@ class MainApp:
# Determine detection based on widget id # Determine detection based on widget id
if cmd[0] == "next": if cmd[0] == "next":
dir = 1 cdir = 1
elif cmd[0] == "prev": elif cmd[0] == "prev":
dir = -1 cdir = -1
# Get name of current img # Get name of current img
start = copy.deepcopy( start = copy.deepcopy(
self.img_name.split("_")[0] self.img_name.split("_")[0]
) # deepcopy cus snaky boi language likes to create pointers ) # deepcopy cus snaky boi language likes to create pointers
next = start inext = start
while start == next: while start == inext:
# Check for limits # Check for limits
self.img_current += dir self.img_current += cdir
if self.img_current == self.img_max: if self.img_current == self.img_max:
self.img_current = 0 self.img_current = 0
elif self.img_current == -1: elif self.img_current == -1:
@ -130,43 +130,22 @@ class MainApp:
break # Stop if only one image should be skipped break # Stop if only one image should be skipped
elif cmd[1] == "tree": elif cmd[1] == "tree":
self.updatePath() self.updatePath()
next = copy.deepcopy(self.img_name.split("_")[0]) inext = copy.deepcopy(self.img_name.split("_")[0])
# Update UI # Update UI
self.update(self) 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 iid = self.export_id.get()
img_arr = self.tk_imgs self.canvas.export(iid, self.img_name.split("_")[0], path)
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): def imgCycle(self, widget_id):
# 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):
""" """
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": if widget_id == "export":
export = True export = True
@ -179,14 +158,14 @@ class MainApp:
if export: if export:
now = datetime.datetime.now() now = datetime.datetime.now()
path = pathlib.Path( path = pathlib.Path(
config_json["out"], config_json["out"]["img"],
f"{self.output[1][img_id]}-all-{now.strftime('%Y-%m-%dT%H.%M.%S')}/", f"{self.canvas.tags[img_id]}-all-{now.strftime('%Y-%m-%dT%H.%M.%S')}/",
) )
os.mkdir(path) os.mkdir(path)
while True: while True:
if export: if export:
self.apply(path=path) self.imgExport(path=path)
self.imgCtl("next_img") self.imgCtl("next_img")
if self.img_current == img_current: if self.img_current == img_current:
@ -195,52 +174,6 @@ class MainApp:
## Ensure display is always correct with image ## Ensure display is always correct with image
self.update() 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): def createPlot(self, columns, rows):
fig, axs = plt.subplots(columns, rows) fig, axs = plt.subplots(columns, rows)
return axs return axs
@ -284,7 +217,7 @@ class MainApp:
area = sum(func) area = sum(func)
self.axs[column, row - 1].clear() 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(func)
self.axs[column, row - 1].plot(diff) self.axs[column, row - 1].plot(diff)
@ -299,15 +232,15 @@ class MainApp:
lambda x, pos: str(x * canny_step) lambda x, pos: str(x * canny_step)
) )
log.add("Canny Mean", func.mean()) self.log.add("Canny Mean", func.mean())
log.add("Canny Std", func.std()) self.log.add("Canny Std", func.std())
log.add("Canny Min", func.min()) self.log.add("Canny Min", func.min())
log.add("Canny Max x", np.where(func==func.max())[0][0]) self.log.add("Canny Max x", np.where(func == func.max())[0][0])
log.add("Canny Max y", func.max()) self.log.add("Canny Max y", func.max())
log.add("Canny Diff max y", diff.max()) self.log.add("Canny Diff max y", diff.max())
log.add("Canny Diff min x",np.where(diff==diff.min())[0][0]) self.log.add("Canny Diff min x", np.where(diff == diff.min())[0][0])
log.add("Canny Diff min y", diff.min()) self.log.add("Canny Diff min y", diff.min())
log.add("Canny Area", area) self.log.add("Canny Area", area)
def writeStats(self, img, labels, column, row): def writeStats(self, img, labels, column, row):
mean, std = imgStats(img) mean, std = imgStats(img)
@ -330,8 +263,8 @@ class MainApp:
) )
for idx, label in enumerate(labels): for idx, label in enumerate(labels):
log.add(f"Mean {label}", mean[idx]) self.log.add(f"Mean {label}", mean[idx])
log.add(f"Std {label}", std[idx]) self.log.add(f"Std {label}", std[idx])
def updatePath(self): def updatePath(self):
""" """
@ -357,7 +290,7 @@ class MainApp:
def update(self, event=None, part_update=None): def update(self, event=None, part_update=None):
## Check if hist and canny hm have to be rerendered ## Check if hist and canny hm have to be rerendered
if ( if (
part_update == None part_update is None
): ## If partial update has not been forced, check if full update is required ): ## 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 self.img_current != self.img_old or self.img_size != self.img_size_old:
part_update = False part_update = False
@ -372,7 +305,10 @@ class MainApp:
print("Full update forced!") print("Full update forced!")
if self.updatePath(): 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 # Get all user vars
ct1 = self.canny_thr1.get() ct1 = self.canny_thr1.get()
@ -383,17 +319,17 @@ class MainApp:
bright = self.brightness.get() bright = self.brightness.get()
# Clear output # Clear output
self.output = [[] for x in range(2)] self.canvas.clear()
# Import and resize image # Import and resize image
# img = cv2.imread(images[self.img_current]) # img = cv2.imread(images[self.img_current])
img = cv2.imread(os.path.join(self.img_path.get(), self.img_name)) img = cv2.imread(os.path.join(self.img_path.get(), self.img_name))
# img = cv2.resize(img, (size, size), interpolation=cv2.INTER_AREA) img = cv2.resize(img, (size, size), interpolation=cv2.INTER_AREA)
self.addOutput(img, "Original") self.canvas.add(img, "Original")
# Set grayscale # Set grayscale
img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
self.addOutput(img_gray, "Grayscale") self.canvas.add(img_gray, "Grayscale")
# Contrast / brightness boost # Contrast / brightness boost
contrast_val = contrast / 100 contrast_val = contrast / 100
@ -401,12 +337,12 @@ class MainApp:
img_contrast = np.clip( img_contrast = np.clip(
contrast_val * (img_gray + bright_val), 0, 255 contrast_val * (img_gray + bright_val), 0, 255
).astype(np.uint8) ).astype(np.uint8)
# self.addOutput(img_contrast, F"Contrast / Brightness\n c+{contrast_val} b+{bright_val}") # self.canvas.add(img_contrast, F"Contrast / Brightness\n c+{contrast_val} b+{bright_val}")
self.addOutput(img_contrast, f"BCG") self.canvas.add(img_contrast, f"BCG")
# Blurred edition # Blurred edition
img_blur = cv2.GaussianBlur(img_gray, (3, 3), 0) img_blur = cv2.GaussianBlur(img_gray, (3, 3), 0)
self.addOutput(img_blur, "Blurred_k3") self.canvas.add(img_blur, "Blurred_k3")
# Sobel edge # Sobel edge
if sxy in ["x", "y", "both"]: if sxy in ["x", "y", "both"]:
@ -426,17 +362,17 @@ class MainApp:
else: else:
img_sobel = img_gray img_sobel = img_gray
self.addOutput(img_sobel, "Sobel_edge") self.canvas.add(img_sobel, "Sobel_edge")
log.add("Sobel nonzero", cv2.countNonZero(img_sobel)) self.log.add("Sobel nonzero", cv2.countNonZero(img_sobel))
# Canny edge # 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.addOutput(img_canny, "Canny_edge") self.canvas.add(img_canny, "Canny_edge")
# BGR # BGR
self.addOutput(img[:, :, 0], "BGR_B") self.canvas.add(img[:, :, 0], "BGR_B")
self.addOutput(img[:, :, 1], "BGR_G") self.canvas.add(img[:, :, 1], "BGR_G")
self.addOutput(img[:, :, 2], "BGR_R") self.canvas.add(img[:, :, 2], "BGR_R")
if img is not None: if img is not None:
self.drawHist(img, ("B", "G", "R"), 0, 0) self.drawHist(img, ("B", "G", "R"), 0, 0)
@ -444,10 +380,10 @@ class MainApp:
# HSV # HSV
img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV) img_hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
self.addOutput(img_hsv, "HSV") self.canvas.add(img_hsv, "HSV")
self.addOutput(img_hsv[:, :, 0], "HSV_H") # H self.canvas.add(img_hsv[:, :, 0], "HSV_H") # H
self.addOutput(img_hsv[:, :, 1], "HSV_S") # S self.canvas.add(img_hsv[:, :, 1], "HSV_S") # S
self.addOutput(img_hsv[:, :, 2], "HSV_V") # V self.canvas.add(img_hsv[:, :, 2], "HSV_V") # V
if not part_update: if not part_update:
if img_hsv is not None: if img_hsv is not None:
@ -460,14 +396,15 @@ class MainApp:
# Write results to CSV file # Write results to CSV file
if not part_update: if not part_update:
log.update() self.log.update()
else: else:
log.clear() # Prevent partial updates from breaking log self.log.clear() # Prevent partial updates from breaking log
# Show all data # Show all data
plt.show(block=False) ## Graphs plt.show(block=False) ## Graphs
self.drawOutput(size) ## Images self.canvas.draw(size) ## Images
if __name__ == "__main__": if __name__ == "__main__":
app = MainApp() app = CVSuite()
app.run() app.run()