diff --git a/app/views/panels/cct_panel.py b/app/views/panels/cct_panel.py index 1faa199..b23ddf4 100644 --- a/app/views/panels/cct_panel.py +++ b/app/views/panels/cct_panel.py @@ -1,4 +1,4 @@ -"""CCT 参数面板及其处理函数(Step 6 重构)。""" +"""CCT 参数面板及其处理函数(与主文件重构版本保持同步)。""" import time import traceback @@ -18,21 +18,16 @@ def create_cct_params_frame(self): ) # 默认值 - self.DEFAULT_CCT_PARAMS = { - "x_ideal": 0.3127, - "x_tolerance": 0.003, - "y_ideal": 0.3290, - "y_tolerance": 0.003, - } + screen_default_cct_params = self.config.get_default_cct_params("screen_module") # 从配置读取屏模组参数 saved_params = self.config.current_test_types.get("screen_module", {}).get( - "cct_params", self.DEFAULT_CCT_PARAMS.copy() + "cct_params", screen_default_cct_params.copy() ) # 色域参考标准 saved_gamut_ref = self.config.current_test_types.get("screen_module", {}).get( - "gamut_reference", "DCI-P3" + "gamut_reference", self.config.get_default_gamut_reference("screen_module") ) # 创建屏模组变量 @@ -66,7 +61,7 @@ def create_cct_params_frame(self): entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) # 绑定失去焦点事件 - default_val = self.DEFAULT_CCT_PARAMS[key] + default_val = screen_default_cct_params[key] entry.bind( "", lambda e, v=var, d=default_val: self.on_cct_param_focus_out(v, d), @@ -142,21 +137,16 @@ def create_cct_params_frame(self): ) # SDR 默认值 - self.SDR_DEFAULT_CCT_PARAMS = { - "x_ideal": 0.3127, - "x_tolerance": 0.003, - "y_ideal": 0.3290, - "y_tolerance": 0.003, - } + sdr_default_cct_params = self.config.get_default_cct_params("sdr_movie") # 从配置读取 SDR 参数 sdr_saved_params = self.config.current_test_types.get("sdr_movie", {}).get( - "cct_params", self.SDR_DEFAULT_CCT_PARAMS.copy() + "cct_params", sdr_default_cct_params.copy() ) # 色域参考标准 sdr_saved_gamut_ref = self.config.current_test_types.get("sdr_movie", {}).get( - "gamut_reference", "BT.709" + "gamut_reference", self.config.get_default_gamut_reference("sdr_movie") ) # 创建 SDR 变量 @@ -190,7 +180,7 @@ def create_cct_params_frame(self): entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) # 绑定失去焦点事件 - default_val = self.SDR_DEFAULT_CCT_PARAMS[key] + default_val = sdr_default_cct_params[key] entry.bind( "", lambda e, v=var, d=default_val: self.on_sdr_cct_param_focus_out(v, d), @@ -264,21 +254,16 @@ def create_cct_params_frame(self): ) # HDR 默认值 - self.HDR_DEFAULT_CCT_PARAMS = { - "x_ideal": 0.3127, - "x_tolerance": 0.003, - "y_ideal": 0.3290, - "y_tolerance": 0.003, - } + hdr_default_cct_params = self.config.get_default_cct_params("hdr_movie") # 从配置读取 HDR 参数 hdr_saved_params = self.config.current_test_types.get("hdr_movie", {}).get( - "cct_params", self.HDR_DEFAULT_CCT_PARAMS.copy() + "cct_params", hdr_default_cct_params.copy() ) # 色域参考标准 hdr_saved_gamut_ref = self.config.current_test_types.get("hdr_movie", {}).get( - "gamut_reference", "BT.2020" + "gamut_reference", self.config.get_default_gamut_reference("hdr_movie") ) # 创建 HDR 变量 @@ -312,7 +297,7 @@ def create_cct_params_frame(self): entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) # 绑定失去焦点事件 - default_val = self.HDR_DEFAULT_CCT_PARAMS[key] + default_val = hdr_default_cct_params[key] entry.bind( "", lambda e, v=var, d=default_val: self.on_hdr_cct_param_focus_out(v, d), @@ -381,15 +366,68 @@ def create_cct_params_frame(self): ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) -def on_sdr_cct_param_focus_out(self, var, default_value): - """SDR 色度参数失去焦点时的处理""" +def _get_cct_var_dict(self, test_type): + """按测试类型返回 CCT 变量映射。""" + if test_type == "sdr_movie": + return { + "x_ideal": self.sdr_cct_x_ideal_var, + "x_tolerance": self.sdr_cct_x_tolerance_var, + "y_ideal": self.sdr_cct_y_ideal_var, + "y_tolerance": self.sdr_cct_y_tolerance_var, + } + if test_type == "hdr_movie": + return { + "x_ideal": self.hdr_cct_x_ideal_var, + "x_tolerance": self.hdr_cct_x_tolerance_var, + "y_ideal": self.hdr_cct_y_ideal_var, + "y_tolerance": self.hdr_cct_y_tolerance_var, + } + return { + "x_ideal": self.cct_x_ideal_var, + "x_tolerance": self.cct_x_tolerance_var, + "y_ideal": self.cct_y_ideal_var, + "y_tolerance": self.cct_y_tolerance_var, + } + + +def _parse_cct_float(self, var, default): + """读取并解析 CCT 输入值,失败时回落默认值。""" try: value = var.get().strip() + if value == "": + return default + return float(value) + except Exception: + return default + +def _save_cct_params_for(self, test_type): + """保存指定测试类型的 CCT 参数。""" + try: + default_params = self.config.get_default_cct_params(test_type) + var_dict = self._get_cct_var_dict(test_type) + cct_params = { + key: self._parse_cct_float(var_dict[key], default_params[key]) + for key in default_params + } + + if test_type not in self.config.current_test_types: + self.config.current_test_types[test_type] = {} + + self.config.current_test_types[test_type]["cct_params"] = cct_params + self.save_pq_config() + except Exception: + pass + + +def _handle_cct_focus_out(self, var, default_value, save_func, label): + """统一处理 CCT 参数失焦校验并保存。""" + try: + value = var.get().strip() if value == "": var.set(str(default_value)) if hasattr(self, "log_gui"): - self.log_gui.log(f"✓ SDR 参数为空,恢复默认值: {default_value}") + self.log_gui.log(f"✓ {label} 参数为空,恢复默认值: {default_value}") else: try: float_val = float(value) @@ -397,124 +435,39 @@ def on_sdr_cct_param_focus_out(self, var, default_value): var.set(str(default_value)) if hasattr(self, "log_gui"): self.log_gui.log( - f"⚠️ SDR 参数超出范围,恢复默认值: {default_value}" + f"⚠️ {label} 参数超出范围,恢复默认值: {default_value}" ) except ValueError: var.set(str(default_value)) if hasattr(self, "log_gui"): - self.log_gui.log(f"⚠️ SDR 参数无效,恢复默认值: {default_value}") + self.log_gui.log( + f"⚠️ {label} 参数无效,恢复默认值: {default_value}" + ) - self.save_sdr_cct_params() + save_func() except Exception as e: if hasattr(self, "log_gui"): - self.log_gui.log(f"处理 SDR 参数失败: {str(e)}") + self.log_gui.log(f"处理 {label} 参数失败: {str(e)}") + + +def on_sdr_cct_param_focus_out(self, var, default_value): + """SDR 色度参数失去焦点时的处理。""" + self._handle_cct_focus_out(var, default_value, self.save_sdr_cct_params, "SDR") def save_sdr_cct_params(self): - """保存 SDR 色度参数""" - try: - - def get_float(var, default): - try: - value = var.get().strip() - if value == "": - return default - return float(value) - except: - return default - - sdr_cct_params = { - "x_ideal": get_float( - self.sdr_cct_x_ideal_var, self.SDR_DEFAULT_CCT_PARAMS["x_ideal"] - ), - "x_tolerance": get_float( - self.sdr_cct_x_tolerance_var, - self.SDR_DEFAULT_CCT_PARAMS["x_tolerance"], - ), - "y_ideal": get_float( - self.sdr_cct_y_ideal_var, self.SDR_DEFAULT_CCT_PARAMS["y_ideal"] - ), - "y_tolerance": get_float( - self.sdr_cct_y_tolerance_var, - self.SDR_DEFAULT_CCT_PARAMS["y_tolerance"], - ), - } - - if "sdr_movie" not in self.config.current_test_types: - self.config.current_test_types["sdr_movie"] = {} - - self.config.current_test_types["sdr_movie"]["cct_params"] = sdr_cct_params - self.save_pq_config() - except: - pass + """保存 SDR 色度参数。""" + self._save_cct_params_for("sdr_movie") def on_hdr_cct_param_focus_out(self, var, default_value): - """HDR 色度参数失去焦点时的处理""" - try: - value = var.get().strip() - - if value == "": - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"✓ HDR 参数为空,恢复默认值: {default_value}") - else: - try: - float_val = float(value) - if float_val < 0 or float_val > 1: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log( - f"⚠️ HDR 参数超出范围,恢复默认值: {default_value}" - ) - except ValueError: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"⚠️ HDR 参数无效,恢复默认值: {default_value}") - - self.save_hdr_cct_params() - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"处理 HDR 参数失败: {str(e)}") + """HDR 色度参数失去焦点时的处理。""" + self._handle_cct_focus_out(var, default_value, self.save_hdr_cct_params, "HDR") def save_hdr_cct_params(self): - """保存 HDR 色度参数""" - try: - - def get_float(var, default): - try: - value = var.get().strip() - if value == "": - return default - return float(value) - except: - return default - - hdr_cct_params = { - "x_ideal": get_float( - self.hdr_cct_x_ideal_var, self.HDR_DEFAULT_CCT_PARAMS["x_ideal"] - ), - "x_tolerance": get_float( - self.hdr_cct_x_tolerance_var, - self.HDR_DEFAULT_CCT_PARAMS["x_tolerance"], - ), - "y_ideal": get_float( - self.hdr_cct_y_ideal_var, self.HDR_DEFAULT_CCT_PARAMS["y_ideal"] - ), - "y_tolerance": get_float( - self.hdr_cct_y_tolerance_var, - self.HDR_DEFAULT_CCT_PARAMS["y_tolerance"], - ), - } - - if "hdr_movie" not in self.config.current_test_types: - self.config.current_test_types["hdr_movie"] = {} - - self.config.current_test_types["hdr_movie"]["cct_params"] = hdr_cct_params - self.save_pq_config() - except: - pass + """保存 HDR 色度参数。""" + self._save_cct_params_for("hdr_movie") def recalculate_cct(self): @@ -630,6 +583,7 @@ def recalculate_gamut(self): # 7. 重新计算 XY 色域覆盖率 xy_points = [[result[0], result[1]] for result in rgb_data] + # 根据参考标准计算 XY 覆盖率 if reference_standard == "BT.2020": area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_BT2020( xy_points @@ -651,10 +605,12 @@ def recalculate_gamut(self): self.log_gui.log(f"✓ 参考标准: {reference_standard}") self.log_gui.log(f"✓ XY 色域覆盖率: {coverage_xy:.1f}%") - # 8. 重新计算 UV 色域覆盖率 + # ========== ✅✅✅ 8. 重新计算 UV 色域覆盖率 ========== + # 将 XY 坐标转换为 UV 坐标 uv_points = [] for x, y in xy_points: try: + # XY转UV公式 denom = -2 * x + 12 * y + 3 if abs(denom) < 1e-10: u, v = 0, 0 @@ -667,6 +623,7 @@ def recalculate_gamut(self): self.log_gui.log(f"✓ 转换后的 UV 点数量: {len(uv_points)}") + # 根据参考标准计算 UV 覆盖率 if reference_standard == "BT.2020": area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_BT2020_uv( uv_points @@ -685,18 +642,19 @@ def recalculate_gamut(self): ) self.log_gui.log(f"✓ UV 色域覆盖率: {coverage_uv:.1f}%") + # ======================================================== - # 9. 更新结果 + # 9. ✅ 更新结果(同时保存 XY 和 UV 覆盖率) self.results.set_test_item_result( "gamut", { - "area": area_xy, - "coverage": coverage_xy, - "area_xy": area_xy, - "coverage_xy": coverage_xy, - "area_uv": area_uv, - "coverage_uv": coverage_uv, - "uv_coverage": coverage_uv, + "area": area_xy, # ← 兼容旧字段 + "coverage": coverage_xy, # ← 兼容旧字段 + "area_xy": area_xy, # ← XY 面积 + "coverage_xy": coverage_xy, # ← XY 覆盖率 + "area_uv": area_uv, # ← UV 面积 + "coverage_uv": coverage_uv, # ← UV 覆盖率 + "uv_coverage": coverage_uv, # ← 兼容字段(Excel 导出用) "reference": reference_standard, }, ) @@ -722,103 +680,14 @@ def recalculate_gamut(self): messagebox.showerror("错误", f"重新计算失败: {str(e)}") -def on_cct_param_change(self, var, default_value): - """色度参数改变时的处理 - 空值恢复默认""" - try: - value = var.get().strip() - - if value == "": - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"输入框为空,恢复默认值: {default_value}") - else: - try: - float_val = float(value) - if float_val < 0 or float_val > 1: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log( - f"参数超出范围 [0, 1],恢复默认值: {default_value}" - ) - except ValueError: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"无效的参数值,恢复默认值: {default_value}") - - self.save_cct_params() - - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"处理参数变化失败: {str(e)}") - - def on_cct_param_focus_out(self, var, default_value): """色度参数失去焦点时的处理 - 空值恢复默认""" - try: - value = var.get().strip() - - if value == "": - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"✓ 输入框为空,恢复默认值: {default_value}") - else: - try: - float_val = float(value) - if float_val < 0 or float_val > 1: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log( - f"⚠️ 参数超出范围 [0, 1],恢复默认值: {default_value}" - ) - except ValueError: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"⚠️ 无效的参数值,恢复默认值: {default_value}") - - self.save_cct_params() - - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"处理参数变化失败: {str(e)}") + self._handle_cct_focus_out(var, default_value, self.save_cct_params, "屏模组") def save_cct_params(self): """保存色度参数 - 简化版""" - try: - current_type = self.config.current_test_type - - def get_float(var, default): - try: - value = var.get().strip() - if value == "": - return default - return float(value) - except: - return default - - cct_params = { - "x_ideal": get_float( - self.cct_x_ideal_var, self.DEFAULT_CCT_PARAMS["x_ideal"] - ), - "x_tolerance": get_float( - self.cct_x_tolerance_var, self.DEFAULT_CCT_PARAMS["x_tolerance"] - ), - "y_ideal": get_float( - self.cct_y_ideal_var, self.DEFAULT_CCT_PARAMS["y_ideal"] - ), - "y_tolerance": get_float( - self.cct_y_tolerance_var, self.DEFAULT_CCT_PARAMS["y_tolerance"] - ), - } - - if current_type not in self.config.current_test_types: - self.config.current_test_types[current_type] = {} - - self.config.current_test_types[current_type]["cct_params"] = cct_params - self.save_pq_config() - - except: - pass + self._save_cct_params_for(self.config.current_test_type) def reload_cct_params(self): @@ -830,7 +699,7 @@ def reload_cct_params(self): ) if saved_params is None: - saved_params = self.DEFAULT_CCT_PARAMS.copy() + saved_params = self.config.get_default_cct_params(current_type) # 更新输入框的值 self.cct_x_ideal_var.set(str(saved_params["x_ideal"])) @@ -841,3 +710,77 @@ def reload_cct_params(self): except Exception as e: if hasattr(self, "log_gui"): self.log_gui.log(f"重新加载色度参数失败: {str(e)}") + + +def toggle_cct_params_frame(self): + """根据测试类型和测试项的选中状态显示对应参数框""" + selected_items = self.get_selected_test_items() + current_test_type = self.config.current_test_type + + # ========== 默认隐藏所有参数框 ========== + self.cct_params_frame.pack_forget() + self.sdr_cct_params_frame.pack_forget() + + # HDR 色度参数框(如果存在的话) + if hasattr(self, "hdr_cct_params_frame"): + self.hdr_cct_params_frame.pack_forget() + + # ========== 根据测试类型和选中项显示对应参数框 ========== + if current_test_type == "screen_module": + # 屏模组:只有色度参数 + if "cct" in selected_items: + self.cct_params_frame.pack(fill=tk.X, padx=5, pady=5) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 显示屏模组色度参数设置") + + elif current_test_type == "sdr_movie": + # SDR:只有色度参数(色准不需要参数设置框) + if "cct" in selected_items: + self.sdr_cct_params_frame.pack(fill=tk.X, padx=5, pady=5) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 显示 SDR 色度参数设置") + + elif current_test_type == "hdr_movie": + # HDR:只有色度参数(色准不需要参数设置框) + if "cct" in selected_items: + if hasattr(self, "hdr_cct_params_frame"): + self.hdr_cct_params_frame.pack(fill=tk.X, padx=5, pady=5) + if hasattr(self, "log_gui"): + self.log_gui.log("✓ 显示 HDR 色度参数设置") + else: + if hasattr(self, "log_gui"): + self.log_gui.log("⚠️ HDR 色度参数框尚未创建") + + +# ---- gamut 参考标准改变回调(统一实现) ---- +_GAMUT_REF_CONFIGS = { + "screen_module": {"var_attr": "screen_gamut_ref_var", "label": "屏模组"}, + "sdr_movie": {"var_attr": "sdr_gamut_ref_var", "label": "SDR"}, + "hdr_movie": {"var_attr": "hdr_gamut_ref_var", "label": "HDR"}, +} + + +def _on_gamut_ref_changed(self, test_type, event=None): + cfg = _GAMUT_REF_CONFIGS[test_type] + try: + new_ref = getattr(self, cfg["var_attr"]).get() + self.log_gui.log(f"✓ {cfg['label']} 色域参考标准已更改为: {new_ref}") + + if test_type not in self.config.current_test_types: + self.config.current_test_types[test_type] = {} + self.config.current_test_types[test_type]["gamut_reference"] = new_ref + self.save_pq_config() + except Exception as e: + self.log_gui.log(f"保存 {cfg['label']} 色域参考标准失败: {str(e)}") + + +def on_screen_gamut_ref_changed(self, event=None): + _on_gamut_ref_changed(self, "screen_module", event) + + +def on_sdr_gamut_ref_changed(self, event=None): + _on_gamut_ref_changed(self, "sdr_movie", event) + + +def on_hdr_gamut_ref_changed(self, event=None): + _on_gamut_ref_changed(self, "hdr_movie", event) diff --git a/app/views/panels/custom_template_panel.py b/app/views/panels/custom_template_panel.py index 33766c6..8e1dba8 100644 --- a/app/views/panels/custom_template_panel.py +++ b/app/views/panels/custom_template_panel.py @@ -605,4 +605,13 @@ def start_custom_template_test(self): self.test_thread.daemon = True self.test_thread.start() - +def update_custom_button_visibility(self): + """只在 SDR 测试时显示客户模版按钮""" + if not hasattr(self, "custom_btn") or not hasattr(self, "test_type_var"): + return + if self.test_type_var.get() == "sdr_movie": + if not self.custom_btn.winfo_manager(): + self.custom_btn.pack(side=tk.LEFT, padx=5) + else: + if self.custom_btn.winfo_manager(): + self.custom_btn.pack_forget() diff --git a/app/views/panels/main_layout.py b/app/views/panels/main_layout.py index f5e3be8..5dbffad 100644 --- a/app/views/panels/main_layout.py +++ b/app/views/panels/main_layout.py @@ -1,5 +1,6 @@ """主布局面板创建函数(Step 6 重构)。""" +import re import tkinter as tk import ttkbootstrap as ttk @@ -496,3 +497,41 @@ def create_operation_frame(self): self.update_custom_button_visibility() + + +def on_screen_module_timing_changed(self, event=None): + """屏模组信号格式改变时的回调""" + try: + selected_timing = self.screen_module_timing_var.get() + + # 记录日志 + self.log_gui.log(f"屏模组信号格式已更改为: {selected_timing}") + + match = re.search(r"(\d+)x(\d+)\s*@\s*(\d+)", selected_timing) + if match: + width = int(match.group(1)) + height = int(match.group(2)) + refresh_rate = int(match.group(3)) + + self.log_gui.log(f" ├─ 分辨率: {width}x{height}") + self.log_gui.log(f" └─ 刷新率: {refresh_rate}Hz") + + # 根据分辨率给出提示 + if width >= 3840: # 4K及以上 + self.log_gui.log(" ℹ️ 检测到4K分辨率") + + if refresh_rate >= 120: + self.log_gui.log(" ℹ️ 检测到高刷新率") + + # 更新配置 + self.config.set_current_timing(selected_timing) + + # 如果正在测试,提示用户 + if self.testing: + self.log_gui.log("⚠️ 警告: 测试进行中,信号格式更改将在下次测试时生效") + + # 保存配置 + self.save_pq_config() + + except Exception as e: + self.log_gui.log(f"❌ 屏模组信号格式更改失败: {str(e)}") diff --git a/app/views/panels/side_panels.py b/app/views/panels/side_panels.py index 673a68f..470713b 100644 --- a/app/views/panels/side_panels.py +++ b/app/views/panels/side_panels.py @@ -182,231 +182,138 @@ def toggle_log_panel(self): self.show_panel("log") -def toggle_screen_debug_panel(self): - """打开/关闭屏模组单步调试面板(独立窗口)""" +# ---- 单步调试面板(统一实现) ---- +DEBUG_PANEL_CONFIGS = { + "screen_module": { + "window_attr": "debug_window", + "btn_attr": "screen_debug_btn", + "title": "🔧 单步调试面板", + "window_log_prefix": "", + "data_log_prefix": "屏模组", + "failure_data_label": "调试数据", + # (item_key, debug_key, (category, subkey), data_label, enable_desc) + "data_items": [ + ("gamma", "gamma", ("shared", "gray"), "灰阶", "Gamma 单步调试"), + ("gamut", "rgb", ("gamut", "rgb"), "RGB", "RGB 单步调试"), + ], + }, + "sdr_movie": { + "window_attr": "sdr_debug_window", + "btn_attr": "sdr_debug_btn", + "title": "🔧 SDR 单步调试面板", + "window_log_prefix": "SDR ", + "data_log_prefix": "SDR", + "failure_data_label": "SDR 调试数据", + "data_items": [ + ("gamma", "gamma", ("shared", "gray"), "灰阶", "Gamma 单步调试"), + ("accuracy", "accuracy", ("accuracy", "measured"), "色准", "色准单步调试"), + ("gamut", "rgb", ("gamut", "rgb"), "RGB", "RGB 单步调试"), + ], + }, + "hdr_movie": { + "window_attr": "hdr_debug_window", + "btn_attr": "hdr_debug_btn", + "title": "🔧 HDR 单步调试面板", + "window_log_prefix": "HDR ", + "data_log_prefix": "HDR", + "failure_data_label": "HDR 调试数据", + "data_items": [ + ("eotf", "eotf", ("shared", "gray"), "灰阶", "EOTF 单步调试"), + ("accuracy", "accuracy", ("accuracy", "measured"), "色准", "色准单步调试"), + ("gamut", "rgb", ("gamut", "rgb"), "RGB", "RGB 单步调试"), + ], + }, +} + + +def _toggle_debug_panel(self, test_type): + """打开/关闭对应测试类型的单步调试面板(独立窗口)。""" + cfg = DEBUG_PANEL_CONFIGS[test_type] + win_attr = cfg["window_attr"] + btn = getattr(self, cfg["btn_attr"]) + wlp = cfg["window_log_prefix"] + # 如果窗口已存在且可见,关闭它 - if hasattr(self, "debug_window") and self.debug_window.winfo_exists(): - self.debug_window.destroy() - self.screen_debug_btn.config(text="打开调试面板") - self.log_gui.log("✓ 单步调试面板已关闭") + existing = getattr(self, win_attr, None) + if existing is not None and existing.winfo_exists(): + existing.destroy() + btn.config(text="打开调试面板") + self.log_gui.log(f"✓ {wlp}单步调试面板已关闭") return # 创建新窗口 - self.debug_window = ttk.Toplevel(self.root) - self.debug_window.title("🔧 单步调试面板") - self.debug_window.geometry("900x400") - self.debug_window.transient(self.root) + win = ttk.Toplevel(self.root) + win.title(cfg["title"]) + win.geometry("900x400") + win.transient(self.root) + setattr(self, win_attr, win) - # 创建调试面板容器 - debug_container = ttk.Frame(self.debug_window, padding=10) - debug_container.pack(fill=tk.BOTH, expand=True) # ← 这个 pack 是对的 - - # 创建调试面板实例 - from app.views.pq_debug_panel import PQDebugPanel + # 调试面板容器 + debug_container = ttk.Frame(win, padding=10) + debug_container.pack(fill=tk.BOTH, expand=True) + # 创建调试面板实例(不要对它调用 pack) debug_panel_instance = PQDebugPanel(debug_container, self) - # ← 这里不应该有任何 pack 调用! - self.log_gui.log("✓ 单步调试面板实例已创建") + self.log_gui.log(f"✓ {wlp}单步调试面板实例已创建") # 重新启用调试(如果有数据) try: - test_type = self.config.current_test_type selected_items = self.get_selected_test_items() - - if test_type == "screen_module": - if "gamma" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if gray_data: - self.log_gui.log(f" → 加载 {len(gray_data)} 个灰阶数据点") - debug_panel_instance.enable_debug( - "screen_module", "gamma", gray_data - ) - self.log_gui.log("✓ 屏模组 Gamma 单步调试已重新启用") - else: + dlp = cfg["data_log_prefix"] + for item_key, debug_key, (cat, sub), data_label, enable_desc in cfg["data_items"]: + if item_key not in selected_items: + continue + data = self.results.get_intermediate_data(cat, sub) + if not data: + if test_type == "screen_module" and item_key == "gamma": self.log_gui.log(" ✗ 没有可用的灰阶数据") - - if "gamut" in selected_items: - rgb_data = self.results.get_intermediate_data("gamut", "rgb") - if rgb_data: - self.log_gui.log(f" → 加载 {len(rgb_data)} 个RGB数据点") - debug_panel_instance.enable_debug( - "screen_module", "rgb", rgb_data - ) - self.log_gui.log("✓ 屏模组 RGB 单步调试已重新启用") - + continue + self.log_gui.log(f" → 加载 {len(data)} 个{data_label}数据点") + debug_panel_instance.enable_debug(test_type, debug_key, data) + self.log_gui.log(f"✓ {dlp} {enable_desc}已重新启用") except Exception as e: - self.log_gui.log(f"⚠️ 加载调试数据失败: {str(e)}") - import traceback - + self.log_gui.log(f"⚠️ 加载{cfg['failure_data_label']}失败: {str(e)}") self.log_gui.log(traceback.format_exc()) - # 更新按钮文字 - self.screen_debug_btn.config(text="关闭调试面板") + btn.config(text="关闭调试面板") - # 窗口关闭时的回调 def on_closing(): - self.screen_debug_btn.config(text="打开调试面板") - self.debug_window.destroy() - self.log_gui.log("✓ 单步调试窗口已关闭") + btn.config(text="打开调试面板") + getattr(self, win_attr).destroy() + self.log_gui.log(f"✓ {wlp}单步调试窗口已关闭") - self.debug_window.protocol("WM_DELETE_WINDOW", on_closing) - self.debug_window.update_idletasks() + win.protocol("WM_DELETE_WINDOW", on_closing) + win.update_idletasks() - self.log_gui.log("✓ 单步调试面板已打开(独立窗口)") + self.log_gui.log(f"✓ {wlp}单步调试面板已打开(独立窗口)") + + +def toggle_screen_debug_panel(self): + _toggle_debug_panel(self, "screen_module") def toggle_sdr_debug_panel(self): - """打开/关闭 SDR 单步调试面板(独立窗口)""" - # 如果窗口已存在且可见,关闭它 - if hasattr(self, "sdr_debug_window") and self.sdr_debug_window.winfo_exists(): - self.sdr_debug_window.destroy() - self.sdr_debug_btn.config(text="打开调试面板") - self.log_gui.log("✓ SDR 单步调试面板已关闭") - return - - # 创建新窗口 - self.sdr_debug_window = ttk.Toplevel(self.root) - self.sdr_debug_window.title("🔧 SDR 单步调试面板") - self.sdr_debug_window.geometry("900x400") - self.sdr_debug_window.transient(self.root) - - # 创建调试面板容器 - debug_container = ttk.Frame(self.sdr_debug_window, padding=10) - debug_container.pack(fill=tk.BOTH, expand=True) - - # ✅ 创建调试面板实例(不要对它调用 pack) - from app.views.pq_debug_panel import PQDebugPanel - - debug_panel_instance = PQDebugPanel(debug_container, self) - # ← 删除:debug_panel_instance.pack(...) - - self.log_gui.log("✓ SDR 单步调试面板实例已创建") - - # ✅ 重新启用调试(如果有数据) - try: - selected_items = self.get_selected_test_items() - - if "gamma" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if gray_data: - self.log_gui.log(f" → 加载 {len(gray_data)} 个灰阶数据点") - debug_panel_instance.enable_debug("sdr_movie", "gamma", gray_data) - self.log_gui.log("✓ SDR Gamma 单步调试已重新启用") - - if "accuracy" in selected_items: - accuracy_data = self.results.get_intermediate_data( - "accuracy", "measured" - ) - if accuracy_data: - self.log_gui.log(f" → 加载 {len(accuracy_data)} 个色准数据点") - debug_panel_instance.enable_debug( - "sdr_movie", "accuracy", accuracy_data - ) - self.log_gui.log("✓ SDR 色准单步调试已重新启用") - - if "gamut" in selected_items: - rgb_data = self.results.get_intermediate_data("gamut", "rgb") - if rgb_data: - self.log_gui.log(f" → 加载 {len(rgb_data)} 个RGB数据点") - debug_panel_instance.enable_debug("sdr_movie", "rgb", rgb_data) - self.log_gui.log("✓ SDR RGB 单步调试已重新启用") - - except Exception as e: - self.log_gui.log(f"⚠️ 加载 SDR 调试数据失败: {str(e)}") - import traceback - - self.log_gui.log(traceback.format_exc()) - - # 更新按钮文字 - self.sdr_debug_btn.config(text="关闭调试面板") - - # 窗口关闭时的回调 - def on_closing(): - self.sdr_debug_btn.config(text="打开调试面板") - self.sdr_debug_window.destroy() - self.log_gui.log("✓ SDR 单步调试窗口已关闭") - - self.sdr_debug_window.protocol("WM_DELETE_WINDOW", on_closing) - self.sdr_debug_window.update_idletasks() - - self.log_gui.log("✓ SDR 单步调试面板已打开(独立窗口)") + _toggle_debug_panel(self, "sdr_movie") def toggle_hdr_debug_panel(self): - """打开/关闭 HDR 单步调试面板(独立窗口)""" - # 如果窗口已存在且可见,关闭它 - if hasattr(self, "hdr_debug_window") and self.hdr_debug_window.winfo_exists(): - self.hdr_debug_window.destroy() - self.hdr_debug_btn.config(text="打开调试面板") - self.log_gui.log("✓ HDR 单步调试面板已关闭") - return - - # 创建新窗口 - self.hdr_debug_window = ttk.Toplevel(self.root) - self.hdr_debug_window.title("🔧 HDR 单步调试面板") - self.hdr_debug_window.geometry("900x400") - self.hdr_debug_window.transient(self.root) - - # 创建调试面板容器 - debug_container = ttk.Frame(self.hdr_debug_window, padding=10) - debug_container.pack(fill=tk.BOTH, expand=True) - - # ✅ 创建调试面板实例(不要对它调用 pack) - from app.views.pq_debug_panel import PQDebugPanel - - debug_panel_instance = PQDebugPanel(debug_container, self) - # ← 删除:debug_panel_instance.pack(...) - - self.log_gui.log("✓ HDR 单步调试面板实例已创建") - - # ✅ 重新启用调试(如果有数据) - try: - selected_items = self.get_selected_test_items() - - if "eotf" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if gray_data: - self.log_gui.log(f" → 加载 {len(gray_data)} 个灰阶数据点") - debug_panel_instance.enable_debug("hdr_movie", "eotf", gray_data) - self.log_gui.log("✓ HDR EOTF 单步调试已重新启用") - - if "accuracy" in selected_items: - accuracy_data = self.results.get_intermediate_data( - "accuracy", "measured" - ) - if accuracy_data: - self.log_gui.log(f" → 加载 {len(accuracy_data)} 个色准数据点") - debug_panel_instance.enable_debug( - "hdr_movie", "accuracy", accuracy_data - ) - self.log_gui.log("✓ HDR 色准单步调试已重新启用") - - if "gamut" in selected_items: - rgb_data = self.results.get_intermediate_data("gamut", "rgb") - if rgb_data: - self.log_gui.log(f" → 加载 {len(rgb_data)} 个RGB数据点") - debug_panel_instance.enable_debug("hdr_movie", "rgb", rgb_data) - self.log_gui.log("✓ HDR RGB 单步调试已重新启用") - - except Exception as e: - self.log_gui.log(f"⚠️ 加载 HDR 调试数据失败: {str(e)}") - import traceback - - self.log_gui.log(traceback.format_exc()) - - # 更新按钮文字 - self.hdr_debug_btn.config(text="关闭调试面板") - - # 窗口关闭时的回调 - def on_closing(): - self.hdr_debug_btn.config(text="打开调试面板") - self.hdr_debug_window.destroy() - self.log_gui.log("✓ HDR 单步调试窗口已关闭") - - self.hdr_debug_window.protocol("WM_DELETE_WINDOW", on_closing) - self.hdr_debug_window.update_idletasks() - - self.log_gui.log("✓ HDR 单步调试面板已打开(独立窗口)") + _toggle_debug_panel(self, "hdr_movie") + +def update_sidebar_selection(self): + """更新侧边栏按钮的选中状态""" + # 重置所有按钮样式为默认 + self.screen_module_btn.configure(style="Sidebar.TButton") + self.sdr_movie_btn.configure(style="Sidebar.TButton") + self.hdr_movie_btn.configure(style="Sidebar.TButton") + + # 设置当前选中按钮的样式 + current_type = self.test_type_var.get() + if current_type == "screen_module": + self.screen_module_btn.configure(style="SidebarSelected.TButton") + elif current_type == "sdr_movie": + self.sdr_movie_btn.configure(style="SidebarSelected.TButton") + elif current_type == "hdr_movie": + self.hdr_movie_btn.configure(style="SidebarSelected.TButton") diff --git a/pqAutomationApp.py b/pqAutomationApp.py index 770c89c..b4156f2 100644 --- a/pqAutomationApp.py +++ b/pqAutomationApp.py @@ -6,34 +6,24 @@ import threading import time import os import datetime -import colour -import json +import re import traceback import numpy as np import matplotlib.pyplot as plt -import matplotlib.image as mpimg -import algorithm.pq_algorithm as pq_algorithm -from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg from app_version import APP_NAME, APP_VERSION, get_app_title -from drivers.caSerail import CASerail -from drivers.tvSerail import tvSerial from drivers.UCD323_Function import UCDController -from drivers.UCD323_Enum import UCDEnum from app.pq.pq_config import PQConfig from app.pq.pq_result import PQResult -from app.data_range_converter import convert_pattern_params -from PIL import Image, ImageTk -from app.views.collapsing_frame import CollapsingFrame -from app.views.pq_log_gui import PQLogGUI -from colormath.color_objects import xyYColor, LabColor -from colormath.color_conversions import convert_color -from colormath.color_diff import delta_e_cie2000 from app.views.pq_debug_panel import PQDebugPanel from app.export import ( save_result_images as _save_result_images_impl, export_excel_report as _export_excel_report_impl, EXCEL_EXPORT_CONFIG as _EXCEL_EXPORT_CONFIG, ) +from app.views.panels import custom_template_panel as _ctp +from app.views.panels import side_panels as _sp +from app.views.panels import cct_panel as _ccp +from app.views.panels import main_layout as _main # Step 0/1 重构:资源工具和纯算法已迁移到 app/ 包,这里重新导入以保持 # 对原函数名/方法名的向后兼容(老代码内部仍用 self.calculate_* 调用)。 @@ -208,22 +198,16 @@ class PQAutomationApp: # 创建日志显示区域 self.create_log_panel() - # 创建 Local Dimming 面板 self.create_local_dimming_panel() - # 创建测试类型选择区域 self.create_test_type_frame() - # 创建操作按钮区域 self.create_operation_frame() - # 创建结果图表区域 self.create_result_chart_frame() - # 创建客户模板结果显示区域(黑底表格) self.create_custom_template_result_panel() - # 在所有控件创建完成后,统一初始化测试类型 self.root.after(100, self.initialize_default_test_type) @@ -257,7 +241,6 @@ class PQAutomationApp: except Exception: pass - get_config_path = _cfg_get_config_path def initialize_default_test_type(self): """初始化默认测试类型(在所有控件创建完成后调用)""" try: @@ -270,6 +253,10 @@ class PQAutomationApp: if hasattr(self, "log_gui"): self.log_gui.log(f"初始化默认测试类型失败: {str(e)}") + get_config_path = _cfg_get_config_path + load_pq_config = _cfg_load_pq_config + save_pq_config = _cfg_save_pq_config + init_gamut_chart = _cf_init_gamut_chart init_gamma_chart = _cf_init_gamma_chart init_eotf_chart = _cf_init_eotf_chart @@ -277,818 +264,82 @@ class PQAutomationApp: init_contrast_chart = _cf_init_contrast_chart init_accuracy_chart = _cf_init_accuracy_chart clear_chart = _cf_clear_chart - def create_floating_config_panel(self): - """创建右上角悬浮配置框""" - cf = CollapsingFrame(self.control_frame_top) - cf.pack(fill="both") - # 创建悬浮框主容器 - self.config_panel_frame = ttk.Frame(cf) - cf.add(self.config_panel_frame, title="配置项") + create_result_chart_frame = _cf_create_result_chart_frame + on_chart_tab_changed = _cf_on_chart_tab_changed + + create_floating_config_panel = _main.create_floating_config_panel + create_test_items_content = _main.create_test_items_content + create_signal_format_content = _main.create_signal_format_content + create_connection_content = _main.create_connection_content + create_operation_frame = _main.create_operation_frame + create_test_type_frame = _main.create_test_type_frame + update_config_info_display = _main.update_config_info_display + on_screen_module_timing_changed = _main.on_screen_module_timing_changed + + create_cct_params_frame = _ccp.create_cct_params_frame + _get_cct_var_dict = _ccp._get_cct_var_dict + _parse_cct_float = _ccp._parse_cct_float + _save_cct_params_for = _ccp._save_cct_params_for + _handle_cct_focus_out = _ccp._handle_cct_focus_out + on_sdr_cct_param_focus_out = _ccp.on_sdr_cct_param_focus_out + save_sdr_cct_params = _ccp.save_sdr_cct_params + on_hdr_cct_param_focus_out = _ccp.on_hdr_cct_param_focus_out + save_hdr_cct_params = _ccp.save_hdr_cct_params + recalculate_cct = _ccp.recalculate_cct + recalculate_gamut = _ccp.recalculate_gamut + on_cct_param_focus_out = _ccp.on_cct_param_focus_out + save_cct_params = _ccp.save_cct_params + reload_cct_params = _ccp.reload_cct_params + toggle_cct_params_frame = _ccp.toggle_cct_params_frame + on_screen_gamut_ref_changed = _ccp.on_screen_gamut_ref_changed + on_sdr_gamut_ref_changed = _ccp.on_sdr_gamut_ref_changed + on_hdr_gamut_ref_changed = _ccp.on_hdr_gamut_ref_changed + + get_available_ucd_ports = _dev_get_available_ucd_ports + get_available_com_ports = _dev_get_available_com_ports + refresh_com_ports = _dev_refresh_com_ports + check_com_connections = _dev_check_com_connections + update_connection_indicator = _dev_update_connection_indicator + check_port_connection = _dev_check_port_connection + enable_com_widgets = _dev_enable_com_widgets + disconnect_com_connections = _dev_disconnect_com_connections + + create_custom_template_result_panel = _ctp.create_custom_template_result_panel + show_custom_result_context_menu = _ctp.show_custom_result_context_menu + set_custom_result_table_locked = _ctp.set_custom_result_table_locked + start_custom_row_single_step = _ctp.start_custom_row_single_step + _clear_custom_result_row = _ctp._clear_custom_result_row + _run_custom_row_single_step = _ctp._run_custom_row_single_step + _update_custom_result_row = _ctp._update_custom_result_row + copy_custom_result_table = _ctp.copy_custom_result_table + fill_custom_result_test_data = _ctp.fill_custom_result_test_data + clear_custom_template_results = _ctp.clear_custom_template_results + auto_expand_custom_result_view = _ctp.auto_expand_custom_result_view + append_custom_template_result = _ctp.append_custom_template_result + start_custom_template_test = _ctp.start_custom_template_test + update_custom_button_visibility = _ctp.update_custom_button_visibility + + create_log_panel = _sp.create_log_panel + create_local_dimming_panel = _sp.create_local_dimming_panel + toggle_local_dimming_panel = _sp.toggle_local_dimming_panel + toggle_log_panel = _sp.toggle_log_panel + update_sidebar_selection = _sp.update_sidebar_selection + + # ---- 单步调试面板(统一实现,委托到 side_panels 模块) ---- + _toggle_debug_panel = _sp._toggle_debug_panel + toggle_screen_debug_panel = _sp.toggle_screen_debug_panel + toggle_sdr_debug_panel = _sp.toggle_sdr_debug_panel + toggle_hdr_debug_panel = _sp.toggle_hdr_debug_panel + + clear_config_file = _cfg_clear_config_file + start_local_dimming_test = _ld_start_local_dimming_test + update_ld_results = _ld_update_ld_results + stop_local_dimming_test = _ld_stop_local_dimming_test + send_ld_window = _ld_send_ld_window + measure_ld_luminance = _ld_measure_ld_luminance + clear_ld_records = _ld_clear_ld_records + save_local_dimming_results = _ld_save_local_dimming_results - # 创建一个统一的frame来替代选项卡控件 - self.config_content_frame = ttk.Frame(self.config_panel_frame) - self.config_content_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) - - # 创建一个横向排列的Frame - config_row_frame = ttk.Frame(self.config_content_frame) - config_row_frame.pack(fill=tk.X, expand=False, padx=5, pady=5) - - # 创建连接内容区域 - self.connection_frame = ttk.LabelFrame(config_row_frame, text="设备连接") - self.connection_frame.pack( - side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5 - ) - - # 创建测试项目区域 - self.test_items_frame = ttk.LabelFrame(config_row_frame, text="测试项目") - self.test_items_frame.pack( - side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5 - ) - - # 创建信号格式区域 - self.signal_format_frame = ttk.LabelFrame(config_row_frame, text="信号格式") - self.signal_format_frame.pack( - side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5 - ) - - # 创建连接内容 - self.create_connection_content() - # 创建测试项目内容 - self.create_test_items_content() - # 创建信号格式内容 - self.create_signal_format_content() - - self.config_panel_frame.grid_remove() - self.config_panel_frame.btn.configure(image="closed") - - def create_test_items_content(self): - """创建测试项目选项卡内容""" - # 创建测试项目字典,用于管理不同测试类型的选项 - self.test_items = { - "screen_module": { - "frame": ttk.Frame(self.test_items_frame), - "items": [ - ("色域", "gamut"), - ("Gamma", "gamma"), - ("色度", "cct"), - ("对比度", "contrast"), - ], - }, - "sdr_movie": { - "frame": ttk.Frame(self.test_items_frame), - "items": [ - ("色域", "gamut"), - ("Gamma", "gamma"), - ("色度", "cct"), - ("对比度", "contrast"), - ("色准", "accuracy"), - ], - }, - "hdr_movie": { - "frame": ttk.Frame(self.test_items_frame), - "items": [ - ("色域", "gamut"), - ("EOTF", "eotf"), - ("色度", "cct"), - ("对比度", "contrast"), - ("色准", "accuracy"), - ], - }, - } - - # 根据当前测试类型创建复选框 - self.test_vars = {} - self.update_test_items() - - # 创建色度参数设置框架 - self.create_cct_params_frame() - - def create_cct_params_frame(self): - """创建色度参数设置区域 - 屏模组、SDR、HDR 独立(✅ 增加色域参考标准选择 + 单步调试按钮)""" - - # ==================== 屏模组色度参数 Frame ==================== - self.cct_params_frame = ttk.LabelFrame( - self.test_items_frame, text="色度参数设置(屏模组)" - ) - - # 默认值 - screen_default_cct_params = self.config.get_default_cct_params("screen_module") - - # 从配置读取屏模组参数 - saved_params = self.config.current_test_types.get("screen_module", {}).get( - "cct_params", screen_default_cct_params.copy() - ) - - # 色域参考标准 - saved_gamut_ref = self.config.current_test_types.get("screen_module", {}).get( - "gamut_reference", self.config.get_default_gamut_reference("screen_module") - ) - - # 创建屏模组变量 - self.cct_x_ideal_var = tk.StringVar( - value=str(saved_params.get("x_ideal", 0.3127)) - ) - self.cct_x_tolerance_var = tk.StringVar( - value=str(saved_params.get("x_tolerance", 0.003)) - ) - self.cct_y_ideal_var = tk.StringVar( - value=str(saved_params.get("y_ideal", 0.3290)) - ) - self.cct_y_tolerance_var = tk.StringVar( - value=str(saved_params.get("y_tolerance", 0.003)) - ) - self.screen_gamut_ref_var = tk.StringVar(value=saved_gamut_ref) - - # 创建屏模组输入框(左侧:色度参数) - params = [ - ("x-ideal:", self.cct_x_ideal_var, "x_ideal"), - ("x-tolerance:", self.cct_x_tolerance_var, "x_tolerance"), - ("y-ideal:", self.cct_y_ideal_var, "y_ideal"), - ("y-tolerance:", self.cct_y_tolerance_var, "y_tolerance"), - ] - - for i, (label_text, var, key) in enumerate(params): - ttk.Label(self.cct_params_frame, text=label_text).grid( - row=i, column=0, sticky=tk.W, padx=5, pady=3 - ) - entry = ttk.Entry(self.cct_params_frame, textvariable=var, width=15) - entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) - - # 绑定失去焦点事件 - default_val = screen_default_cct_params[key] - entry.bind( - "", - lambda e, v=var, d=default_val: self.on_cct_param_focus_out(v, d), - ) - - # 色域参考标准选择(右侧第一行) - ttk.Label(self.cct_params_frame, text="色域参考标准:").grid( - row=0, column=2, sticky=tk.W, padx=(20, 5), pady=3 - ) - screen_gamut_combo = ttk.Combobox( - self.cct_params_frame, - textvariable=self.screen_gamut_ref_var, - values=["BT.2020", "BT.709", "DCI-P3"], - state="disabled", - width=12, - ) - screen_gamut_combo.grid(row=0, column=3, sticky=tk.W, padx=5, pady=3) - screen_gamut_combo.bind( - "<>", self.on_screen_gamut_ref_changed - ) - self.screen_gamut_combo = screen_gamut_combo - - # ==================== ✅ 单步调试按钮(右侧第二行)==================== - ttk.Label(self.cct_params_frame, text="单步调试:").grid( - row=1, column=2, sticky=tk.W, padx=(20, 5), pady=3 - ) - - self.screen_debug_btn = ttk.Button( - self.cct_params_frame, - text="打开调试面板", - command=self.toggle_screen_debug_panel, - bootstyle="info-outline", - state=tk.DISABLED, # 初始禁用 - width=15, - ) - self.screen_debug_btn.grid(row=1, column=3, sticky=tk.W, padx=5, pady=3) - - # 重新计算按钮(屏模组) - self.recalc_cct_btn = ttk.Button( - self.cct_params_frame, - text="应用新参数并重绘", - command=self.recalculate_cct, - bootstyle="success", - ) - self.recalc_cct_btn.grid( - row=4, column=0, columnspan=2, pady=10, padx=5, sticky="ew" - ) - self.recalc_cct_btn.grid_remove() - - # 色域重新计算按钮 - self.recalc_gamut_btn = ttk.Button( - self.cct_params_frame, - text="应用色域参考并重绘", - command=self.recalculate_gamut, - bootstyle="warning", - ) - self.recalc_gamut_btn.grid( - row=4, column=2, columnspan=2, pady=10, padx=5, sticky="ew" - ) - self.recalc_gamut_btn.grid_remove() - - # 提示文字 - ttk.Label( - self.cct_params_frame, - text="提示: 清空输入框将恢复默认值", - font=("SimHei", 8), - foreground="gray", - ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) - - # ==================== SDR 色度参数 Frame ==================== - self.sdr_cct_params_frame = ttk.LabelFrame( - self.test_items_frame, text="色度参数设置(SDR)" - ) - - # SDR 默认值 - sdr_default_cct_params = self.config.get_default_cct_params("sdr_movie") - - # 从配置读取 SDR 参数 - sdr_saved_params = self.config.current_test_types.get("sdr_movie", {}).get( - "cct_params", sdr_default_cct_params.copy() - ) - - # 色域参考标准 - sdr_saved_gamut_ref = self.config.current_test_types.get("sdr_movie", {}).get( - "gamut_reference", self.config.get_default_gamut_reference("sdr_movie") - ) - - # 创建 SDR 变量 - self.sdr_cct_x_ideal_var = tk.StringVar( - value=str(sdr_saved_params.get("x_ideal", 0.3127)) - ) - self.sdr_cct_x_tolerance_var = tk.StringVar( - value=str(sdr_saved_params.get("x_tolerance", 0.003)) - ) - self.sdr_cct_y_ideal_var = tk.StringVar( - value=str(sdr_saved_params.get("y_ideal", 0.3290)) - ) - self.sdr_cct_y_tolerance_var = tk.StringVar( - value=str(sdr_saved_params.get("y_tolerance", 0.003)) - ) - self.sdr_gamut_ref_var = tk.StringVar(value=sdr_saved_gamut_ref) - - # 创建 SDR 输入框 - sdr_params = [ - ("x-ideal:", self.sdr_cct_x_ideal_var, "x_ideal"), - ("x-tolerance:", self.sdr_cct_x_tolerance_var, "x_tolerance"), - ("y-ideal:", self.sdr_cct_y_ideal_var, "y_ideal"), - ("y-tolerance:", self.sdr_cct_y_tolerance_var, "y_tolerance"), - ] - - for i, (label_text, var, key) in enumerate(sdr_params): - ttk.Label(self.sdr_cct_params_frame, text=label_text).grid( - row=i, column=0, sticky=tk.W, padx=5, pady=3 - ) - entry = ttk.Entry(self.sdr_cct_params_frame, textvariable=var, width=15) - entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) - - # 绑定失去焦点事件 - default_val = sdr_default_cct_params[key] - entry.bind( - "", - lambda e, v=var, d=default_val: self.on_sdr_cct_param_focus_out(v, d), - ) - - # 色域参考标准选择(右侧第一行) - ttk.Label(self.sdr_cct_params_frame, text="色域参考标准:").grid( - row=0, column=2, sticky=tk.W, padx=(20, 5), pady=3 - ) - sdr_gamut_combo = ttk.Combobox( - self.sdr_cct_params_frame, - textvariable=self.sdr_gamut_ref_var, - values=["BT.2020", "BT.709", "DCI-P3"], - state="disabled", - width=12, - ) - sdr_gamut_combo.grid(row=0, column=3, sticky=tk.W, padx=5, pady=3) - sdr_gamut_combo.bind("<>", self.on_sdr_gamut_ref_changed) - self.sdr_gamut_combo = sdr_gamut_combo - - # ==================== ✅ SDR 单步调试按钮(右侧第二行)==================== - ttk.Label(self.sdr_cct_params_frame, text="单步调试:").grid( - row=1, column=2, sticky=tk.W, padx=(20, 5), pady=3 - ) - - self.sdr_debug_btn = ttk.Button( - self.sdr_cct_params_frame, - text="打开调试面板", - command=self.toggle_sdr_debug_panel, - bootstyle="info-outline", - state=tk.DISABLED, # 初始禁用 - width=15, - ) - self.sdr_debug_btn.grid(row=1, column=3, sticky=tk.W, padx=5, pady=3) - - # 重新计算按钮(SDR) - self.sdr_recalc_cct_btn = ttk.Button( - self.sdr_cct_params_frame, - text="应用新参数并重绘", - command=self.recalculate_cct, - bootstyle="success", - ) - self.sdr_recalc_cct_btn.grid( - row=4, column=0, columnspan=2, pady=10, padx=5, sticky="ew" - ) - self.sdr_recalc_cct_btn.grid_remove() - - # 色域重新计算按钮(SDR) - self.sdr_recalc_gamut_btn = ttk.Button( - self.sdr_cct_params_frame, - text="应用色域参考并重绘", - command=self.recalculate_gamut, - bootstyle="warning", - ) - self.sdr_recalc_gamut_btn.grid( - row=4, column=2, columnspan=2, pady=10, padx=5, sticky="ew" - ) - self.sdr_recalc_gamut_btn.grid_remove() - - # 提示文字 - ttk.Label( - self.sdr_cct_params_frame, - text="提示: 清空输入框将恢复默认值", - font=("SimHei", 8), - foreground="gray", - ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) - - # ==================== HDR 色度参数 Frame ==================== - self.hdr_cct_params_frame = ttk.LabelFrame( - self.test_items_frame, text="色度参数设置(HDR)" - ) - - # HDR 默认值 - hdr_default_cct_params = self.config.get_default_cct_params("hdr_movie") - - # 从配置读取 HDR 参数 - hdr_saved_params = self.config.current_test_types.get("hdr_movie", {}).get( - "cct_params", hdr_default_cct_params.copy() - ) - - # 色域参考标准 - hdr_saved_gamut_ref = self.config.current_test_types.get("hdr_movie", {}).get( - "gamut_reference", self.config.get_default_gamut_reference("hdr_movie") - ) - - # 创建 HDR 变量 - self.hdr_cct_x_ideal_var = tk.StringVar( - value=str(hdr_saved_params.get("x_ideal", 0.3127)) - ) - self.hdr_cct_x_tolerance_var = tk.StringVar( - value=str(hdr_saved_params.get("x_tolerance", 0.003)) - ) - self.hdr_cct_y_ideal_var = tk.StringVar( - value=str(hdr_saved_params.get("y_ideal", 0.3290)) - ) - self.hdr_cct_y_tolerance_var = tk.StringVar( - value=str(hdr_saved_params.get("y_tolerance", 0.003)) - ) - self.hdr_gamut_ref_var = tk.StringVar(value=hdr_saved_gamut_ref) - - # 创建 HDR 输入框 - hdr_params = [ - ("x-ideal:", self.hdr_cct_x_ideal_var, "x_ideal"), - ("x-tolerance:", self.hdr_cct_x_tolerance_var, "x_tolerance"), - ("y-ideal:", self.hdr_cct_y_ideal_var, "y_ideal"), - ("y-tolerance:", self.hdr_cct_y_tolerance_var, "y_tolerance"), - ] - - for i, (label_text, var, key) in enumerate(hdr_params): - ttk.Label(self.hdr_cct_params_frame, text=label_text).grid( - row=i, column=0, sticky=tk.W, padx=5, pady=3 - ) - entry = ttk.Entry(self.hdr_cct_params_frame, textvariable=var, width=15) - entry.grid(row=i, column=1, sticky=tk.W, padx=5, pady=3) - - # 绑定失去焦点事件 - default_val = hdr_default_cct_params[key] - entry.bind( - "", - lambda e, v=var, d=default_val: self.on_hdr_cct_param_focus_out(v, d), - ) - - # 色域参考标准选择(右侧第一行) - ttk.Label(self.hdr_cct_params_frame, text="色域参考标准:").grid( - row=0, column=2, sticky=tk.W, padx=(20, 5), pady=3 - ) - hdr_gamut_combo = ttk.Combobox( - self.hdr_cct_params_frame, - textvariable=self.hdr_gamut_ref_var, - values=["BT.2020", "BT.709", "DCI-P3"], - state="disabled", - width=12, - ) - hdr_gamut_combo.grid(row=0, column=3, sticky=tk.W, padx=5, pady=3) - hdr_gamut_combo.bind("<>", self.on_hdr_gamut_ref_changed) - self.hdr_gamut_combo = hdr_gamut_combo - - # ==================== ✅ HDR 单步调试按钮(右侧第二行)==================== - ttk.Label(self.hdr_cct_params_frame, text="单步调试:").grid( - row=1, column=2, sticky=tk.W, padx=(20, 5), pady=3 - ) - - self.hdr_debug_btn = ttk.Button( - self.hdr_cct_params_frame, - text="打开调试面板", - command=self.toggle_hdr_debug_panel, - bootstyle="info-outline", - state=tk.DISABLED, # 初始禁用 - width=15, - ) - self.hdr_debug_btn.grid(row=1, column=3, sticky=tk.W, padx=5, pady=3) - - # 重新计算按钮(HDR) - self.hdr_recalc_cct_btn = ttk.Button( - self.hdr_cct_params_frame, - text="应用新参数并重绘", - command=self.recalculate_cct, - bootstyle="success", - ) - self.hdr_recalc_cct_btn.grid( - row=4, column=0, columnspan=2, pady=10, padx=5, sticky="ew" - ) - self.hdr_recalc_cct_btn.grid_remove() - - # 色域重新计算按钮(HDR) - self.hdr_recalc_gamut_btn = ttk.Button( - self.hdr_cct_params_frame, - text="应用色域参考并重绘", - command=self.recalculate_gamut, - bootstyle="warning", - ) - self.hdr_recalc_gamut_btn.grid( - row=4, column=2, columnspan=2, pady=10, padx=5, sticky="ew" - ) - self.hdr_recalc_gamut_btn.grid_remove() - - # 提示文字 - ttk.Label( - self.hdr_cct_params_frame, - text="提示: 清空输入框将恢复默认值", - font=("SimHei", 8), - foreground="gray", - ).grid(row=5, column=0, columnspan=4, sticky=tk.W, padx=5, pady=5) - - def _get_cct_var_dict(self, test_type): - """按测试类型返回 CCT 变量映射。""" - if test_type == "sdr_movie": - return { - "x_ideal": self.sdr_cct_x_ideal_var, - "x_tolerance": self.sdr_cct_x_tolerance_var, - "y_ideal": self.sdr_cct_y_ideal_var, - "y_tolerance": self.sdr_cct_y_tolerance_var, - } - if test_type == "hdr_movie": - return { - "x_ideal": self.hdr_cct_x_ideal_var, - "x_tolerance": self.hdr_cct_x_tolerance_var, - "y_ideal": self.hdr_cct_y_ideal_var, - "y_tolerance": self.hdr_cct_y_tolerance_var, - } - return { - "x_ideal": self.cct_x_ideal_var, - "x_tolerance": self.cct_x_tolerance_var, - "y_ideal": self.cct_y_ideal_var, - "y_tolerance": self.cct_y_tolerance_var, - } - - def _parse_cct_float(self, var, default): - """读取并解析 CCT 输入值,失败时回落默认值。""" - try: - value = var.get().strip() - if value == "": - return default - return float(value) - except Exception: - return default - - def _save_cct_params_for(self, test_type): - """保存指定测试类型的 CCT 参数。""" - try: - default_params = self.config.get_default_cct_params(test_type) - var_dict = self._get_cct_var_dict(test_type) - cct_params = { - key: self._parse_cct_float(var_dict[key], default_params[key]) - for key in default_params - } - - if test_type not in self.config.current_test_types: - self.config.current_test_types[test_type] = {} - - self.config.current_test_types[test_type]["cct_params"] = cct_params - self.save_pq_config() - except Exception: - pass - - def _handle_cct_focus_out(self, var, default_value, save_func, label): - """统一处理 CCT 参数失焦校验并保存。""" - try: - value = var.get().strip() - if value == "": - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"✓ {label} 参数为空,恢复默认值: {default_value}") - else: - try: - float_val = float(value) - if float_val < 0 or float_val > 1: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log( - f"⚠️ {label} 参数超出范围,恢复默认值: {default_value}" - ) - except ValueError: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log( - f"⚠️ {label} 参数无效,恢复默认值: {default_value}" - ) - - save_func() - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"处理 {label} 参数失败: {str(e)}") - - def on_sdr_cct_param_focus_out(self, var, default_value): - """SDR 色度参数失去焦点时的处理。""" - self._handle_cct_focus_out(var, default_value, self.save_sdr_cct_params, "SDR") - - def save_sdr_cct_params(self): - """保存 SDR 色度参数。""" - self._save_cct_params_for("sdr_movie") - - def on_hdr_cct_param_focus_out(self, var, default_value): - """HDR 色度参数失去焦点时的处理。""" - self._handle_cct_focus_out(var, default_value, self.save_hdr_cct_params, "HDR") - - def save_hdr_cct_params(self): - """保存 HDR 色度参数。""" - self._save_cct_params_for("hdr_movie") - - def recalculate_cct(self): - """重新计算并绘制色度图""" - try: - # 1. 保存新参数 - self.save_cct_params() - self.log_gui.log("✓ 色度参数已更新") - - # 2. 收起配置项 - if hasattr(self, "config_panel_frame"): - try: - if self.config_panel_frame.winfo_viewable(): - self.config_panel_frame.btn.invoke() - self.root.update_idletasks() - time.sleep(0.1) - except: - pass - - # 3. 跳转到色度图Tab - self.chart_notebook.select(self.cct_chart_frame) - self.root.update_idletasks() - - # 4. 检查是否有数据 - if not hasattr(self, "results") or not self.results: - self.log_gui.log("⚠️ 没有测试数据,无法重新绘制") - messagebox.showwarning("警告", "请先完成测试后再重新计算") - return - - # 5. 获取保存的灰阶数据 - gray_data = self.results.get_intermediate_data("shared", "gray") - if not gray_data: - gray_data = self.results.get_intermediate_data("cct", "gray") - - if not gray_data or len(gray_data) < 2: - self.log_gui.log("⚠️ 没有可用的灰阶数据") - messagebox.showwarning("警告", "没有找到色度测试数据") - return - - # 6. 重新计算 CCT - self.log_gui.log("=" * 50) - self.log_gui.log("开始重新计算色度一致性...") - self.log_gui.log("=" * 50) - - import algorithm.pq_algorithm as pq_algorithm - - cct_values = pq_algorithm.calculate_cct_from_results(gray_data) - - # 7. 更新结果 - self.results.set_test_item_result("cct", {"cct_values": cct_values}) - - # 8. 重新绘制色度图 - test_type = self.config.current_test_type - self.plot_cct(test_type) - - self.log_gui.log("✓ 色度图已重新绘制") - self.log_gui.log("=" * 50) - - messagebox.showinfo("成功", "色度图已根据新参数重新绘制!") - - except Exception as e: - self.log_gui.log(f"❌ 重新计算失败: {str(e)}") - import traceback - - self.log_gui.log(traceback.format_exc()) - messagebox.showerror("错误", f"重新计算失败: {str(e)}") - - def recalculate_gamut(self): - """重新计算并绘制色域图(使用新的参考标准)""" - try: - # 1. 收起配置项 - if hasattr(self, "config_panel_frame"): - try: - if self.config_panel_frame.winfo_viewable(): - self.config_panel_frame.btn.invoke() - self.root.update_idletasks() - time.sleep(0.1) - except: - pass - - # 2. 跳转到色域图Tab - self.chart_notebook.select(self.gamut_chart_frame) - self.root.update_idletasks() - - # 3. 检查是否有数据 - if not hasattr(self, "results") or not self.results: - self.log_gui.log("⚠️ 没有测试数据,无法重新绘制") - messagebox.showwarning("警告", "请先完成测试后再重新计算") - return - - # 4. 获取保存的色域数据 - rgb_data = self.results.get_intermediate_data("gamut", "rgb") - - if not rgb_data or len(rgb_data) < 3: - self.log_gui.log("⚠️ 没有可用的色域数据") - messagebox.showwarning("警告", "没有找到色域测试数据") - return - - # 5. 获取当前测试类型 - test_type = self.config.current_test_type - - # 6. 获取用户选择的参考标准 - if test_type == "screen_module": - reference_standard = self.screen_gamut_ref_var.get() - elif test_type == "sdr_movie": - reference_standard = self.sdr_gamut_ref_var.get() - elif test_type == "hdr_movie": - reference_standard = self.hdr_gamut_ref_var.get() - else: - reference_standard = "DCI-P3" - - self.log_gui.log("=" * 50) - self.log_gui.log(f"开始重新计算色域(参考标准: {reference_standard})...") - self.log_gui.log("=" * 50) - - # 7. 重新计算 XY 色域覆盖率 - xy_points = [[result[0], result[1]] for result in rgb_data] - - # 根据参考标准计算 XY 覆盖率 - if reference_standard == "BT.2020": - area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_BT2020( - xy_points - ) - elif reference_standard == "BT.709": - area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_BT709( - xy_points - ) - elif reference_standard == "DCI-P3": - area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_DCIP3( - xy_points - ) - else: - area_xy, coverage_xy = pq_algorithm.calculate_gamut_coverage_DCIP3( - xy_points - ) - reference_standard = "DCI-P3" - - self.log_gui.log(f"✓ 参考标准: {reference_standard}") - self.log_gui.log(f"✓ XY 色域覆盖率: {coverage_xy:.1f}%") - - # ========== ✅✅✅ 8. 重新计算 UV 色域覆盖率 ========== - # 将 XY 坐标转换为 UV 坐标 - uv_points = [] - for x, y in xy_points: - try: - # XY转UV公式 - denom = -2 * x + 12 * y + 3 - if abs(denom) < 1e-10: - u, v = 0, 0 - else: - u = 4 * x / denom - v = 9 * y / denom - uv_points.append([u, v]) - except ZeroDivisionError: - continue - - self.log_gui.log(f"✓ 转换后的 UV 点数量: {len(uv_points)}") - - # 根据参考标准计算 UV 覆盖率 - if reference_standard == "BT.2020": - area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_BT2020_uv( - uv_points - ) - elif reference_standard == "BT.709": - area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_BT709_uv( - uv_points - ) - elif reference_standard == "DCI-P3": - area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_DCIP3_uv( - uv_points - ) - else: - area_uv, coverage_uv = pq_algorithm.calculate_gamut_coverage_DCIP3_uv( - uv_points - ) - - self.log_gui.log(f"✓ UV 色域覆盖率: {coverage_uv:.1f}%") - # ======================================================== - - # 9. ✅ 更新结果(同时保存 XY 和 UV 覆盖率) - self.results.set_test_item_result( - "gamut", - { - "area": area_xy, # ← 兼容旧字段 - "coverage": coverage_xy, # ← 兼容旧字段 - "area_xy": area_xy, # ← XY 面积 - "coverage_xy": coverage_xy, # ← XY 覆盖率 - "area_uv": area_uv, # ← UV 面积 - "coverage_uv": coverage_uv, # ← UV 覆盖率 - "uv_coverage": coverage_uv, # ← 兼容字段(Excel 导出用) - "reference": reference_standard, - }, - ) - - self.log_gui.log("✓ 测试结果已更新到 results 对象") - - # 10. 重新绘制色域图 - self.plot_gamut(rgb_data, coverage_xy, test_type) - - self.log_gui.log("✓ 色域图已重新绘制") - self.log_gui.log("=" * 50) - - messagebox.showinfo( - "成功", - f"色域图已根据新参考标准 {reference_standard} 重新绘制!\n\n" - f"XY 覆盖率: {coverage_xy:.1f}%\n" - f"UV 覆盖率: {coverage_uv:.1f}%", - ) - - except Exception as e: - self.log_gui.log(f"❌ 重新计算失败: {str(e)}") - import traceback - - self.log_gui.log(traceback.format_exc()) - messagebox.showerror("错误", f"重新计算失败: {str(e)}") - - def on_cct_param_change(self, var, default_value): - """色度参数改变时的处理 - 空值恢复默认""" - try: - value = var.get().strip() - - if value == "": - # 空值:恢复默认值 - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"输入框为空,恢复默认值: {default_value}") - else: - # 验证是否为有效数字 - try: - float_val = float(value) - if float_val < 0 or float_val > 1: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log( - f"参数超出范围 [0, 1],恢复默认值: {default_value}" - ) - except ValueError: - var.set(str(default_value)) - if hasattr(self, "log_gui"): - self.log_gui.log(f"无效的参数值,恢复默认值: {default_value}") - - # 保存配置 - self.save_cct_params() - - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"处理参数变化失败: {str(e)}") - - def on_cct_param_focus_out(self, var, default_value): - """色度参数失去焦点时的处理 - 空值恢复默认""" - self._handle_cct_focus_out(var, default_value, self.save_cct_params, "屏模组") - - def save_cct_params(self): - """保存色度参数 - 简化版""" - self._save_cct_params_for(self.config.current_test_type) - - def reload_cct_params(self): - """切换测试类型时重新加载色度参数""" - try: - current_type = self.config.current_test_type - saved_params = self.config.current_test_types.get(current_type, {}).get( - "cct_params", None - ) - - if saved_params is None: - saved_params = self.config.get_default_cct_params(current_type) - - # 更新输入框的值 - self.cct_x_ideal_var.set(str(saved_params["x_ideal"])) - self.cct_x_tolerance_var.set(str(saved_params["x_tolerance"])) - self.cct_y_ideal_var.set(str(saved_params["y_ideal"])) - self.cct_y_tolerance_var.set(str(saved_params["y_tolerance"])) - - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"重新加载色度参数失败: {str(e)}") def update_test_items(self): """根据当前测试类型更新测试项目复选框""" @@ -1150,993 +401,6 @@ class PQAutomationApp: } return display_names.get(test_type, test_type) - def create_signal_format_content(self): - """创建信号格式选项卡内容""" - self.signal_tabs = ttk.Notebook(self.signal_format_frame) - self.signal_tabs.pack(fill=tk.BOTH, expand=True) - - # ==================== 屏模组格式设置 ==================== - self.screen_module_signal_frame = ttk.Frame(self.signal_tabs) - self.screen_module_signal_frame.grid_columnconfigure(0, weight=1) - self.signal_tabs.add(self.screen_module_signal_frame, text="屏模组测试") - - self.screen_module_timing_var = tk.StringVar( - value=self.config.current_test_types[self.config.current_test_type][ - "timing" - ] - ) - screen_module_timing_combo = ttk.Combobox( - self.screen_module_signal_frame, - textvariable=self.screen_module_timing_var, - values=UCDEnum.TimingInfo.get_formatted_resolution_list(), - state="readonly", - ) - screen_module_timing_combo.bind( - "<>", self.on_screen_module_timing_changed - ) - screen_module_timing_combo.grid(row=0, column=0, sticky="ew", padx=5, pady=5) - - # ==================== SDR信号格式设置 ==================== - self.sdr_signal_frame = ttk.Frame(self.signal_tabs) - # 配置列权重 - self.sdr_signal_frame.grid_columnconfigure(0, weight=0) - self.sdr_signal_frame.grid_columnconfigure(1, weight=1) - self.signal_tabs.add(self.sdr_signal_frame, text="SDR测试") - - # 色彩空间 - ttk.Label(self.sdr_signal_frame, text="色彩空间:").grid( - row=0, column=0, sticky=tk.W, padx=5, pady=2 - ) - self.sdr_color_space_var = tk.StringVar(value="BT.709") - sdr_color_space_combo = ttk.Combobox( - self.sdr_signal_frame, - textvariable=self.sdr_color_space_var, - values=["BT.709", "BT.601", "BT.2020"], - width=10, - state="readonly", - ) - sdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2) - - # Gamma - ttk.Label(self.sdr_signal_frame, text="Gamma:").grid( - row=1, column=0, sticky=tk.W, padx=5, pady=2 - ) - self.sdr_gamma_type_var = tk.StringVar(value="2.2") - sdr_gamma_combo = ttk.Combobox( - self.sdr_signal_frame, - textvariable=self.sdr_gamma_type_var, - values=["2.2", "2.4", "2.6"], - width=10, - state="readonly", - ) - sdr_gamma_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2) - - # 数据范围 - ttk.Label(self.sdr_signal_frame, text="数据范围:").grid( - row=2, column=0, sticky=tk.W, padx=5, pady=2 - ) - self.sdr_data_range_var = tk.StringVar(value="Full") - sdr_range_combo = ttk.Combobox( - self.sdr_signal_frame, - textvariable=self.sdr_data_range_var, - values=["Full", "Limited"], - width=10, - state="readonly", - ) - sdr_range_combo.grid(row=2, column=1, sticky=tk.W, padx=5, pady=2) - - # 编码位深 - ttk.Label(self.sdr_signal_frame, text="编码位深:").grid( - row=3, column=0, sticky=tk.W, padx=5, pady=2 - ) - self.sdr_bit_depth_var = tk.StringVar(value="8bit") - sdr_bit_depth_combo = ttk.Combobox( - self.sdr_signal_frame, - textvariable=self.sdr_bit_depth_var, - values=["8bit", "10bit", "12bit"], - width=10, - state="readonly", - ) - sdr_bit_depth_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2) - - # ==================== HDR信号格式设置 ==================== - self.hdr_signal_frame = ttk.Frame(self.signal_tabs) - # 配置列权重 - self.hdr_signal_frame.grid_columnconfigure(0, weight=0) - self.hdr_signal_frame.grid_columnconfigure(1, weight=1) - self.signal_tabs.add(self.hdr_signal_frame, text="HDR") - - # 色彩空间 - ttk.Label(self.hdr_signal_frame, text="色彩空间:").grid( - row=0, column=0, sticky=tk.W, padx=5, pady=2 - ) - self.hdr_color_space_var = tk.StringVar(value="BT.2020") - hdr_color_space_combo = ttk.Combobox( - self.hdr_signal_frame, - textvariable=self.hdr_color_space_var, - values=["BT.2020", "DCI-P3"], - width=10, - state="readonly", - ) - hdr_color_space_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2) - - # Metadata设置 - ttk.Label(self.hdr_signal_frame, text="Metadata:").grid( - row=1, column=0, sticky=tk.W, padx=5, pady=2 - ) - self.hdr_metadata_frame = ttk.Frame(self.hdr_signal_frame) - self.hdr_metadata_frame.grid( - row=1, column=1, rowspan=2, sticky=tk.W, padx=5, pady=2 - ) - - ttk.Label(self.hdr_metadata_frame, text="MaxCLL:").grid( - row=0, column=0, sticky=tk.W - ) - self.hdr_maxcll_var = tk.StringVar(value="1000") - ttk.Entry( - self.hdr_metadata_frame, textvariable=self.hdr_maxcll_var, width=6 - ).grid(row=0, column=1, padx=2) - - ttk.Label(self.hdr_metadata_frame, text="MaxFALL:").grid( - row=1, column=0, sticky=tk.W - ) - self.hdr_maxfall_var = tk.StringVar(value="400") - ttk.Entry( - self.hdr_metadata_frame, textvariable=self.hdr_maxfall_var, width=6 - ).grid(row=1, column=1, padx=2) - - # 数据范围 - ttk.Label(self.hdr_signal_frame, text="数据范围:").grid( - row=3, column=0, sticky=tk.W, padx=5, pady=2 - ) - self.hdr_data_range_var = tk.StringVar(value="Full") - hdr_range_combo = ttk.Combobox( - self.hdr_signal_frame, - textvariable=self.hdr_data_range_var, - values=["Full", "Limited"], - width=10, - state="readonly", - ) - hdr_range_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2) - - # 编码位深 - ttk.Label(self.hdr_signal_frame, text="编码位深:").grid( - row=4, column=0, sticky=tk.W, padx=5, pady=2 - ) - self.hdr_bit_depth_var = tk.StringVar(value="8bit") - hdr_bit_depth_combo = ttk.Combobox( - self.hdr_signal_frame, - textvariable=self.hdr_bit_depth_var, - values=["8bit", "10bit", "12bit"], - width=10, - state="readonly", - ) - hdr_bit_depth_combo.grid(row=4, column=1, sticky=tk.W, padx=5, pady=2) - - # ==================== 初始化:默认只启用屏模组 Tab ==================== - self.signal_tabs.select(0) # 选中屏模组 - self.signal_tabs.tab(1, state="disabled") # 禁用 SDR - self.signal_tabs.tab(2, state="disabled") # 禁用 HDR - - def create_connection_content(self): - """创建设备连接区域""" - # 创建设备连接区域的主框架 - com_frame = ttk.Frame(self.connection_frame) - com_frame.pack(fill=tk.X, pady=5) - - # 获取可用的COM端口列表 - available_ports = self.get_available_com_ports() - - # 使用网格布局,更整齐 - ttk.Label(com_frame, text="UCD列表:").grid( - row=0, column=0, sticky=ttk.W, padx=5, pady=3 - ) - self.ucd_list_var = tk.StringVar(value=self.config.device_config["ucd_list"]) - self.ucd_list_combo = ttk.Combobox( - com_frame, - textvariable=self.ucd_list_var, - values=available_ports, - width=10, - state="readonly", - ) - self.ucd_list_combo.grid(row=0, column=1, sticky=ttk.W, padx=5, pady=3) - self.ucd_list_combo.bind("<>", self.update_config) - - # 添加UCD连接状态指示器 - self.ucd_status_indicator = tk.Canvas( - com_frame, width=15, height=15, bg="gray", highlightthickness=0 - ) - self.ucd_status_indicator.grid(row=0, column=2, padx=(10, 20)) - self.ucd_status_indicator.config(bg="gray") - - # 添加按钮框架 - button_frame = ttk.Frame(com_frame) - button_frame.grid(row=3, column=0, columnspan=3, pady=3, sticky="w") - - connect_icon = load_icon("assets/connect-svgrepo-com.png") - self.check_button = ttk.Button( - button_frame, - image=connect_icon, - bootstyle="link", - takefocus=False, - command=self.check_com_connections, - ) - self.check_button.image = connect_icon - self.check_button.pack(side="left", padx=0, pady=3) - - disconnect_icon = load_icon("assets/disconnect-svgrepo-com.png") - # 断开连接按钮 - self.disconnect_button = ttk.Button( - button_frame, - image=disconnect_icon, - bootstyle="link", - takefocus=False, - command=self.disconnect_com_connections, - ) - self.disconnect_button.image = disconnect_icon # 防止图标被垃圾回收 - self.disconnect_button.pack(side="left", padx=0, pady=3) - - refresh_icon = load_icon("assets/refresh-svgrepo-com.png") - self.refresh_button = ttk.Button( - button_frame, - image=refresh_icon, - bootstyle="link", - takefocus=False, - command=self.refresh_com_ports, - ) - self.refresh_button.image = refresh_icon # 防止图标被垃圾回收 - self.refresh_button.pack(side="left", padx=0, pady=3) - - # CA端口 - ttk.Label(com_frame, text="CA端口:").grid( - row=1, column=0, sticky=ttk.W, padx=5, pady=3 - ) - self.ca_com_var = tk.StringVar(value=self.config.device_config["ca_com"]) - self.ca_com_combo = ttk.Combobox( - com_frame, - textvariable=self.ca_com_var, - values=available_ports, - width=10, - state="readonly", - ) - self.ca_com_combo.grid(row=1, column=1, sticky=ttk.W, padx=5, pady=3) - self.ca_com_combo.bind("<>", self.update_config) - - # 添加CA连接状态指示器 - self.ca_status_indicator = tk.Canvas( - com_frame, width=15, height=15, bg="gray", highlightthickness=0 - ) - self.ca_status_indicator.grid(row=1, column=2, padx=(10, 20)) - self.ca_status_indicator.config(bg="gray") - - # 添加CA通道设置 - ttk.Label(com_frame, text="CA通道:").grid( - row=2, column=0, sticky=tk.W, padx=5, pady=3 - ) - self.ca_channel_var = tk.StringVar( - value=self.config.device_config["ca_channel"] - ) - ca_channel_combo = ttk.Combobox( - com_frame, - textvariable=self.ca_channel_var, - values=[str(i) for i in range(11)], - width=10, - state="readonly", - ) - ca_channel_combo.grid(row=2, column=1, sticky=ttk.W, padx=5, pady=3) - ca_channel_combo.bind("<>", self.update_config) - - get_available_ucd_ports = _dev_get_available_ucd_ports - get_available_com_ports = _dev_get_available_com_ports - refresh_com_ports = _dev_refresh_com_ports - check_com_connections = _dev_check_com_connections - update_connection_indicator = _dev_update_connection_indicator - check_port_connection = _dev_check_port_connection - enable_com_widgets = _dev_enable_com_widgets - disconnect_com_connections = _dev_disconnect_com_connections - def create_test_type_frame(self): - """创建测试类型选择区域(侧边栏形式)""" - # 设置测试类型变量 - self.test_type_var = tk.StringVar(value="screen_module") - - # 创建测试类型按钮并放置在侧边栏 - test_types = [ - ("屏模组性能测试", "screen_module"), - ("SDR Movie测试", "sdr_movie"), - ("HDR Movie测试", "hdr_movie"), - ] - - for text, type_value in test_types: - btn = ttk.Button( - master=self.sidebar_frame, - text=text, - style="Sidebar.TButton", - padding=10, - command=lambda v=type_value: self.change_test_type(v), - takefocus=False, - ) - btn.pack(fill=tk.X, padx=0, pady=1) - - # 保存按钮引用以便后续更新样式 - setattr(self, f"{type_value}_btn", btn) - - # 添加分隔线 - ttk.Separator(self.sidebar_frame, orient="horizontal").pack( - fill=tk.X, padx=10, pady=10 - ) - - # ✅ 只保留日志按钮 - self.log_btn = ttk.Button( - self.sidebar_frame, - text="测试日志", - style="Sidebar.TButton", - command=self.toggle_log_panel, - takefocus=False, - ) - self.log_btn.pack(fill=tk.X, padx=0, pady=1) - - # Local Dimming 测试按钮 - self.local_dimming_btn = ttk.Button( - self.sidebar_frame, - text="Local Dimming", - style="Sidebar.TButton", - command=self.toggle_local_dimming_panel, - takefocus=False, - ) - self.local_dimming_btn.pack(fill=tk.X, padx=0, pady=1) - - # 注册面板按钮(只保留日志) - if hasattr(self, "panels"): - if "log" in self.panels: - self.panels["log"]["button"] = self.log_btn - if "local_dimming" in self.panels: - self.panels["local_dimming"]["button"] = self.local_dimming_btn - - def update_config_info_display(self): - """更新配置信息显示""" - if hasattr(self, "config") and hasattr(self.config, "get_current_config"): - current_config = self.config.get_current_config() - - info_text = f"测试类型: {current_config.get('name', '未知')}\n" - info_text += ( - f"测试项目: {', '.join(current_config.get('test_items', []))}\n" - ) - info_text += f"信号格式: {current_config.get('signal_format', 'none')}\n" - info_text += f"色彩空间: {current_config.get('color_space', 'unknown')}\n" - info_text += f"位深度: {current_config.get('bit_depth', 'unknown')}" - - # 高亮当前选中的测试类型 - self.update_sidebar_selection() - - def create_operation_frame(self): - """创建操作按钮区域""" - operation_frame = ttk.Frame(self.control_frame_top) - operation_frame.pack(fill=tk.X, padx=5, pady=10) - - self.start_btn = ttk.Button( - operation_frame, - text="开始测试", - command=self.start_test, - style="success.TButton", - ) - self.start_btn.pack(side=tk.LEFT, padx=5) - - self.stop_btn = ttk.Button( - operation_frame, - text="停止测试", - command=self.stop_test, - style="danger.TButton", - state=tk.DISABLED, - ) - self.stop_btn.pack(side=tk.LEFT, padx=5) - - self.save_btn = ttk.Button( - operation_frame, - text="保存结果", - command=self.save_results, - state=tk.DISABLED, - ) - self.save_btn.pack(side=tk.LEFT, padx=5) - - self.clear_config_btn = ttk.Button( - operation_frame, - text="清理配置", - command=self.clear_config_file, - ) - self.clear_config_btn.pack(side=tk.LEFT, padx=5) - - self.custom_btn = ttk.Button( - operation_frame, - text="客户模版", - command=self.start_custom_template_test, - style="info.TButton", - ) - self.custom_btn.pack(side=tk.LEFT, padx=5) - self.update_custom_button_visibility() - - def create_custom_template_result_panel(self): - """创建客户模板结果显示区域(黑底表格)""" - self.custom_result_frame = ttk.LabelFrame( - self.custom_template_tab_frame, text="客户模板结果显示" - ) - self.custom_result_frame.pack( - side=tk.TOP, fill=tk.BOTH, expand=True, padx=5, pady=5 - ) - - table_container = tk.Frame( - self.custom_result_frame, - bg="#000000", - highlightthickness=1, - highlightbackground="#5a5a5a", - ) - table_container.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) - - style = ttk.Style() - style.configure( - "CustomResult.Treeview", - background="#000000", - fieldbackground="#000000", - foreground="#ffffff", - rowheight=28, - borderwidth=0, - ) - style.configure( - "CustomResult.Treeview.Heading", - background="#2f2f2f", - foreground="#f5f5f5", - font=("Microsoft YaHei", 10, "bold"), - relief="flat", - ) - style.map( - "CustomResult.Treeview", - background=[("selected", "#1f4e79")], - foreground=[("selected", "#ffffff")], - ) - style.map( - "CustomResult.Treeview.Heading", - background=[("active", "#3b3b3b")], - ) - - columns = ( - "Pattern", - "No.", - "X", - "Y", - "Z", - "x", - "y", - "Lv", - "u'", - "v'", - "Tcp", - "duv", - "λd/λc", - "Pe" - ) - - self.custom_result_tree = ttk.Treeview( - table_container, - columns=columns, - show="headings", - height=4, - style="CustomResult.Treeview", - ) - - column_widths = { - "Pattern": 90, - "No.": 60, - "X": 80, - "Y": 80, - "Z": 80, - "x": 80, - "y": 80, - "Lv": 80, - "u'": 80, - "v'": 80, - "Tcp": 90, - "duv": 80, - "λd/λc": 95, - "Pe": 80, - } - - for col in columns: - self.custom_result_tree.heading(col, text=col) - self.custom_result_tree.column( - col, - width=column_widths.get(col, 80), - minwidth=60, - anchor=tk.CENTER, - stretch=False, - ) - - y_scroll = ttk.Scrollbar( - table_container, - orient=tk.VERTICAL, - command=self.custom_result_tree.yview, - ) - x_scroll = ttk.Scrollbar( - table_container, - orient=tk.HORIZONTAL, - command=self.custom_result_tree.xview, - ) - - self.custom_result_tree.configure( - yscrollcommand=y_scroll.set, - xscrollcommand=x_scroll.set, - ) - - self.custom_result_tree.grid(row=0, column=0, sticky="nsew") - y_scroll.grid(row=0, column=1, sticky="ns") - x_scroll.grid(row=1, column=0, sticky="ew") - - # 右键菜单:复制全部数据(Excel 可直接按行列粘贴) - self.custom_result_menu = tk.Menu(self.root, tearoff=0) - self.custom_result_menu.add_command( - label="复制全部数据", - command=self.copy_custom_result_table, - ) - self.custom_result_menu.add_command( - label="单步测试", - command=self.start_custom_row_single_step, - ) - - # self.custom_result_menu.add_separator() - # self.custom_result_menu.add_command( - # label="单步测试", - # command=self.fill_custom_result_test_data, - # ) - self.custom_result_tree.bind("", self.show_custom_result_context_menu) - - table_container.grid_rowconfigure(0, weight=1) - table_container.grid_columnconfigure(0, weight=1) - - def show_custom_result_context_menu(self, event): - """显示客户模板结果右键菜单""" - if not hasattr(self, "custom_result_tree") or not hasattr( - self, "custom_result_menu" - ): - return - - if self.testing: - # 测试进行中锁定客户模板结果表,禁止右键菜单。 - return - - row_id = self.custom_result_tree.identify_row(event.y) - if row_id: - self.custom_result_tree.selection_set(row_id) - self.custom_result_tree.focus(row_id) - - has_rows = len(self.custom_result_tree.get_children()) > 0 - has_selection = len(self.custom_result_tree.selection()) > 0 - can_single_step = ( - has_selection - and self.ca is not None - and self.ucd is not None - and not self.testing - ) - try: - self.custom_result_menu.entryconfigure( - 0, - state=("normal" if has_rows else "disabled"), - ) - self.custom_result_menu.entryconfigure( - 1, - state=("normal" if can_single_step else "disabled"), - ) - self.custom_result_menu.tk_popup(event.x_root, event.y_root) - finally: - self.custom_result_menu.grab_release() - - def set_custom_result_table_locked(self, locked): - """锁定/解锁客户模板结果表(测试期间禁选择、禁右键)""" - if not hasattr(self, "custom_result_tree"): - return - - try: - self.custom_result_tree.configure(selectmode=("none" if locked else "browse")) - except Exception: - pass - - def start_custom_row_single_step(self): - """单步测试当前选中行:发送该行 pattern 并覆盖该行测量结果""" - if not hasattr(self, "custom_result_tree"): - return - - if self.ca is None or self.ucd is None: - messagebox.showerror("错误", "请先连接CA410和信号发生器") - return - - if self.testing: - messagebox.showinfo("提示", "测试进行中,无法执行单步测试") - return - - selected = self.custom_result_tree.selection() - if not selected: - messagebox.showinfo("提示", "请先选中一行再执行单步测试") - return - - item_id = selected[0] - values = self.custom_result_tree.item(item_id, "values") - if not values: - messagebox.showinfo("提示", "选中行没有有效数据") - return - - row_no = None - if len(values) > 1: - try: - row_no = int(float(values[1])) - except Exception: - row_no = None - - if row_no is None or row_no <= 0: - children = list(self.custom_result_tree.get_children()) - row_no = children.index(item_id) + 1 if item_id in children else 1 - - self._clear_custom_result_row(item_id, row_no) - - threading.Thread( - target=self._run_custom_row_single_step, - args=(item_id, row_no), - daemon=True, - ).start() - - def _clear_custom_result_row(self, item_id, row_no): - """单步测试开始前清空指定行的测量数据""" - if not hasattr(self, "custom_result_tree"): - return - - old_values = list(self.custom_result_tree.item(item_id, "values")) - pattern_name = old_values[0] if len(old_values) > 0 else f"P {row_no}" - - cleared_values = ( - pattern_name, - row_no, - "---", - "---", - "---", - "---", - "---", - "---", - "---", - "---", - "---", - "---", - "---", - "---", - ) - - self.custom_result_tree.item(item_id, values=cleared_values) - self.custom_result_tree.see(item_id) - - def _run_custom_row_single_step(self, item_id, row_no): - """后台执行客户模板单步测试""" - try: - self._dispatch_ui(self.status_var.set, f"单步测试第 {row_no} 行...") - self.log_gui.log(f"开始单步测试第 {row_no} 行") - - self.config.set_current_pattern("custom") - - # 与批量 custom 测试保持一致:根据当前 SDR 配置转换 pattern 数据。 - import copy - - data_range = self.sdr_data_range_var.get() - original_params = copy.deepcopy(self.config.default_pattern_temp["pattern_params"]) - converted_params = convert_pattern_params( - pattern_params=original_params, - data_range=data_range, - verbose=False, - ) - - temp_config = self.config.get_temp_config_with_converted_params( - mode="custom", - converted_params=converted_params, - ) - - if row_no > len(converted_params): - self.log_gui.log(f"❌ 行号超出 pattern 范围: {row_no}/{len(converted_params)}") - self._dispatch_ui(self.status_var.set, "单步测试失败:行号超范围") - return - - self.ucd.set_ucd_params(temp_config) - pattern_param = converted_params[row_no - 1] - self.ucd.set_pattern(self.ucd.current_pattern, pattern_param) - self.ucd.run() - - time.sleep(self.pattern_settle_time) - - # 测量:显示模式1读取 Tcp/duv/Lv,显示模式8读取 λd/Pe/Lv 与 XYZ。 - self.ca.set_Display(1) - tcp, duv, lv, _, _, _ = self.ca.readAllDisplay() - - self.ca.set_Display(8) - lambda_d, pe, lv, X, Y, Z = self.ca.readAllDisplay() - - xy = colour.XYZ_to_xy(np.array([X, Y, Z])) - u_prime, v_prime, _ = colour.XYZ_to_CIE1976UCS(np.array([X, Y, Z])) - - row_data = { - "X": X, - "Y": Y, - "Z": Z, - "x": xy[0], - "y": xy[1], - "Lv": lv, - "u_prime": u_prime, - "v_prime": v_prime, - "Tcp": tcp, - "duv": duv, - "lambda_d": lambda_d, - "Pe": pe, - } - - self._dispatch_ui( - self._update_custom_result_row, item_id, row_no, row_data - ) - - self.log_gui.log(f"✓ 第 {row_no} 行单步测试完成并已覆盖") - self._dispatch_ui(self.status_var.set, f"第 {row_no} 行单步测试完成") - - except Exception as e: - self.log_gui.log(f"❌ 单步测试失败: {str(e)}") - self._dispatch_ui(self.status_var.set, "单步测试失败") - - def _update_custom_result_row(self, item_id, row_no, result_data): - """覆盖更新客户模板结果表中指定行""" - - def fmt(value, digits=4): - if value is None: - return "--" - if isinstance(value, (int, float, np.floating)): - # CA 返回异常哨兵值(如 -99999999)时,显示为占位符。 - if (not np.isfinite(value)) or value <= -99999998: - return "---" - return f"{value:.{digits}f}" - try: - numeric_value = float(value) - if (not np.isfinite(numeric_value)) or numeric_value <= -99999998: - return "---" - except (TypeError, ValueError): - pass - return str(value) - - old_values = list(self.custom_result_tree.item(item_id, "values")) - pattern_name = old_values[0] if len(old_values) > 0 else f"P {row_no}" - - new_values = ( - pattern_name, - row_no, - fmt(result_data.get("X")), - fmt(result_data.get("Y")), - fmt(result_data.get("Z")), - fmt(result_data.get("x")), - fmt(result_data.get("y")), - fmt(result_data.get("Lv"), 3), - fmt(result_data.get("u_prime")), - fmt(result_data.get("v_prime")), - fmt(result_data.get("Tcp"), 1), - fmt(result_data.get("duv"), 5), - fmt(result_data.get("lambda_d"), 1), - fmt(result_data.get("Pe"), 1), - ) - - self.custom_result_tree.item(item_id, values=new_values) - - def copy_custom_result_table(self): - """复制客户模板结果表格到剪贴板(不含标题行/No./Pattern)""" - if not hasattr(self, "custom_result_tree"): - return - - items = self.custom_result_tree.get_children() - if not items: - messagebox.showinfo("提示", "当前没有可复制的数据") - return - - lines = [] - columns = tuple(self.custom_result_tree["columns"]) - excluded_col_indexes = { - idx - for idx, col_name in enumerate(columns) - if col_name in ("No.", "Pattern") - } - - for item in items: - values = self.custom_result_tree.item(item, "values") - # 跳过 No. 和 Pattern 两列,只保留测量数据列。 - data_values = [ - v for idx, v in enumerate(values) if idx not in excluded_col_indexes - ] - row = [ - str(v).replace("\t", " ").replace("\n", " ") - for v in data_values - ] - lines.append("\t".join(row)) - - clipboard_text = "\n".join(lines) - self.root.clipboard_clear() - self.root.clipboard_append(clipboard_text) - self.root.update_idletasks() - - if hasattr(self, "status_var"): - self.status_var.set(f"已复制 {len(items)} 行客户模板数据到剪贴板") - if hasattr(self, "log_gui"): - self.log_gui.log(f"✓ 已复制客户模板表格数据({len(items)} 行)") - - def fill_custom_result_test_data(self): - """填充 147 行客户模板测试数据(用于界面验证)""" - if not hasattr(self, "custom_result_tree"): - return - - self.clear_custom_template_results() - - pattern_names = [] - if hasattr(self, "config") and hasattr(self.config, "get_temp_pattern_names"): - pattern_names = self.config.get_temp_pattern_names() - - total_rows = 147 - for i in range(1, total_rows + 1): - ratio = (i - 1) / (total_rows - 1) if total_rows > 1 else 0 - row_data = { - "pattern_name": ( - pattern_names[i - 1] if i - 1 < len(pattern_names) else f"P {i}" - ), - "X": 0.8 + ratio * 120, - "Y": 0.9 + ratio * 135, - "Z": 1.1 + ratio * 145, - "x": 0.24 + ratio * 0.10, - "y": 0.26 + ratio * 0.10, - "Lv": 1.0 + ratio * 500, - "u_prime": 0.16 + ratio * 0.12, - "v_prime": 0.42 + ratio * 0.08, - "Tcp": 1800 + ratio * 12000, - "duv": -0.01 + ratio * 0.03, - "lambda_d": 430 + ratio * 200, - "Pe": 10 + ratio * 90, - } - self.append_custom_template_result(i, row_data) - - if hasattr(self, "chart_notebook") and hasattr(self, "custom_template_tab_frame"): - self.chart_notebook.select(self.custom_template_tab_frame) - - if hasattr(self, "status_var"): - self.status_var.set("已填充 147 行客户模板测试数据") - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 已填充 147 行客户模板测试数据") - - def clear_custom_template_results(self): - """清空客户模板结果表格""" - if not hasattr(self, "custom_result_tree"): - return - for item in self.custom_result_tree.get_children(): - self.custom_result_tree.delete(item) - - def auto_expand_custom_result_view(self): - """当客户模板表格有数据时,自动扩展窗口以尽量完整显示所有列""" - if not hasattr(self, "custom_result_tree"): - return - - if len(self.custom_result_tree.get_children()) == 0: - return - - try: - self.root.update_idletasks() - - columns = tuple(self.custom_result_tree["columns"]) - columns_total_width = 0 - for col in columns: - columns_total_width += int(self.custom_result_tree.column(col, "width")) - - left_panel_width = self.left_frame.winfo_width() if hasattr(self, "left_frame") else 180 - if left_panel_width <= 1: - left_panel_width = 180 - - # 列宽 + 左侧导航 + 滚动条/边框/外边距。 - target_width = int(left_panel_width + columns_total_width + 120) - - screen_max_width = max(900, self.root.winfo_screenwidth() - 40) - target_width = min(target_width, screen_max_width) - - current_width = self.root.winfo_width() - current_height = self.root.winfo_height() - - # 只扩不缩,避免用户窗口被反复改变。 - if target_width > current_width: - self.root.geometry(f"{target_width}x{current_height}") - self.root.update_idletasks() - except Exception as e: - if hasattr(self, "log_gui"): - self.log_gui.log(f"⚠️ 自动扩展客户模板窗口失败: {str(e)}") - - def append_custom_template_result(self, row_no, result_data): - """追加一条客户模板结果到表格""" - - def fmt(value, digits=4): - if value is None: - return "--" - if isinstance(value, (int, float, np.floating)): - # CA 返回异常哨兵值(如 -99999999)时,显示为占位符。 - if (not np.isfinite(value)) or value <= -99999998: - return "---" - return f"{value:.{digits}f}" - try: - numeric_value = float(value) - if (not np.isfinite(numeric_value)) or numeric_value <= -99999998: - return "---" - except (TypeError, ValueError): - pass - return str(value) - - row_values = ( - result_data.get("pattern_name", f"P {row_no}"), - row_no, - fmt(result_data.get("X")), - fmt(result_data.get("Y")), - fmt(result_data.get("Z")), - fmt(result_data.get("x")), - fmt(result_data.get("y")), - fmt(result_data.get("Lv"), 3), - fmt(result_data.get("u_prime")), - fmt(result_data.get("v_prime")), - fmt(result_data.get("Tcp"), 1), - fmt(result_data.get("duv"), 5), - fmt(result_data.get("lambda_d"), 1), - fmt(result_data.get("Pe"), 1) - ) - - if hasattr(self, "custom_result_tree"): - item_id = self.custom_result_tree.insert("", tk.END, values=row_values) - # 新增数据后自动跳转到最新行。 - self.custom_result_tree.see(item_id) - self.auto_expand_custom_result_view() - - def start_custom_template_test(self): - """开始客户模板测试(SDR)""" - - if hasattr(self, "chart_notebook") and hasattr(self, "custom_template_tab_frame"): - self.chart_notebook.select(self.custom_template_tab_frame) - - if self.ca is None or self.ucd is None: - messagebox.showerror("错误", "请先连接CA410和信号发生器") - return - - if self.testing: - messagebox.showinfo("提示", "测试已在进行中") - return - - if hasattr(self, "debug_container"): - self.debug_container.pack_forget() - - self.testing = True - self.start_btn.config(state=tk.DISABLED) - self.stop_btn.config(state=tk.NORMAL) - self.save_btn.config(state=tk.DISABLED) - self.clear_config_btn.config(state=tk.DISABLED) - self.custom_btn.config(state=tk.DISABLED) - self.status_var.set("客户模板测试进行中...") - - self.log_gui.clear_log() - self.clear_custom_template_results() - - confirm = messagebox.askyesno( - "确认测试", "开始客户模板测试(SDR)?\n\n将采集并显示客户模板格式结果。" - ) - - if not confirm: - self.testing = False - self.start_btn.config(state=tk.NORMAL) - self.stop_btn.config(state=tk.DISABLED) - self.clear_config_btn.config(state=tk.NORMAL) - self.custom_btn.config(state=tk.NORMAL) - self.status_var.set("测试已取消") - self.set_custom_result_table_locked(False) - return - - self.set_custom_result_table_locked(True) - - self.test_thread = threading.Thread(target=self.run_custom_sdr_test, args=([],)) - self.test_thread.daemon = True - self.test_thread.start() - - def register_panel(self, panel_name, frame, button, visible_attr): """注册一个面板到管理系统""" self.panels[panel_name] = { @@ -2199,179 +463,6 @@ class PQAutomationApp: self.current_panel = None - def create_log_panel(self): - """创建日志面板""" - self.log_frame = ttk.Frame(self.content_frame) - self.log_gui = PQLogGUI(self.log_frame) - self.log_gui.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) - - # 默认隐藏日志面板 - self.log_visible = False - - # 注册到面板管理系统 - self.register_panel( - "log", self.log_frame, None, "log_visible" - ) # button会在后面设置 - - def create_local_dimming_panel(self): - """创建 Local Dimming 测试面板 - 手动控制版""" - self.local_dimming_frame = ttk.Frame(self.content_frame) - - # 主容器 - main_container = ttk.Frame(self.local_dimming_frame, padding=10) - main_container.pack(fill=tk.BOTH, expand=True) - - # ==================== 1. 标题 ==================== - title_frame = ttk.Frame(main_container) - title_frame.pack(fill=tk.X, pady=(0, 10)) - - ttk.Label( - title_frame, - text="🔆 Local Dimming 窗口测试", - font=("微软雅黑", 14, "bold"), - ).pack(side=tk.LEFT) - - # ==================== 2. 窗口百分比按钮 ==================== - window_frame = ttk.LabelFrame( - main_container, text="🔆 窗口百分比(点击发送)", padding=10 - ) - window_frame.pack(fill=tk.X, pady=(0, 10)) - - # 说明文字 - ttk.Label( - window_frame, - text="点击按钮发送对应百分比的白色窗口(黑色背景 + 居中白色矩形)", - font=("", 9), - foreground="#28a745", - ).pack(pady=(0, 8)) - - # 第一行:1%, 2%, 5%, 10%, 18% - row1 = ttk.Frame(window_frame) - row1.pack(fill=tk.X, pady=(0, 5)) - - percentages_row1 = [1, 2, 5, 10, 18] - for p in percentages_row1: - ttk.Button( - row1, - text=f"{p}%", - command=lambda p=p: self.send_ld_window(p), - bootstyle="success", - width=12, - ).pack(side=tk.LEFT, padx=3) - - # 第二行:25%, 50%, 75%, 100% - row2 = ttk.Frame(window_frame) - row2.pack(fill=tk.X) - - percentages_row2 = [25, 50, 75, 100] - for p in percentages_row2: - ttk.Button( - row2, - text=f"{p}%", - command=lambda p=p: self.send_ld_window(p), - bootstyle="success", - width=12, - ).pack(side=tk.LEFT, padx=3) - - # ==================== 4. CA410 采集按钮 ==================== - measure_frame = ttk.LabelFrame(main_container, text="📊 CA410 测量", padding=10) - measure_frame.pack(fill=tk.X, pady=(0, 10)) - - measure_btn_frame = ttk.Frame(measure_frame) - measure_btn_frame.pack(fill=tk.X) - - self.ld_measure_btn = ttk.Button( - measure_btn_frame, - text="📏 采集当前亮度", - command=self.measure_ld_luminance, - bootstyle="primary", - width=15, - ) - self.ld_measure_btn.pack(side=tk.LEFT, padx=(0, 5)) - - # 显示测量结果 - self.ld_result_label = ttk.Label( - measure_btn_frame, - text="亮度: -- cd/m² | x: -- | y: --", - font=("Consolas", 10), - foreground="#007bff", - ) - self.ld_result_label.pack(side=tk.LEFT, padx=(10, 0)) - - # ==================== 5. 测试结果表格 ==================== - result_frame = ttk.LabelFrame(main_container, text="📋 测试记录", padding=10) - result_frame.pack(fill=tk.BOTH, expand=True, pady=(0, 10)) - - # Treeview - columns = ("窗口百分比", "亮度 (cd/m²)", "x", "y", "时间") - self.ld_tree = ttk.Treeview( - result_frame, columns=columns, show="headings", height=10 - ) - - for col in columns: - self.ld_tree.heading(col, text=col) - if col == "窗口百分比": - self.ld_tree.column(col, width=100, anchor=tk.CENTER) - elif col == "时间": - self.ld_tree.column(col, width=120, anchor=tk.CENTER) - else: - self.ld_tree.column(col, width=100, anchor=tk.CENTER) - - self.ld_tree.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) - - # 滚动条 - scrollbar = ttk.Scrollbar( - result_frame, orient=tk.VERTICAL, command=self.ld_tree.yview - ) - scrollbar.pack(side=tk.RIGHT, fill=tk.Y) - self.ld_tree.configure(yscrollcommand=scrollbar.set) - - # ==================== 6. 底部操作按钮 ==================== - bottom_frame = ttk.Frame(main_container) - bottom_frame.pack(fill=tk.X) - - self.ld_clear_btn = ttk.Button( - bottom_frame, - text="🗑️ 清空记录", - command=self.clear_ld_records, - bootstyle="danger-outline", - width=12, - ) - self.ld_clear_btn.pack(side=tk.LEFT, padx=(0, 5)) - - self.ld_save_btn = ttk.Button( - bottom_frame, - text="💾 保存结果", - command=self.save_local_dimming_results, - bootstyle="info", - width=12, - ) - self.ld_save_btn.pack(side=tk.LEFT) - - # 默认隐藏 - self.local_dimming_visible = False - - # 注册到面板管理系统 - self.register_panel( - "local_dimming", - self.local_dimming_frame, - None, - "local_dimming_visible", - ) - - # 初始化当前窗口百分比(用于记录) - self.current_ld_percentage = None - - def toggle_local_dimming_panel(self): - """切换 Local Dimming 面板显示""" - self.show_panel("local_dimming") - - def toggle_log_panel(self): - """切换日志面板的显示状态""" - self.show_panel("log") - - create_result_chart_frame = _cf_create_result_chart_frame - on_chart_tab_changed = _cf_on_chart_tab_changed def change_test_type(self, test_type): """切换测试类型""" # 切换测试类型时,自动隐藏日志面板和 Local Dimming 面板 @@ -2522,22 +613,6 @@ class PQAutomationApp: if hasattr(self, "log_gui"): self.log_gui.log(f"切换 Gamma/EOTF Tab 失败: {str(e)}") - def update_sidebar_selection(self): - """更新侧边栏按钮的选中状态""" - # 重置所有按钮样式为默认 - self.screen_module_btn.configure(style="Sidebar.TButton") - self.sdr_movie_btn.configure(style="Sidebar.TButton") - self.hdr_movie_btn.configure(style="Sidebar.TButton") - - # 设置当前选中按钮的样式 - current_type = self.test_type_var.get() - if current_type == "screen_module": - self.screen_module_btn.configure(style="SidebarSelected.TButton") - elif current_type == "sdr_movie": - self.sdr_movie_btn.configure(style="SidebarSelected.TButton") - elif current_type == "hdr_movie": - self.hdr_movie_btn.configure(style="SidebarSelected.TButton") - def on_test_type_change(self): """根据测试类型更新内容区域""" test_type = self.test_type_var.get() @@ -2552,17 +627,6 @@ class PQAutomationApp: # SDR 选中时显示客户模版按钮 self.update_custom_button_visibility() - def update_custom_button_visibility(self): - """只在 SDR 测试时显示客户模版按钮""" - if not hasattr(self, "custom_btn") or not hasattr(self, "test_type_var"): - return - if self.test_type_var.get() == "sdr_movie": - if not self.custom_btn.winfo_manager(): - self.custom_btn.pack(side=tk.LEFT, padx=5) - else: - if self.custom_btn.winfo_manager(): - self.custom_btn.pack_forget() - def start_test(self): """开始测试""" # 检查设备连接状态 @@ -2930,25 +994,22 @@ class PQAutomationApp: test_eotf = _run_test_eotf test_cct = _run_test_cct test_contrast = _run_test_contrast - calculate_delta_e_2000 = staticmethod(_calc_delta_e_2000) - test_color_accuracy = _run_test_color_accuracy + + calculate_delta_e_2000 = staticmethod(_calc_delta_e_2000) get_accuracy_color_standards = staticmethod(_get_accuracy_color_standards) - calculate_gamut_coverage = staticmethod(_calc_gamut_coverage) - calculate_gamma = staticmethod(_calc_gamma) - calculate_color_accuracy = staticmethod(_calc_color_accuracy) + calculate_pq_curve = staticmethod(_calc_pq_curve) plot_gamut = _plot_gamut plot_gamma = _plot_gamma plot_eotf = _plot_eotf - calculate_pq_curve = staticmethod(_calc_pq_curve) - plot_cct = _plot_cct plot_contrast = _plot_contrast plot_accuracy = _plot_accuracy + on_test_completed = _run_on_test_completed on_custom_template_test_completed = _run_on_custom_template_test_completed get_current_test_result = _run_get_current_test_result @@ -2985,10 +1046,8 @@ class PQAutomationApp: # 保存当前选中的测试项到配置 self.config.set_current_test_items(self.get_selected_test_items()) - # 待修改为三种测试类型的timing值 self.config.set_current_timing(self.screen_module_timing_var.get()) - # 自动保存配置到文件 self.save_pq_config() @@ -3020,87 +1079,6 @@ class PQAutomationApp: # 控制参数框的显示 self.toggle_cct_params_frame() - def toggle_cct_params_frame(self): - """根据测试类型和测试项的选中状态显示对应参数框""" - selected_items = self.get_selected_test_items() - current_test_type = self.config.current_test_type - - # ========== 默认隐藏所有参数框 ========== - self.cct_params_frame.pack_forget() - self.sdr_cct_params_frame.pack_forget() - - # HDR 色度参数框(如果存在的话) - if hasattr(self, "hdr_cct_params_frame"): - self.hdr_cct_params_frame.pack_forget() - - # ========== 根据测试类型和选中项显示对应参数框 ========== - if current_test_type == "screen_module": - # 屏模组:只有色度参数 - if "cct" in selected_items: - self.cct_params_frame.pack(fill=tk.X, padx=5, pady=5) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 显示屏模组色度参数设置") - - elif current_test_type == "sdr_movie": - # SDR:只有色度参数(色准不需要参数设置框) - if "cct" in selected_items: - self.sdr_cct_params_frame.pack(fill=tk.X, padx=5, pady=5) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 显示 SDR 色度参数设置") - - elif current_test_type == "hdr_movie": - # HDR:只有色度参数(色准不需要参数设置框) - if "cct" in selected_items: - if hasattr(self, "hdr_cct_params_frame"): - self.hdr_cct_params_frame.pack(fill=tk.X, padx=5, pady=5) - if hasattr(self, "log_gui"): - self.log_gui.log("✓ 显示 HDR 色度参数设置") - else: - if hasattr(self, "log_gui"): - self.log_gui.log("⚠️ HDR 色度参数框尚未创建") - - def on_screen_module_timing_changed(self, event=None): - """屏模组信号格式改变时的回调""" - try: - selected_timing = self.screen_module_timing_var.get() - - # 记录日志 - self.log_gui.log(f"屏模组信号格式已更改为: {selected_timing}") - - # 解析分辨率和刷新率 - import re - - match = re.search(r"(\d+)x(\d+)\s*@\s*(\d+)", selected_timing) - if match: - width = int(match.group(1)) - height = int(match.group(2)) - refresh_rate = int(match.group(3)) - - self.log_gui.log(f" ├─ 分辨率: {width}x{height}") - self.log_gui.log(f" └─ 刷新率: {refresh_rate}Hz") - - # 根据分辨率给出提示 - if width >= 3840: # 4K及以上 - self.log_gui.log(" ℹ️ 检测到4K分辨率") - - if refresh_rate >= 120: - self.log_gui.log(" ℹ️ 检测到高刷新率") - - # 更新配置 - self.config.set_current_timing(selected_timing) - - # 如果正在测试,提示用户 - if self.testing: - self.log_gui.log("⚠️ 警告: 测试进行中,信号格式更改将在下次测试时生效") - - # 保存配置 - self.save_pq_config() - - except Exception as e: - self.log_gui.log(f"❌ 屏模组信号格式更改失败: {str(e)}") - - load_pq_config = _cfg_load_pq_config - save_pq_config = _cfg_save_pq_config def on_closing(self): """窗口关闭时的处理""" try: @@ -3124,160 +1102,6 @@ class PQAutomationApp: print(f"关闭窗口时出错: {str(e)}") self.root.destroy() - # ---- gamut 参考标准改变回调(统一实现) ---- - _GAMUT_REF_CONFIGS = { - "screen_module": {"var_attr": "screen_gamut_ref_var", "label": "屏模组"}, - "sdr_movie": {"var_attr": "sdr_gamut_ref_var", "label": "SDR"}, - "hdr_movie": {"var_attr": "hdr_gamut_ref_var", "label": "HDR"}, - } - - def _on_gamut_ref_changed(self, test_type, event=None): - cfg = self._GAMUT_REF_CONFIGS[test_type] - try: - new_ref = getattr(self, cfg["var_attr"]).get() - self.log_gui.log(f"✓ {cfg['label']} 色域参考标准已更改为: {new_ref}") - - if test_type not in self.config.current_test_types: - self.config.current_test_types[test_type] = {} - self.config.current_test_types[test_type]["gamut_reference"] = new_ref - self.save_pq_config() - except Exception as e: - self.log_gui.log(f"保存 {cfg['label']} 色域参考标准失败: {str(e)}") - - def on_screen_gamut_ref_changed(self, event=None): - self._on_gamut_ref_changed("screen_module", event) - - def on_sdr_gamut_ref_changed(self, event=None): - self._on_gamut_ref_changed("sdr_movie", event) - - def on_hdr_gamut_ref_changed(self, event=None): - self._on_gamut_ref_changed("hdr_movie", event) - - # ---- 单步调试面板(统一实现) ---- - _DEBUG_PANEL_CONFIGS = { - "screen_module": { - "window_attr": "debug_window", - "btn_attr": "screen_debug_btn", - "title": "🔧 单步调试面板", - "window_log_prefix": "", - "data_log_prefix": "屏模组", - "failure_data_label": "调试数据", - # (item_key, debug_key, (category, subkey), data_label, enable_desc) - "data_items": [ - ("gamma", "gamma", ("shared", "gray"), "灰阶", "Gamma 单步调试"), - ("gamut", "rgb", ("gamut", "rgb"), "RGB", "RGB 单步调试"), - ], - }, - "sdr_movie": { - "window_attr": "sdr_debug_window", - "btn_attr": "sdr_debug_btn", - "title": "🔧 SDR 单步调试面板", - "window_log_prefix": "SDR ", - "data_log_prefix": "SDR", - "failure_data_label": "SDR 调试数据", - "data_items": [ - ("gamma", "gamma", ("shared", "gray"), "灰阶", "Gamma 单步调试"), - ("accuracy", "accuracy", ("accuracy", "measured"), "色准", "色准单步调试"), - ("gamut", "rgb", ("gamut", "rgb"), "RGB", "RGB 单步调试"), - ], - }, - "hdr_movie": { - "window_attr": "hdr_debug_window", - "btn_attr": "hdr_debug_btn", - "title": "🔧 HDR 单步调试面板", - "window_log_prefix": "HDR ", - "data_log_prefix": "HDR", - "failure_data_label": "HDR 调试数据", - "data_items": [ - ("eotf", "eotf", ("shared", "gray"), "灰阶", "EOTF 单步调试"), - ("accuracy", "accuracy", ("accuracy", "measured"), "色准", "色准单步调试"), - ("gamut", "rgb", ("gamut", "rgb"), "RGB", "RGB 单步调试"), - ], - }, - } - - def _toggle_debug_panel(self, test_type): - """打开/关闭对应测试类型的单步调试面板(独立窗口)。""" - cfg = self._DEBUG_PANEL_CONFIGS[test_type] - win_attr = cfg["window_attr"] - btn = getattr(self, cfg["btn_attr"]) - wlp = cfg["window_log_prefix"] - - # 如果窗口已存在且可见,关闭它 - existing = getattr(self, win_attr, None) - if existing is not None and existing.winfo_exists(): - existing.destroy() - btn.config(text="打开调试面板") - self.log_gui.log(f"✓ {wlp}单步调试面板已关闭") - return - - # 创建新窗口 - win = ttk.Toplevel(self.root) - win.title(cfg["title"]) - win.geometry("900x400") - win.transient(self.root) - setattr(self, win_attr, win) - - # 调试面板容器 - debug_container = ttk.Frame(win, padding=10) - debug_container.pack(fill=tk.BOTH, expand=True) - - # 创建调试面板实例(不要对它调用 pack) - debug_panel_instance = PQDebugPanel(debug_container, self) - - self.log_gui.log(f"✓ {wlp}单步调试面板实例已创建") - - # 重新启用调试(如果有数据) - try: - selected_items = self.get_selected_test_items() - dlp = cfg["data_log_prefix"] - for item_key, debug_key, (cat, sub), data_label, enable_desc in cfg["data_items"]: - if item_key not in selected_items: - continue - data = self.results.get_intermediate_data(cat, sub) - if not data: - if test_type == "screen_module" and item_key == "gamma": - self.log_gui.log(" ✗ 没有可用的灰阶数据") - continue - self.log_gui.log(f" → 加载 {len(data)} 个{data_label}数据点") - debug_panel_instance.enable_debug(test_type, debug_key, data) - self.log_gui.log(f"✓ {dlp} {enable_desc}已重新启用") - except Exception as e: - self.log_gui.log(f"⚠️ 加载{cfg['failure_data_label']}失败: {str(e)}") - import traceback - - self.log_gui.log(traceback.format_exc()) - - btn.config(text="关闭调试面板") - - def on_closing(): - btn.config(text="打开调试面板") - getattr(self, win_attr).destroy() - self.log_gui.log(f"✓ {wlp}单步调试窗口已关闭") - - win.protocol("WM_DELETE_WINDOW", on_closing) - win.update_idletasks() - - self.log_gui.log(f"✓ {wlp}单步调试面板已打开(独立窗口)") - - def toggle_screen_debug_panel(self): - self._toggle_debug_panel("screen_module") - - def toggle_sdr_debug_panel(self): - self._toggle_debug_panel("sdr_movie") - - def toggle_hdr_debug_panel(self): - self._toggle_debug_panel("hdr_movie") - - clear_config_file = _cfg_clear_config_file - start_local_dimming_test = _ld_start_local_dimming_test - update_ld_results = _ld_update_ld_results - stop_local_dimming_test = _ld_stop_local_dimming_test - send_ld_window = _ld_send_ld_window - measure_ld_luminance = _ld_measure_ld_luminance - clear_ld_records = _ld_clear_ld_records - save_local_dimming_results = _ld_save_local_dimming_results - def main(): try: