修改AI生图接口、修改设备连接UI、修改LocalDimming逻辑和UI

This commit is contained in:
xinzhu.yin
2026-05-29 14:40:39 +08:00
parent 21455f3916
commit 85ac47e8de
13 changed files with 811 additions and 304 deletions

View File

@@ -184,6 +184,13 @@ def create_ai_image_panel(self: "PQAutomationApp"):
self._ai_image_tooltip = None
self._ai_image_tooltip_label = None
self._ai_image_tooltip_item = ""
# 会话级参考图 URL图生图模式session_id -> upload_image_url
self._ai_image_session_refs = {}
# 本轮发送前手动上传的参考图(覆盖会话级)
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
self._ai_image_uploading = False
self.ai_image_ref_var = tk.StringVar(value="未设置参考图(文生图模式)")
container = ttk.Frame(frame, padding=10)
container.pack(fill=tk.BOTH, expand=True)
@@ -328,6 +335,29 @@ def create_ai_image_panel(self: "PQAutomationApp"):
input_frame = ttk.LabelFrame(right, text="提示输入Ctrl+Enter 发送)", padding=6)
input_frame.pack(fill=tk.X, pady=(4, 0))
# 参考图行(图生图)
ref_row = ttk.Frame(input_frame)
ref_row.pack(fill=tk.X, pady=(0, 4))
ttk.Label(
ref_row, text="参考图:",
foreground=palette["muted"], font=("微软雅黑", 9),
).pack(side=tk.LEFT)
self.ai_image_ref_label = ttk.Label(
ref_row, textvariable=self.ai_image_ref_var,
foreground=palette["fg"], font=("微软雅黑", 9),
)
self.ai_image_ref_label.pack(side=tk.LEFT, padx=(4, 0), fill=tk.X, expand=True)
self.ai_image_clear_ref_btn = ttk.Button(
ref_row, text="清除", width=6, bootstyle="secondary-outline",
command=lambda: _clear_reference_image(self),
)
self.ai_image_clear_ref_btn.pack(side=tk.RIGHT, padx=(4, 0))
self.ai_image_upload_ref_btn = ttk.Button(
ref_row, text="上传参考图…", width=12, bootstyle="info-outline",
command=lambda: _upload_reference_image(self),
)
self.ai_image_upload_ref_btn.pack(side=tk.RIGHT)
self.ai_image_input = tk.Text(
input_frame,
height=3,
@@ -435,6 +465,20 @@ def reload_ai_image_list(self: "PQAutomationApp", auto_select_first=True):
self.ai_image_records = records
sessions = _svc.group_records_by_session(self.ai_image_records)
# \u4f1a\u8bdd\u7ea7\u53c2\u8003\u56fe\u94fe\u8def\uff1a\u4ee5\u6bcf\u4e2a\u4f1a\u8bdd\u6700\u8fd1\u4e00\u5f20\u751f\u6210\u56fe\u7684 imageUrl \u4f5c\u4e3a\u53c2\u8003
refs_map = getattr(self, "_ai_image_session_refs", None)
if isinstance(refs_map, dict):
for sess in sessions:
sid = sess.get("session_id") or ""
if not sid or sid in refs_map:
continue
for r in sess.get("records") or []:
src = ""
if isinstance(r.extra, dict):
src = (r.extra.get("source_url") or "").strip()
if src:
refs_map[sid] = src
break
flat = []
current_sid = _svc.get_session_id()
for idx, sess in enumerate(sessions, start=1):
@@ -493,6 +537,10 @@ def reload_ai_image_list(self: "PQAutomationApp", auto_select_first=True):
self._ai_image_list_loaded = True
finally:
self._ai_image_reloading = False
try:
_refresh_ref_label(self)
except Exception:
pass
def _format_session_header(index: int, sess: dict, is_current: bool) -> str:
@@ -655,6 +703,10 @@ def _start_new_session(self: "PQAutomationApp"):
return
_svc.reset_session()
self.ai_image_status_var.set("已开启新对话")
# 新会话清除参考图(未设置时默认为文生图模式)
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
_refresh_ref_label(self)
# 新对话创建后不要自动选中历史图片,否则会立即把 session 切回旧会话。
reload_ai_image_list(self, auto_select_first=False)
try:
@@ -667,6 +719,103 @@ def _start_new_session(self: "PQAutomationApp"):
self.ai_image_meta_var.set("新对话已开启,等待生成图片")
def _resolve_pending_ref_url(self: "PQAutomationApp") -> str:
"""返回本次发送应使用的 upload_image_url。
优先使用用户在当前会话中手动上传的参考图;其次使用上一轮 imageUrl 自动链路。
"""
pending = (getattr(self, "_ai_image_pending_ref_url", "") or "").strip()
if pending:
return pending
sid = _svc.get_session_id()
refs = getattr(self, "_ai_image_session_refs", None) or {}
return (refs.get(sid) or "").strip()
def _refresh_ref_label(self: "PQAutomationApp"):
"""根据当前 pending 上传 / 会话链路状态刷新参考图提示标签。"""
var = getattr(self, "ai_image_ref_var", None)
if var is None:
return
pending = (getattr(self, "_ai_image_pending_ref_url", "") or "").strip()
if pending:
name = getattr(self, "_ai_image_pending_ref_name", "") or "参考图"
var.set(f"[图生图] {name}")
return
sid = _svc.get_session_id()
refs = getattr(self, "_ai_image_session_refs", None) or {}
chained = (refs.get(sid) or "").strip()
if chained:
# 自动链路:显示来源是"上一轮"
var.set("[图生图] 沿用上一轮生成图为参考")
return
var.set("未设置参考图(文生图模式)")
def _upload_reference_image(self: "PQAutomationApp"):
"""选择本地图片并上传到后端,成功后作为本次发送的参考图。"""
if getattr(self, "_ai_image_requesting", False):
messagebox.showinfo("提示", "请等待当前请求完成")
return
if getattr(self, "_ai_image_uploading", False):
return
path = filedialog.askopenfilename(
title="选择参考图PNG/JPG/JPEG超过限制将自动缩放",
filetypes=[("图片", "*.png;*.jpg;*.jpeg"), ("所有文件", "*.*")],
)
if not path:
return
self._ai_image_uploading = True
try:
self.ai_image_upload_ref_btn.configure(state=tk.DISABLED, text="上传中…")
self.ai_image_clear_ref_btn.configure(state=tk.DISABLED)
except Exception:
pass
self.ai_image_status_var.set("正在上传参考图…")
name = os.path.basename(path)
def _ok(url: str):
self.root.after(0, lambda: _on_upload_done(self, name, url, None))
def _err(exc: Exception):
self.root.after(0, lambda: _on_upload_done(self, name, "", exc))
_svc.upload_image_async(path, on_success=_ok, on_error=_err)
def _on_upload_done(self: "PQAutomationApp", name: str, url: str, exc):
self._ai_image_uploading = False
try:
self.ai_image_upload_ref_btn.configure(state=tk.NORMAL, text="上传参考图…")
self.ai_image_clear_ref_btn.configure(state=tk.NORMAL)
except Exception:
pass
if exc is not None:
self.ai_image_status_var.set(f"上传失败: {exc}")
messagebox.showerror("上传失败", str(exc))
return
self._ai_image_pending_ref_url = url
self._ai_image_pending_ref_name = name
_refresh_ref_label(self)
self.ai_image_status_var.set(f"参考图已上传:{name}")
def _clear_reference_image(self: "PQAutomationApp"):
"""清除手动上传的参考图,同时清除当前会话的自动链路参考。"""
if getattr(self, "_ai_image_requesting", False):
return
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
sid = _svc.get_session_id()
refs = getattr(self, "_ai_image_session_refs", None)
if isinstance(refs, dict):
refs.pop(sid, None)
_refresh_ref_label(self)
self.ai_image_status_var.set("已清除参考图,切换为文生图模式")
def _session_id_for_item(self: "PQAutomationApp", item_id: str) -> str:
session_map = getattr(self, "_ai_image_session_node_map", None) or {}
parent = item_id
@@ -704,6 +853,10 @@ def _switch_to_session(
if item_id:
_set_tree_selection(self, item_id)
self.ai_image_status_var.set("已切换到历史对话")
# 切换会话后刷新参考图标签pending 仅当前会话有效,故清除)
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
_refresh_ref_label(self)
if show_message:
messagebox.showinfo("提示", "已切换到所选历史对话")
@@ -720,6 +873,9 @@ def _update_request_progress(self: "PQAutomationApp"):
def _send_prompt(self: "PQAutomationApp"):
if getattr(self, "_ai_image_requesting", False):
return
if getattr(self, "_ai_image_uploading", False):
messagebox.showinfo("提示", "参考图上传中,请稍后再发送")
return
prompt = self.ai_image_input.get("1.0", tk.END).strip()
if not prompt:
messagebox.showinfo("提示", "请输入内容")
@@ -749,12 +905,17 @@ def _send_prompt(self: "PQAutomationApp"):
)
return
ref_url = _resolve_pending_ref_url(self)
if ref_url:
self.ai_image_status_var.set("后端处理中(图生图)…")
_svc.request_image_async(
prompt,
on_success=_success,
on_error=_error,
base_dir=_get_app_base_dir(self),
cancel_event=self._ai_image_cancel_event,
upload_image_url=ref_url or None,
)
@@ -764,6 +925,10 @@ def _set_requesting(self: "PQAutomationApp", flag: bool):
self.ai_image_send_btn.configure(state=tk.DISABLED if flag else tk.NORMAL)
self.ai_image_new_session_btn.configure(state=tk.DISABLED if flag else tk.NORMAL)
self.ai_image_stop_btn.configure(state=tk.NORMAL if flag else tk.DISABLED)
if hasattr(self, "ai_image_upload_ref_btn"):
self.ai_image_upload_ref_btn.configure(state=tk.DISABLED if flag else tk.NORMAL)
if hasattr(self, "ai_image_clear_ref_btn"):
self.ai_image_clear_ref_btn.configure(state=tk.DISABLED if flag else tk.NORMAL)
except Exception:
pass
if flag:
@@ -797,9 +962,24 @@ def _on_request_done(self: "PQAutomationApp", record, exc, req_seq):
return
self.ai_image_status_var.set("完成")
self.ai_image_input.delete("1.0", tk.END)
# 多轮对话链路:本轮返回的 imageUrl 作为下一轮的参考图
next_ref = ""
try:
if record is not None and isinstance(record.extra, dict):
next_ref = (record.extra.get("source_url") or "").strip()
except Exception:
next_ref = ""
sid = (record.session_id if record is not None else "") or _svc.get_session_id()
if next_ref and sid:
self._ai_image_session_refs[sid] = next_ref
# 手动上传的 pending 参考图只对本次发送生效,发完清除
self._ai_image_pending_ref_url = ""
self._ai_image_pending_ref_name = ""
_refresh_ref_label(self)
reload_ai_image_list(self)
if record is not None:
logger.info("[AIImagePanel] 生成完成并入列 id=%s sid=%s", record.id, (record.session_id or "")[:8])
logger.info("[AIImagePanel] 生成完成并入列 id=%s sid=%s next_ref=%s",
record.id, (record.session_id or "")[:8], next_ref or "-")
if record is not None and self.ai_image_records:
item_id = _find_tree_item_by_record_id(self, record.id)
if item_id:

View File

@@ -36,13 +36,7 @@ def _make_card(parent, icon: str, title: str) -> ttk.Frame:
def create_floating_config_panel(self: "PQAutomationApp"):
"""创建顶部"配置项"现代化折叠面板
布局变化vs 旧版):
- 用 Unicode chevron + 整条 header 可点击折叠/展开;
- header 上额外显示折叠状态预览(``config_preview_var``
- header 右侧承载常驻操作工具条(开始/停止/保存 等),不再放中部;
- 内部三个区段从 LabelFrame 改为统一的 Card 样式。
"""创建顶部"配置项"现代化折叠面板
"""
cf = CollapsingFrame(self.control_frame_top)
cf.pack(fill="both")
@@ -54,8 +48,7 @@ def create_floating_config_panel(self: "PQAutomationApp"):
# 折叠预览:呈现"测试类型 · 已选测试项"
self.config_preview_var = tk.StringVar(value="")
# header 右侧工具条占位 —— create_operation_frame 之后向这里挂按钮
self.toolbar_actions_frame: ttk.Frame | None = None
self.toolbar_actions_frame = None
def _header_actions(parent: ttk.Frame):
# 暴露给 create_operation_frame 使用
@@ -90,7 +83,7 @@ def create_floating_config_panel(self: "PQAutomationApp"):
signal_card.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=(6, 0))
self.signal_format_frame = signal_card._body # type: ignore[attr-defined]
# 创建卡片内部内容(沿用旧函数,父级已是 body Frame
# 创建卡片内部内容
self.create_connection_content()
self.create_test_items_content()
self.create_signal_format_content()
@@ -111,6 +104,7 @@ def refresh_config_preview(self: "PQAutomationApp") -> None:
"screen_module": "屏模组",
"sdr_movie": "SDR Movie",
"hdr_movie": "HDR Movie",
"local_dimming": "Local Dimming",
}
current_type = getattr(self.config, "current_test_type", "")
type_label = type_labels.get(current_type, "")
@@ -171,6 +165,10 @@ def create_test_items_content(self: "PQAutomationApp"):
("色准", "accuracy"),
],
},
"local_dimming": {
"frame": ttk.Frame(self.test_items_frame),
"items": [],
},
}
# 根据当前测试类型创建复选框
@@ -473,10 +471,103 @@ def create_signal_format_content(self: "PQAutomationApp"):
hdr_output_format_combo.bind("<<ComboboxSelected>>", self.on_hdr_output_format_changed)
hdr_output_format_combo.grid(row=5, column=1, sticky=tk.W, padx=5, pady=2)
# ==================== Local Dimming 信号格式设置 ====================
self.local_dimming_signal_frame = ttk.Frame(self.signal_tabs)
self.local_dimming_signal_frame.grid_columnconfigure(0, weight=0)
self.local_dimming_signal_frame.grid_columnconfigure(1, weight=1)
self.signal_tabs.add(self.local_dimming_signal_frame, text="Local Dimming")
ld_cfg = self.config.current_test_types.get("local_dimming", {})
ttk.Label(self.local_dimming_signal_frame, text="分辨率:").grid(
row=0, column=0, sticky=tk.W, padx=5, pady=2
)
self.local_dimming_timing_var = tk.StringVar(
value=ld_cfg.get("timing", "DMT 1920x 1080 @ 60Hz")
)
ld_timing_combo = ttk.Combobox(
self.local_dimming_signal_frame,
textvariable=self.local_dimming_timing_var,
values=UCDEnum.TimingInfo.get_formatted_resolution_list(),
width=20,
state="readonly",
)
ld_timing_combo.bind("<<ComboboxSelected>>", self.on_local_dimming_timing_changed)
ld_timing_combo.grid(row=0, column=1, sticky=tk.W, padx=5, pady=2)
ttk.Label(self.local_dimming_signal_frame, text="色彩空间:").grid(
row=1, column=0, sticky=tk.W, padx=5, pady=2
)
self.local_dimming_color_space_var = tk.StringVar(
value=ld_cfg.get("colorimetry", "sRGB")
)
ld_color_space_combo = ttk.Combobox(
self.local_dimming_signal_frame,
textvariable=self.local_dimming_color_space_var,
values=["sRGB", "BT.709", "BT.601", "BT.2020", "DCI-P3"],
width=10,
state="readonly",
)
ld_color_space_combo.bind("<<ComboboxSelected>>", self.on_local_dimming_signal_format_changed)
ld_color_space_combo.grid(row=1, column=1, sticky=tk.W, padx=5, pady=2)
ttk.Label(self.local_dimming_signal_frame, text="数据范围:").grid(
row=2, column=0, sticky=tk.W, padx=5, pady=2
)
self.local_dimming_data_range_var = tk.StringVar(
value=ld_cfg.get("data_range", UCDEnum.SignalFormat.DataRange.FULL)
)
ld_data_range_combo = ttk.Combobox(
self.local_dimming_signal_frame,
textvariable=self.local_dimming_data_range_var,
values=UCDEnum.SignalFormat.DataRange.get_list(),
width=10,
state="readonly",
)
ld_data_range_combo.bind("<<ComboboxSelected>>", self.on_local_dimming_signal_format_changed)
ld_data_range_combo.grid(row=2, column=1, sticky=tk.W, padx=5, pady=2)
default_ld_bpc = int(ld_cfg.get("bpc", 8))
default_ld_bit_depth = (
f"{default_ld_bpc}bit"
if f"{default_ld_bpc}bit" in UCDEnum.SignalFormat.BitDepth.get_list()
else UCDEnum.SignalFormat.BitDepth.BIT_8
)
ttk.Label(self.local_dimming_signal_frame, text="编码位深:").grid(
row=3, column=0, sticky=tk.W, padx=5, pady=2
)
self.local_dimming_bit_depth_var = tk.StringVar(value=default_ld_bit_depth)
ld_bit_depth_combo = ttk.Combobox(
self.local_dimming_signal_frame,
textvariable=self.local_dimming_bit_depth_var,
values=UCDEnum.SignalFormat.BitDepth.get_list(),
width=10,
state="readonly",
)
ld_bit_depth_combo.bind("<<ComboboxSelected>>", self.on_local_dimming_signal_format_changed)
ld_bit_depth_combo.grid(row=3, column=1, sticky=tk.W, padx=5, pady=2)
ttk.Label(self.local_dimming_signal_frame, text="色彩格式:").grid(
row=4, column=0, sticky=tk.W, padx=5, pady=2
)
self.local_dimming_output_format_var = tk.StringVar(
value=ld_cfg.get("color_format", UCDEnum.SignalFormat.OutputFormat.RGB)
)
ld_output_format_combo = ttk.Combobox(
self.local_dimming_signal_frame,
textvariable=self.local_dimming_output_format_var,
values=UCDEnum.SignalFormat.OutputFormat.get_list(),
width=10,
state="readonly",
)
ld_output_format_combo.bind("<<ComboboxSelected>>", self.on_local_dimming_signal_format_changed)
ld_output_format_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
self.signal_tabs.tab(3, state="disabled") # 禁用 Local Dimming
def create_connection_content(self: "PQAutomationApp"):
@@ -513,41 +604,50 @@ def create_connection_content(self: "PQAutomationApp"):
# 添加按钮框架
button_frame = ttk.Frame(com_frame)
button_frame.grid(row=3, column=0, columnspan=3, pady=3, sticky="w")
button_frame.grid(row=3, column=0, columnspan=3, pady=3, sticky="ew")
button_frame.grid_columnconfigure(0, weight=1)
button_frame.grid_columnconfigure(1, weight=1)
button_frame.grid_columnconfigure(2, weight=1)
connect_icon = load_icon("assets/connect-svgrepo-com.png")
# connect_icon = load_icon("assets/connect-svgrepo-com.png")
self.check_button = ttk.Button(
button_frame,
image=connect_icon,
bootstyle="link",
# image=connect_icon,
# bootstyle="link",
text="连接",
bootstyle="success",
takefocus=False,
command=self.check_com_connections,
)
self.check_button.image = connect_icon
self.check_button.pack(side="left", padx=0, pady=3)
# self.check_button.image = connect_icon
self.check_button.grid(row=0, column=0, padx=(0, 4), pady=3, sticky="ew")
disconnect_icon = load_icon("assets/disconnect-svgrepo-com.png")
# disconnect_icon = load_icon("assets/disconnect-svgrepo-com.png")
# 断开连接按钮
self.disconnect_button = ttk.Button(
button_frame,
image=disconnect_icon,
bootstyle="link",
# image=disconnect_icon,
# bootstyle="link",
text="断开",
bootstyle="danger",
takefocus=False,
command=self.disconnect_com_connections,
)
self.disconnect_button.image = disconnect_icon # 防止图标被垃圾回收
self.disconnect_button.pack(side="left", padx=0, pady=3)
# self.disconnect_button.image = disconnect_icon # 防止图标被垃圾回收
self.disconnect_button.grid(row=0, column=1, padx=4, pady=3, sticky="ew")
refresh_icon = load_icon("assets/refresh-svgrepo-com.png")
# refresh_icon = load_icon("assets/refresh-svgrepo-com.png")
self.refresh_button = ttk.Button(
button_frame,
image=refresh_icon,
bootstyle="link",
# image=refresh_icon,
# bootstyle="link",
text="刷新",
bootstyle="info",
takefocus=False,
command=self.refresh_com_ports,
)
self.refresh_button.image = refresh_icon # 防止图标被垃圾回收
self.refresh_button.pack(side="left", padx=0, pady=3)
# self.refresh_button.image = refresh_icon # 防止图标被垃圾回收
self.refresh_button.grid(row=0, column=2, padx=(4, 0), pady=3, sticky="ew")
# CA端口
ttk.Label(com_frame, text="CA端口:").grid(
@@ -591,12 +691,6 @@ def create_connection_content(self: "PQAutomationApp"):
def create_test_type_frame(self: "PQAutomationApp"):
"""创建测试类型选择区域(侧边栏形式)。
新版v3改进
- 深灰分层背景,接近 Calman 的侧栏密度;
- 纯文字按钮,不使用 emoji
- 用更克制的字号 / 间距做层级区分;
- 不再使用 padding=10 硬覆盖(交给 Sidebar.TButton 样式统一管理)。
"""
# 设置测试类型变量
self.test_type_var = tk.StringVar(value="screen_module")
@@ -621,6 +715,7 @@ def create_test_type_frame(self: "PQAutomationApp"):
("屏模组性能测试", "screen_module"),
("SDR Movie", "sdr_movie"),
("HDR Movie", "hdr_movie"),
("Local Dimming", "local_dimming"),
]
for text, type_value in test_types:
@@ -643,7 +738,6 @@ def create_test_type_frame(self: "PQAutomationApp"):
panel_buttons = [
("log_btn", "测试日志", self.toggle_log_panel),
("local_dimming_btn", "Local Dimming", self.toggle_local_dimming_panel),
("ai_image_btn", "AI 图片", self.toggle_ai_image_panel),
("pantone_baseline_btn", "Pantone 摸底", self.toggle_pantone_baseline_panel),
("gamma_pattern_btn", "Gamma 图案", self.toggle_gamma_pattern_panel),
@@ -688,8 +782,6 @@ def create_test_type_frame(self: "PQAutomationApp"):
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
if "ai_image" in self.panels:
self.panels["ai_image"]["button"] = self.ai_image_btn
if "single_step" in self.panels:
@@ -785,16 +877,6 @@ def create_operation_frame(self: "PQAutomationApp"):
)
self.start_btn.pack(side=tk.LEFT, **btn_pad)
self.simulate_btn = ttk.Button(
parent,
text="模拟测试",
command=self.run_simulation_test,
bootstyle="warning-outline",
padding=(12, 6),
takefocus=False,
)
self.simulate_btn.pack(side=tk.LEFT, **btn_pad)
self.stop_btn = ttk.Button(
parent,
text="\u25a0 停止",
@@ -995,6 +1077,65 @@ def on_hdr_output_format_changed(self: "PQAutomationApp", event=None):
self.log_gui.log(f"HDR色彩格式更改失败: {str(e)}", level="error")
def on_local_dimming_timing_changed(self: "PQAutomationApp", event=None):
"""Local Dimming 分辨率改变时的回调。"""
try:
selected_timing = self.local_dimming_timing_var.get()
self.log_gui.log(f"Local Dimming 分辨率已更改为: {selected_timing}", level="info")
self.config.current_test_types.setdefault("local_dimming", {})["timing"] = selected_timing
if self.testing:
self.log_gui.log("警告: 测试进行中,分辨率更改将在下次测试时生效", level="error")
self.save_pq_config()
except Exception as e:
self.log_gui.log(f"Local Dimming 分辨率更改失败: {str(e)}", level="error")
def on_local_dimming_signal_format_changed(self: "PQAutomationApp", event=None):
"""Local Dimming ColorInfo 相关选项变更回调。"""
try:
color_space = self.local_dimming_color_space_var.get()
data_range = self.local_dimming_data_range_var.get()
bit_depth = self.local_dimming_bit_depth_var.get()
output_format = self.local_dimming_output_format_var.get()
ld_cfg = self.config.current_test_types.setdefault("local_dimming", {})
ld_cfg["colorimetry"] = color_space
ld_cfg["color_format"] = output_format
ld_cfg["bpc"] = UCDEnum.SignalFormat.BitDepth.get_bit_value(bit_depth)
ld_cfg["data_range"] = data_range
self.log_gui.log(
(
"Local Dimming 信号格式已更新: "
f"色彩空间={color_space}, 数据范围={data_range}, "
f"位深={bit_depth}, 色彩格式={output_format}"
),
level="info",
)
if self.testing:
self.log_gui.log("警告: 测试进行中,格式更改将在下次测试时生效", level="error")
self.save_pq_config()
return
if getattr(self.ucd, "status", False):
ok = self.signal_service.update_signal_format(
color_space=color_space,
data_range=data_range,
bit_depth=bit_depth,
output_format=output_format,
)
if not ok:
self.log_gui.log("Local Dimming 信号格式应用到UCD失败", level="error")
self.save_pq_config()
except Exception as e:
self.log_gui.log(f"Local Dimming 信号格式更改失败: {str(e)}", level="error")
def update_test_items(self: "PQAutomationApp"):
"""根据当前测试类型更新测试项目复选框"""
# 先隐藏所有测试项目框架
@@ -1023,6 +1164,7 @@ def update_test_items(self: "PQAutomationApp"):
)
# 添加复选框
toggle_bootstyle = "success-round-toggle"
for i, (text, var_name) in enumerate(config["items"]):
is_checked = var_name in saved_test_items
var = tk.BooleanVar(value=is_checked)
@@ -1032,7 +1174,7 @@ def update_test_items(self: "PQAutomationApp"):
frame,
text=text,
variable=var,
bootstyle="round-toggle",
bootstyle=toggle_bootstyle,
command=self.update_config_and_tabs,
).grid(row=i // 2 + 1, column=i % 2, sticky=tk.W, padx=10, pady=5)
@@ -1054,6 +1196,12 @@ def on_test_type_change(self: "PQAutomationApp"):
# SDR 选中时显示客户模版按钮
self.update_custom_button_visibility()
# Local Dimming 作为并列测试类型时,自动显示其专用面板。
if self.config.current_test_type == "local_dimming":
self.show_panel("local_dimming")
elif getattr(self, "current_panel", None) == "local_dimming":
self.hide_all_panels()
class MainLayoutMixin:
@@ -1074,5 +1222,7 @@ class MainLayoutMixin:
on_sdr_timing_changed = on_sdr_timing_changed
on_sdr_output_format_changed = on_sdr_output_format_changed
on_hdr_output_format_changed = on_hdr_output_format_changed
on_local_dimming_timing_changed = on_local_dimming_timing_changed
on_local_dimming_signal_format_changed = on_local_dimming_signal_format_changed
update_test_items = update_test_items
on_test_type_change = on_test_type_change

View File

@@ -365,6 +365,7 @@ def update_sidebar_selection(self: "PQAutomationApp"):
self.screen_module_btn.configure(style="Sidebar.TButton")
self.sdr_movie_btn.configure(style="Sidebar.TButton")
self.hdr_movie_btn.configure(style="Sidebar.TButton")
self.local_dimming_btn.configure(style="Sidebar.TButton")
# 设置当前选中按钮的样式
current_type = self.test_type_var.get()
@@ -374,6 +375,8 @@ def update_sidebar_selection(self: "PQAutomationApp"):
self.sdr_movie_btn.configure(style="SidebarSelected.TButton")
elif current_type == "hdr_movie":
self.hdr_movie_btn.configure(style="SidebarSelected.TButton")
elif current_type == "local_dimming":
self.local_dimming_btn.configure(style="SidebarSelected.TButton")
class SidePanelsMixin: