diff --git a/app/pq/pq_config.py b/app/pq/pq_config.py index 423a750..c3f0cef 100644 --- a/app/pq/pq_config.py +++ b/app/pq/pq_config.py @@ -344,6 +344,8 @@ class PQConfig: temp_config.current_pattern = copy.deepcopy(self.default_pattern_gray) elif mode == "accuracy": temp_config.current_pattern = copy.deepcopy(self.default_pattern_accuracy) + elif mode == "custom": + temp_config.current_pattern = copy.deepcopy(self.default_pattern_temp) # 3. 替换为转换后的参数 temp_config.current_pattern["pattern_params"] = converted_params diff --git a/app/runner/test_runner.py b/app/runner/test_runner.py index 9e8c138..111c97f 100644 --- a/app/runner/test_runner.py +++ b/app/runner/test_runner.py @@ -13,7 +13,6 @@ import colour import numpy as np import algorithm.pq_algorithm as pq_algorithm -from app.data_range_converter import convert_pattern_params from app.pq.pq_result import PQResult def new_pq_results(self, test_type, test_name): @@ -310,205 +309,12 @@ def send_fix_pattern(self, mode): results = [] try: - # 1. 设置图案模式 - if mode == "rgb": - self.config.set_current_pattern("rgb") - elif mode == "gray": - self.config.set_current_pattern("gray") - elif mode == "accuracy": # 色准模式(SDR 和 HDR 通用 29色) - self.config.set_current_pattern("accuracy") - elif mode == "custom": - self.config.set_current_pattern("custom") - else: - self.log_gui.log(f"未知的图案模式: {mode}", level="error") - return None - - # 2. 获取当前测试类型 - test_type = self.config.current_test_type - - # 3. 根据测试类型设置信号格式和图案 - if test_type == "screen_module": - # 屏模组测试:使用 Timing - self.log_gui.log("=" * 50, level="separator") - self.log_gui.log("设置屏模组信号格式:", level="info") - self.log_gui.log("=" * 50, level="separator") - - timing_str = self.config.current_test_types[test_type]["timing"] - self.log_gui.log(f" Timing: {timing_str}", level="info") - - # 屏模组测试:直接使用原始配置 - self.ucd.set_ucd_params(self.config) - - elif test_type == "sdr_movie": - # SDR 测试:设置色彩空间、Gamma 等 - self.log_gui.log("=" * 50, level="separator") - self.log_gui.log("设置 SDR 信号格式:", level="info") - self.log_gui.log("=" * 50, level="separator") - - color_space = self.sdr_color_space_var.get() - gamma = self.sdr_gamma_type_var.get() - data_range = self.sdr_data_range_var.get() - bit_depth = self.sdr_bit_depth_var.get() - - self.log_gui.log(f" 色彩空间: {color_space}", level="info") - self.log_gui.log(f" Gamma: {gamma}", level="info") - self.log_gui.log(f" 数据范围: {data_range}", level="info") - self.log_gui.log(f" 编码位深: {bit_depth}", level="info") - - success = self.ucd.set_sdr_format( - color_space=color_space, - gamma=gamma, - data_range=data_range, - bit_depth=bit_depth, - ) - - if success: - self.log_gui.log("SDR 信号格式设置成功", level="success") - else: - self.log_gui.log("SDR 信号格式设置失败", level="error") - - # 设置图案参数 - if mode == "accuracy": - self.log_gui.log(f"设置 SDR 29色色准测试图案...", level="info") - else: - self.log_gui.log(f"设置 SDR 测试图案({mode} 模式)...", level="info") - - # ========== ✅✅修改:使用临时配置对象 ========== - import copy - - # 从原始配置获取参数(每次都是干净的) - if mode == "rgb": - original_params = copy.deepcopy( - self.config.default_pattern_rgb["pattern_params"] - ) - elif mode == "gray": - original_params = copy.deepcopy( - self.config.default_pattern_gray["pattern_params"] - ) - elif mode == "accuracy": - original_params = copy.deepcopy( - self.config.default_pattern_accuracy["pattern_params"] - ) - elif mode == "custom": - original_params = copy.deepcopy( - self.config.default_pattern_temp["pattern_params"] - ) - - self.log_gui.log(f" 使用原始 RGB 参数(前 3 个):", level="info") - for i in range(min(3, len(original_params))): - self.log_gui.log(f" [{i+1}] {original_params[i]}", level="info") - - # 根据 data_range 转换 - converted_params = convert_pattern_params( - pattern_params=original_params, data_range=data_range, verbose=False - ) - - if data_range == "Limited": - self.log_gui.log(" 转换为 Limited Range (16-235):", level="info") - for i in range(min(3, len(converted_params))): - self.log_gui.log( - f" {original_params[i]} → {converted_params[i]}" - , level="info") - else: - self.log_gui.log("Full Range,RGB 保持不变", level="success") - - # 创建临时配置对象(不修改 self.config) - temp_config = self.config.get_temp_config_with_converted_params( - mode=mode, converted_params=converted_params - ) - - # 使用临时配置设置参数 - self.ucd.set_ucd_params(temp_config) - - self.log_gui.log(f"图案参数已设置,共 {len(converted_params)} 个图案", level="success") - # ========== 修改结束 ========== - - elif test_type == "hdr_movie": - # HDR 测试:设置色彩空间、Metadata 等 - self.log_gui.log("=" * 50, level="separator") - self.log_gui.log("设置 HDR 信号格式:", level="info") - self.log_gui.log("=" * 50, level="separator") - - color_space = self.hdr_color_space_var.get() - data_range = self.hdr_data_range_var.get() - bit_depth = self.hdr_bit_depth_var.get() - max_cll = self.hdr_maxcll_var.get() - max_fall = self.hdr_maxfall_var.get() - - self.log_gui.log(f" 色彩空间: {color_space}", level="info") - self.log_gui.log(f" 数据范围: {data_range}", level="info") - self.log_gui.log(f" 编码位深: {bit_depth}", level="info") - self.log_gui.log(f" MaxCLL: {max_cll}", level="info") - self.log_gui.log(f" MaxFALL: {max_fall}", level="info") - - success = self.ucd.set_hdr_format( - color_space=color_space, - data_range=data_range, - bit_depth=bit_depth, - max_cll=max_cll, - max_fall=max_fall, - ) - - if success: - self.log_gui.log("HDR 信号格式设置成功", level="success") - else: - self.log_gui.log("HDR 信号格式设置失败", level="error") - - # 设置图案参数 - if mode == "accuracy": - self.log_gui.log(f"设置 HDR 29色色准测试图案...", level="info") - else: - self.log_gui.log(f"设置 HDR 测试图案({mode} 模式)...", level="info") - - # ========== ✅✅修改:使用临时配置对象 ========== - import copy - - # 从原始配置获取参数 - if mode == "rgb": - original_params = copy.deepcopy( - self.config.default_pattern_rgb["pattern_params"] - ) - elif mode == "gray": - original_params = copy.deepcopy( - self.config.default_pattern_gray["pattern_params"] - ) - elif mode == "accuracy": - original_params = copy.deepcopy( - self.config.default_pattern_accuracy["pattern_params"] - ) - - self.log_gui.log(f" 使用原始 RGB 参数(前 3 个):", level="info") - for i in range(min(3, len(original_params))): - self.log_gui.log(f" [{i+1}] {original_params[i]}", level="info") - - # 根据 data_range 转换 - converted_params = convert_pattern_params( - pattern_params=original_params, data_range=data_range, verbose=False - ) - - if data_range == "Limited": - self.log_gui.log(" 转换为 Limited Range (16-235):", level="info") - for i in range(min(3, len(converted_params))): - self.log_gui.log( - f" {original_params[i]} → {converted_params[i]}" - , level="info") - else: - self.log_gui.log("Full Range,RGB 保持不变", level="success") - - # 创建临时配置对象 - temp_config = self.config.get_temp_config_with_converted_params( - mode=mode, converted_params=converted_params - ) - - self.ucd.set_ucd_params(temp_config) - - self.log_gui.log(f"图案参数已设置,共 {len(converted_params)} 个图案", level="success") - # ========== 修改结束 ========== + session = self.pattern_service.prepare_session(mode, log_details=True) self.log_gui.log("=" * 50, level="separator") - # 4. 循环发送图案并采集数据(使用原始配置的数量) - total_patterns = len(self.config.current_pattern["pattern_params"]) + # 4. 循环发送图案并采集数据 + total_patterns = session.total_patterns self.log_gui.log(f"开始采集数据,共 {total_patterns} 个图案", level="info") settle_time = max(0.2, float(getattr(self, "pattern_settle_time", 1.0))) progress_step = max( @@ -518,14 +324,7 @@ def send_fix_pattern(self, mode): f"采集等待时间: {settle_time:.2f}s(可通过 pattern_settle_time 调整)" , level="info") - # 获取颜色名称列表(用于日志显示) - color_names = None - if mode == "accuracy": - color_names = self.config.get_accuracy_color_names() - - custom_pattern_names = [] - if mode == "custom" and hasattr(self.config, "get_temp_pattern_names"): - custom_pattern_names = self.config.get_temp_pattern_names() + display_names = session.display_names for i in range(total_patterns): if not self.testing: @@ -538,17 +337,15 @@ def send_fix_pattern(self, mode): or ((i + 1) % progress_step == 0) ) - # 设置下一个图案(显示颜色名称) if should_log_detail: - if color_names and i < len(color_names): + if display_names and i < len(display_names): self.log_gui.log( - f"发送第 {i+1}/{total_patterns} 个图案: {color_names[i]}..." + f"发送第 {i+1}/{total_patterns} 个图案: {display_names[i]}..." , level="info") else: self.log_gui.log(f"发送第 {i+1}/{total_patterns} 个图案...", level="info") - self.ucd.set_next_pattern() - self.ucd.run() + self.pattern_service.send_session_pattern(session, i) time.sleep(settle_time) # 测量数据 @@ -582,8 +379,8 @@ def send_fix_pattern(self, mode): ) row_data = { "pattern_name": ( - custom_pattern_names[i] - if i < len(custom_pattern_names) + display_names[i] + if i < len(display_names) else f"P {i + 1}" ), "X": X, diff --git a/app/services/__init__.py b/app/services/__init__.py index e69de29..a27abe7 100644 --- a/app/services/__init__.py +++ b/app/services/__init__.py @@ -0,0 +1 @@ +from app.services.pattern_service import PatternService, PatternSession diff --git a/app/services/pattern_service.py b/app/services/pattern_service.py new file mode 100644 index 0000000..5029a94 --- /dev/null +++ b/app/services/pattern_service.py @@ -0,0 +1,199 @@ +from __future__ import annotations + +import copy +from dataclasses import dataclass + +from app.data_range_converter import convert_pattern_params +from drivers.ucd_helpers import send_solid_rgb_pattern + + +@dataclass +class PatternSession: + mode: str + test_type: str + active_config: object + pattern_params: list[list[int]] + total_patterns: int + display_names: list[str] + + +class PatternService: + def __init__(self, app): + self.app = app + + def prepare_session(self, mode, *, test_type=None, log_details=False): + test_type = test_type or self.app.config.current_test_type + if not self.app.config.set_current_pattern(mode): + raise ValueError(f"未知的图案模式: {mode}") + + active_config = self.app.config + source_params = self._get_source_pattern_params(mode) + + if test_type == "screen_module": + if log_details: + self._log("=" * 50, "separator") + self._log("设置屏模组信号格式:", "info") + self._log("=" * 50, "separator") + self._log( + f" Timing: {self.app.config.current_test_types[test_type]['timing']}", + "info", + ) + self.app.ucd.set_ucd_params(active_config) + elif test_type == "sdr_movie": + active_config = self._prepare_video_session( + mode=mode, + test_type=test_type, + source_params=source_params, + data_range=self.app.sdr_data_range_var.get(), + log_title="设置 SDR 信号格式:", + setup_message=f"设置 SDR 测试图案({mode} 模式)..." + if mode != "accuracy" + else "设置 SDR 29色色准测试图案...", + setup_format=lambda: self.app.ucd.set_sdr_format( + color_space=self.app.sdr_color_space_var.get(), + gamma=self.app.sdr_gamma_type_var.get(), + data_range=self.app.sdr_data_range_var.get(), + bit_depth=self.app.sdr_bit_depth_var.get(), + ), + log_items=[ + ("色彩空间", self.app.sdr_color_space_var.get()), + ("Gamma", self.app.sdr_gamma_type_var.get()), + ("数据范围", self.app.sdr_data_range_var.get()), + ("编码位深", self.app.sdr_bit_depth_var.get()), + ], + log_details=log_details, + ) + elif test_type == "hdr_movie": + active_config = self._prepare_video_session( + mode=mode, + test_type=test_type, + source_params=source_params, + data_range=self.app.hdr_data_range_var.get(), + log_title="设置 HDR 信号格式:", + setup_message=f"设置 HDR 测试图案({mode} 模式)..." + if mode != "accuracy" + else "设置 HDR 29色色准测试图案...", + setup_format=lambda: self.app.ucd.set_hdr_format( + color_space=self.app.hdr_color_space_var.get(), + data_range=self.app.hdr_data_range_var.get(), + bit_depth=self.app.hdr_bit_depth_var.get(), + max_cll=self.app.hdr_maxcll_var.get(), + max_fall=self.app.hdr_maxfall_var.get(), + ), + log_items=[ + ("色彩空间", self.app.hdr_color_space_var.get()), + ("数据范围", self.app.hdr_data_range_var.get()), + ("编码位深", self.app.hdr_bit_depth_var.get()), + ("MaxCLL", self.app.hdr_maxcll_var.get()), + ("MaxFALL", self.app.hdr_maxfall_var.get()), + ], + log_details=log_details, + ) + else: + raise ValueError(f"不支持的测试类型: {test_type}") + + pattern_params = copy.deepcopy(active_config.current_pattern["pattern_params"]) + return PatternSession( + mode=mode, + test_type=test_type, + active_config=active_config, + pattern_params=pattern_params, + total_patterns=len(pattern_params), + display_names=self._get_display_names(mode, len(pattern_params)), + ) + + def send_session_pattern(self, session, index): + if index < 0 or index >= session.total_patterns: + raise IndexError(f"pattern 索引越界: {index}") + + pattern_param = session.pattern_params[index] + if not self.app.ucd.send_current_pattern_params(pattern_param): + raise RuntimeError(f"发送 pattern 失败: {index}") + return pattern_param + + def send_rgb(self, rgb, *, session=None, test_type=None): + active_session = session or self.prepare_session( + "rgb", + test_type=test_type, + log_details=False, + ) + converted_rgb = self._convert_rgb_for_test_type(rgb, active_session.test_type) + send_solid_rgb_pattern(self.app.ucd, converted_rgb, raise_on_error=True) + return True + + def _prepare_video_session( + self, + *, + mode, + test_type, + source_params, + data_range, + log_title, + setup_message, + setup_format, + log_items, + log_details, + ): + if log_details: + self._log("=" * 50, "separator") + self._log(log_title, "info") + self._log("=" * 50, "separator") + for label, value in log_items: + self._log(f" {label}: {value}", "info") + + success = setup_format() + if log_details: + self._log( + f"{test_type.split('_')[0].upper()} 信号格式设置{'成功' if success else '失败'}", + "success" if success else "error", + ) + self._log(setup_message, "info") + + converted_params = convert_pattern_params( + pattern_params=source_params, + data_range=data_range, + verbose=False, + ) + active_config = self.app.config.get_temp_config_with_converted_params( + mode=mode, + converted_params=converted_params, + ) + self.app.ucd.set_ucd_params(active_config) + + if log_details: + self._log(f"图案参数已设置,共 {len(converted_params)} 个图案", "success") + + return active_config + + def _get_source_pattern_params(self, mode): + config = self.app.config + if mode == "rgb": + return copy.deepcopy(config.default_pattern_rgb["pattern_params"]) + if mode == "gray": + return copy.deepcopy(config.default_pattern_gray["pattern_params"]) + if mode == "accuracy": + return copy.deepcopy(config.default_pattern_accuracy["pattern_params"]) + if mode == "custom": + return copy.deepcopy(config.default_pattern_temp["pattern_params"]) + raise ValueError(f"未知的图案模式: {mode}") + + def _get_display_names(self, mode, total_patterns): + if mode == "accuracy": + return self.app.config.get_accuracy_color_names() + if mode == "custom" and hasattr(self.app.config, "get_temp_pattern_names"): + return self.app.config.get_temp_pattern_names() + return [f"P {index + 1}" for index in range(total_patterns)] + + def _convert_rgb_for_test_type(self, rgb, test_type): + if test_type == "sdr_movie": + data_range = self.app.sdr_data_range_var.get() + elif test_type == "hdr_movie": + data_range = self.app.hdr_data_range_var.get() + else: + data_range = "Full" + + return convert_pattern_params([list(rgb)], data_range=data_range, verbose=False)[0] + + def _log(self, message, level): + if hasattr(self.app, "log_gui"): + self.app.log_gui.log(message, level=level) \ No newline at end of file diff --git a/app/views/panels/main_layout.py b/app/views/panels/main_layout.py index cd862a4..687f819 100644 --- a/app/views/panels/main_layout.py +++ b/app/views/panels/main_layout.py @@ -435,14 +435,14 @@ def create_test_type_frame(self): ) self.ai_image_btn.pack(fill=tk.X, padx=0, pady=1) - self.single_step_btn = ttk.Button( - self.sidebar_frame, - text="单步调试", - style="Sidebar.TButton", - command=self.toggle_single_step_panel, - takefocus=False, - ) - self.single_step_btn.pack(fill=tk.X, padx=0, pady=1) + # self.single_step_btn = ttk.Button( + # self.sidebar_frame, + # text="单步调试", + # style="Sidebar.TButton", + # command=self.toggle_single_step_panel, + # takefocus=False, + # ) + # self.single_step_btn.pack(fill=tk.X, padx=0, pady=1) self.pantone_baseline_btn = ttk.Button( self.sidebar_frame, diff --git a/app/views/panels/pantone_baseline_panel.py b/app/views/panels/pantone_baseline_panel.py index fb70567..bcb831e 100644 --- a/app/views/panels/pantone_baseline_panel.py +++ b/app/views/panels/pantone_baseline_panel.py @@ -2,23 +2,16 @@ from __future__ import annotations -import csv import datetime import os -import tempfile import threading import tkinter as tk from tkinter import filedialog, messagebox import ttkbootstrap as ttk -from PIL import Image - -from drivers.ucd_helpers import get_current_resolution, send_image_pattern -_PATTERN_FILE = "pantone_patterns_2670.csv" _TEMPLATE_FILE = "pantone\xa02670\xa0colors.xlsx" -_TARGET_RESULT_COUNT = 2670 def create_pantone_baseline_panel(self): @@ -34,6 +27,7 @@ def create_pantone_baseline_panel(self): self._pantone_pause_requested = False self._pantone_stop_requested = False self._pantone_next_index = 0 + self._pantone_target_count = 0 root = ttk.Frame(frame, padding=10) root.pack(fill=tk.BOTH, expand=True) @@ -47,12 +41,12 @@ def create_pantone_baseline_panel(self): ).pack(side=tk.LEFT) self.pantone_status_var = tk.StringVar(value="未开始") - self.pantone_progress_var = tk.StringVar(value="0 / 2670") + self.pantone_progress_var = tk.StringVar(value="0 / 0") self.pantone_settle_var = tk.StringVar(value="0.35") config_row = ttk.LabelFrame(root, text="任务配置", padding=8) config_row.pack(fill=tk.X) - ttk.Label(config_row, text=f"Pattern来源: settings/{_PATTERN_FILE}").pack( + ttk.Label(config_row, text=f"Pattern来源: settings/{_TEMPLATE_FILE}").pack( side=tk.LEFT ) ttk.Label(config_row, text="稳定等待(s):").pack(side=tk.LEFT, padx=(14, 4)) @@ -160,38 +154,38 @@ def toggle_pantone_baseline_panel(self): def _load_patterns(self): - path = os.path.join("settings", _PATTERN_FILE) + path = os.path.join("settings", _TEMPLATE_FILE) if not os.path.isfile(path): - raise FileNotFoundError(f"未找到 pattern 文件: {path}") + raise FileNotFoundError(f"未找到模板文件: {path}") + + from openpyxl import load_workbook patterns = [] - with open(path, "r", encoding="utf-8-sig", newline="") as fp: - reader = csv.DictReader(fp) - for row in reader: + wb = load_workbook(path, read_only=True, data_only=True) + ws = wb.active + try: + for row in ws.iter_rows(min_row=2, values_only=True): + if not row: + continue try: - r = int(row.get("R", "").strip()) - g = int(row.get("G", "").strip()) - b = int(row.get("B", "").strip()) + r = int(row[0]) if row[0] is not None else None + g = int(row[1]) if len(row) > 1 and row[1] is not None else None + b = int(row[2]) if len(row) > 2 and row[2] is not None else None except Exception: continue + if r is None or g is None or b is None: + continue if min(r, g, b) < 0 or max(r, g, b) > 255: continue patterns.append((r, g, b)) + finally: + wb.close() if not patterns: - raise RuntimeError("pattern 文件为空或格式不正确,需包含列 R,G,B") + raise RuntimeError("模板中未找到有效 RGB 列表(需包含 R/G/B 三列)") return patterns -def _build_temp_patch(self, rgb): - width, height = get_current_resolution(self.ucd) - temp_dir = os.path.join(tempfile.gettempdir(), "pq_pantone_baseline") - os.makedirs(temp_dir, exist_ok=True) - file_path = os.path.join(temp_dir, "pantone_current_patch.png") - Image.new("RGB", (width, height), rgb).save(file_path, format="PNG") - return file_path - - def _start_pantone_baseline(self): if self._pantone_running: messagebox.showinfo("提示", "Pantone 任务正在执行") @@ -213,6 +207,7 @@ def _start_pantone_baseline(self): try: self.pantone_patterns = _load_patterns(self) + self._pantone_target_count = len(self.pantone_patterns) except Exception as exc: messagebox.showerror("读取失败", str(exc)) return @@ -228,7 +223,7 @@ def _start_pantone_baseline(self): self._pantone_control_event = threading.Event() self._pantone_next_index = 0 self.pantone_status_var.set("执行中") - self.pantone_progress_var.set(f"0 / {_TARGET_RESULT_COUNT}") + self.pantone_progress_var.set(f"0 / {self._pantone_target_count}") self.pantone_results = [] for item in self.pantone_tree.get_children(): self.pantone_tree.delete(item) @@ -244,9 +239,6 @@ def _resume_pantone_baseline(self): if not self._pantone_paused: messagebox.showinfo("提示", "当前没有可继续的暂停任务") return - if self._pantone_next_index >= _TARGET_RESULT_COUNT: - messagebox.showinfo("提示", "任务已完成,无需继续") - return if not getattr(self, "ucd", None) or not self.ucd.status: messagebox.showwarning("警告", "请先连接 UCD323") return @@ -264,10 +256,15 @@ def _resume_pantone_baseline(self): try: self.pantone_patterns = _load_patterns(self) + self._pantone_target_count = len(self.pantone_patterns) except Exception as exc: messagebox.showerror("读取失败", str(exc)) return + if self._pantone_next_index >= self._pantone_target_count: + messagebox.showinfo("提示", "任务已完成,无需继续") + return + self._pantone_running = True self._pantone_paused = False self._pantone_pause_requested = False @@ -280,13 +277,14 @@ def _resume_pantone_baseline(self): def _launch_worker(self, start_index, settle): - total = _TARGET_RESULT_COUNT + total = self._pantone_target_count or len(self.pantone_patterns) def worker(): end_state = "completed" try: src = self.pantone_patterns src_count = len(src) + rgb_session = self.pattern_service.prepare_session("rgb", log_details=False) self._dispatch_ui( self.log_gui.log, f"Pantone 认证摸底启动: source={src_count}, target={total}, start={start_index + 1}", @@ -301,9 +299,10 @@ def _launch_worker(self, start_index, settle): break r, g, b = src[i % src_count] - image_path = _build_temp_patch(self, (r, g, b)) - if not send_image_pattern(self.ucd, image_path): - raise RuntimeError(f"第 {i + 1} 组发送失败") + try: + self.pattern_service.send_rgb((r, g, b), session=rgb_session) + except Exception as exc: + raise RuntimeError(f"第 {i + 1} 组发送失败: {exc}") from exc if settle > 0 and self._pantone_control_event is not None: self._pantone_control_event.clear() @@ -360,6 +359,19 @@ def _launch_worker(self, start_index, settle): f"Pantone 任务完成,共 {len(self.pantone_results)} 条数据", "success", ) + try: + auto_path = _auto_save_template(self) + self._dispatch_ui( + self.log_gui.log, + f"Pantone 模板已自动保存: {auto_path}", + "success", + ) + except Exception as exc: + self._dispatch_ui( + self.log_gui.log, + f"Pantone 自动保存模板失败: {exc}", + "error", + ) except Exception as exc: self._pantone_paused = False self._dispatch_ui(self.pantone_status_var.set, "执行失败") @@ -430,7 +442,8 @@ def _clear_results(self): self._pantone_next_index = 0 for item in self.pantone_tree.get_children(): self.pantone_tree.delete(item) - self.pantone_progress_var.set(f"0 / {_TARGET_RESULT_COUNT}") + self._pantone_target_count = 0 + self.pantone_progress_var.set("0 / 0") self.pantone_status_var.set("结果已清空") _set_button_states(self) @@ -447,7 +460,7 @@ def _set_button_states(self): self.pantone_pause_btn.configure(state=tk.DISABLED) self.pantone_end_btn.configure(state=tk.NORMAL if (self._pantone_paused or self.pantone_results) else tk.DISABLED) - can_resume = self._pantone_paused and self._pantone_next_index < _TARGET_RESULT_COUNT + can_resume = self._pantone_paused and self._pantone_next_index < self._pantone_target_count self.pantone_resume_btn.configure(state=tk.NORMAL if can_resume else tk.DISABLED) @@ -456,7 +469,7 @@ def _save_as_template(self): messagebox.showinfo("提示", "暂无可导出的结果") return - default_name = "pantone 2670 colors.xlsx" + default_name = _TEMPLATE_FILE.replace("\xa0", " ") path = filedialog.asksaveasfilename( title="另存为 Pantone 模板", defaultextension=".xlsx", @@ -466,41 +479,69 @@ def _save_as_template(self): if not path: return - # 优先复制 settings 模板,再覆盖数据区;没有模板时自动创建同结构表。 - template_path = os.path.join("settings", _TEMPLATE_FILE) try: - from openpyxl import load_workbook, Workbook - - if os.path.isfile(template_path): - wb = load_workbook(template_path) - ws = wb.active - else: - wb = Workbook() - ws = wb.active - ws.title = "Sheet1" - ws.cell(row=1, column=1, value="R") - ws.cell(row=1, column=2, value="G") - ws.cell(row=1, column=3, value="B") - ws.cell(row=1, column=4, value="L") - ws.cell(row=1, column=5, value="x") - ws.cell(row=1, column=6, value="y") - - # 清空旧数据 - max_row = max(ws.max_row, 2) - for row in range(2, max_row + 1): - for col in range(1, 7): - ws.cell(row=row, column=col, value=None) - - for idx, item in enumerate(self.pantone_results, start=2): - ws.cell(row=idx, column=1, value=int(item["r"])) - ws.cell(row=idx, column=2, value=int(item["g"])) - ws.cell(row=idx, column=3, value=int(item["b"])) - ws.cell(row=idx, column=4, value=float(item["l"])) - ws.cell(row=idx, column=5, value=float(item["x"])) - ws.cell(row=idx, column=6, value=float(item["y"])) - - wb.save(path) + _write_template_xlsx(self, path) self.log_gui.log(f"Pantone 模板已保存: {path}", level="success") self.pantone_status_var.set(f"已保存: {os.path.basename(path)}") except Exception as exc: messagebox.showerror("保存失败", f"写入 xlsx 失败: {exc}") + + +def _resolve_results_dir(self): + if getattr(self, "config_file", None): + root_dir = os.path.dirname(os.path.dirname(self.config_file)) + else: + root_dir = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + ) + results_dir = os.path.join(root_dir, "results") + os.makedirs(results_dir, exist_ok=True) + return results_dir + + +def _auto_save_template(self): + results_dir = _resolve_results_dir(self) + target_count = len(self.pantone_results) + filename = ( + f"pantone_{target_count}_baseline_" + f"{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.xlsx" + ) + path = os.path.join(results_dir, filename) + _write_template_xlsx(self, path) + return path + + +def _write_template_xlsx(self, path): + # 优先复制 settings 模板,再覆盖数据区;没有模板时自动创建同结构表。 + template_path = os.path.join("settings", _TEMPLATE_FILE) + from openpyxl import load_workbook, Workbook + + if os.path.isfile(template_path): + wb = load_workbook(template_path) + ws = wb.active + else: + wb = Workbook() + ws = wb.active + ws.title = "Sheet1" + ws.cell(row=1, column=1, value="R") + ws.cell(row=1, column=2, value="G") + ws.cell(row=1, column=3, value="B") + ws.cell(row=1, column=4, value="L") + ws.cell(row=1, column=5, value="x") + ws.cell(row=1, column=6, value="y") + + # 清空旧数据 + max_row = max(ws.max_row, 2) + for row in range(2, max_row + 1): + for col in range(1, 7): + ws.cell(row=row, column=col, value=None) + + for idx, item in enumerate(self.pantone_results, start=2): + ws.cell(row=idx, column=1, value=int(item["r"])) + ws.cell(row=idx, column=2, value=int(item["g"])) + ws.cell(row=idx, column=3, value=int(item["b"])) + ws.cell(row=idx, column=4, value=float(item["l"])) + ws.cell(row=idx, column=5, value=float(item["x"])) + ws.cell(row=idx, column=6, value=float(item["y"])) + + wb.save(path) diff --git a/app/views/pq_debug_panel.py b/app/views/pq_debug_panel.py index 91181de..50977bc 100644 --- a/app/views/pq_debug_panel.py +++ b/app/views/pq_debug_panel.py @@ -768,28 +768,25 @@ class PQDebugPanel: # 禁用按钮 self._disable_test_button(test_type, test_item) - # 根据测试类型设置信号格式 - self._setup_signal_format(test_type) - # 获取图案索引并发送 if test_item in ["gamma", "eotf"]: pattern_index = self.get_gray_index(selected) - self.app.config.set_current_pattern("gray") + pattern_mode = "gray" elif test_item == "accuracy": pattern_index = self.get_color_index(selected) - self.app.config.set_current_pattern("accuracy") + pattern_mode = "accuracy" elif test_item == "rgb": pattern_index = self.get_rgb_index(selected) - self.app.config.set_current_pattern("rgb") + pattern_mode = "rgb" + else: + raise ValueError(f"不支持的测试项目: {test_item}") - # 设置图案 - self.app.ucd.set_ucd_params(self.app.config) - - # 跳转到目标图案 - for i in range(pattern_index + 1): - self.app.ucd.set_next_pattern() - - self.app.ucd.run() + session = self.app.pattern_service.prepare_session( + pattern_mode, + test_type=test_type, + log_details=False, + ) + self.app.pattern_service.send_session_pattern(session, pattern_index) time.sleep(1.5) # 测量数据 @@ -815,28 +812,6 @@ class PQDebugPanel: self.app.log_gui.log(traceback.format_exc(), level="error") self._enable_test_button(test_type, test_item) - def _setup_signal_format(self, test_type): - """设置信号格式""" - if test_type == "screen_module": - self.app.ucd.set_ucd_params(self.app.config) - - elif test_type == "sdr_movie": - self.app.ucd.set_sdr_format( - color_space=self.app.sdr_color_space_var.get(), - gamma=self.app.sdr_gamma_type_var.get(), - data_range=self.app.sdr_data_range_var.get(), - bit_depth=self.app.sdr_bit_depth_var.get(), - ) - - elif test_type == "hdr_movie": - self.app.ucd.set_hdr_format( - color_space=self.app.hdr_color_space_var.get(), - data_range=self.app.hdr_data_range_var.get(), - bit_depth=self.app.hdr_bit_depth_var.get(), - max_cll=self.app.hdr_maxcll_var.get(), - max_fall=self.app.hdr_maxfall_var.get(), - ) - def _compare_and_display(self, test_type, test_item, selected, new_data): """对比数据并显示""" # 获取原始数据 diff --git a/app_version.py b/app_version.py index 6db5dc5..2551a50 100644 --- a/app_version.py +++ b/app_version.py @@ -1,5 +1,5 @@ APP_NAME = "PQ 自动化测试工具" -APP_VERSION = "1.1.0" +APP_VERSION = "0.1.0" def get_app_title(): diff --git a/docs/pantone_patterns_2670.csv b/docs/pantone_patterns_2670.csv deleted file mode 100644 index 1a3343e..0000000 --- a/docs/pantone_patterns_2670.csv +++ /dev/null @@ -1,2671 +0,0 @@ -R,G,B -255,255,255 -238,238,238 -221,221,221 -204,204,204 -187,187,187 -170,170,170 -153,153,153 -136,136,136 -119,119,119 -102,102,102 -85,85,85 -68,68,68 -51,51,51 -34,34,34 -17,17,17 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -255,215,0 -249,56,39 -215,36,159 -79,0,146 -0,35,157 -246,221,0 -255,86,0 -176,65,177 -61,3,133 -201,47,191 -231,208,131 -232,200,113 -225,183,76 -221,173,51 -217,165,27 -207,150,22 -188,123,40 -188,158,122 -175,140,98 -164,127,84 -157,119,72 -135,95,50 -123,81,39 -103,80,45 -240,178,133 -244,157,97 -251,145,74 -245,129,50 -239,107,13 -226,87,0 -215,90,27 -245,192,181 -234,177,158 -230,150,132 -220,130,107 -213,117,89 -195,98,63 -186,91,63 -209,175,173 -199,158,153 -186,143,133 -177,130,120 -169,118,105 -160,106,90 -148,94,75 -190,142,152 -172,118,126 -162,102,110 -147,86,92 -134,69,73 -123,57,60 -97,46,46 -243,144,158 -240,133,146 -237,123,134 -229,108,115 -208,77,70 -210,79,78 -187,69,46 -251,168,208 -251,131,173 -231,110,147 -246,92,135 -221,86,129 -211,67,104 -202,58,87 -210,94,161 -201,76,145 -196,68,138 -190,60,128 -180,49,116 -178,46,109 -164,43,95 -208,174,198 -200,155,178 -192,142,167 -180,128,152 -171,117,143 -156,99,125 -152,98,122 -147,120,152 -116,82,118 -103,69,104 -97,62,95 -88,55,86 -70,42,66 -57,39,52 -192,148,206 -179,126,192 -163,105,177 -158,96,172 -144,80,155 -131,62,141 -119,53,128 -200,206,234 -174,181,216 -158,167,205 -139,150,194 -124,137,193 -107,116,172 -84,96,154 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -140,143,199 -132,136,194 -119,121,183 -111,114,180 -95,95,165 -79,82,155 -66,69,142 -144,165,219 -121,146,203 -102,128,195 -92,119,182 -64,83,151 -33,59,139 -40,56,131 -181,191,213 -165,179,206 -143,164,192 -120,144,176 -113,136,168 -98,122,157 -59,92,127 -108,135,151 -102,128,144 -89,116,130 -75,103,117 -70,97,111 -50,70,77 -46,60,65 -58,165,213 -0,156,207 -0,139,196 -0,133,191 -0,124,183 -0,116,170 -0,93,156 -99,179,202 -54,162,188 -0,147,173 -0,133,157 -0,114,139 -0,122,153 -0,101,127 -130,202,211 -113,185,191 -75,166,170 -54,157,160 -4,145,147 -0,133,135 -0,125,126 -132,206,195 -99,192,173 -39,176,147 -0,164,129 -0,150,110 -0,138,96 -0,129,83 -212,224,234 -188,213,218 -158,198,198 -129,184,182 -99,161,147 -69,139,119 -38,120,89 -114,166,148 -90,150,128 -72,137,111 -51,127,96 -23,114,84 -13,93,63 -23,76,56 -59,172,121 -0,162,108 -0,159,100 -0,147,83 -0,140,72 -0,117,58 -0,119,66 -116,187,125 -103,177,113 -91,167,99 -80,150,89 -46,136,64 -43,126,61 -47,114,62 -154,192,165 -135,177,143 -113,163,123 -91,151,104 -73,135,89 -64,129,78 -54,117,66 -121,135,114 -111,128,103 -99,113,88 -87,102,77 -79,92,67 -67,82,59 -45,61,50 -227,234,186 -226,233,166 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -210,225,119 -198,216,67 -181,201,0 -151,179,0 -148,176,0 -215,217,210 -201,204,192 -189,190,174 -181,181,160 -161,162,136 -148,151,120 -144,144,111 -167,163,146 -153,145,121 -143,134,110 -137,128,103 -120,111,85 -109,100,72 -96,88,61 -141,148,155 -126,131,137 -114,120,125 -107,112,115 -96,102,103 -80,82,80 -70,73,70 -148,159,172 -131,144,155 -118,130,141 -106,120,131 -95,110,119 -80,94,103 -71,85,93 -245,235,103 -247,234,80 -252,228,0 -254,222,0 -197,169,0 -175,152,0 -137,122,42 -245,225,170 -235,216,156 -237,212,138 -244,218,69 -242,205,0 -241,196,0 -203,160,86 -247,228,160 -248,225,131 -248,225,106 -250,220,126 -252,215,133 -234,187,81 -223,164,42 -249,229,78 -251,225,45 -253,219,0 -255,209,0 -217,169,0 -170,138,0 -156,132,22 -250,225,90 -251,222,71 -253,218,47 -255,205,0 -201,150,0 -172,132,0 -137,116,37 -243,221,115 -243,213,84 -243,208,70 -242,168,0 -204,139,0 -160,116,0 -108,87,28 -233,219,164 -220,198,136 -213,200,159 -201,183,126 -192,173,129 -187,163,118 -173,150,101 -248,223,148 -251,216,120 -255,200,75 -255,184,37 -198,146,25 -172,132,34 -136,107,39 -251,219,109 -252,216,94 -254,210,73 -255,199,52 -234,170,0 -181,132,0 -154,118,19 -255,197,0 -255,181,0 -208,143,0 -179,126,0 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -115,83,29 -90,69,35 -74,61,43 -210,158,24 -183,139,34 -159,125,39 -151,114,41 -143,106,44 -125,98,49 -107,92,54 -253,210,118 -255,198,94 -255,191,69 -255,164,8 -222,124,0 -175,109,6 -116,83,30 -239,191,114 -238,182,101 -255,173,0 -237,154,54 -238,149,0 -255,151,0 -184,110,0 -253,208,141 -255,198,118 -255,181,80 -255,157,34 -213,121,4 -152,95,24 -109,75,30 -241,199,98 -241,190,78 -241,180,59 -237,139,0 -207,128,0 -167,109,18 -114,93,45 -245,175,43 -241,156,80 -229,105,22 -223,117,81 -234,114,68 -204,108,45 -184,103,78 -246,183,0 -221,187,139 -197,144,20 -255,147,39 -239,106,0 -222,136,51 -194,94,32 -212,199,105 -214,191,64 -222,184,67 -209,178,79 -195,168,83 -191,159,0 -176,147,57 -247,190,6 -240,179,39 -254,173,125 -230,165,98 -210,129,57 -220,133,54 -193,108,31 -189,155,100 -214,154,50 -219,138,17 -204,121,42 -173,101,54 -137,84,49 -120,81,55 -214,136,41 -211,131,47 -197,125,52 -182,114,54 -166,102,45 -157,106,58 -130,93,51 -206,180,132 -211,180,109 -212,173,109 -215,160,78 -188,140,66 -142,99,36 -131,96,46 -233,205,174 -241,202,182 -235,203,196 -245,202,201 -247,188,181 -204,149,125 -199,150,122 -254,203,147 -255,194,130 -255,178,97 -255,130,2 -229,114,0 -189,106,23 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -154,89,27 -239,209,166 -239,190,131 -236,161,89 -232,119,38 -203,96,23 -161,86,29 -97,62,33 -255,173,104 -255,143,36 -255,105,0 -254,81,0 -185,71,0 -148,69,13 -100,55,25 -255,185,152 -255,160,113 -255,127,56 -255,107,26 -216,96,30 -166,85,38 -139,71,34 -252,200,160 -253,190,141 -252,169,104 -246,140,50 -234,118,0 -211,93,0 -190,78,0 -248,207,175 -255,181,119 -247,172,114 -255,117,0 -214,107,0 -189,84,0 -156,72,22 -250,169,147 -249,147,118 -247,123,91 -255,137,67 -244,98,62 -237,82,68 -235,51,0 -255,190,167 -255,157,117 -255,127,72 -255,103,36 -227,83,12 -190,83,31 -115,57,31 -218,133,82 -223,125,64 -218,106,50 -219,88,45 -191,80,51 -134,74,54 -103,72,55 -216,162,128 -222,146,88 -222,140,95 -219,122,86 -227,119,99 -193,112,94 -182,82,65 -255,163,146 -255,141,116 -255,106,63 -252,77,11 -220,68,10 -169,67,32 -131,57,33 -255,179,180 -255,134,123 -255,92,63 -251,71,28 -207,69,36 -150,56,36 -107,54,43 -196,99,49 -186,88,39 -176,92,58 -158,82,49 -146,76,48 -122,76,55 -92,71,58 -211,181,164 -192,125,93 -176,84,53 -157,67,46 -124,59,47 -107,61,48 -92,61,51 -209,65,40 -189,72,46 -179,62,41 -140,62,45 -130,65,46 -122,73,51 -103,66,50 -227,213,218 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -225,186,185 -213,146,142 -194,109,99 -163,73,63 -131,59,54 -104,52,50 -223,184,167 -221,166,162 -225,166,173 -255,157,163 -221,134,146 -208,133,145 -176,110,123 -248,193,192 -222,163,162 -240,148,151 -207,111,124 -182,109,114 -182,89,105 -168,85,106 -192,154,158 -180,125,123 -178,115,111 -173,105,102 -149,106,105 -129,94,93 -123,93,87 -221,188,181 -202,154,146 -188,137,130 -163,126,119 -134,103,100 -107,76,78 -88,61,63 -234,190,183 -192,156,136 -180,106,88 -170,92,90 -163,81,74 -154,105,84 -138,58,30 -242,191,166 -228,161,139 -196,133,119 -181,146,142 -168,111,100 -159,95,88 -142,58,37 -225,196,182 -196,162,145 -186,150,136 -178,151,135 -171,140,123 -162,130,111 -151,130,114 -236,196,185 -235,186,175 -234,168,154 -233,145,129 -218,42,30 -154,51,38 -102,49,38 -241,129,132 -221,121,121 -213,104,105 -207,89,91 -197,69,71 -231,59,66 -214,0,29 -241,128,117 -255,109,112 -255,88,110 -225,6,0 -229,84,83 -203,55,9 -175,35,29 -217,172,157 -224,136,127 -219,136,128 -207,123,111 -169,83,68 -165,83,72 -135,79,75 -226,101,98 -228,74,92 -193,46,71 -189,54,71 -189,98,99 -168,68,74 -155,63,56 -255,177,197 -255,127,146 -255,88,98 -250,67,64 -224,60,54 -190,59,56 -129,50,49 -255,163,189 -255,141,169 -248,72,100 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -238,38,59 -210,39,52 -174,39,50 -125,38,44 -251,175,200 -251,98,131 -244,55,81 -239,51,68 -203,52,64 -163,52,60 -100,51,54 -218,98,88 -238,56,49 -219,53,19 -205,0,23 -193,0,23 -116,46,52 -71,36,39 -198,110,81 -192,76,57 -183,49,46 -171,36,42 -148,41,46 -138,42,43 -128,47,46 -225,83,64 -199,54,43 -167,42,44 -158,43,46 -109,50,51 -99,50,50 -87,45,47 -230,185,172 -229,106,87 -225,78,59 -205,84,96 -176,74,94 -154,33,68 -101,28,51 -250,188,213 -252,155,188 -246,81,123 -228,0,47 -201,18,51 -166,25,49 -119,36,50 -235,179,197 -229,174,187 -235,160,165 -232,158,166 -228,143,156 -199,77,102 -181,59,86 -235,199,214 -233,156,181 -223,69,101 -213,0,53 -186,11,49 -157,34,55 -135,39,54 -249,162,196 -246,115,160 -240,66,118 -229,0,75 -192,14,66 -155,39,70 -120,47,66 -245,182,214 -245,154,195 -239,74,135 -225,0,81 -197,0,66 -166,10,65 -138,21,58 -245,218,232 -246,206,224 -249,181,204 -248,144,169 -239,96,126 -224,62,85 -203,44,49 -242,212,222 -243,194,211 -242,171,190 -230,134,158 -210,91,118 -184,58,78 -158,43,50 -236,179,212 -232,129,176 -224,68,128 -206,0,57 -165,0,55 -135,32,69 -109,37,62 -249,157,209 -245,124,189 -240,77,159 -227,26,126 -207,15,110 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -172,21,94 -126,33,74 -247,189,222 -243,149,206 -238,95,169 -229,55,141 -218,7,95 -160,33,81 -111,38,62 -244,205,219 -224,98,140 -226,69,138 -181,36,88 -165,19,67 -151,26,48 -107,44,65 -207,135,165 -223,122,161 -198,71,120 -125,26,76 -130,53,84 -130,66,93 -131,69,88 -214,201,211 -195,163,173 -192,103,137 -198,54,104 -188,33,78 -145,46,73 -126,45,67 -235,190,229 -229,108,185 -218,22,137 -206,0,92 -166,0,83 -146,0,75 -108,29,72 -146,108,119 -147,63,88 -142,45,76 -114,46,76 -102,45,71 -87,44,65 -80,43,60 -240,200,217 -240,188,212 -215,67,141 -213,65,158 -209,115,166 -183,66,117 -195,54,115 -239,148,216 -235,109,196 -223,25,155 -209,0,117 -170,0,102 -137,11,91 -102,33,72 -244,167,225 -242,117,207 -233,59,180 -225,0,158 -199,0,131 -162,0,107 -132,11,88 -234,211,234 -230,187,222 -223,159,207 -217,133,192 -199,87,159 -174,36,119 -150,1,83 -233,196,218 -231,146,189 -221,116,167 -207,87,143 -165,68,116 -151,56,100 -132,44,79 -229,205,227 -226,200,224 -222,189,219 -201,149,188 -175,107,154 -153,72,124 -124,40,88 -238,215,238 -220,193,215 -199,159,188 -178,127,162 -157,111,143 -143,93,125 -126,72,105 -231,192,224 -205,166,202 -208,176,212 -176,144,175 -168,100,151 -135,89,151 -113,60,102 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -228,198,221 -220,181,209 -208,160,193 -190,132,170 -168,100,143 -136,58,106 -97,34,67 -235,190,210 -232,179,203 -228,169,195 -213,145,177 -132,52,82 -111,44,67 -86,41,52 -226,186,211 -220,169,199 -202,128,164 -181,91,133 -167,57,103 -155,50,93 -135,38,84 -233,205,215 -227,190,200 -215,162,176 -196,132,148 -179,106,125 -151,72,89 -137,61,74 -243,198,217 -241,188,209 -233,162,186 -220,134,160 -143,50,59 -127,47,56 -92,41,46 -233,196,206 -229,186,201 -218,164,181 -198,132,149 -122,62,61 -106,55,55 -81,47,47 -201,183,181 -188,165,172 -183,159,158 -168,138,135 -164,139,142 -143,115,109 -123,96,97 -202,167,189 -174,143,153 -146,105,123 -139,90,112 -120,73,80 -116,77,85 -101,68,64 -223,193,201 -219,183,195 -203,160,171 -175,123,135 -156,96,110 -135,75,86 -64,33,34 -172,133,138 -126,77,81 -98,33,42 -85,28,39 -81,37,50 -84,58,61 -102,63,54 -241,167,230 -237,133,218 -229,91,200 -220,60,185 -198,39,161 -176,20,139 -128,34,99 -207,146,190 -187,133,178 -185,124,167 -199,106,174 -172,81,140 -179,68,146 -165,48,128 -239,185,234 -227,118,214 -213,55,188 -201,0,168 -177,0,147 -158,0,130 -132,0,104 -234,183,237 -229,155,229 -222,126,220 -200,34,184 -188,19,170 -165,20,148 -127,38,111 -165,110,141 -167,59,118 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -153,29,105 -137,26,100 -113,33,89 -106,42,94 -94,38,83 -231,185,238 -221,155,233 -202,98,215 -188,38,194 -173,22,178 -153,27,156 -115,35,112 -223,161,219 -221,167,228 -210,140,218 -192,104,201 -177,76,187 -163,54,173 -143,24,153 -235,198,232 -230,190,229 -226,172,223 -212,138,207 -147,49,146 -131,48,122 -97,44,83 -189,121,184 -181,100,172 -192,76,171 -146,78,144 -136,2,127 -115,0,101 -98,18,70 -149,135,154 -162,120,161 -161,90,154 -142,57,132 -109,43,101 -105,51,98 -92,54,85 -216,199,215 -211,192,212 -191,165,190 -154,118,151 -126,84,120 -105,60,96 -81,42,69 -197,169,186 -173,145,171 -165,131,157 -159,120,143 -138,109,132 -124,86,110 -140,108,120 -220,205,221 -208,189,208 -198,176,197 -175,148,172 -134,100,128 -102,67,93 -74,48,68 -163,141,152 -156,140,153 -149,128,136 -152,131,137 -114,100,124 -70,56,81 -64,55,71 -212,194,222 -201,176,215 -185,155,203 -165,126,184 -100,46,110 -89,49,97 -74,47,73 -238,218,245 -204,173,214 -213,157,224 -178,136,192 -162,119,172 -159,91,198 -150,58,196 -215,168,237 -201,137,228 -173,78,205 -156,37,189 -136,21,162 -120,36,135 -101,49,104 -224,200,241 -215,184,237 -202,161,230 -182,128,217 -128,49,172 -113,47,142 -87,44,97 -214,190,230 -198,160,215 -140,71,158 -109,31,122 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -100,38,106 -92,39,97 -81,40,80 -203,163,226 -184,131,212 -161,93,189 -132,48,161 -112,30,135 -104,29,122 -95,32,107 -180,156,193 -154,125,168 -152,115,177 -134,93,161 -120,77,148 -150,92,206 -127,52,184 -185,150,201 -178,123,196 -142,81,168 -155,110,182 -150,114,196 -164,141,209 -97,0,125 -198,177,230 -183,149,219 -175,149,218 -148,104,195 -135,101,193 -134,88,186 -110,62,167 -153,145,170 -141,109,156 -122,65,135 -106,47,122 -100,49,124 -95,54,114 -91,77,101 -193,160,227 -167,122,210 -131,69,182 -92,1,146 -80,4,124 -72,8,108 -60,15,85 -215,197,239 -193,166,235 -144,98,212 -118,58,195 -95,34,163 -88,43,135 -81,45,112 -222,204,240 -161,139,211 -155,124,219 -130,91,205 -114,71,195 -102,55,186 -77,17,166 -197,180,236 -173,149,228 -151,119,219 -126,84,206 -69,0,159 -53,0,118 -46,26,72 -184,172,222 -165,151,199 -142,127,180 -120,99,182 -102,79,168 -99,68,193 -75,36,174 -177,162,209 -159,143,209 -138,116,216 -101,87,181 -88,72,171 -72,54,156 -56,31,126 -184,172,216 -156,139,188 -120,108,182 -165,167,195 -144,145,182 -96,94,123 -81,65,103 -179,180,230 -149,149,217 -116,116,199 -37,18,98 -36,22,84 -34,23,73 -34,28,53 -145,156,212 -81,33,121 -81,29,132 -92,69,128 -71,43,116 -64,1,134 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -49,19,124 -167,163,232 -139,131,222 -104,90,206 -47,0,143 -40,0,116 -38,14,100 -33,22,73 -110,124,165 -103,109,164 -97,93,160 -87,82,153 -81,69,140 -76,64,135 -83,84,138 -221,218,243 -182,184,229 -167,162,202 -137,134,210 -93,71,122 -74,55,78 -66,40,61 -135,139,186 -124,127,177 -117,101,165 -110,79,149 -104,70,145 -85,59,133 -82,48,123 -229,225,240 -224,218,236 -198,187,216 -160,145,183 -124,104,150 -96,74,124 -63,42,88 -216,215,233 -198,196,219 -179,176,204 -141,137,170 -88,83,123 -64,58,99 -31,26,54 -172,162,179 -126,114,135 -123,120,146 -111,111,144 -107,105,132 -95,92,119 -101,87,110 -129,129,155 -110,113,138 -104,111,132 -82,90,113 -72,75,96 -59,58,76 -59,58,72 -176,181,213 -143,152,191 -119,127,172 -102,107,156 -90,96,145 -69,77,127 -53,31,102 -165,175,236 -132,141,207 -110,120,194 -94,102,182 -68,73,160 -51,53,123 -44,45,103 -106,108,211 -80,76,181 -76,71,194 -60,63,186 -47,40,152 -52,4,144 -39,28,120 -197,208,225 -187,200,223 -162,178,207 -143,159,194 -27,54,94 -32,43,69 -29,32,43 -217,224,236 -164,187,203 -152,164,181 -118,134,152 -65,84,102 -38,56,73 -20,31,42 -157,165,187 -133,155,179 -125,147,172 -103,128,154 -86,113,141 -78,107,135 -38,75,95 -160,162,183 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -123,133,161 -97,110,135 -71,93,122 -76,94,118 -79,89,111 -57,61,74 -114,130,163 -76,95,131 -78,91,119 -50,84,114 -56,73,104 -59,69,91 -40,51,76 -185,211,230 -162,198,219 -141,185,211 -107,164,192 -0,61,80 -0,49,62 -11,43,50 -190,206,223 -183,200,220 -166,187,209 -122,152,178 -91,127,155 -79,116,143 -9,31,45 -157,180,209 -138,165,198 -116,147,183 -92,129,168 -77,118,158 -60,104,146 -39,86,128 -146,164,213 -116,139,197 -131,148,189 -109,126,172 -99,118,149 -76,104,150 -57,82,131 -209,220,241 -197,213,238 -156,184,220 -125,161,205 -95,138,187 -34,96,151 -0,45,95 -219,226,244 -206,217,240 -166,187,222 -125,155,200 -50,98,154 -0,59,115 -0,37,85 -221,228,247 -200,215,246 -177,201,242 -123,164,228 -65,126,208 -0,53,151 -0,26,115 -183,200,234 -152,181,237 -95,140,225 -85,117,215 -55,90,194 -8,86,199 -28,87,168 -188,197,228 -137,170,237 -128,147,230 -123,165,231 -95,142,187 -58,91,179 -97,110,184 -189,201,242 -142,171,224 -154,173,236 -122,146,227 -110,122,218 -85,97,206 -48,56,182 -201,209,244 -159,173,238 -73,90,207 -32,31,176 -25,25,148 -22,31,113 -19,27,79 -184,204,244 -92,135,227 -0,69,194 -9,0,145 -0,24,117 -0,29,100 -9,29,75 -195,214,248 -167,197,247 -50,126,234 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -17,3,161 -0,25,117 -0,30,98 -19,40,76 -170,202,241 -138,183,242 -66,142,231 -0,18,141 -2,33,108 -0,31,94 -4,29,67 -148,194,243 -109,172,237 -0,114,214 -0,49,164 -0,48,138 -0,44,117 -13,35,65 -25,97,174 -60,87,161 -47,61,127 -4,38,126 -27,55,116 -34,50,111 -47,41,81 -74,100,167 -61,78,131 -67,84,115 -59,58,91 -46,51,80 -27,29,55 -38,33,64 -148,168,210 -104,135,190 -66,108,174 -56,93,161 -45,85,154 -32,80,149 -29,65,140 -138,189,239 -126,171,234 -66,136,209 -0,101,187 -0,76,168 -0,52,141 -0,37,120 -198,218,241 -189,213,240 -164,199,234 -124,175,221 -0,60,117 -0,49,90 -1,38,60 -185,217,245 -155,203,246 -104,179,240 -0,61,168 -0,46,110 -0,40,87 -5,29,45 -141,200,241 -98,181,238 -0,156,229 -0,87,188 -0,76,155 -0,56,102 -0,38,63 -128,175,217 -95,155,204 -70,158,230 -20,122,214 -0,132,218 -0,117,210 -0,104,201 -93,147,225 -0,137,221 -39,116,178 -0,96,163 -44,103,200 -6,96,203 -0,80,184 -97,151,181 -139,169,205 -82,124,189 -0,103,175 -20,81,149 -27,78,141 -15,65,118 -113,197,241 -66,182,238 -0,163,231 -0,94,187 -0,75,137 -0,59,93 -0,42,59 -72,152,208 -39,142,199 -0,117,172 -0,97,155 -0,85,138 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -0,72,119 -2,66,108 -98,145,186 -76,130,173 -54,116,160 -35,106,165 -22,100,145 -0,85,128 -0,69,129 -0,146,194 -0,131,217 -0,117,168 -0,72,134 -0,69,120 -0,65,107 -0,51,73 -153,214,243 -91,194,240 -0,168,230 -0,118,203 -0,97,157 -0,79,115 -0,61,82 -84,182,237 -0,162,231 -0,144,223 -0,143,223 -0,133,217 -0,117,211 -0,105,180 -123,167,194 -98,152,179 -78,135,165 -65,115,144 -52,101,130 -22,91,128 -0,87,121 -165,183,199 -126,159,178 -106,142,161 -82,122,141 -63,108,128 -43,91,111 -22,73,92 -174,204,222 -136,177,201 -119,159,185 -101,154,181 -82,136,166 -72,133,162 -6,79,114 -185,201,212 -151,191,210 -101,173,200 -57,127,155 -0,86,132 -0,72,98 -48,76,93 -187,220,240 -113,177,207 -65,151,186 -0,133,194 -0,125,192 -0,85,145 -0,43,76 -153,219,241 -87,202,240 -0,181,233 -0,132,205 -0,107,167 -0,87,125 -0,59,74 -162,218,241 -138,211,240 -77,194,232 -0,174,221 -0,149,205 -0,130,189 -0,102,161 -138,186,221 -0,188,225 -0,166,200 -0,148,213 -0,110,179 -41,125,164 -122,147,156 -76,159,205 -2,124,174 -0,128,168 -21,103,136 -0,146,207 -0,139,209 -0,152,181 -71,168,203 -0,155,195 -0,133,176 -0,112,153 -0,106,145 -0,97,130 -0,86,115 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -116,210,238 -70,200,234 -0,187,226 -2,168,204 -0,173,218 -0,165,227 -0,134,176 -184,221,234 -155,211,229 -119,197,220 -61,177,206 -0,147,182 -0,115,153 -0,94,132 -104,209,236 -0,195,228 -0,169,210 -0,146,191 -0,127,165 -0,102,128 -0,72,82 -103,211,232 -0,193,218 -0,174,203 -0,142,172 -0,119,141 -0,98,114 -0,77,88 -98,177,194 -0,167,187 -0,150,172 -0,133,159 -0,124,141 -0,119,131 -0,97,106 -113,175,193 -91,163,182 -70,150,171 -40,138,159 -25,130,152 -0,119,142 -0,104,128 -176,227,236 -135,219,230 -43,204,217 -0,155,166 -0,140,150 -0,115,121 -0,95,98 -118,213,232 -59,202,224 -88,190,206 -0,165,191 -0,155,183 -0,139,161 -0,123,146 -169,211,204 -143,202,190 -28,129,117 -29,105,98 -65,117,116 -50,100,94 -36,69,71 -160,209,210 -62,193,177 -0,175,188 -0,163,175 -0,115,155 -0,94,136 -0,90,115 -125,221,218 -91,184,183 -37,152,141 -0,118,132 -72,122,127 -14,81,89 -35,76,91 -110,154,168 -79,132,148 -50,112,123 -47,110,124 -44,85,101 -45,76,89 -0,58,64 -183,207,217 -172,200,210 -147,183,194 -127,168,180 -79,134,146 -16,93,105 -7,40,47 -112,175,185 -87,166,179 -77,158,169 -39,147,159 -42,144,156 -2,134,145 -0,104,117 -0,148,146 -0,132,127 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -0,117,116 -0,108,106 -0,98,93 -0,93,95 -0,80,83 -156,219,225 -98,204,208 -0,177,174 -0,134,119 -0,115,105 -0,104,95 -0,83,78 -147,189,185 -105,219,200 -0,178,162 -47,155,140 -0,164,151 -0,125,128 -0,147,157 -112,219,219 -39,210,206 -0,191,182 -0,164,154 -0,133,121 -0,89,80 -0,76,69 -0,183,194 -0,187,183 -0,172,161 -0,177,155 -50,167,155 -0,156,135 -0,137,123 -139,170,157 -104,152,133 -86,175,150 -106,175,175 -38,149,148 -90,141,135 -91,117,115 -182,200,201 -164,189,193 -155,185,188 -129,176,175 -132,196,198 -131,192,198 -148,187,202 -124,224,217 -42,213,200 -0,199,180 -0,178,153 -0,150,130 -0,119,100 -0,78,68 -108,205,189 -72,197,183 -0,171,145 -0,155,122 -0,130,102 -0,105,83 -3,70,58 -184,220,217 -161,214,208 -134,201,195 -106,187,179 -0,112,99 -0,89,77 -27,59,51 -181,227,224 -165,223,218 -152,219,213 -106,202,191 -0,129,109 -0,109,92 -23,63,54 -173,203,193 -154,190,177 -134,177,161 -111,163,142 -38,114,82 -31,92,67 -39,71,53 -165,185,160 -128,158,139 -106,141,118 -72,123,88 -77,100,74 -68,91,65 -29,66,32 -190,206,200 -168,190,183 -146,172,165 -126,156,148 -92,127,116 -67,105,93 -23,48,41 -186,197,192 -176,189,183 -162,177,170 -148,165,156 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -113,133,120 -94,116,100 -34,55,44 -197,206,203 -188,190,182 -168,175,160 -150,164,133 -144,153,134 -115,134,116 -106,120,106 -185,193,187 -154,170,165 -136,162,160 -106,142,142 -104,124,128 -68,93,94 -28,74,82 -187,201,205 -176,192,196 -157,176,179 -130,153,155 -93,120,121 -62,93,91 -25,52,49 -167,178,170 -149,165,164 -133,149,141 -98,104,94 -86,99,100 -100,101,106 -72,86,93 -139,155,148 -106,126,116 -89,105,96 -89,108,102 -79,104,96 -56,78,72 -47,72,64 -209,225,223 -182,205,201 -154,185,178 -120,159,148 -81,128,115 -40,92,79 -20,51,44 -166,231,221 -139,227,214 -57,219,197 -0,171,132 -0,151,117 -0,123,95 -0,101,79 -0,206,183 -0,192,160 -86,161,147 -0,163,121 -0,148,124 -25,128,111 -0,133,85 -143,214,195 -109,206,184 -0,179,140 -0,151,97 -0,123,85 -0,103,74 -15,86,65 -80,166,137 -0,149,111 -0,135,87 -0,123,77 -0,111,71 -0,103,70 -0,87,70 -122,226,198 -69,215,177 -0,195,140 -0,174,102 -0,119,74 -0,99,66 -20,71,52 -166,212,192 -118,193,158 -112,183,147 -91,169,129 -0,179,117 -0,172,114 -0,166,73 -71,213,154 -52,183,146 -0,187,128 -0,177,121 -34,157,109 -0,134,62 -0,124,89 -160,217,183 -144,215,179 -111,204,157 -0,154,71 -0,133,64 -6,107,58 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -44,82,54 -162,228,188 -142,226,182 -126,224,172 -0,176,65 -0,150,58 -0,122,52 -33,87,51 -153,227,196 -34,208,127 -0,191,113 -0,182,81 -0,158,79 -39,93,57 -0,87,63 -187,232,207 -172,222,185 -133,210,154 -86,194,117 -0,173,81 -0,133,34 -1,114,35 -121,190,117 -31,111,50 -0,137,47 -0,119,55 -0,110,51 -0,73,30 -42,80,78 -75,149,100 -34,136,75 -0,121,64 -0,111,67 -38,96,64 -54,87,61 -56,85,68 -107,165,61 -70,162,66 -49,155,69 -57,145,66 -66,135,64 -74,120,64 -68,105,64 -193,209,174 -167,193,141 -141,174,135 -125,161,124 -63,112,87 -56,88,77 -48,79,68 -180,206,179 -153,186,149 -138,172,140 -115,149,111 -106,149,98 -91,125,82 -50,84,36 -172,220,151 -160,216,137 -107,194,79 -65,175,44 -79,158,49 -77,141,46 -74,119,42 -154,224,157 -119,216,128 -123,204,113 -44,199,80 -0,186,50 -0,153,23 -35,114,38 -0,198,97 -46,183,0 -0,167,45 -0,170,20 -38,159,0 -0,140,21 -5,95,31 -208,223,195 -188,225,155 -141,222,106 -119,215,79 -115,170,83 -112,153,77 -121,134,63 -206,219,184 -194,220,153 -136,168,82 -85,148,28 -77,128,31 -95,126,43 -74,106,30 -193,225,142 -182,221,126 -164,214,99 -119,190,36 -100,168,16 -101,141,29 -83,97,36 -177,214,0 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -173,207,0 -121,195,0 -76,174,4 -31,168,36 -62,154,44 -94,121,48 -214,232,158 -197,232,154 -165,223,103 -177,211,128 -145,217,76 -121,203,0 -89,196,0 -211,236,147 -205,235,133 -196,232,114 -151,215,0 -132,189,0 -122,154,6 -89,98,30 -199,226,125 -196,216,127 -169,219,35 -166,212,0 -145,200,17 -137,195,0 -120,170,0 -223,235,141 -213,232,105 -191,223,31 -182,219,91 -163,210,54 -167,193,65 -141,172,22 -196,215,171 -188,209,161 -183,207,156 -169,197,133 -119,157,77 -102,130,60 -77,91,50 -208,218,155 -134,173,68 -106,144,54 -91,121,87 -82,110,65 -62,89,38 -75,94,37 -207,209,176 -197,200,161 -186,189,145 -162,165,109 -139,142,79 -110,114,49 -61,68,30 -210,206,164 -203,199,153 -192,187,141 -175,170,115 -160,153,93 -136,129,63 -85,81,38 -175,181,164 -164,170,157 -136,138,125 -123,125,101 -115,117,104 -98,105,84 -62,73,58 -195,199,173 -179,185,156 -163,169,135 -138,145,104 -116,124,80 -94,103,59 -63,74,40 -139,131,119 -134,129,105 -106,104,85 -89,86,65 -91,92,76 -88,83,78 -99,92,83 -191,204,134 -187,197,153 -155,174,141 -143,153,65 -118,136,32 -122,114,90 -90,98,55 -173,184,102 -157,178,89 -159,171,80 -156,166,0 -127,139,38 -103,111,19 -92,85,29 -186,188,28 -171,173,39 -153,155,52 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -136,141,52 -124,128,54 -113,115,57 -101,102,55 -226,232,110 -219,228,73 -205,221,0 -196,215,0 -168,173,0 -148,147,0 -121,114,35 -234,236,114 -227,233,60 -224,232,43 -208,223,0 -181,190,0 -154,149,0 -130,122,7 -228,229,147 -224,226,129 -219,223,119 -210,216,90 -183,192,24 -142,141,21 -99,93,33 -210,207,23 -192,190,0 -187,194,98 -176,176,130 -191,190,112 -171,164,102 -141,139,87 -240,237,123 -238,234,65 -236,233,37 -224,224,0 -191,184,0 -173,164,0 -160,146,0 -242,234,98 -242,230,0 -239,223,0 -238,220,0 -187,166,0 -153,134,0 -104,92,32 -241,235,164 -240,233,150 -240,232,129 -237,224,79 -234,219,45 -225,205,0 -207,181,0 -235,229,161 -233,226,141 -231,222,124 -224,213,89 -215,200,44 -196,176,0 -178,155,0 -233,223,158 -228,215,133 -222,206,104 -217,199,93 -184,158,28 -163,142,45 -105,91,37 -220,214,160 -214,207,147 -208,200,136 -192,182,102 -172,159,65 -159,145,44 -138,123,28 -202,182,80 -207,176,40 -193,160,35 -160,133,44 -136,118,50 -114,102,55 -103,93,53 -212,195,15 -196,178,0 -145,133,47 -116,113,56 -93,99,59 -89,92,61 -84,85,56 -186,178,39 -180,169,36 -170,157,51 -142,126,58 -112,96,56 -99,88,59 -77,73,53 -213,202,164 -207,196,153 -197,184,136 -179,163,109 -153,133,70 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -140,119,53 -97,79,38 -183,176,156 -176,161,136 -158,142,118 -139,124,99 -129,120,90 -118,102,73 -103,89,63 -201,195,187 -202,191,173 -186,172,143 -136,118,83 -131,106,81 -126,105,83 -90,80,67 -202,200,175 -191,187,158 -175,170,131 -156,149,100 -88,74,38 -82,70,40 -75,67,44 -241,230,187 -224,209,175 -217,200,163 -206,184,142 -168,154,110 -148,121,98 -130,97,68 -205,195,178 -209,193,177 -214,194,173 -195,173,146 -187,168,142 -184,162,128 -183,162,139 -221,203,170 -211,188,147 -198,170,123 -185,151,96 -139,91,42 -115,78,40 -93,71,45 -187,165,148 -191,153,120 -173,150,125 -170,144,119 -148,125,116 -111,93,91 -50,41,36 -218,194,135 -184,146,112 -152,107,80 -144,88,62 -154,127,101 -166,137,123 -95,64,43 -240,220,187 -252,211,163 -225,183,131 -214,164,101 -198,137,66 -184,119,45 -166,99,28 -236,199,169 -231,183,144 -221,163,116 -200,131,72 -179,106,40 -146,77,19 -125,64,22 -243,208,188 -242,199,174 -240,191,162 -229,158,115 -185,97,41 -164,91,45 -104,63,36 -224,192,164 -217,179,148 -205,160,124 -180,128,85 -158,102,50 -118,65,20 -98,52,19 -189,153,127 -179,134,98 -163,116,77 -138,91,50 -111,69,36 -111,81,59 -78,53,36 -230,205,189 -216,191,176 -204,166,139 -182,153,134 -194,146,107 -170,117,70 -145,73,16 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -224,198,180 -220,191,173 -205,167,141 -191,148,121 -173,124,93 -149,97,59 -78,44,29 -205,168,151 -157,110,75 -168,113,84 -171,110,78 -172,112,87 -155,100,76 -154,95,62 -225,183,175 -212,162,138 -197,139,108 -152,84,45 -133,67,32 -109,79,73 -93,75,62 -215,196,191 -205,181,174 -192,162,150 -173,137,125 -150,109,92 -125,77,61 -91,52,40 -219,200,189 -211,187,176 -199,170,152 -170,128,106 -112,62,43 -97,59,42 -79,55,43 -193,178,170 -180,160,148 -171,146,126 -149,134,117 -142,122,113 -126,106,98 -78,69,70 -214,210,204 -197,185,179 -182,169,160 -163,147,136 -122,104,88 -99,80,63 -71,55,42 -208,204,196 -182,176,161 -166,159,141 -167,172,167 -148,154,148 -142,143,140 -75,79,86 -208,211,221 -192,197,208 -162,169,179 -124,135,146 -90,102,115 -51,63,74 -30,37,46 -177,183,193 -172,174,178 -158,155,164 -143,139,150 -126,126,135 -90,83,90 -37,42,55 -199,200,207 -177,180,183 -158,161,168 -137,140,146 -112,114,118 -84,87,92 -36,39,43 -190,198,202 -163,173,177 -145,157,162 -113,124,129 -79,86,90 -63,69,69 -54,56,54 -185,186,182 -168,169,163 -145,147,141 -126,127,119 -100,102,94 -81,83,76 -33,35,34 -179,177,185 -177,170,178 -169,161,167 -115,106,120 -105,97,112 -95,84,96 -69,65,68 -207,205,209 -173,167,170 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -140,137,137 -112,109,108 -108,100,102 -92,78,73 -78,75,73 -196,191,188 -175,168,164 -157,150,145 -140,132,127 -119,111,105 -105,97,91 -45,42,40 -184,188,188 -153,153,159 -146,139,144 -117,110,115 -113,109,114 -100,91,103 -87,82,90 -195,187,189 -177,167,167 -151,139,140 -132,120,119 -116,102,101 -95,82,81 -57,48,47 -208,196,204 -193,178,189 -171,152,163 -122,100,108 -89,68,73 -69,53,55 -56,46,46 -196,184,194 -177,161,157 -161,135,131 -130,107,109 -112,88,86 -75,53,59 -63,52,48 -215,210,210 -203,196,195 -191,183,181 -182,173,170 -172,163,159 -165,156,153 -150,140,135 -140,130,125 -131,119,115 -121,110,105 -110,98,93 -217,217,223 -208,209,214 -200,201,207 -187,188,194 -177,179,185 -166,168,175 -151,153,160 -136,138,146 -116,119,126 -99,102,109 -84,87,94 -50,46,32 -32,38,33 -48,38,29 -62,43,48 -16,25,33 -60,57,54 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -255,255,255 -238,238,238 -221,221,221 -204,204,204 -187,187,187 -170,170,170 -153,153,153 -136,136,136 -119,119,119 -102,102,102 -85,85,85 -68,68,68 -51,51,51 -34,34,34 -17,17,17 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -197,179,170 -199,175,162 -205,175,156 -195,173,163 -199,171,151 -202,170,151 -201,170,149 -202,171,147 -199,171,146 -204,176,149 -197,167,147 -204,167,150 -207,166,150 -198,168,147 -200,168,148 -199,168,146 -198,168,144 -195,168,143 -201,174,146 -197,165,146 -200,164,148 -203,164,150 -194,164,143 -198,165,145 -196,166,143 -196,166,143 -195,167,143 -195,169,143 -193,162,141 -196,159,144 -196,160,147 -196,159,148 -197,158,150 -193,162,141 -194,161,141 -192,161,141 -192,163,139 -190,163,139 -188,164,141 -189,157,138 -192,154,137 -193,153,139 -194,153,141 -194,152,142 -189,156,133 -188,155,133 -187,156,135 -186,156,133 -187,158,133 -186,152,130 -187,148,130 -193,150,134 -191,147,133 -191,148,138 -186,151,127 -187,152,129 -184,151,128 -183,152,126 -182,152,124 -181,144,121 -180,140,122 -184,140,124 -186,140,126 -189,144,132 -180,144,119 -180,144,121 -179,144,118 -178,145,116 -175,144,115 -178,135,108 -178,134,113 -179,133,117 -179,132,118 -176,135,107 -174,135,110 -174,136,108 -171,138,109 -168,137,107 -169,128,101 -171,126,104 -172,126,108 -172,125,112 -167,127,98 -168,128,102 -165,129,101 -164,128,97 -161,129,98 -161,119,91 -164,116,94 -164,115,96 -169,120,100 -159,117,89 -159,120,94 -153,118,92 -152,120,92 -155,125,94 -150,108,81 -153,106,83 -152,106,87 -152,107,92 -147,107,79 -255,255,255 -0,0,0 -255,0,0 -0,255,0 -0,0,255 -150,110,83 -143,110,82 -142,111,82 -133,96,72 -139,97,77 -136,94,76 -137,97,80 -134,97,72 -136,98,75 -132,99,72 -132,101,73 -118,85,65 -119,84,68 -118,83,68 -118,84,70 -118,86,65 -120,89,68 -118,88,63 -100,75,59 -98,74,63 -101,75,64 -99,72,62 -100,76,61 -109,81,58 -108,82,58 -96,74,59 -93,72,60 -94,73,61 -93,71,61 -100,73,60 -104,77,60 -108,80,59 -83,67,56 -80,66,56 -83,66,58 -83,65,57 -103,77,60 diff --git a/drivers/UCD323_Function.py b/drivers/UCD323_Function.py index 19f6282..e3b0950 100644 --- a/drivers/UCD323_Function.py +++ b/drivers/UCD323_Function.py @@ -16,6 +16,7 @@ class UCDController: self.config = None self.color_mode = None self.status = False + self.current_interface = "HDMI" self.current_timing = None self.current_pattern = None @@ -42,12 +43,14 @@ class UCDController: try: self.role = temp_dev.select_role(UniTAP.dev.UCD323.HDMISource) self.dev = temp_dev + self.current_interface = "HDMI" except Exception as role_error: self._close_device_object(temp_dev) raise role_error - self.timing_manager = self.role.hdtx.pg.timing_manager + pg, _ = self.get_tx_modules() + self.timing_manager = pg.timing_manager self.color_mode = UniTAP.ColorInfo() self.status = True @@ -72,6 +75,7 @@ class UCDController: self.current_pattern_param = None self.current_pattern_params = None self.current_pattern_index = 0 + self.current_interface = "HDMI" self.lUniTAP = None @@ -94,6 +98,7 @@ class UCDController: self.current_pattern_param = None self.current_pattern_params = None self.current_pattern_index = 0 + self.current_interface = "HDMI" try: self.lUniTAP = None @@ -139,10 +144,51 @@ class UCDController: self.current_pattern_param = None self.current_pattern_params = None self.current_pattern_index = 0 + self.current_interface = "HDMI" except Exception as e: pass + def get_tx_modules(self): + """根据当前接口返回 (pg, ag) 模块。""" + if not self.role: + raise RuntimeError("UCD 未打开,无法获取 TX 模块") + + interface = getattr(self, "current_interface", None) + if interface in (None, "HDMI"): + return self.role.hdtx.pg, self.role.hdtx.ag + if interface in ("DP", "Type-C"): + return self.role.dptx.pg, self.role.dptx.ag + raise ValueError(f"不支持的接口类型: {interface}") + + def _resolve_timing(self, pg=None): + """优先从 current_timing 读取 timing,必要时回退到 TX 模块。""" + if self.current_timing is not None: + return self.current_timing + + if pg is not None: + get_vm = getattr(pg, "get_vm", None) + if callable(get_vm): + try: + vm = get_vm() + return getattr(vm, "timing", None) + except Exception: + pass + + return None + + def get_current_resolution(self, default=(3840, 2160)): + """从当前 timing 获取 (width, height),失败时返回 default。""" + try: + pg, _ = self.get_tx_modules() + timing = self._resolve_timing(pg) + if timing and hasattr(timing, "h_active") and hasattr(timing, "v_active"): + return timing.h_active, timing.v_active + except Exception: + pass + + return default + def _cleanup(self): """清理设备资源""" try: @@ -161,6 +207,9 @@ class UCDController: self.config = config test_type = self.config.current_test_type + pg, _ = self.get_tx_modules() + self.timing_manager = pg.timing_manager + if test_type == "hdr_movie": color_format_str = self.config.current_test_types[test_type]["color_format"] color_format = UCDEnum.ColorInfo.get_color_format(color_format_str) @@ -197,9 +246,58 @@ class UCDController: """运行设备""" self.apply_video_mode() self.apply_pattern() - self.role.hdtx.pg.apply() + pg, _ = self.get_tx_modules() + pg.apply() return True + def send_image_pattern(self, image_path): + """发送图片 Pattern(依赖当前 timing/color_mode 状态)。""" + if not self.status or not self.role: + return False + + try: + pg, _ = self.get_tx_modules() + self.apply_video_mode() + pg.set_pattern(pattern=image_path) + pg.apply() + return True + except Exception: + return False + + def send_solid_rgb_pattern(self, rgb): + """发送纯色 RGB Pattern(依赖当前 timing/color_mode 状态)。""" + if not self.status or not self.role: + return False + + try: + self.current_pattern = UCDEnum.VideoPatternInfo.get_video_pattern("solidcolor") + if self.current_pattern is None: + return False + + return self.send_current_pattern_params(list(rgb)) + except Exception: + return False + + def send_current_pattern_params(self, pattern_params): + """发送当前已配置的 pattern,并可附带当前 pattern 参数。""" + if not self.status or not self.role: + return False + + try: + if self.current_pattern is None: + return False + + if pattern_params is not None and not self.set_pattern( + self.current_pattern, + pattern_params, + ): + return False + + self.run() + return True + except Exception: + return False + def set_color_mode(self, cf, bpc, cm): """设置颜色模式""" current_dynamic_range = None @@ -246,7 +344,8 @@ class UCDController: video_mode = UniTAP.VideoMode( timing=self.current_timing, color_info=self.color_mode ) - self.role.hdtx.pg.set_vm(vm=video_mode) + pg, _ = self.get_tx_modules() + pg.set_vm(vm=video_mode) return True def set_pattern(self, pattern, pattern_params=None): @@ -292,10 +391,11 @@ class UCDController: def apply_pattern(self): """应用当前pattern""" if self.current_pattern: - self.role.hdtx.pg.set_pattern(self.current_pattern) + pg, _ = self.get_tx_modules() + pg.set_pattern(self.current_pattern) if self.current_pattern_param: - self.role.hdtx.pg.set_pattern_params(self.current_pattern_param) + pg.set_pattern_params(self.current_pattern_param) return True return False diff --git a/drivers/ucd_helpers.py b/drivers/ucd_helpers.py index 13609a1..ff5cc69 100644 --- a/drivers/ucd_helpers.py +++ b/drivers/ucd_helpers.py @@ -1,80 +1,46 @@ """通用 UCD323/UCDController 辅助函数。 -封装"按当前接口取 tx 模块"、"读取分辨率"、"发送图片 Pattern"等所有 -测试模块共用的低层 UCD 操作,避免在多个业务模块中重复 if/else。 +保留为兼容层和薄代理,避免业务模块直接依赖控制器内部实现细节。 """ -import UniTAP - def get_tx_modules(ucd): - """根据当前接口返回 (pg, ag) 模块。 - - 兼容 UCD323Controller(多接口,含 current_interface 属性) - 与老的 UCDController(仅 HDMI)。 - """ - interface = getattr(ucd, "current_interface", None) - if interface in (None, "HDMI"): - return ucd.role.hdtx.pg, ucd.role.hdtx.ag - if interface in ("DP", "Type-C"): - return ucd.role.dptx.pg, ucd.role.dptx.ag - raise ValueError(f"不支持的接口类型: {interface}") + """根据当前接口返回 (pg, ag) 模块。""" + return ucd.get_tx_modules() def get_current_resolution(ucd, default=(3840, 2160)): """从 UCD 当前 timing 获取 (width, height),失败时返回 default。""" - try: - pg, _ = get_tx_modules(ucd) - vm = pg.get_vm() - timing = getattr(vm, "timing", None) - if timing and hasattr(timing, "h_active") and hasattr(timing, "v_active"): - return timing.h_active, timing.v_active - except Exception: - pass - - timing = getattr(ucd, "current_timing", None) - if timing and hasattr(timing, "h_active") and hasattr(timing, "v_active"): - return timing.h_active, timing.v_active - - return default + return ucd.get_current_resolution(default) -def send_image_pattern(ucd, image_path, *, bpc=8, color_format=None, colorimetry=None): - """通过 UCD 发送一张本地图片作为显示 Pattern。 - - 自动停止音频以避免蜂鸣声。颜色参数留空时使用 RGB / sRGB 默认值。 - 返回 True/False。 - """ +def send_image_pattern(ucd, image_path): + """通过 UCDController 发送一张本地图片作为显示 Pattern。""" if not getattr(ucd, "status", False): return False - try: - pg, ag = get_tx_modules(ucd) - except Exception: + send_via_controller = getattr(ucd, "send_image_pattern", None) + if not callable(send_via_controller): + return False + return bool(send_via_controller(image_path)) + + +def send_solid_rgb_pattern(ucd, rgb, *, raise_on_error=False): + """通过 UCDController 当前状态发送一组纯色 RGB Pattern。""" + if not getattr(ucd, "status", False): + if raise_on_error: + raise RuntimeError("UCD 未连接,无法发送纯色 Pattern") return False - try: - ag.stop_generate() - except Exception: - pass - - color_mode = UniTAP.ColorInfo() - color_mode.color_format = color_format or UniTAP.ColorInfo.ColorFormat.CF_RGB - color_mode.bpc = bpc - color_mode.colorimetry = colorimetry or UniTAP.ColorInfo.Colorimetry.CM_sRGB - - timing = None - try: - vm = pg.get_vm() - timing = getattr(vm, "timing", None) - except Exception: - pass - - try: - if timing: - pg.set_vm(vm=UniTAP.VideoMode(timing=timing, color_info=color_mode)) - pg.set_pattern(pattern=image_path) - pg.apply() - return True - except Exception: + send_via_controller = getattr(ucd, "send_solid_rgb_pattern", None) + if not callable(send_via_controller): + if raise_on_error: + raise RuntimeError("UCDController 未实现 send_solid_rgb_pattern") return False + + ok = bool(send_via_controller(list(rgb))) + if not ok and raise_on_error: + raise RuntimeError(f"发送纯色 Pattern 失败: {rgb}") + return ok + + diff --git a/pqAutomationApp.py b/pqAutomationApp.py index 4cfa5ba..1c4fb0a 100644 --- a/pqAutomationApp.py +++ b/pqAutomationApp.py @@ -74,6 +74,7 @@ from app.tests.local_dimming import ( stop_local_dimming_test as _ld_stop_local_dimming_test, update_ld_results as _ld_update_ld_results, ) +from app.services import PatternService from app.device.connection import ( check_com_connections as _dev_check_com_connections, check_port_connection as _dev_check_port_connection, @@ -139,6 +140,7 @@ class PQAutomationApp: # 创建配置对象 self.config = PQConfig() + self.pattern_service = PatternService(self) # 结果管理器:按 test_type 保留每次测试结果,始终存在,避免未初始化错误 self.results = PQResultStore() diff --git a/settings/pq_config.json b/settings/pq_config.json index 3201522..eb8bafe 100644 --- a/settings/pq_config.json +++ b/settings/pq_config.json @@ -50,7 +50,7 @@ } }, "device_config": { - "ca_com": "COM1", + "ca_com": "COM3", "ucd_list": "0: UCD-323 [2128C209]", "ca_channel": "0" },