Codes » History » Version 5
Dylan WIDJAJA, 11/06/2024 10:26 PM
1 | 1 | Dylan WIDJAJA | ___ |
---|---|---|---|
2 | |||
3 | 4 | Ty Hikaru DAULTON | [[/|Home]] | [[About Us]] | [[About the Project]] | [[UML Diagrams]] | [[Codes]] | [[Outcome]] | [[ System Requirements]] |
4 | 1 | Dylan WIDJAJA | |
5 | |||
6 | ___ |
||
7 | |||
8 | 2 | Ty Hikaru DAULTON | h1=. <pre> Codes |
9 | </pre> |
||
10 | 5 | Dylan WIDJAJA | |
11 | h2. main.py: This file initializes the entire project |
||
12 | <pre><code class="python"> |
||
13 | import tkinter as tk |
||
14 | from GUI import GUI |
||
15 | if __name__ == "__main__": |
||
16 | root = tk.Tk() |
||
17 | root.geometry("200x200") |
||
18 | app = GUI(root) |
||
19 | root.mainloop() |
||
20 | |||
21 | </code></pre> |
||
22 | |||
23 | h2. Commands.py: A class to handle various commands related to image processing and display |
||
24 | <pre><code class="python"> |
||
25 | import cv2 as cv |
||
26 | import numpy as np |
||
27 | import tkinter as tk |
||
28 | from tkinter import filedialog, messagebox |
||
29 | import os |
||
30 | from PIL import Image, ImageTk |
||
31 | import configparser |
||
32 | from ImageIO import ImageIO |
||
33 | |||
34 | class Commands: |
||
35 | def __init__(self, gui, shared_data): |
||
36 | self.gui = gui |
||
37 | self.shared_data = shared_data |
||
38 | self.io = ImageIO(shared_data) |
||
39 | |||
40 | def load_left_image(self): |
||
41 | filepath = filedialog.askopenfilename(title="Select Left Image") |
||
42 | if filepath: |
||
43 | self.shared_data.left_image = self.io.load_image(filepath) |
||
44 | print("Left image loaded.") |
||
45 | |||
46 | def load_right_image(self): |
||
47 | filepath = filedialog.askopenfilename(title="Select Right Image") |
||
48 | if filepath: |
||
49 | self.shared_data.right_image = self.io.load_image(filepath) |
||
50 | print("Right image loaded.") |
||
51 | |||
52 | def smooth_gradient(self,length, rate): |
||
53 | # Create a linear gradient that transitions from full brightness (1) to zero (0) |
||
54 | x = np.linspace(0, 1, length) |
||
55 | # Apply a non-linear function (e.g., quadratic) for a smoother transition |
||
56 | gradient = 1 - (x ** 2) # Quadratic falloff to create a smooth transition |
||
57 | gradient = np.clip(gradient * rate, 0, 1) # Scale the gradient by the rate |
||
58 | return gradient |
||
59 | |||
60 | def show_left_image(self): |
||
61 | if self.shared_data.left_image is not None: |
||
62 | |||
63 | |||
64 | |||
65 | self.shared_data.isLeft = True |
||
66 | left = self.shared_data.left_image.copy() |
||
67 | height = left.shape[0] |
||
68 | left_overlap_start = left.shape[1] - self.shared_data.overlap_region |
||
69 | |||
70 | # Create a smooth gradient alpha mask for the left image (1 to 0) |
||
71 | gradient_alpha = np.linspace(1, 0, self.shared_data.overlap_region) |
||
72 | gradient_alpha = np.tile(gradient_alpha, (height, 1)) |
||
73 | |||
74 | # Apply the alpha gradient to the overlapping region |
||
75 | left_overlap = left[:, left_overlap_start:, :].copy() |
||
76 | left_overlap = (left_overlap * gradient_alpha[:, :, np.newaxis]).astype(np.uint8) |
||
77 | |||
78 | # Replace the modified overlapping region back into the original left image |
||
79 | left[:, left_overlap_start:, :] = left_overlap |
||
80 | |||
81 | #Debug code |
||
82 | #Write the edited image to file |
||
83 | self.io.write_image(self, img=left, fileName="left_Modified.png") |
||
84 | print("Left image written to file.") |
||
85 | # Show the modified image |
||
86 | self.io.show_image(left, canvas=self.gui.canvas) |
||
87 | print("Left image displayed.") |
||
88 | |||
89 | |||
90 | else: |
||
91 | messagebox.showerror("Error", "No left image loaded!") |
||
92 | |||
93 | def show_right_image(self): |
||
94 | if self.shared_data.right_image is not None: |
||
95 | self.shared_data.isLeft = False |
||
96 | right = self.shared_data.right_image.copy() |
||
97 | height = right.shape[0] |
||
98 | right_overlap_end = self.shared_data.overlap_region |
||
99 | |||
100 | # Create a smooth gradient alpha mask for the right image (0 to 1) |
||
101 | gradient_alpha = np.linspace(0, 1, right_overlap_end) |
||
102 | gradient_alpha = np.tile(gradient_alpha, (height, 1)) |
||
103 | |||
104 | # Apply the alpha gradient to the overlapping region |
||
105 | right_overlap = right[:, :right_overlap_end, :].copy() |
||
106 | right_overlap = (right_overlap * gradient_alpha[:, :, np.newaxis]).astype(np.uint8) |
||
107 | |||
108 | # Replace the modified overlapping region back into the original right image |
||
109 | right[:, :right_overlap_end, :] = right_overlap |
||
110 | |||
111 | #Write modified image to file |
||
112 | self.io.write_image(self, img=right, fileName="right_Modified.png") |
||
113 | print("Right image written to file.") |
||
114 | # Show the modified image |
||
115 | self.io.show_image(right, canvas=self.gui.canvas) |
||
116 | print("Right image displayed.") |
||
117 | |||
118 | else: |
||
119 | messagebox.showerror("Error", "No right image loaded!") |
||
120 | |||
121 | def show_right_original(self): |
||
122 | # Show original image |
||
123 | if self.shared_data.right_image is not None: |
||
124 | self.shared_data.isLeft = False |
||
125 | right = self.shared_data.right_image.copy() |
||
126 | self.io.show_image(right, canvas=self.gui.canvas) |
||
127 | print("Right image displayed.") |
||
128 | else: |
||
129 | messagebox.showerror("Error", "No right image loaded!") |
||
130 | |||
131 | def show_left_original(self): |
||
132 | # Show original image |
||
133 | if self.shared_data.left_image is not None: |
||
134 | self.shared_data.isLeft = True |
||
135 | left = self.shared_data.left_image.copy() |
||
136 | self.io.show_image(left, canvas=self.gui.canvas) |
||
137 | print("Left image displayed.") |
||
138 | else: |
||
139 | messagebox.showerror("Error", "No left image loaded!") |
||
140 | |||
141 | |||
142 | def set_overlap_region(self): |
||
143 | self.shared_data.overlap_region = self.gui.overlap.get() |
||
144 | print(f"Overlap region set to {self.shared_data.overlap_region}") |
||
145 | if(self.shared_data.isLeft): |
||
146 | self.show_left_image() |
||
147 | else: |
||
148 | self.show_right_image() |
||
149 | |||
150 | def increase_overlap(self): |
||
151 | current_overlap = self.gui.overlap.get() |
||
152 | self.gui.overlap.set(current_overlap + 1) |
||
153 | self.set_overlap_region() |
||
154 | if(self.shared_data.isLeft): |
||
155 | self.show_left_image() |
||
156 | else: |
||
157 | self.show_right_image() |
||
158 | |||
159 | |||
160 | |||
161 | |||
162 | def decrease_overlap(self): |
||
163 | current_overlap = self.gui.overlap.get() |
||
164 | self.gui.overlap.set(max(0, current_overlap - 1)) # Prevents negative overlap |
||
165 | self.set_overlap_region() |
||
166 | if(self.shared_data.isLeft): |
||
167 | self.show_left_image() |
||
168 | else: |
||
169 | self.show_right_image() |
||
170 | |||
171 | </code></pre> |
||
172 | |||
173 | h2. GUI.py: This class creates a graphical user interface for the image projector |
||
174 | <pre><code class="python"> |
||
175 | import cv2 as cv |
||
176 | import numpy as np |
||
177 | import tkinter as tk |
||
178 | from tkinter import filedialog, messagebox |
||
179 | import os |
||
180 | from PIL import Image, ImageTk |
||
181 | import configparser |
||
182 | from SharedData import SharedData |
||
183 | from Commands import Commands |
||
184 | class GUI: |
||
185 | def __init__(self, master): |
||
186 | self.master = master |
||
187 | self.master.title("Image Projector") |
||
188 | self.canvas = tk.Canvas(self.master, bg="black") |
||
189 | self.canvas.pack(fill=tk.BOTH, expand=True) |
||
190 | self.shared_data = SharedData() |
||
191 | self.commands = Commands(self, self.shared_data) |
||
192 | self.control_window() |
||
193 | |||
194 | def control_window(self): |
||
195 | self.control_window = tk.Toplevel(self.master) |
||
196 | self.control_window.title("Controls") |
||
197 | |||
198 | self.overlap = tk.IntVar(value=200) |
||
199 | |||
200 | # Label and entry for overlap region width |
||
201 | self.overlapLabel = tk.Label(self.control_window, text='Overlap region Width', font=('calibre', 10, 'bold')) |
||
202 | self.overlapBox = tk.Entry(self.control_window, textvariable=self.overlap) |
||
203 | |||
204 | self.overlapLabel.grid(row=0, column=0, padx=5, pady=5) |
||
205 | self.overlapBox.grid(row=0, column=1, padx=5, pady=5) |
||
206 | |||
207 | # Button to set overlap region |
||
208 | self.set_overlap_button = tk.Button(self.control_window, text="Set Overlap Region", command=self.commands.set_overlap_region) |
||
209 | self.set_overlap_button.grid(row=1, column=0, columnspan=2, padx=5, pady=5) |
||
210 | |||
211 | # New buttons for increasing and decreasing the overlap region |
||
212 | self.increase_overlap_button = tk.Button(self.control_window, text="Increase Overlap", command=self.commands.increase_overlap) |
||
213 | self.increase_overlap_button.grid(row=2, column=0, padx=5, pady=5) |
||
214 | |||
215 | self.decrease_overlap_button = tk.Button(self.control_window, text="Decrease Overlap", command=self.commands.decrease_overlap) |
||
216 | self.decrease_overlap_button.grid(row=2, column=1, padx=5, pady=5) |
||
217 | |||
218 | # Load left and right image buttons |
||
219 | self.button_load_left = tk.Button(self.control_window, text="Load Left Image", command=self.commands.load_left_image) |
||
220 | self.button_load_left.grid(row=3, column=0, padx=5, pady=5) |
||
221 | |||
222 | self.button_load_right = tk.Button(self.control_window, text="Load Right Image", command=self.commands.load_right_image) |
||
223 | self.button_load_right.grid(row=3, column=1, padx=5, pady=5) |
||
224 | |||
225 | # Show left and right image buttons |
||
226 | self.button_show_left = tk.Button(self.control_window, text="Show Left Image", command=self.commands.show_left_image) |
||
227 | self.button_show_left.grid(row=4, column=0, padx=5, pady=5) |
||
228 | |||
229 | self.button_show_right = tk.Button(self.control_window, text="Show Right Image", command=self.commands.show_right_image) |
||
230 | self.button_show_right.grid(row=4, column=1, padx=5, pady=5) |
||
231 | |||
232 | # Show original left and right image buttons |
||
233 | self.button_show_left_original = tk.Button(self.control_window, text="Show Left Original", command=self.commands.show_left_original) |
||
234 | self.button_show_left_original.grid(row=5, column=0, padx=5, pady=5) |
||
235 | |||
236 | self.button_show_right_original = tk.Button(self.control_window, text="Show Right Original", command=self.commands.show_right_original) |
||
237 | self.button_show_right_original.grid(row=5, column=1, padx=5, pady=5) |
||
238 | |||
239 | self.save_config = tk.Button(self.control_window, text="Save Config", command=self.shared_data.write_to_config) |
||
240 | self.save_config.grid(row=6, column=0, padx=5, pady=5) |
||
241 | |||
242 | self.load_config = tk.Button(self.control_window, text="Load Config", command=self.shared_data.read_from_config) |
||
243 | self.load_config.grid(row=6, column=1, padx=5, pady=5) |
||
244 | |||
245 | </code></pre> |
||
246 | |||
247 | h2. ImageIO.py: This class is in charge of image input and output |
||
248 | <pre><code class="python"> |
||
249 | import cv2 as cv |
||
250 | import numpy as np |
||
251 | import os |
||
252 | from PIL import Image |
||
253 | import tkinter as tk |
||
254 | |||
255 | class ImageIO: |
||
256 | def __init__(self, shared_data): |
||
257 | self.shared_data = shared_data |
||
258 | |||
259 | def load_image(self, path): |
||
260 | return cv.imread(path, cv.IMREAD_COLOR) |
||
261 | |||
262 | def write_image(self, img, fileName="image.png"): |
||
263 | if cv.imwrite(fileName, img): |
||
264 | print(f"File {fileName} Written Successfully") |
||
265 | else: |
||
266 | print("File writing failed") |
||
267 | |||
268 | def show_image(self, img, title="Image Title", canvas=None): |
||
269 | # Set up OpenCV fullscreen window in the main thread |
||
270 | cv.namedWindow(title, cv.WINDOW_NORMAL) |
||
271 | cv.setWindowProperty(title, cv.WND_PROP_FULLSCREEN, cv.WINDOW_FULLSCREEN) |
||
272 | |||
273 | # Resize the image to 1920x1080 resolution |
||
274 | img_resized = cv.resize(img, (1920, 1080), interpolation=cv.INTER_LINEAR) |
||
275 | |||
276 | # Display the resized image in OpenCV and keep it open without blocking Tkinter |
||
277 | cv.imshow(title, img_resized) |
||
278 | |||
279 | # Run a non-blocking loop to keep OpenCV window and Tkinter GUI responsive |
||
280 | while cv.getWindowProperty(title, cv.WND_PROP_VISIBLE) >= 1: |
||
281 | cv.waitKey(1) |
||
282 | if canvas: |
||
283 | canvas.update_idletasks() |
||
284 | canvas.update() |
||
285 | |||
286 | # Close the OpenCV window once the loop ends |
||
287 | cv.destroyAllWindows() |
||
288 | |||
289 | </code></pre> |
||
290 | |||
291 | h2. SharedData.py: This class is used to store shared data between different classes |
||
292 | <pre><code class="python"> |
||
293 | import cv2 as cv |
||
294 | import numpy as np |
||
295 | import tkinter as tk |
||
296 | from tkinter import filedialog, messagebox |
||
297 | import os |
||
298 | from PIL import Image, ImageTk |
||
299 | import configparser |
||
300 | |||
301 | class SharedData: |
||
302 | def __init__(self): |
||
303 | self.left_image = None |
||
304 | self.right_image = None |
||
305 | self.overlap_region = 200 |
||
306 | self.leftSide = True |
||
307 | |||
308 | def write_to_config(self, filename='config.ini'): |
||
309 | config = configparser.ConfigParser() |
||
310 | config['DEFAULT'] = { |
||
311 | 'overlap_region': str(self.overlap_region), |
||
312 | 'leftSide': str(self.leftSide) |
||
313 | } |
||
314 | with open(filename, 'w') as configfile: |
||
315 | config.write(configfile) |
||
316 | |||
317 | def read_from_config(self, filename='config.ini'): |
||
318 | config = configparser.ConfigParser() |
||
319 | config.read(filename) |
||
320 | self.overlap_region = config['DEFAULT'].getint('overlap_region', self.overlap_region) |
||
321 | self.leftSide = config['DEFAULT'].getboolean('leftSide', self.leftSide) |
||
322 | |||
323 | </code></pre> |
||
324 |