修改calman灰阶点击异常、修改色准结果显示异常

This commit is contained in:
xinzhu.yin
2026-06-02 17:34:46 +08:00
parent 85ac47e8de
commit 3aa975c4d3
19 changed files with 968 additions and 157 deletions

View File

@@ -803,17 +803,22 @@ def _on_upload_done(self: "PQAutomationApp", name: str, url: str, exc):
def _clear_reference_image(self: "PQAutomationApp"):
"""清除手动上传的参考图,同时清除当前会话的自动链路参考。"""
"""清除手动上传的参考图
v2.1 规则要求:从第二轮开始应使用最近一次成功生成图作为输入,
因此这里不清除会话级自动链路参考;若需彻底重置,请点“新对话”。
"""
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("已清除参考图,切换为文生图模式")
sid = _svc.get_session_id()
refs = getattr(self, "_ai_image_session_refs", None) or {}
if (refs.get(sid) or "").strip():
self.ai_image_status_var.set("已清除手动参考图,当前会话仍沿用上一轮生成图")
else:
self.ai_image_status_var.set("已清除参考图,当前为文生图模式")
def _session_id_for_item(self: "PQAutomationApp", item_id: str) -> str:

View File

@@ -232,6 +232,62 @@ def _apply_calman_tree_style(self: "PQAutomationApp") -> None:
self.calman_data_tree.configure(style="Calman.Treeview")
def _calman_log(self: "PQAutomationApp", message: str, level: str = "info") -> None:
"""统一输出 Calman 面板日志。"""
logger = getattr(self, "log_gui", None)
if logger is None:
return
self._dispatch_ui(self.log_gui.log, f"CALMAN: {message}", level)
def _build_calman_config_summary(self: "PQAutomationApp") -> str:
"""生成顶部配置摘要,跟随当前测试类型展示 UCD 参数。"""
cfg = getattr(self, "config", None)
test_type = getattr(cfg, "current_test_type", "screen_module")
test_cfg = {}
if cfg is not None:
test_cfg = getattr(cfg, "current_test_types", {}).get(test_type, {})
if test_type == "screen_module":
color_space = getattr(getattr(self, "screen_module_color_space_var", None), "get", lambda: test_cfg.get("colorimetry", "-"))()
output_format = getattr(getattr(self, "screen_module_output_format_var", None), "get", lambda: test_cfg.get("color_format", "-"))()
bit_depth = getattr(getattr(self, "screen_module_bit_depth_var", None), "get", lambda: f"{int(test_cfg.get('bpc', 8))}bit")()
data_range = getattr(getattr(self, "screen_module_data_range_var", None), "get", lambda: test_cfg.get("data_range", "Full"))()
timing = test_cfg.get("timing", "-")
profile_name = "Screen"
elif test_type == "sdr_movie":
color_space = getattr(getattr(self, "sdr_color_space_var", None), "get", lambda: test_cfg.get("colorimetry", "-"))()
output_format = getattr(getattr(self, "sdr_output_format_var", None), "get", lambda: test_cfg.get("color_format", "-"))()
bit_depth = getattr(getattr(self, "sdr_bit_depth_var", None), "get", lambda: f"{int(test_cfg.get('bpc', 8))}bit")()
data_range = getattr(getattr(self, "sdr_data_range_var", None), "get", lambda: test_cfg.get("data_range", "Full"))()
timing = test_cfg.get("timing", "-")
profile_name = "SDR"
elif test_type == "hdr_movie":
color_space = getattr(getattr(self, "hdr_color_space_var", None), "get", lambda: test_cfg.get("colorimetry", "-"))()
output_format = getattr(getattr(self, "hdr_output_format_var", None), "get", lambda: test_cfg.get("color_format", "-"))()
bit_depth = getattr(getattr(self, "hdr_bit_depth_var", None), "get", lambda: f"{int(test_cfg.get('bpc', 10))}bit")()
data_range = getattr(getattr(self, "hdr_data_range_var", None), "get", lambda: test_cfg.get("data_range", "Limited"))()
timing = test_cfg.get("timing", "-")
profile_name = "HDR"
else:
color_space = test_cfg.get("colorimetry", "-")
output_format = test_cfg.get("color_format", "-")
bit_depth = test_cfg.get("bpc", "-")
data_range = test_cfg.get("data_range", "-")
timing = test_cfg.get("timing", "-")
profile_name = test_type
return (
f"Profile: {profile_name} | Timing: {timing} | CS: {color_space} | "
f"Fmt: {output_format} | Depth: {bit_depth} | Range: {data_range}"
)
def _refresh_calman_config_summary(self: "PQAutomationApp") -> None:
if hasattr(self, "calman_config_summary_var"):
self.calman_config_summary_var.set(_build_calman_config_summary(self))
def create_calman_panel(self: "PQAutomationApp") -> None:
"""创建 CALMAN 风格灰阶测试面板,注册到 panel_manager。"""
palette = _get_calman_palette()
@@ -242,6 +298,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
self.calman_results = {}
self.calman_stop_event = threading.Event()
self.calman_running = False
self.calman_patch_send_busy = False
self.calman_current_level = None
self.calman_last_record = None
self.calman_last_step_seconds = None
@@ -298,6 +355,15 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
)
self.calman_elapsed_label.pack(side=tk.LEFT)
self.calman_config_summary_var = tk.StringVar(value="")
self.calman_config_summary_label = ttk.Label(
control_row,
textvariable=self.calman_config_summary_var,
foreground=palette["status_fg"],
anchor=tk.W,
)
self.calman_config_summary_label.pack(side=tk.LEFT, padx=(12, 0), fill=tk.X, expand=True)
metrics_row = ttk.Frame(chart_frame)
metrics_row.grid(row=2, column=0, sticky=tk.EW, pady=(2, 0))
metrics_row.columnconfigure((0, 1, 2, 3), weight=1)
@@ -478,7 +544,13 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
)
def _bind_click(widget, p=pct):
widget.bind("<Button-1>", lambda _e, pp=p: send_patch(self, pp))
def _on_click(_e, pp=p):
send_patch(self, pp)
# Prevent event bubbling from canvas -> parent cell, which would
# otherwise trigger duplicated sends for a single click.
return "break"
widget.bind("<Button-1>", _on_click)
for w in (cell, target_canvas):
_bind_click(w)
@@ -581,6 +653,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
right.bind("<Configure>", lambda _e: _adaptive_matrix_columns(self))
_refresh_metric_table(self)
_refresh_calman_config_summary(self)
_update_target_strip(self)
_update_actual_strip(self)
_redraw_calman_charts(self)
@@ -592,6 +665,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
def toggle_calman_panel(self: "PQAutomationApp") -> None:
"""切换 CALMAN 灰阶面板显示。"""
self.show_panel("calman")
_refresh_calman_config_summary(self)
# ---------------------------------------------------------------------------
@@ -604,23 +678,43 @@ def send_patch(self: "PQAutomationApp", pct: int) -> None:
if not self.signal_service.is_connected:
messagebox.showwarning("提示", "请先连接 UCD323 设备")
return
if getattr(self, "calman_patch_send_busy", False):
_calman_log(self, f"send busy, ignore click pct={pct}", "warning")
self.calman_status_var.set("发送进行中,请稍候...")
return
rgb_val = int(round(pct * 255 / 100))
self.calman_current_level = pct
self.calman_status_var.set(f"发送 {pct}%RGB={rgb_val}...")
_highlight_patch(self, pct)
_refresh_calman_config_summary(self)
_calman_log(self, f"click patch pct={pct}, rgb=({rgb_val}, {rgb_val}, {rgb_val})")
self.calman_patch_send_busy = True
def worker():
try:
self.signal_service.send_solid_rgb((rgb_val, rgb_val, rgb_val))
_calman_log(self, f"send_solid_rgb start pct={pct}")
test_type = getattr(self.config, "current_test_type", "screen_module")
if hasattr(self, "pattern_service") and self.pattern_service is not None:
self.pattern_service.send_rgb(
(rgb_val, rgb_val, rgb_val),
test_type=test_type,
)
else:
self.signal_service.send_solid_rgb((rgb_val, rgb_val, rgb_val))
_calman_log(self, f"send_solid_rgb success pct={pct}")
_calman_log(self, f"ucd profile applied test_type={test_type}")
self._dispatch_ui(
self.log_gui.log, f"CALMAN: 已发送 {pct}% 灰阶 (RGB={rgb_val})",
"info",
)
self._dispatch_ui(self.calman_status_var.set, f"{pct}% 已发送")
except Exception as exc:
_calman_log(self, f"send_solid_rgb failed pct={pct}: {exc}", "error")
self._dispatch_ui(self.log_gui.log, f"发送失败: {exc}", "error")
self._dispatch_ui(self.calman_status_var.set, f"发送失败: {exc}")
finally:
self.calman_patch_send_busy = False
threading.Thread(target=worker, daemon=True).start()
@@ -693,14 +787,32 @@ def measure_current_patch(self: "PQAutomationApp") -> None:
def worker():
t0 = time.perf_counter()
_calman_log(self, f"measure start pct={pct}")
self._dispatch_ui(self.calman_status_var.set, f"采集 {pct}% 中...")
rec = _measure_once(self, pct)
if rec is None:
_calman_log(self, f"measure failed pct={pct}", "error")
self._dispatch_ui(self.calman_status_var.set, "采集失败")
return
step_s = time.perf_counter() - t0
self.calman_last_step_seconds = step_s
self.calman_results[pct] = rec
_calman_log(
self,
(
"measure success pct={pct}, x={x:.4f}, y={y:.4f}, Y={Y:.3f}, "
"cct={cct:.0f}, gamma={gamma:.3f}, de2000={de:.3f}, step={step:.2f}s"
).format(
pct=pct,
x=rec["x"],
y=rec["y"],
Y=rec["Y"],
cct=rec["cct"] if rec["cct"] == rec["cct"] else float("nan"),
gamma=rec["gamma"] if rec["gamma"] == rec["gamma"] else float("nan"),
de=rec["de2000"] if rec["de2000"] == rec["de2000"] else float("nan"),
step=step_s,
),
)
self._dispatch_ui(_apply_record_to_ui, self, rec)
self._dispatch_ui(
self.calman_status_var.set, f"{pct}% 采集完成 ({step_s:.2f}s)"
@@ -726,15 +838,28 @@ def start_sequence_test(self: "PQAutomationApp") -> None:
settle = float(getattr(self, "pattern_settle_time", 0.4))
self.calman_progress["value"] = 0
self.calman_progress_var.set("0 / 0")
_refresh_calman_config_summary(self)
_calman_log(self, f"sequence start levels={len(self.calman_levels)}, settle={settle:.2f}s")
def worker():
seq_t0 = time.perf_counter()
try:
test_type = getattr(self.config, "current_test_type", "screen_module")
rgb_session = None
if hasattr(self, "pattern_service") and self.pattern_service is not None:
rgb_session = self.pattern_service.prepare_session(
"rgb",
test_type=test_type,
log_details=False,
)
_calman_log(self, f"sequence ucd profile applied test_type={test_type}")
order = sorted(self.calman_levels, reverse=True)
total = len(order)
self._dispatch_ui(self.calman_progress_var.set, f"0 / {total}")
for i, pct in enumerate(order, 1):
if self.calman_stop_event.is_set():
_calman_log(self, f"sequence stop requested at step={i-1}/{total}", "warning")
self._dispatch_ui(self.log_gui.log, "已停止连续测试", "warning")
break
step_t0 = time.perf_counter()
@@ -742,12 +867,20 @@ def start_sequence_test(self: "PQAutomationApp") -> None:
self._dispatch_ui(
self.calman_status_var.set, f"[{i}/{total}] 发送 {pct}%"
)
_calman_log(self, f"sequence send step={i}/{total}, pct={pct}, rgb={rgb_val}")
self._dispatch_ui(_highlight_patch, self, pct)
try:
self.signal_service.send_solid_rgb(
(rgb_val, rgb_val, rgb_val)
)
if rgb_session is not None:
self.pattern_service.send_rgb(
(rgb_val, rgb_val, rgb_val),
session=rgb_session,
)
else:
self.signal_service.send_solid_rgb(
(rgb_val, rgb_val, rgb_val)
)
except Exception as exc:
_calman_log(self, f"sequence send failed step={i}/{total}, pct={pct}: {exc}", "error")
self._dispatch_ui(
self.log_gui.log, f"发送 {pct}% 失败: {exc}", "error"
)
@@ -755,11 +888,30 @@ def start_sequence_test(self: "PQAutomationApp") -> None:
self.calman_current_level = pct
# 等待稳定,停止事件触发时尽快退出
if self.calman_stop_event.wait(settle):
_calman_log(self, f"sequence interrupted during settle step={i}/{total}, pct={pct}", "warning")
break
rec = _measure_once(self, pct)
if rec is None:
_calman_log(self, f"sequence measure failed step={i}/{total}, pct={pct}", "error")
continue
self.calman_results[pct] = rec
_calman_log(
self,
(
"sequence measure step={i}/{total}, pct={pct}, x={x:.4f}, y={y:.4f}, Y={Y:.3f}, "
"cct={cct:.0f}, gamma={gamma:.3f}, de2000={de:.3f}"
).format(
i=i,
total=total,
pct=pct,
x=rec["x"],
y=rec["y"],
Y=rec["Y"],
cct=rec["cct"] if rec["cct"] == rec["cct"] else float("nan"),
gamma=rec["gamma"] if rec["gamma"] == rec["gamma"] else float("nan"),
de=rec["de2000"] if rec["de2000"] == rec["de2000"] else float("nan"),
),
)
self._dispatch_ui(_apply_record_to_ui, self, rec)
step_s = time.perf_counter() - step_t0
total_s = time.perf_counter() - seq_t0
@@ -772,9 +924,11 @@ def start_sequence_test(self: "PQAutomationApp") -> None:
total_s,
)
else:
_calman_log(self, f"sequence complete total={total}")
self._dispatch_ui(self.calman_status_var.set, "连续测试完成")
self._dispatch_ui(self.log_gui.log, "CALMAN: 连续测试完成", "success")
return
_calman_log(self, "sequence stopped", "warning")
self._dispatch_ui(self.calman_status_var.set, "已停止")
finally:
self.calman_running = False
@@ -785,14 +939,17 @@ def start_sequence_test(self: "PQAutomationApp") -> None:
def stop_sequence_test(self: "PQAutomationApp") -> None:
"""请求停止连续测试。"""
if self.calman_running:
_calman_log(self, "stop requested", "warning")
self.calman_stop_event.set()
self.calman_status_var.set("正在停止...")
else:
_calman_log(self, "stop requested but no sequence is running", "warning")
self.calman_status_var.set("当前没有运行中的连续测试")
def clear_results(self: "PQAutomationApp") -> None:
"""清空结果表和图表。"""
_calman_log(self, "clear results")
self.calman_results.clear()
self.calman_last_record = None
self.calman_reading_var.set(
@@ -1135,6 +1292,8 @@ def refresh_calman_theme(self: "PQAutomationApp") -> None:
if hasattr(self, "calman_elapsed_label"):
self.calman_elapsed_label.configure(foreground=palette["status_fg"])
if hasattr(self, "calman_config_summary_label"):
self.calman_config_summary_label.configure(foreground=palette["status_fg"])
if hasattr(self, "calman_status_label"):
self.calman_status_label.configure(foreground=palette["status_fg"])
if hasattr(self, "calman_reading_summary_label"):
@@ -1151,6 +1310,7 @@ def refresh_calman_theme(self: "PQAutomationApp") -> None:
)
_refresh_metric_table(self)
_refresh_calman_config_summary(self)
_redraw_calman_charts(self)

View File

@@ -831,6 +831,18 @@ def _on_toggle_theme(self: "PQAutomationApp") -> None:
self.update_sidebar_selection()
except Exception:
pass
# 以新的 dark_mode 值重绘当前测试类型的所有图表
if hasattr(self, "_chart_snapshots") and hasattr(self, "config"):
test_type = getattr(self.config, "current_test_type", None)
if test_type:
snapshots = self._chart_snapshots.get(test_type, {})
for chart_name, args in snapshots.items():
plot_fn = getattr(self, f"plot_{chart_name}", None)
if plot_fn:
try:
plot_fn(*args)
except Exception:
pass
def update_config_info_display(self: "PQAutomationApp"):