Add DearPyGUI observer/server UI and demo launcher

This commit is contained in:
administrator
2026-06-04 08:57:36 +02:00
parent e86c05a885
commit aa17d7ffd3
9 changed files with 869 additions and 8 deletions

View File

@@ -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")