Reputation: 9701
The following code is work in progress. It is an upgrade from the one I posted here regarding another issue. It uses a pipe for communicating (currently only the close event) data between the GUI (producer) and the controller (consumer) that controls the termination of the application (just like in the previous version of the code). The essential upgrade is the replacement of the pipe (for communicating data between the GUI and the image processing process) with a queue.
The reason I have picked a queue is because it is thread-safe and in the future I would like to move each independent image processing task to a separate thread (if avaialble). For example after generating a grayscale image from the original one there are multiple independent steps that I can undertakes using it - Gaussian and the Harris corner detection (1), Canny edge detection (2, currently an issue with displaying the resulting image using ImageTk
so ignore) etc. The GUI is there to just visualize so the order it receives the separate items is not important (hence the queue).
from multiprocessing import Process, Pipe, Queue
from threading import Thread
from concurrent.futures import ThreadPoolExecutor # For future use
import tkinter as tk
from tkinter import filedialog, ttk
from PIL import ImageTk, Image
import atexit
import cv2
import os
class ProcessGUI(object):
def __init__(self, queue_gui_ip, pipe_gui_co_out):
print("[ProcessGUI]: PID = " + str(os.getpid()))
self.queue_gui_ip = queue_gui_ip
self.pipe_gui_co_out = pipe_gui_co_out
self.poll_image_data_thread = Thread(target=self.poll_image_data)
self.setup_gui()
self.run()
def setup_gui(self):
self.app = tk.Tk()
self.app.protocol("WM_DELETE_WINDOW", self.close_event)
fr_browse_controls = ttk.Frame(self.app)
fr_browse_controls.pack(side=tk.TOP, expand=True, fill="x")
lb_browse = ttk.Label(fr_browse_controls, text="Path to image file:")
lb_browse.pack(side=tk.LEFT)
self.ent_browse = ttk.Entry(fr_browse_controls)
self.ent_browse.pack(side=tk.LEFT, expand=True, fill="x")
btn_browse = ttk.Button(fr_browse_controls, text="Browse", command=self.btn_browse_clicked)
btn_browse.pack(side=tk.RIGHT)
self.edge_algs = tk.StringVar(self.app)
om_edge_algs = ttk.OptionMenu(self.app, self.edge_algs, "canny", "canny", "sobelx", "sobely", "sobelxy")
om_edge_algs.pack(side=tk.TOP, expand=True, fill="x")
self.pb_load = ttk.Progressbar(self.app, orient=tk.HORIZONTAL, mode="determinate")
self.pb_load.pack(side=tk.TOP, expand=True, fill="x")
tw_images = ttk.Notebook(self.app)
tw_images.pack(side=tk.BOTTOM, expand=True, fill="both")
tb_original = ttk.Frame(tw_images)
self.image_original = None
tw_images.add(tb_original, text="RGB")
self.lb_image_original = ttk.Label(tb_original, image=None)
self.lb_image_original.pack(expand=True, fill="both")
tb_gray = ttk.Frame(tw_images)
self.image_gray = None
tw_images.add(tb_gray, text="Grayscale")
self.lb_image_gray = ttk.Label(tb_gray, image=None)
self.lb_image_gray.pack(expand=True, fill="both")
tb_gaussian = ttk.Frame(tw_images)
self.image_gaussian = None
tw_images.add(tb_gaussian, text="Gaussian")
self.lb_image_gaussian = ttk.Label(tb_gaussian, image=None)
self.lb_image_gaussian.pack(expand=True, fill="both")
tb_edges = ttk.Frame(tw_images)
self.image_edges = None
tw_images.add(tb_edges, text="Edges")
self.lb_image_edges = ttk.Label(tb_edges, image=None)
self.lb_image_edges.pack(expand=True, fill="both")
tb_corners = ttk.Frame(tw_images)
self.image_corners = None
tw_images.add(tb_corners, text="Harris corners")
self.lb_image_corners = ttk.Label(tb_corners, image=None)
self.lb_image_corners.pack(expand=True, fill="both")
def run(self):
try:
self.poll_image_data_thread.start()
self.app.mainloop()
except KeyboardInterrupt:
self.close_event()
def poll_image_data(self):
while True:
request = self.queue_gui_ip.get()
method = request[0]
args = request[1:]
print("------------[ProcessGUI]------------")
print("Method: " + method)
print("------------------------------------")
try:
getattr(self, method + "_callback")(*args)
except AttributeError as ae:
print("Unknown callback received from pipe", str(ae))
def display_image_dims_callback(self, height, width, channels):
print("[ProcessGUI]")
print("Height: " + str(height))
print("Width: " + str(width))
print("Channels: " + str(channels))
def display_image_processing_progress_callback(self, progress):
progress = 0 + ((100 - 0) / (len(ProcessImageProcessing.ProcessingSteps) - 0)) * (progress - 0)
print("[ProcessGUI]: Updating progress to " + str(progress) + "%")
self.pb_load["value"] = progress
def display_image_original_gray_callback(self, image_original, image_gray):
self.image_original = ImageTk.PhotoImage(Image.fromarray(image_original))
self.lb_image_original.configure(image=self.image_original)
self.image_gray = ImageTk.PhotoImage(Image.fromarray(image_gray))
self.lb_image_gray.configure(image=self.image_gray)
def display_image_gaussian_callback(self, image_gaussian):
self.image_gaussian = ImageTk.PhotoImage(Image.fromarray(image_gaussian))
self.lb_image_gaussian.configure(image=self.image_gaussian)
def display_image_edges_callback(self, image_edges):
self.image_edges = ImageTk.PhotoImage(Image.fromarray(image_edges))
self.lb_image_edges.configure(image=self.image_edges)
def display_image_corners_callback(self, image_corners):
self.image_corners = ImageTk.PhotoImage(Image.fromarray(image_corners))
self.lb_image_corners.configure(image=self.image_corners)
def btn_browse_clicked(self):
filename = tk.filedialog.askopenfilename(initialdir=".",
title="Select image",
filetypes=(
("Portable Network graphics", "*.png"),
("All files", "*.*")))
self.ent_browse.delete(0, tk.END)
self.ent_browse.insert(0, filename)
edge_alg = self.edge_algs.get()
self.queue_gui_ip.put(["process_image", filename, edge_alg])
def close_event(self):
print("[ProcessGUI]: Shutting down")
self.pipe_gui_co_out.send(["close"])
if self.poll_image_data_thread.is_alive():
self.poll_image_data_thread.join()
self.pipe_gui_co_out.close()
self.app.destroy()
class ProcessImageProcessing(object):
ProcessingSteps = [
"rgb",
"gray",
"gaussian",
"edges",
"corners"
]
def __init__(self, queue_gui_ip):
print("[ProcessImageProcessing]: PID = " + str(os.getpid()))
self.queue_gui_ip = queue_gui_ip
# atexit.register(self.close)
self.run()
def run(self):
while True:
request = self.queue_gui_ip.get()
method = request[0]
args = request[1:]
print("------[ProcessImageProcessing]------")
print("Method: " + method)
print("------------------------------------")
if "display_" in method:
# Skip incoming requests that contain methods meant for ProcessGUI class
continue
try:
getattr(self, method + "_callback")(*args)
except AttributeError as ae:
print("Unknown callback received from pipe", str(ae))
def process_image_callback(self, image_path, edge_alg):
print("[ProcessImageProcessing]: Received file \"" + image_path + "\"")
try:
progress = 0
original_bgr = cv2.imread(image_path)
original_rgb = cv2.cvtColor(original_bgr, cv2.COLOR_BGR2RGB)
(height, width, channels) = original_bgr.shape[:3]
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_dims", height, width, channels])
gray = cv2.cvtColor(original_bgr, cv2.COLOR_BGR2GRAY)
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_original_gray", original_rgb, gray])
gaussian = self.process_image_gaussian(gray)
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_gaussian", gaussian])
try:
edges = self.process_image_edges(gaussian, edge_alg)
if not edges:
print("Unknown edge detection algorithm")
else:
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_edges", edges])
except:
pass
corners = self.process_image_corners(original_rgb, gray)
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_corners", corners])
except Exception as ex:
print("Exception: " + str(ex))
def process_image_gaussian(self, image_gray):
gaussian = cv2.GaussianBlur(image_gray, (3, 3), cv2.BORDER_DEFAULT)
return gaussian
def process_image_edges(self, image_gaussian, edge_alg):
edges = None
if edge_alg not in ["canny", "sobelx", "sobely", "sobelxy"]:
return edges
# Sobel edge detection
# Sobel edge detection on the X axis
if edge_alg == "sobelx":
print("Sobel X")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=5)
# Sobel edge detection on the Y axis
elif edge_alg == "sobely":
print("Sobel Y")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=5)
# Combined X and Y Sobel edge detection
elif edge_alg == "sobelxy":
print("Sobel XY")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=1, dy=1, ksize=5)
# Canny edge detection
elif edge_alg == "canny":
print("Canny")
edges = cv2.Canny(image=image_gaussian, threshold1=100, threshold2=200)
return edges
def process_image_corners(self, image_original, image_gray):
original_with_corners = image_original
corners = cv2.cornerHarris(image_gray, 2, 3, 0.04)
# result is dilated for marking the corners, not important
corners = cv2.dilate(corners, None)
# Threshold for an optimal value, it may vary depending on the image.
original_with_corners[corners > 0.01 * corners.max()] = [0, 0, 255]
return original_with_corners
def close(self):
print("[ProcessImageProcessing]: Shutting down")
class Controller(object):
def __init__(self):
print("[Controller]: PID = " + str(os.getpid()))
queue_gui_ip = Queue()
pipe_gui_co_out, self.pipe_co_in = Pipe()
self.gui = Process(
target=ProcessGUI,
args=(queue_gui_ip, pipe_gui_co_out)
)
self.ip = Process(
target=ProcessImageProcessing,
args=(queue_gui_ip,)
)
def run(self):
try:
self.gui.start()
self.ip.start()
while self.pipe_co_in.poll(None):
try:
request = self.pipe_co_in.recv()
method = request[0]
args = request[1:]
try:
getattr(self, method + "_callback")(*args)
except AttributeError as ae:
print("Unknown callback received from pipe", str(ae))
except EOFError:
# Raised when nothing to receive from pipe
pass
except KeyboardInterrupt:
self.close_callback()
except BrokenPipeError:
self.close_callback()
def close_callback(self):
print("Quitting processes...")
self.gui.join(1)
if self.gui.is_alive():
self.gui.terminate()
self.ip.join(1)
if self.ip.is_alive():
self.ip.terminate()
print("Finished")
def main():
c = Controller()
c.run()
if __name__ == "__main__":
main()
My problem is in the way a Queue
works. Unlike a duplex Pipe
, where I have never encountered this issue, a Queue offers its items to both processes - ProcessGUI
and ProcessImageProcessing
. After an item is retrieved it is (naturally) consumed. The problem is that my main producer - the ProcessImageProcessing
- received a large amount of the items it has put into the queue for the lesser producer - the ProcessGUI
. This leads to items being consumed by the wrong consumer.
I am looking for a way to solve this problem. At first I thought that I can consume an item and, if it is not meant for the consumer that consumed it, put it back into the queue (hence the print statements and the
if "display_" in method:
continue
inside ProcessImageProcessing
, where I was thinking of bouncing back the item that is meant for ProcessGUI
. This clearly has many flaws including checking every item for validity and more importantly the absence of a guarantee that an item, that was put back into the queue, will next time be consumed by the correct consumer. This can potentially lead to a scenario, where the item is continuously consumed by the wrong consumer and bounced back indefinitely.
My next thought is to perhaps add a Pipe
for incoming data (ProcessGUI
to ProcessImageProcessing
) and have the Queue
be used for transferring the image data from a single producer (ProcessImageProcessing
) and a single consumer (ProcessGUI
). This seems like the right way to go but I am curious if there is another way so that I learn more about IPC and Pipe
/Queue
in Python.
Here is the updated code (far from perfect :D) with the additional Pipe
and a "single producer, single consumer" Queue
:
from multiprocessing import Process, Pipe, Queue
from threading import Thread
from concurrent.futures import ThreadPoolExecutor
import tkinter as tk
from tkinter import filedialog, ttk
from PIL import ImageTk, Image
import atexit
import cv2
import os
class ProcessGUI(object):
def __init__(self, queue_ip_gui, pipe_gui_ip_in, pipe_gui_co_out):
print("[ProcessGUI]: PID = " + str(os.getpid()))
self.queue_ip_gui = queue_ip_gui
self.pipe_gui_ip_in = pipe_gui_ip_in
self.pipe_gui_co_out = pipe_gui_co_out
self.poll_image_data_thread = Thread(target=self.poll_image_data)
self.setup_gui()
self.run()
def setup_gui(self):
# SAME AS ABOVE
# ...
def run(self):
try:
self.poll_image_data_thread.start()
self.app.mainloop()
except KeyboardInterrupt:
self.close_event()
def poll_image_data(self):
while True:
request = self.queue_ip_gui.get()
method = request[0]
args = request[1:]
try:
getattr(self, method + "_callback")(*args)
except AttributeError as ae:
print("Unknown callback received from pipe", str(ae))
def display_image_dims_callback(self, height, width, channels):
print("[ProcessGUI]")
print("Height: " + str(height))
print("Width: " + str(width))
print("Channels: " + str(channels))
def display_image_processing_progress_callback(self, progress):
progress = 0 + ((100 - 0) / (len(ProcessImageProcessing.ProcessingSteps) - 0)) * (progress - 0)
print("[ProcessGUI]: Updating progress to " + str(progress) + "%")
self.pb_load["value"] = progress
def display_image_original_gray_callback(self, image_original, image_gray):
self.image_original = ImageTk.PhotoImage(Image.fromarray(image_original))
self.lb_image_original.configure(image=self.image_original)
self.image_gray = ImageTk.PhotoImage(Image.fromarray(image_gray))
self.lb_image_gray.configure(image=self.image_gray)
def display_image_gaussian_callback(self, image_gaussian):
self.image_gaussian = ImageTk.PhotoImage(Image.fromarray(image_gaussian))
self.lb_image_gaussian.configure(image=self.image_gaussian)
def display_image_edges_callback(self, image_edges):
self.image_edges = ImageTk.PhotoImage(Image.fromarray(image_edges))
self.lb_image_edges.configure(image=self.image_edges)
def display_image_corners_callback(self, image_corners):
self.image_corners = ImageTk.PhotoImage(Image.fromarray(image_corners))
self.lb_image_corners.configure(image=self.image_corners)
def btn_browse_clicked(self):
filename = tk.filedialog.askopenfilename(initialdir=".",
title="Select image",
filetypes=(
("Portable Network graphics", "*.png"),
("All files", "*.*")))
self.ent_browse.delete(0, tk.END)
self.ent_browse.insert(0, filename)
edge_alg = self.edge_algs.get()
self.pipe_gui_ip_in.send(["process_image", filename, edge_alg])
def close_event(self):
print("[ProcessGUI]: Shutting down")
self.pipe_gui_co_out.send(["close"])
if self.poll_image_data_thread.is_alive():
self.poll_image_data_thread.join()
self.pipe_gui_co_out.close()
self.app.destroy()
class ProcessImageProcessing(object):
ProcessingSteps = [
"rgb",
"gray",
"gaussian",
"edges",
"corners"
]
def __init__(self, queue_gui_ip, pipe_gui_ip_out):
print("[ProcessImageProcessing]: PID = " + str(os.getpid()))
self.queue_gui_ip = queue_gui_ip
self.pipe_gui_ip_out = pipe_gui_ip_out
self.run()
def run(self):
try:
while self.pipe_gui_ip_out.poll(None):
try:
request = self.pipe_gui_ip_out.recv()
method = request[0]
args = request[1:]
try:
getattr(self, method + "_callback")(*args)
except AttributeError as ae:
print("Unknown callback received from pipe", str(ae))
except EOFError:
# Raised when nothing to receive from pipe
pass
except KeyboardInterrupt:
self.close()
except BrokenPipeError:
# Raised when exiting the process
self.close()
def process_image_callback(self, image_path, edge_alg):
print("[ProcessImageProcessing]: Received file \"" + image_path + "\"")
try:
progress = 0
original_bgr = cv2.imread(image_path)
original_rgb = cv2.cvtColor(original_bgr, cv2.COLOR_BGR2RGB)
(height, width, channels) = original_bgr.shape[:3]
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_dims", height, width, channels])
gray = cv2.cvtColor(original_bgr, cv2.COLOR_BGR2GRAY)
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_original_gray", original_rgb, gray])
gaussian = self.process_image_gaussian(gray)
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_gaussian", gaussian])
try:
edges = self.process_image_edges(gaussian, edge_alg)
if not edges:
print("Unknown edge detection algorithm")
else:
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_edges", edges])
except:
pass
corners = self.process_image_corners(original_rgb, gray)
progress += 1
self.queue_gui_ip.put(["display_image_processing_progress", progress])
self.queue_gui_ip.put(["display_image_corners", corners])
except Exception as ex:
print("Exception: " + str(ex))
def process_image_gaussian(self, image_gray):
gaussian = cv2.GaussianBlur(image_gray, (3, 3), cv2.BORDER_DEFAULT)
return gaussian
def process_image_edges(self, image_gaussian, edge_alg):
edges = None
if edge_alg not in ["canny", "sobelx", "sobely", "sobelxy"]:
return edges
if edge_alg == "sobelx":
print("Sobel X")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=5)
elif edge_alg == "sobely":
print("Sobel Y")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=5)
elif edge_alg == "sobelxy":
print("Sobel XY")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=1, dy=1, ksize=5)
elif edge_alg == "canny":
print("Canny")
edges = cv2.Canny(image=image_gaussian, threshold1=100, threshold2=200)
return edges
def process_image_corners(self, image_original, image_gray):
original_with_corners = image_original
corners = cv2.cornerHarris(image_gray, 2, 3, 0.04)
corners = cv2.dilate(corners, None)
original_with_corners[corners > 0.01 * corners.max()] = [0, 0, 255]
return original_with_corners
def close(self):
print("[ProcessImageProcessing]: Shutting down")
class Controller(object):
def __init__(self):
print("[Controller]: PID = " + str(os.getpid()))
queue_ip_gui = Queue()
pipe_gui_ip_in, pipe_gui_ip_out = Pipe()
pipe_gui_co_out, self.pipe_co_in = Pipe()
self.gui = Process(
target=ProcessGUI,
args=(queue_ip_gui, pipe_gui_ip_in, pipe_gui_co_out)
)
self.ip = Process(
target=ProcessImageProcessing,
args=(queue_ip_gui, pipe_gui_ip_out)
)
def run(self):
try:
self.gui.start()
self.ip.start()
while self.pipe_co_in.poll(None):
try:
request = self.pipe_co_in.recv()
method = request[0]
args = request[1:]
try:
getattr(self, method + "_callback")(*args)
except AttributeError as ae:
print("Unknown callback received from pipe", str(ae))
except EOFError:
# Raised when nothing to receive from pipe
pass
except KeyboardInterrupt:
self.close_callback()
except BrokenPipeError:
self.close_callback()
def close_callback(self):
print("Quitting processes...")
self.gui.join(1)
if self.gui.is_alive():
self.gui.terminate()
self.ip.join(1)
if self.ip.is_alive():
self.ip.terminate()
print("Finished")
def pipes():
c = Controller()
c.run()
if __name__ == "__main__":
pipes()
Upvotes: 0
Views: 84
Reputation: 44283
I believe the best way to go is to have an input queue for the ProcessImageProcessing
process and a output queue that this process will write its results to and from which ProcessGUI
will read from.
I would also make ProcessGui
a thread launched from the main process and get rid of using a Pipe
to communicate the close event and use instead an actual threading.Event
instance. This allows the main thread to simply wait for the GUI to close without burning CPU cycles in a polling loop. Finally, by using daemon thread for doing the queue get
requests in ProcessGui
and making the ProcessImageProcessing
process a daemon process, the logic becomes somewhat simplified.
from multiprocessing import Process, Pipe, Queue
from threading import Thread, Event
import tkinter as tk
from tkinter import filedialog, ttk
from PIL import ImageTk, Image
import cv2
import os
class ProcessGUI(object):
def __init__(self, queue_gui_ip, queue_gui_op, termination_event):
print("[ProcessGUI]: PID = " + str(os.getpid()))
self.queue_gui_ip = queue_gui_ip
self.queue_gui_op = queue_gui_op
self.termination_event = termination_event
self.poll_image_data_thread = Thread(target=self.poll_image_data, daemon=True)
self.setup_gui()
self.run()
def setup_gui(self):
self.app = tk.Tk()
self.app.protocol("WM_DELETE_WINDOW", self.close_event)
fr_browse_controls = ttk.Frame(self.app)
fr_browse_controls.pack(side=tk.TOP, expand=True, fill="x")
lb_browse = ttk.Label(fr_browse_controls, text="Path to image file:")
lb_browse.pack(side=tk.LEFT)
self.ent_browse = ttk.Entry(fr_browse_controls)
self.ent_browse.pack(side=tk.LEFT, expand=True, fill="x")
btn_browse = ttk.Button(fr_browse_controls, text="Browse", command=self.btn_browse_clicked)
btn_browse.pack(side=tk.RIGHT)
self.edge_algs = tk.StringVar(self.app)
om_edge_algs = ttk.OptionMenu(self.app, self.edge_algs, "canny", "canny", "sobelx", "sobely", "sobelxy")
om_edge_algs.pack(side=tk.TOP, expand=True, fill="x")
self.pb_load = ttk.Progressbar(self.app, orient=tk.HORIZONTAL, mode="determinate")
self.pb_load.pack(side=tk.TOP, expand=True, fill="x")
tw_images = ttk.Notebook(self.app)
tw_images.pack(side=tk.BOTTOM, expand=True, fill="both")
tb_original = ttk.Frame(tw_images)
self.image_original = None
tw_images.add(tb_original, text="RGB")
self.lb_image_original = ttk.Label(tb_original, image=None)
self.lb_image_original.pack(expand=True, fill="both")
tb_gray = ttk.Frame(tw_images)
self.image_gray = None
tw_images.add(tb_gray, text="Grayscale")
self.lb_image_gray = ttk.Label(tb_gray, image=None)
self.lb_image_gray.pack(expand=True, fill="both")
tb_gaussian = ttk.Frame(tw_images)
self.image_gaussian = None
tw_images.add(tb_gaussian, text="Gaussian")
self.lb_image_gaussian = ttk.Label(tb_gaussian, image=None)
self.lb_image_gaussian.pack(expand=True, fill="both")
tb_edges = ttk.Frame(tw_images)
self.image_edges = None
tw_images.add(tb_edges, text="Edges")
self.lb_image_edges = ttk.Label(tb_edges, image=None)
self.lb_image_edges.pack(expand=True, fill="both")
tb_corners = ttk.Frame(tw_images)
self.image_corners = None
tw_images.add(tb_corners, text="Harris corners")
self.lb_image_corners = ttk.Label(tb_corners, image=None)
self.lb_image_corners.pack(expand=True, fill="both")
def run(self):
try:
self.poll_image_data_thread.start()
self.app.mainloop()
except KeyboardInterrupt:
self.close_event()
def poll_image_data(self):
while True:
request = self.queue_gui_op.get()
method = request[0]
args = request[1:]
print("------------[ProcessGUI]------------")
print("Method: " + method)
print("------------------------------------")
try:
getattr(self, method + "_callback")(*args)
except AttributeError as ae:
print("Unknown callback received from pipe", str(ae))
def display_image_dims_callback(self, height, width, channels):
print("[ProcessGUI]")
print("Height: " + str(height))
print("Width: " + str(width))
print("Channels: " + str(channels))
def display_image_processing_progress_callback(self, progress):
progress = 0 + ((100 - 0) / (len(ProcessImageProcessing.ProcessingSteps) - 0)) * (progress - 0)
print("[ProcessGUI]: Updating progress to " + str(progress) + "%")
self.pb_load["value"] = progress
def display_image_original_gray_callback(self, image_original, image_gray):
self.image_original = ImageTk.PhotoImage(Image.fromarray(image_original))
self.lb_image_original.configure(image=self.image_original)
self.image_gray = ImageTk.PhotoImage(Image.fromarray(image_gray))
self.lb_image_gray.configure(image=self.image_gray)
def display_image_gaussian_callback(self, image_gaussian):
self.image_gaussian = ImageTk.PhotoImage(Image.fromarray(image_gaussian))
self.lb_image_gaussian.configure(image=self.image_gaussian)
def display_image_edges_callback(self, image_edges):
self.image_edges = ImageTk.PhotoImage(Image.fromarray(image_edges))
self.lb_image_edges.configure(image=self.image_edges)
def display_image_corners_callback(self, image_corners):
self.image_corners = ImageTk.PhotoImage(Image.fromarray(image_corners))
self.lb_image_corners.configure(image=self.image_corners)
def btn_browse_clicked(self):
filename = tk.filedialog.askopenfilename(initialdir=".",
title="Select image",
filetypes=(
("Portable Network graphics", "*.png"),
("All files", "*.*")))
self.ent_browse.delete(0, tk.END)
self.ent_browse.insert(0, filename)
edge_alg = self.edge_algs.get()
self.queue_gui_ip.put(["process_image", filename, edge_alg])
def close_event(self):
print("[ProcessGUI]: Shutting down")
self.app.destroy()
self.termination_event.set() # show were are terminating
class ProcessImageProcessing(object):
ProcessingSteps = [
"rgb",
"gray",
"gaussian",
"edges",
"corners"
]
def __init__(self, queue_gui_ip, queue_gui_op):
print("[ProcessImageProcessing]: PID = " + str(os.getpid()))
self.queue_gui_ip = queue_gui_ip
self.queue_gui_op = queue_gui_op
self.run()
def run(self):
while True:
request = self.queue_gui_ip.get()
method = request[0]
args = request[1:]
print("------[ProcessImageProcessing]------")
print("Method: " + method)
print("------------------------------------")
if "display_" in method:
# Skip incoming requests that contain methods meant for ProcessGUI class
continue
try:
getattr(self, method + "_callback")(*args)
except AttributeError as ae:
print("Unknown callback received from pipe", str(ae))
def process_image_callback(self, image_path, edge_alg):
print("[ProcessImageProcessing]: Received file \"" + image_path + "\"")
try:
progress = 0
original_bgr = cv2.imread(image_path)
original_rgb = cv2.cvtColor(original_bgr, cv2.COLOR_BGR2RGB)
(height, width, channels) = original_bgr.shape[:3]
progress += 1
self.queue_gui_op.put(["display_image_processing_progress", progress])
self.queue_gui_op.put(["display_image_dims", height, width, channels])
gray = cv2.cvtColor(original_bgr, cv2.COLOR_BGR2GRAY)
progress += 1
self.queue_gui_op.put(["display_image_processing_progress", progress])
self.queue_gui_op.put(["display_image_original_gray", original_rgb, gray])
gaussian = self.process_image_gaussian(gray)
progress += 1
self.queue_gui_op.put(["display_image_processing_progress", progress])
self.queue_gui_op.put(["display_image_gaussian", gaussian])
try:
edges = self.process_image_edges(gaussian, edge_alg)
if not edges:
print("Unknown edge detection algorithm")
else:
progress += 1
self.queue_gui_op.put(["display_image_processing_progress", progress])
self.queue_gui_op.put(["display_image_edges", edges])
except:
pass
corners = self.process_image_corners(original_rgb, gray)
progress += 1
self.queue_gui_op.put(["display_image_processing_progress", progress])
self.queue_gui_op.put(["display_image_corners", corners])
except Exception as ex:
print("Exception: " + str(ex))
def process_image_gaussian(self, image_gray):
gaussian = cv2.GaussianBlur(image_gray, (3, 3), cv2.BORDER_DEFAULT)
return gaussian
def process_image_edges(self, image_gaussian, edge_alg):
edges = None
if edge_alg not in ["canny", "sobelx", "sobely", "sobelxy"]:
return edges
# Sobel edge detection
# Sobel edge detection on the X axis
if edge_alg == "sobelx":
print("Sobel X")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=1, dy=0, ksize=5)
# Sobel edge detection on the Y axis
elif edge_alg == "sobely":
print("Sobel Y")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=0, dy=1, ksize=5)
# Combined X and Y Sobel edge detection
elif edge_alg == "sobelxy":
print("Sobel XY")
edges = cv2.Sobel(src=image_gaussian, ddepth=cv2.CV_64F, dx=1, dy=1, ksize=5)
# Canny edge detection
elif edge_alg == "canny":
print("Canny")
edges = cv2.Canny(image=image_gaussian, threshold1=100, threshold2=200)
return edges
def process_image_corners(self, image_original, image_gray):
original_with_corners = image_original
corners = cv2.cornerHarris(image_gray, 2, 3, 0.04)
# result is dilated for marking the corners, not important
corners = cv2.dilate(corners, None)
# Threshold for an optimal value, it may vary depending on the image.
original_with_corners[corners > 0.01 * corners.max()] = [0, 0, 255]
return original_with_corners
def close(self):
print("[ProcessImageProcessing]: Shutting down")
class Controller(object):
def __init__(self):
print("[Controller]: PID = " + str(os.getpid()))
queue_gui_ip = Queue()
queue_gui_op = Queue()
# A Thread!
self.termination_event = Event()
self.gui = Thread(
target=ProcessGUI,
args=(queue_gui_ip, queue_gui_op, self.termination_event)
)
# Make it a daemon process:
self.ip = Process(
target=ProcessImageProcessing,
args=(queue_gui_ip, queue_gui_op),
daemon=True
)
def run(self):
try:
self.gui.start()
self.ip.start()
self.termination_event.wait() # wait for termination
self.close_callback()
except KeyboardInterrupt:
self.close_callback()
def close_callback(self):
print("Quitting processes...")
self.gui.join(1)
if self.gui.is_alive():
self.gui.terminate()
print("Finished")
def main():
c = Controller()
c.run()
if __name__ == "__main__":
main()
Upvotes: 1