aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPinapelz <yukais@pinapelz.com>2025-02-24 01:57:33 -0800
committerPinapelz <yukais@pinapelz.com>2025-02-24 01:57:33 -0800
commitc4bb2dc96ce19707740d5074ee2745ccb9c32cba (patch)
treed804250c06432e72e3d72ccde05bcc25367585d1
parent5d488d56434f486b42dae04d5326a5a81106ad31 (diff)
refactor: multithread checks
-rw-r--r--chuni-hands-evolved.py586
1 files changed, 166 insertions, 420 deletions
diff --git a/chuni-hands-evolved.py b/chuni-hands-evolved.py
index 0dee3fd..3bce15c 100644
--- a/chuni-hands-evolved.py
+++ b/chuni-hands-evolved.py
@@ -6,6 +6,8 @@ from PIL import Image, ImageTk
import numpy as np
import keyboard # Requires `keyboard` library
import json
+import threading
+import time
zones = [
{"x": 300, "y": 100, "width": 50, "height": 50},
@@ -32,6 +34,10 @@ CAMERA_WIDTH = 1280
CAMERA_HEIGHT = 720
ZONE_TRIGGERED_STATE = [False] * len(zones)
+# Shared variables between threads
+latest_frame = None
+frame_lock = threading.Lock()
+running = True # Control flag for the camera thread
def get_avg_brightness(frame, zone):
x, y, w, h = zone["x"], zone["y"], zone["width"], zone["height"]
@@ -40,7 +46,6 @@ def get_avg_brightness(frame, zone):
return 0
return np.mean(cv2.cvtColor(roi, cv2.COLOR_BGR2GRAY))
-
def get_available_cameras():
print("Now testing to see which cameras are available... (disregard the errors here)")
available_cameras = []
@@ -51,21 +56,17 @@ def get_available_cameras():
cap.release()
return available_cameras
-
def calculate_preview_size(width, height, max_height=720):
- """Calculate preview dimensions maintaining aspect ratio"""
if height <= max_height:
return width, height
-
aspect_ratio = width / height
new_height = max_height
new_width = int(aspect_ratio * new_height)
return new_width, new_height
+def setup_gui(camera_width: int, camera_height: int, preview_width: int, preview_height: int) -> tk.Tk:
+ global current_camera_index, x_slider, y_slider, spacing_slider, keystrokes_enabled, chuniio_enabled, update_rate, video_canvas
-def setup_gui(
- camera_width: int, camera_height: int, preview_width: int, preview_height: int
-) -> tk.Tk:
root = tk.Tk()
root.title("chuni-hands-evolved")
root.attributes("-topmost", True)
@@ -75,7 +76,6 @@ def setup_gui(
window_width = min(int(screen_width * 0.8), preview_width + 40)
window_height = min(int(screen_height * 0.8), preview_height + 300)
-
x = (screen_width - window_width) // 2
y = (screen_height - window_height) // 2
root.geometry(f"{window_width}x{window_height}+{x}+{y}")
@@ -89,207 +89,82 @@ def setup_gui(
"label_font": ("Arial", 10),
"button_font": ("Arial", 10, "bold"),
}
-
root.configure(bg=style["bg"])
- main_container = tk.Frame(
- root, bg=style["bg"], padx=style["padding"], pady=style["padding"]
- )
- main_container.pack(
- fill="both", expand=True, padx=style["padding"], pady=style["padding"]
- )
+ main_container = tk.Frame(root, bg=style["bg"], padx=style["padding"], pady=style["padding"])
+ main_container.pack(fill="both", expand=True, padx=style["padding"], pady=style["padding"])
# Video canvas
video_frame = tk.Frame(main_container, bg=style["frame_bg"], relief="ridge", bd=2)
video_frame.pack(fill="both", expand=True, pady=(0, style["padding"]))
-
video_canvas = tk.Canvas(video_frame, width=preview_width, height=preview_height)
video_canvas.pack(fill="both", expand=True)
- # Camera selection
- camera_frame = tk.Frame(
- main_container, bg=style["frame_bg"], relief="ridge", bd=2, padx=10, pady=10
- )
+ # Camera selection controls
+ camera_frame = tk.Frame(main_container, bg=style["frame_bg"], relief="ridge", bd=2, padx=10, pady=10)
camera_frame.pack(fill="x", pady=(0, style["padding"]))
- tk.Label(
- camera_frame,
- text="Camera Number:",
- font=style["label_font"],
- bg=style["frame_bg"],
- ).pack(side="left")
-
+ tk.Label(camera_frame, text="Camera Number:", font=style["label_font"], bg=style["frame_bg"]).pack(side="left")
available_cameras = get_available_cameras()
if not available_cameras:
print("No cameras found!")
available_cameras = [0]
-
camera_var = StringVar()
- camera_var.set(
- str(
- current_camera_index
- if current_camera_index in available_cameras
- else available_cameras[0]
- )
- )
+ camera_var.set(str(current_camera_index if current_camera_index in available_cameras else available_cameras[0]))
camera_dropdown = tk.OptionMenu(camera_frame, camera_var, *available_cameras)
camera_dropdown.configure(width=10)
camera_dropdown.pack(side="left", padx=(10, 0))
- tk.Label(
- camera_frame,
- text="Update Rate (ms):",
- font=style["label_font"],
- bg=style["frame_bg"],
- ).pack(side="left")
-
- update_rate = tk.StringVar(value="5")
- update_rate_spinbox = tk.Spinbox(
- camera_frame,
- from_=1,
- to=100,
- textvariable=update_rate,
- width=5,
- )
+ tk.Label(camera_frame, text="Update Rate (ms):", font=style["label_font"], bg=style["frame_bg"]).pack(side="left")
+ update_rate = StringVar(value="5")
+ update_rate_spinbox = tk.Spinbox(camera_frame, from_=1, to=100, textvariable=update_rate, width=5)
update_rate_spinbox.pack(side="left", padx=5)
- tk.Label(
- camera_frame,
- text="You should set the update rate to something like 100 while editing for a smoother experience, then set it back to 5 for normal use.",
- font=style["label_font"],
- bg=style["frame_bg"],
- ).pack(side="top", pady=(0, style["padding"]))
+ tk.Label(camera_frame,
+ text="Set a high update rate (e.g. 100) while editing, then a lower value (e.g. 5) for normal use.",
+ font=style["label_font"], bg=style["frame_bg"]).pack(side="top", pady=(0, style["padding"]))
- controls_frame = tk.Frame(
- main_container, bg=style["frame_bg"], relief="ridge", bd=2, padx=10, pady=10
- )
+ # Position and spacing controls
+ controls_frame = tk.Frame(main_container, bg=style["frame_bg"], relief="ridge", bd=2, padx=10, pady=10)
controls_frame.pack(fill="x", pady=(0, style["padding"]))
-
+
inputs_frame = tk.Frame(controls_frame, bg=style["frame_bg"])
inputs_frame.pack(fill="x")
+ # X Position
x_frame = tk.Frame(inputs_frame, bg=style["frame_bg"])
x_frame.pack(fill="x", pady=(0, 5))
- tk.Label(
- x_frame,
- text="X Position:",
- font=style["label_font"],
- bg=style["frame_bg"],
- width=15,
- ).pack(side="left")
-
- x_var = tk.StringVar(value="0")
- x_spinbox = tk.Spinbox(
- x_frame,
- from_=-500,
- to=1280,
- textvariable=x_var,
- increment=10,
- width=10,
- command=lambda: update_position("x"),
- )
- x_spinbox.pack(side="left", padx=5)
-
- x_slider = Scale(
- x_frame,
- from_=-500,
- to=1280,
- orient="horizontal",
- length=400,
- showvalue=0,
- command=lambda v: x_var.set(str(int(float(v)))),
- )
+ tk.Label(x_frame, text="X Position:", font=style["label_font"], bg=style["frame_bg"], width=15).pack(side="left")
+ x_var = StringVar(value="0")
+ x_slider = Scale(x_frame, from_=-500, to=1280, orient="horizontal", length=400, showvalue=0,
+ command=lambda v: x_var.set(str(int(float(v)))))
x_slider.pack(side="left", fill="x", expand=True, padx=5)
+ # Y Position
y_frame = tk.Frame(inputs_frame, bg=style["frame_bg"])
y_frame.pack(fill="x", pady=(0, 5))
- tk.Label(
- y_frame,
- text="Y Position:",
- font=style["label_font"],
- bg=style["frame_bg"],
- width=15,
- ).pack(side="left")
-
- y_var = tk.StringVar(value="0")
- y_spinbox = tk.Spinbox(
- y_frame,
- from_=-200,
- to=720,
- textvariable=y_var,
- increment=10,
- width=10,
- command=lambda: update_position("y"),
- )
- y_spinbox.pack(side="left", padx=5)
-
- y_slider = Scale(
- y_frame,
- from_=-200,
- to=720,
- orient="horizontal",
- length=400,
- showvalue=0,
- command=lambda v: y_var.set(str(int(float(v)))),
- )
+ tk.Label(y_frame, text="Y Position:", font=style["label_font"], bg=style["frame_bg"], width=15).pack(side="left")
+ y_var = StringVar(value="0")
+ y_slider = Scale(y_frame, from_=-200, to=720, orient="horizontal", length=400, showvalue=0,
+ command=lambda v: y_var.set(str(int(float(v)))))
y_slider.pack(side="left", fill="x", expand=True, padx=5)
- # Spacing controls
+ # Spacing
spacing_frame = tk.Frame(inputs_frame, bg=style["frame_bg"])
spacing_frame.pack(fill="x")
- tk.Label(
- spacing_frame,
- text="Sensor Spacing:",
- font=style["label_font"],
- bg=style["frame_bg"],
- width=15,
- ).pack(side="left")
-
- spacing_var = tk.StringVar(value="100")
- spacing_spinbox = tk.Spinbox(
- spacing_frame,
- from_=10,
- to=300,
- textvariable=spacing_var,
- increment=5,
- width=10,
- command=lambda: update_position("spacing"),
- )
- spacing_spinbox.pack(side="left", padx=5)
-
- spacing_slider = Scale(
- spacing_frame,
- from_=10,
- to=300,
- orient="horizontal",
- length=400,
- showvalue=0,
- command=lambda v: spacing_var.set(str(int(float(v)))),
- )
+ tk.Label(spacing_frame, text="Sensor Spacing:", font=style["label_font"], bg=style["frame_bg"], width=15).pack(side="left")
+ spacing_var = StringVar(value="100")
+ spacing_slider = Scale(spacing_frame, from_=10, to=300, orient="horizontal", length=400, showvalue=0,
+ command=lambda v: spacing_var.set(str(int(float(v)))))
spacing_slider.pack(side="left", fill="x", expand=True, padx=5)
+ # Sensor Width
width_frame = tk.Frame(inputs_frame, bg=style["frame_bg"])
width_frame.pack(fill="x")
- tk.Label(
- width_frame,
- text="Sensor Width:",
- font=style["label_font"],
- bg=style["frame_bg"],
- width=15,
- ).pack(side="left")
-
- width_var = tk.StringVar(value="50")
- width_spinbox = tk.Spinbox(
- width_frame,
- from_=10,
- to=1000,
- textvariable=width_var,
- increment=5,
- width=10,
- command=lambda: update_width(),
- )
+ tk.Label(width_frame, text="Sensor Width:", font=style["label_font"], bg=style["frame_bg"], width=15).pack(side="left")
+ width_var = StringVar(value="50")
+ width_spinbox = tk.Spinbox(width_frame, from_=10, to=1000, textvariable=width_var, increment=5, width=10)
width_spinbox.pack(side="left", padx=5)
-
def update_width():
try:
value = int(width_var.get())
@@ -298,40 +173,11 @@ def setup_gui(
zone["width"] = value
except ValueError:
pass
+ width_spinbox.config(command=update_width)
- def update_position(type):
- try:
- if type == "x":
- value = int(x_var.get())
- if -500 <= value <= 1280:
- x_slider.set(value)
- elif type == "y":
- value = int(y_var.get())
- if -200 <= value <= 720:
- y_slider.set(value)
- elif type == "spacing":
- value = int(spacing_var.get())
- if 10 <= value <= 300:
- spacing_slider.set(value)
- except ValueError:
- pass
-
- def on_mousewheel(event, spinbox):
- if event.delta > 0:
- spinbox.invoke("buttonup")
- else:
- spinbox.invoke("buttondown")
-
- x_spinbox.bind("<MouseWheel>", lambda e: on_mousewheel(e, x_spinbox))
- y_spinbox.bind("<MouseWheel>", lambda e: on_mousewheel(e, y_spinbox))
- spacing_spinbox.bind("<MouseWheel>", lambda e: on_mousewheel(e, spacing_spinbox))
- width_spinbox.bind("<MouseWheel>", lambda e: on_mousewheel(e, width_spinbox))
-
- buttons_frame = tk.Frame(
- main_container, bg=style["frame_bg"], relief="ridge", bd=2, padx=10, pady=10
- )
+ # Buttons
+ buttons_frame = tk.Frame(main_container, bg=style["frame_bg"], relief="ridge", bd=2, padx=10, pady=10)
buttons_frame.pack(fill="x")
-
def recalibrate():
ret, frame = cap.read()
if ret:
@@ -339,7 +185,9 @@ def setup_gui(
print("Recalibration complete.")
else:
print("Error: Could not capture frame for recalibration.")
-
+ calibration_button = Button(buttons_frame, text="Recalibrate", command=recalibrate, bg=style["button_bg"],
+ fg=style["button_fg"], font=style["button_font"], relief="flat", padx=20, pady=5)
+ calibration_button.pack(side="left", padx=5)
def save_config():
config = {
"x_offset": x_slider.get(),
@@ -352,113 +200,50 @@ def setup_gui(
with open(CONFIG_FILE, "w") as f:
json.dump(config, f)
messagebox.showinfo("Config", "Configuration saved successfully.")
-
- button_kwargs = {
- "bg": style["button_bg"],
- "fg": style["button_fg"],
- "font": style["button_font"],
- "relief": "flat",
- "padx": 20,
- "pady": 5,
- }
-
- calibration_button = Button(
- buttons_frame, text="Recalibrate", command=recalibrate, **button_kwargs
- )
- calibration_button.pack(side="left", padx=5)
-
- save_button = Button(
- buttons_frame, text="Save Config", command=save_config, **button_kwargs
- )
+ save_button = Button(buttons_frame, text="Save Config", command=save_config, bg=style["button_bg"],
+ fg=style["button_fg"], font=style["button_font"], relief="flat", padx=20, pady=5)
save_button.pack(side="left", padx=5)
-
chuniio_enabled = BooleanVar(value=True)
- chuniio_button = Checkbutton(
- buttons_frame,
- text="Brokenithm Evolved chuniio",
- variable=chuniio_enabled,
- font=style["button_font"],
- relief="flat",
- padx=20,
- pady=5,
- bg=style["frame_bg"],
- )
+ chuniio_button = Checkbutton(buttons_frame, text="Brokenithm Evolved chuniio", variable=chuniio_enabled,
+ font=style["button_font"], relief="flat", padx=20, pady=5, bg=style["frame_bg"])
chuniio_button.pack(side="right", padx=5)
-
keystrokes_enabled = BooleanVar(value=False)
- keystroke_button = Button(
- buttons_frame,
- text="⌨ Keystrokes: OFF",
- command=lambda: toggle_keystrokes(),
- **button_kwargs,
- )
+ keystroke_button = Button(buttons_frame, text="⌨ Keystrokes: OFF",
+ command=lambda: toggle_keystrokes(), bg=style["button_bg"],
+ fg=style["button_fg"], font=style["button_font"], relief="flat", padx=20, pady=5)
keystroke_button.pack(side="right", padx=5)
-
def toggle_keystrokes():
current = keystrokes_enabled.get()
keystrokes_enabled.set(not current)
- keystroke_button.config(
- text="⌨ Keystrokes: ON" if not current else "⌨ Keystrokes: OFF",
- bg="#4a90e2" if not current else "#e74c3c",
- )
-
+ keystroke_button.config(text="⌨ Keystrokes: ON" if not current else "⌨ Keystrokes: OFF",
+ bg="#4a90e2" if not current else "#e74c3c")
+ def switch_camera(camera_width: int, camera_height: int):
+ global cap, current_camera_index
+ current_camera_index = int(camera_var.get())
+ cap.release()
+ cap = cv2.VideoCapture(current_camera_index)
+ cap.set(cv2.CAP_PROP_FRAME_WIDTH, camera_width)
+ cap.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_height)
+ if not cap.isOpened():
+ messagebox.showerror("Camera Error", "Failed to access the selected camera.")
+ camera_var.trace_add("write", lambda *args: switch_camera(camera_width, camera_height))
+
def on_window_resize(event):
if event.widget == root:
new_width = video_frame.winfo_width()
new_height = video_frame.winfo_height()
- aspect_ratio = preview_width / preview_height
-
- if new_width / new_height > aspect_ratio:
+ aspect_ratio = preview_width/preview_height
+ if new_width/new_height > aspect_ratio:
canvas_height = new_height
- canvas_width = int(canvas_height * aspect_ratio)
+ canvas_width = int(canvas_height*aspect_ratio)
else:
canvas_width = new_width
- canvas_height = int(canvas_width / aspect_ratio)
-
+ canvas_height = int(canvas_width/aspect_ratio)
video_canvas.configure(width=canvas_width, height=canvas_height)
-
root.bind("<Configure>", on_window_resize)
- controls_frame = tk.Frame(
- main_container, bg=style["frame_bg"], relief="ridge", bd=2
- )
- controls_frame.pack(fill="x", pady=(0, style["padding"]))
-
- buttons_frame = tk.Frame(main_container, bg=style["frame_bg"], relief="ridge", bd=2)
- buttons_frame.pack(fill="x")
- for slider in [x_slider, y_slider, spacing_slider]:
- slider.pack(fill="x", expand=True, padx=5)
-
- def switch_camera(camera_width: int, camera_height: int):
- global cap, current_camera_index
- current_camera_index = int(camera_var.get())
- cap.release()
- cap = cv2.VideoCapture(current_camera_index)
- cap.set(cv2.CAP_PROP_FRAME_WIDTH, camera_width)
- cap.set(cv2.CAP_PROP_FRAME_HEIGHT, camera_height)
- if not cap.isOpened():
- messagebox.showerror(
- "Camera Error", "Failed to access the selected camera."
- )
-
- camera_var.trace_add(
- "write",
- lambda *args: switch_camera(
- camera_width=camera_width, camera_height=camera_height
- ),
- )
-
- return (
- root,
- video_canvas,
- x_slider,
- y_slider,
- width_var,
- spacing_slider,
- keystrokes_enabled,
- chuniio_enabled,
- update_rate,
- )
-
+
+ return (root, video_canvas, x_slider, y_slider, width_var, spacing_slider,
+ keystrokes_enabled, chuniio_enabled, update_rate)
def calibrate(frame):
global zone_color_state
@@ -468,108 +253,89 @@ def calibrate(frame):
zone_color_state.append(brightness)
print("Calibration completed. Initial lighting values:", zone_color_state)
+def camera_loop(preview_width, preview_height, chuniio_shared_memory):
+ global cap, latest_frame, running
+ while running:
+ ret, frame = cap.read()
+ if not ret:
+ print("Error: Could not capture frame.")
+ continue
+ frame = cv2.flip(frame, 1)
+ # Get offsets and spacing from the UI controls (assumed to be safe for reading)
+ try:
+ x_offset = int(x_slider.get())
+ y_offset = int(y_slider.get())
+ spacing = int(spacing_slider.get())
+ except Exception:
+ x_offset, y_offset, spacing = 0, 0, 100
+
+ # Update zone positions
+ for i, zone in enumerate(zones):
+ zone["x"] = base_positions[i]["x"] + x_offset
+ zone["y"] = base_positions[0]["y"] + y_offset + i * spacing
-def update_frame(
- preview_width,
- preview_height,
- chuniio_shared_memory,
-):
- global cap
- ret, frame = cap.read()
- if not ret:
- print("Error: Could not capture frame.")
- return
+ # Process zones
+ for i, zone in enumerate(zones):
+ current_brightness = get_avg_brightness(frame, zone)
+ if abs(current_brightness - zone_color_state[i]) > 20:
+ if not ZONE_TRIGGERED_STATE[i]:
+ if keystrokes_enabled.get():
+ print(f"Key '{_UMIGIRI_32_AIRZONE_LAYOUT[i]}' pressed.")
+ keyboard.press(_UMIGIRI_32_AIRZONE_LAYOUT[i])
+ print(f"Zone {i} triggered")
+ ZONE_TRIGGERED_STATE[i] = True
+ cv2.rectangle(frame, (zone["x"], zone["y"]),
+ (zone["x"]+zone["width"], zone["y"]+zone["height"]), (0,255,0), 3)
+ else:
+ if ZONE_TRIGGERED_STATE[i]:
+ if keystrokes_enabled.get():
+ keyboard.release(_UMIGIRI_32_AIRZONE_LAYOUT[i])
+ print(f"Key '{_UMIGIRI_32_AIRZONE_LAYOUT[i]}' released.")
+ ZONE_TRIGGERED_STATE[i] = False
+ cv2.rectangle(frame, (zone["x"], zone["y"]),
+ (zone["x"]+zone["width"], zone["y"]+zone["height"]), (0,0,255), 2)
- frame = cv2.flip(frame, 1)
- x_offset = x_slider.get()
- y_offset = y_slider.get()
- spacing = spacing_slider.get()
+ if chuniio_enabled.get():
+ chuniio.write_to_airzone(ZONE_TRIGGERED_STATE, chuniio_shared_memory)
- # Update zone positions
- for i, zone in enumerate(zones):
- zone["x"] = base_positions[i]["x"] + x_offset
- zone["y"] = base_positions[0]["y"] + y_offset + i * spacing
+ # Convert frame to RGB
+ rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
+
+ with frame_lock:
+ latest_frame = rgb_frame
+
+ try:
+ rate = max(1, int(update_rate.get()))
+ except ValueError:
+ rate = 5
+ time.sleep(rate/1000)
- # Process zones
- for i, zone in enumerate(zones):
- current_brightness = get_avg_brightness(frame, zone)
- if abs(current_brightness - zone_color_state[i]) > 20:
- if not ZONE_TRIGGERED_STATE[i]:
- if keystrokes_enabled.get():
- print(f"Key '{_UMIGIRI_32_AIRZONE_LAYOUT[i]}' pressed.")
- keyboard.press(_UMIGIRI_32_AIRZONE_LAYOUT[i])
- print(f"Zone {i} triggered")
- ZONE_TRIGGERED_STATE[i] = True
- cv2.rectangle(
- frame,
- (zone["x"], zone["y"]),
- (zone["x"] + zone["width"], zone["y"] + zone["height"]),
- (0, 255, 0),
- 3,
- )
+def update_canvas():
+ global latest_frame
+ with frame_lock:
+ frame_copy = latest_frame.copy() if latest_frame is not None else None
+ if frame_copy is not None:
+ canvas_width = video_canvas.winfo_width()
+ canvas_height = video_canvas.winfo_height()
+ aspect_ratio = frame_copy.shape[1] / frame_copy.shape[0]
+ if canvas_width / canvas_height > aspect_ratio:
+ display_height = canvas_height
+ display_width = int(display_height * aspect_ratio)
else:
- if ZONE_TRIGGERED_STATE[i]:
- if keystrokes_enabled.get():
- keyboard.release(_UMIGIRI_32_AIRZONE_LAYOUT[i])
- print(f"Key '{_UMIGIRI_32_AIRZONE_LAYOUT[i]}' released.")
- ZONE_TRIGGERED_STATE[i] = False
- cv2.rectangle(
- frame,
- (zone["x"], zone["y"]),
- (zone["x"] + zone["width"], zone["y"] + zone["height"]),
- (0, 0, 255),
- 2,
- )
-
- if chuniio_enabled.get():
- chuniio.write_to_airzone(ZONE_TRIGGERED_STATE, chuniio_shared_memory)
-
- # Convert to RGB
- rgb_frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
-
- # Get canvas dimensions
- canvas_width = video_canvas.winfo_width()
- canvas_height = video_canvas.winfo_height()
-
- # Ensure valid dimensions before resizing
- if canvas_width <= 0 or canvas_height <= 0:
- canvas_width = preview_width
- canvas_height = preview_height
-
- # Calculate aspect ratio preserving dimensions
- aspect_ratio = frame.shape[1] / frame.shape[0]
- if canvas_width / canvas_height > aspect_ratio:
- display_height = canvas_height
- display_width = int(display_height * aspect_ratio)
- else:
- display_width = canvas_width
- display_height = int(display_width / aspect_ratio)
-
- # Ensure minimum dimensions
- display_width = max(display_width, 1)
- display_height = max(display_height, 1)
-
- # Resize frame
- try:
- rgb_frame = cv2.resize(rgb_frame, (display_width, display_height))
- except cv2.error as e:
- print(f"Resize error: {display_width}x{display_height}")
- return
-
- # Display frame
- img = ImageTk.PhotoImage(Image.fromarray(rgb_frame))
- video_canvas.delete("all")
- video_canvas.create_image(
- canvas_width // 2, canvas_height // 2, anchor="center", image=img
- )
- video_canvas.image = img
-
- try:
- rate = max(1, int(update_rate.get()))
- except ValueError:
- rate = 5
- root.after(rate, update_frame, preview_width, preview_height, chuniio_shared_memory)
-
+ display_width = canvas_width
+ display_height = int(display_width / aspect_ratio)
+ display_width = max(display_width, 1)
+ display_height = max(display_height, 1)
+ try:
+ resized_frame = cv2.resize(frame_copy, (display_width, display_height))
+ except Exception as e:
+ print("Resize error:", e)
+ resized_frame = frame_copy
+ img = ImageTk.PhotoImage(Image.fromarray(resized_frame))
+ video_canvas.delete("all")
+ video_canvas.create_image(canvas_width//2, canvas_height//2, anchor="center", image=img)
+ video_canvas.image = img
+ root.after(33, update_canvas) # refresh UI at ~30 FPS
def load_config() -> dict:
try:
@@ -578,22 +344,8 @@ def load_config() -> dict:
except FileNotFoundError:
return {}
-
def main():
- global \
- root, \
- current_camera_index, \
- keystrokes_enabled, \
- chuniio_enabled, \
- video_canvas, \
- x_slider, \
- y_slider, \
- width_var, \
- spacing_slider, \
- cap, \
- update_rate, \
- CAMERA_WIDTH, \
- CAMERA_HEIGHT
+ global root, current_camera_index, cap, video_canvas, x_slider, y_slider, width_var, spacing_slider, keystrokes_enabled, chuniio_enabled, update_rate, CAMERA_WIDTH, CAMERA_HEIGHT, latest_frame, running
print("Now starting up... please wait a bit...")
config = load_config()
current_camera_index = config.get("camera_index", 0)
@@ -606,11 +358,8 @@ def main():
actual_width = cap.get(cv2.CAP_PROP_FRAME_WIDTH)
actual_height = cap.get(cv2.CAP_PROP_FRAME_HEIGHT)
-
if actual_width != CAMERA_WIDTH or actual_height != CAMERA_HEIGHT:
- print(
- f"Resolution {CAMERA_WIDTH}x{CAMERA_HEIGHT} not supported. Using {actual_width}x{actual_height}."
- )
+ print(f"Resolution {CAMERA_WIDTH}x{CAMERA_HEIGHT} not supported. Using {actual_width}x{actual_height}.")
CAMERA_WIDTH = int(actual_width)
CAMERA_HEIGHT = int(actual_height)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, actual_width)
@@ -621,29 +370,26 @@ def main():
else:
print("Error: Could not capture initial frame. Check your camera.")
- (
- root,
- video_canvas,
- x_slider,
- y_slider,
- width_var,
- spacing_slider,
- keystrokes_enabled,
- chuniio_enabled,
- update_rate,
- ) = setup_gui(CAMERA_WIDTH, CAMERA_HEIGHT, preview_width, preview_height)
+ (root, video_canvas, x_slider, y_slider, width_var, spacing_slider,
+ keystrokes_enabled, chuniio_enabled, update_rate) = setup_gui(CAMERA_WIDTH, CAMERA_HEIGHT, preview_width, preview_height)
x_slider.set(config.get("x_offset", 0))
y_slider.set(config.get("y_offset", 0))
spacing_slider.set(config.get("spacing", 100))
chuniio_enabled.set(config.get("chuniio_enabled", True))
- chuniio_shared_memory = chuniio.open_sharedmem() if chuniio_enabled.get() else None
width_var.set(str(config.get("width", 50)))
for zone in zones:
zone["width"] = int(width_var.get())
- update_frame(preview_width, preview_height, chuniio_shared_memory)
+
+ chuniio_shared_memory = chuniio.open_sharedmem() if chuniio_enabled.get() else None
+
+ cam_thread = threading.Thread(target=camera_loop, args=(preview_width, preview_height, chuniio_shared_memory), daemon=True)
+ cam_thread.start()
+
+ update_canvas()
+
root.mainloop()
+ running = False
cap.release()
-
main()
send patches to the email below
yukais@pinapelz.com
include the subject [PATCH repo_name]
pinapelz.com
homepage