"""Local Dimming 测试逻辑(应用层)。 整合自原 drivers/local_dimming_test.py:窗口图片生成与测试主循环 直接落在本模块,UCD 通用操作下沉到 drivers.ucd_helpers。 """ import atexit import csv import datetime import os import shutil import sys import threading import time import tkinter as tk from tkinter import filedialog, messagebox import numpy as np from PIL import Image from drivers.ucd_helpers import get_current_resolution, send_image_pattern # -------------------------------------------------------------------------- # 模块级常量与窗口图片缓存 # -------------------------------------------------------------------------- DEFAULT_WINDOW_PERCENTAGES = [1, 2, 5, 10, 18, 25, 50, 75, 100] _TEMP_DIR = None _IMAGE_CACHE = {} # {(width, height, percentage): file_path} def _cleanup_temp_dir(): global _TEMP_DIR if _TEMP_DIR and os.path.exists(_TEMP_DIR): try: shutil.rmtree(_TEMP_DIR) except Exception: pass _TEMP_DIR = None _IMAGE_CACHE.clear() def _get_temp_dir(): global _TEMP_DIR if _TEMP_DIR is None: if getattr(sys, "frozen", False): base = os.path.dirname(sys.executable) else: base = os.getcwd() _TEMP_DIR = os.path.join(base, "temp_local_dimming") os.makedirs(_TEMP_DIR, exist_ok=True) atexit.register(_cleanup_temp_dir) return _TEMP_DIR def _make_window_image_array(width, height, percentage): """生成黑底+居中白窗的 numpy 图像,保持屏幕比例。""" image = np.zeros((height, width, 3), dtype=np.uint8) if percentage >= 100: ww, wh = width, height else: scale = (percentage / 100.0) ** 0.5 ww = int(width * scale) wh = int(height * scale) x1 = (width - ww) // 2 y1 = (height - wh) // 2 image[y1:y1 + wh, x1:x1 + ww] = 255 return image def _ensure_window_image(width, height, percentage): """生成或复用缓存的窗口 PNG 文件,返回路径。""" key = (width, height, percentage) cached = _IMAGE_CACHE.get(key) if cached and os.path.exists(cached): return cached arr = _make_window_image_array(width, height, percentage) fname = f"window_{width}x{height}_{percentage:03d}percent.png" path = os.path.join(_get_temp_dir(), fname) Image.fromarray(arr, mode="RGB").save(path, format="PNG") _IMAGE_CACHE[key] = path return path # -------------------------------------------------------------------------- # GUI 入口(绑定为 PQAutomationApp 方法) # -------------------------------------------------------------------------- def start_local_dimming_test(self): """开始 Local Dimming 测试。""" if not self.ca or not self.ucd.status: messagebox.showerror("错误", "请先连接 CA410 和 UCD323") return self.ld_start_btn.config(state=tk.DISABLED) self.ld_stop_btn.config(state=tk.NORMAL) self.ld_save_btn.config(state=tk.DISABLED) for item in self.ld_tree.get_children(): self.ld_tree.delete(item) wait_time = float(self.ld_wait_time_var.get()) stop_event = threading.Event() self.ld_stop_event = stop_event def worker(): log = self.log_gui.log log("=" * 60) log("开始 Local Dimming 测试") log("=" * 60) width, height = get_current_resolution(self.ucd) total = len(DEFAULT_WINDOW_PERCENTAGES) log(f" 分辨率: {width}x{height}") log(f" 测试窗口: {DEFAULT_WINDOW_PERCENTAGES}") log(f" 等待时间: {wait_time} 秒") results = [] for i, percentage in enumerate(DEFAULT_WINDOW_PERCENTAGES, 1): if stop_event.is_set(): log("⚠️ 测试已停止") break log(f"[{i}/{total}] 测试 {percentage}% 窗口...") try: image_path = _ensure_window_image(width, height, percentage) except Exception as e: log(f" ❌ 图像生成失败: {e}") continue if not send_image_pattern(self.ucd, image_path): log(f" ❌ {percentage}% 窗口发送失败,跳过") continue log(f" ⏳ 等待 {wait_time} 秒...") time.sleep(wait_time) try: x, y, lv, _X, _Y, _Z = self.ca.readAllDisplay() except Exception as e: log(f" ❌ 采集亮度异常: {e}") continue if lv is None: log(f" ❌ {percentage}% 窗口采集失败") continue log(f" ✓ 采集亮度: {lv:.2f} cd/m²") results.append((percentage, x, y, lv, _X, _Y, _Z)) log("=" * 60) log(f"✅ Local Dimming 测试完成 ({len(results)}/{total})") log("=" * 60) self.ld_test_results = results self._dispatch_ui(self.update_ld_results, results) self._dispatch_ui(self.ld_start_btn.config, state=tk.NORMAL) self._dispatch_ui(self.ld_stop_btn.config, state=tk.DISABLED) self._dispatch_ui(self.ld_save_btn.config, state=tk.NORMAL) threading.Thread(target=worker, daemon=True).start() def update_ld_results(self, results): """把批量测试结果填入 Treeview。""" for percentage, x, y, lv, _X, _Y, _Z in results: self.ld_tree.insert( "", tk.END, values=(f"{percentage}%", f"{lv:.2f}", f"{x:.4f}", f"{y:.4f}"), ) def stop_local_dimming_test(self): """请求停止当前 Local Dimming 测试。""" ev = getattr(self, "ld_stop_event", None) if ev: ev.set() def send_ld_window(self, percentage): """发送指定百分比的白色窗口(手动模式)。""" if not self.ucd.status: messagebox.showwarning("警告", "请先连接 UCD323 设备") return self.log_gui.log(f"🔆 发送 {percentage}% 窗口...") self.current_ld_percentage = percentage def send(): width, height = get_current_resolution(self.ucd) try: image_path = _ensure_window_image(width, height, percentage) except Exception as e: self._dispatch_ui(self.log_gui.log, f"❌ 图像生成失败: {e}") return ok = send_image_pattern(self.ucd, image_path) msg = ( f"✅ {percentage}% 窗口已发送" if ok else f"❌ {percentage}% 窗口发送失败" ) self._dispatch_ui(self.log_gui.log, msg) threading.Thread(target=send, daemon=True).start() def measure_ld_luminance(self): """测量当前显示的亮度并追加一行到 Treeview。""" if not self.ca: messagebox.showwarning("警告", "请先连接 CA410 色度计") return if self.current_ld_percentage is None: messagebox.showinfo("提示", "请先发送一个窗口图案") return self.log_gui.log("📏 正在采集亮度...") def measure(): try: x, y, lv, _X, _Y, _Z = self.ca.readAllDisplay() except Exception as e: self._dispatch_ui(self.log_gui.log, f"❌ 采集异常: {str(e)}") return if lv is None: self._dispatch_ui(self.log_gui.log, "❌ 采集失败") return timestamp = datetime.datetime.now().strftime("%H:%M:%S") self._dispatch_ui( self.ld_result_label.config, text=f"亮度: {lv:.2f} cd/m² | x: {x:.4f} | y: {y:.4f}", ) self._dispatch_ui( self.ld_tree.insert, "", tk.END, values=( f"{self.current_ld_percentage}%", f"{lv:.2f}", f"{x:.4f}", f"{y:.4f}", timestamp, ), ) self._dispatch_ui(self.log_gui.log, f"✅ 采集完成: {lv:.2f} cd/m²") threading.Thread(target=measure, daemon=True).start() def clear_ld_records(self): """清空 Treeview 中的测试记录。""" for item in self.ld_tree.get_children(): self.ld_tree.delete(item) self.ld_result_label.config(text="亮度: -- cd/m² | x: -- | y: --") self.current_ld_percentage = None self.log_gui.log("🗑️ 测试记录已清空") def save_local_dimming_results(self): """把 Treeview 中的全部记录导出为 CSV。""" if len(self.ld_tree.get_children()) == 0: messagebox.showinfo("提示", "没有可保存的数据") return default_name = ( f"LocalDimming_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" ) save_path = filedialog.asksaveasfilename( title="保存测试结果", initialfile=default_name, defaultextension=".csv", filetypes=[("CSV 文件", "*.csv"), ("所有文件", "*.*")], ) if not save_path: return try: with open(save_path, "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["窗口百分比", "亮度 (cd/m²)", "x", "y", "时间"]) for item in self.ld_tree.get_children(): writer.writerow(self.ld_tree.item(item, "values")) self.log_gui.log(f"✓ 测试结果已保存: {save_path}") messagebox.showinfo("成功", f"测试结果已保存到:\n{save_path}") except Exception as e: self.log_gui.log(f"❌ 保存失败: {str(e)}") messagebox.showerror("错误", f"保存失败: {str(e)}")