Add DearPyGUI observer/server UI and demo launcher
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import argparse
|
||||
import asyncio
|
||||
import configparser
|
||||
import ctypes
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
@@ -17,6 +18,11 @@ import numpy as np
|
||||
import uvicorn
|
||||
from fastapi import FastAPI, File, Form, UploadFile
|
||||
|
||||
try:
|
||||
import dearpygui.dearpygui as dpg
|
||||
except ModuleNotFoundError:
|
||||
dpg = None
|
||||
|
||||
|
||||
DEFAULT_CONFIG_PATH = "flywms_navigation.ini"
|
||||
CUDA_MIN_PIXELS = 640 * 360
|
||||
@@ -258,6 +264,8 @@ def parse_args():
|
||||
ap.add_argument("--host", default=defaults["wms_server_host"], help="Host bind server")
|
||||
ap.add_argument("--port", type=int, default=defaults["wms_server_port"], help="Porta server")
|
||||
ap.add_argument("--received-dir", default=defaults["wms_received_dir"], help="Directory upload ricevuti")
|
||||
ap.add_argument("--ui-backend", choices=["auto", "opencv", "dearpygui"], default="dearpygui",
|
||||
help="Backend UI server WMS")
|
||||
ap.add_argument(
|
||||
"--fake-ack-mode",
|
||||
choices=["always-ack", "always-nack", "alternate", "random"],
|
||||
@@ -670,6 +678,60 @@ def safe_name(value: str) -> str:
|
||||
return "".join(ch if ch in allowed else "_" for ch in value)[:160]
|
||||
|
||||
|
||||
def choose_ui_backend(requested: str) -> str:
|
||||
if requested == "opencv":
|
||||
return "opencv"
|
||||
if requested == "dearpygui":
|
||||
if dpg is None:
|
||||
raise RuntimeError("DearPyGUI non disponibile")
|
||||
return "dearpygui"
|
||||
if dpg is not None:
|
||||
return "dearpygui"
|
||||
return "opencv"
|
||||
|
||||
|
||||
def placeholder_frame(width: int, height: int, text: str) -> np.ndarray:
|
||||
canvas = np.full((height, width, 3), 235, dtype=np.uint8)
|
||||
cv2.putText(
|
||||
canvas,
|
||||
text,
|
||||
(24, height // 2),
|
||||
cv2.FONT_HERSHEY_SIMPLEX,
|
||||
0.8,
|
||||
(30, 30, 30),
|
||||
2,
|
||||
cv2.LINE_AA,
|
||||
)
|
||||
return canvas
|
||||
|
||||
|
||||
def frame_to_texture_data(frame: np.ndarray, target_w: int, target_h: int) -> list[float]:
|
||||
src_h, src_w = frame.shape[:2]
|
||||
if src_h <= 0 or src_w <= 0:
|
||||
canvas = np.zeros((target_h, target_w, 3), dtype=np.uint8)
|
||||
else:
|
||||
scale = min(target_w / float(src_w), target_h / float(src_h))
|
||||
draw_w = max(1, int(round(src_w * scale)))
|
||||
draw_h = max(1, int(round(src_h * scale)))
|
||||
resized = cv2.resize(frame, (draw_w, draw_h), interpolation=cv2.INTER_AREA if scale < 1.0 else cv2.INTER_LINEAR)
|
||||
canvas = np.full((target_h, target_w, 3), 18, dtype=np.uint8)
|
||||
x0 = (target_w - draw_w) // 2
|
||||
y0 = (target_h - draw_h) // 2
|
||||
canvas[y0:y0 + draw_h, x0:x0 + draw_w] = resized
|
||||
rgba = cv2.cvtColor(canvas, cv2.COLOR_BGR2RGBA)
|
||||
return (rgba.astype(np.float32).ravel() / 255.0).tolist()
|
||||
|
||||
|
||||
def restore_window_by_title(title: str) -> None:
|
||||
if not hasattr(ctypes, "windll"):
|
||||
return
|
||||
user32 = ctypes.windll.user32
|
||||
hwnd = user32.FindWindowW(None, title)
|
||||
if hwnd:
|
||||
user32.ShowWindow(hwnd, 9) # SW_RESTORE
|
||||
user32.SetForegroundWindow(hwnd)
|
||||
|
||||
|
||||
def server_ui_loop() -> None:
|
||||
try:
|
||||
cv2.namedWindow("wms immagine ricevuta", cv2.WINDOW_NORMAL)
|
||||
@@ -714,6 +776,70 @@ def server_ui_loop() -> None:
|
||||
cv2.destroyWindow("wms payload")
|
||||
|
||||
|
||||
def server_dpg_ui_loop() -> None:
|
||||
if dpg is None:
|
||||
log("UI DearPyGUI non disponibile, impossibile avviare il monitor WMS")
|
||||
with ui_lock:
|
||||
ui_state["stop"] = True
|
||||
return
|
||||
|
||||
image_texture = "wms_image_texture"
|
||||
payload_text = "wms_payload_text"
|
||||
status_text = "wms_status_text"
|
||||
image_w = 720
|
||||
image_h = 420
|
||||
|
||||
def create_texture(tag: str, data: list[float], width: int, height: int) -> None:
|
||||
dpg.add_dynamic_texture(width=width, height=height, default_value=data, tag=tag)
|
||||
|
||||
def set_texture(frame: np.ndarray) -> None:
|
||||
dpg.set_value(image_texture, frame_to_texture_data(frame, image_w, image_h))
|
||||
|
||||
dpg.create_context()
|
||||
with dpg.texture_registry(show=False):
|
||||
create_texture(
|
||||
image_texture,
|
||||
frame_to_texture_data(placeholder_frame(image_w, image_h, "In attesa immagine etichetta"), image_w, image_h),
|
||||
image_w,
|
||||
image_h,
|
||||
)
|
||||
|
||||
with dpg.window(label="FlyWMS WMS Server", width=1450, height=900):
|
||||
with dpg.group(horizontal=True):
|
||||
with dpg.child_window(width=760, height=840, border=True):
|
||||
dpg.add_text("Ultima immagine ricevuta")
|
||||
dpg.add_image(image_texture)
|
||||
with dpg.child_window(width=640, height=840, border=True):
|
||||
dpg.add_text("Stato")
|
||||
dpg.add_text("Server WMS in attesa payload", tag=status_text, wrap=600)
|
||||
dpg.add_separator()
|
||||
dpg.add_text("Payload")
|
||||
dpg.add_input_text(tag=payload_text, multiline=True, readonly=True, width=600, height=720, default_value="In attesa di payload WMS")
|
||||
|
||||
dpg.create_viewport(title="FlyWMS WMS Server", width=1450, height=900)
|
||||
dpg.setup_dearpygui()
|
||||
dpg.show_viewport()
|
||||
time.sleep(0.05)
|
||||
restore_window_by_title("FlyWMS WMS Server")
|
||||
|
||||
try:
|
||||
while dpg.is_dearpygui_running():
|
||||
with ui_lock:
|
||||
stop = bool(ui_state["stop"])
|
||||
image = None if ui_state["image"] is None else ui_state["image"].copy()
|
||||
lines = list(ui_state["payload_lines"])
|
||||
if stop:
|
||||
break
|
||||
if image is not None:
|
||||
set_texture(image)
|
||||
dpg.set_value(status_text, f"Richieste ricevute: {server_state['counter']}")
|
||||
dpg.set_value(payload_text, "\n".join(lines))
|
||||
dpg.render_dearpygui_frame()
|
||||
time.sleep(0.03)
|
||||
finally:
|
||||
dpg.destroy_context()
|
||||
|
||||
|
||||
def resize_preview(frame: np.ndarray, max_width: int) -> np.ndarray:
|
||||
h, w = frame.shape[:2]
|
||||
if max_width <= 0 or w <= max_width:
|
||||
@@ -745,6 +871,7 @@ def draw_payload_window(lines: list[str]) -> np.ndarray:
|
||||
|
||||
def main() -> int:
|
||||
args = parse_args()
|
||||
ui_backend = choose_ui_backend(args.ui_backend) if args.ui_enabled else "disabled"
|
||||
server_state["received_dir"] = args.received_dir
|
||||
server_state["fake_ack_mode"] = args.fake_ack_mode
|
||||
server_state["fake_processing_sec"] = args.fake_processing_sec
|
||||
@@ -770,9 +897,11 @@ def main() -> int:
|
||||
f"mode={args.fake_ack_mode} received_dir={args.received_dir}"
|
||||
)
|
||||
log(f"OpenCV CUDA: {'attivo' if OPENCV_CUDA_AVAILABLE else 'non disponibile'}")
|
||||
log(f"UI backend: {ui_backend}")
|
||||
ui_thread = None
|
||||
if args.ui_enabled:
|
||||
ui_thread = threading.Thread(target=server_ui_loop, name="wms-server-ui", daemon=True)
|
||||
ui_target = server_dpg_ui_loop if ui_backend == "dearpygui" else server_ui_loop
|
||||
ui_thread = threading.Thread(target=ui_target, name="wms-server-ui", daemon=True)
|
||||
ui_thread.start()
|
||||
try:
|
||||
uvicorn.run(app, host=args.host, port=args.port, log_level="info")
|
||||
|
||||
Reference in New Issue
Block a user