修改Calman灰阶中结果图显示、修改UI主题样式应用

This commit is contained in:
xinzhu.yin
2026-06-04 10:36:15 +08:00
parent 3aa975c4d3
commit 49d82da8b9
16 changed files with 597 additions and 210 deletions

View File

@@ -22,6 +22,7 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
from app.tests.color_accuracy import calculate_delta_e_2000
from app.views.modern_styles import get_theme_palette
if TYPE_CHECKING:
from pqAutomationApp import PQAutomationApp
@@ -35,10 +36,6 @@ D65_X = 0.3127
D65_Y = 0.3290
TARGET_CCT = 6504
TARGET_GAMMA = 2.2
_DARK_BG = "#2f2f2f"
_AX_BG = "#262626"
_FG = "#d8d8d8"
_GRID = "#5b5b5b"
DE_FORMULAS = ["2000", "94", "76"]
@@ -60,7 +57,7 @@ def _contrast_fg(gray_value: int) -> str:
def _set_canvas_patch(canvas: tk.Canvas, color: str, text: str) -> None:
"""统一设置色块 Canvas 颜色与文字,避免主题对 Label 背景渲染的干扰。"""
gray = int(color[1:3], 16)
canvas.configure(bg=color, highlightbackground="#666666")
canvas.configure(bg=color, highlightbackground=get_theme_palette()["border"])
canvas.itemconfigure("patch_bg", fill=color, outline=color)
canvas.itemconfigure("patch_text", text=text, fill=_contrast_fg(gray))
@@ -115,6 +112,7 @@ def _get_calman_palette() -> dict[str, str]:
"""根据当前主题生成 Calman 调试面板色板。"""
style = ttk.Style()
colors = style.colors
theme_palette = get_theme_palette()
bg = colors.bg
fg = colors.fg
dark_mode = _is_dark_hex(bg)
@@ -131,22 +129,22 @@ def _get_calman_palette() -> dict[str, str]:
reading_fg = _mix(fg, "#ffffff", 0.06)
status_fg = _mix(fg, bg, 0.35)
reading_accent = colors.info
xy_series = "#d7dce4"
d65_mark = "#ffffff"
xy_series = _mix(fg, "#ffffff", 0.10)
d65_mark = _mix(fg, "#ffffff", 0.04)
else:
figure_bg = _mix(bg, "#dfe7ef", 0.45)
axes_bg = _mix(bg, "#eff4f9", 0.72)
grid = _mix("#5f6f82", axes_bg, 0.55)
tree_bg = "#ffffff"
tree_even = "#ffffff"
tree_bg = theme_palette["input_bg"]
tree_even = theme_palette["input_bg"]
tree_odd = "#f3f7fb"
heading_bg = _mix(colors.primary, "#ffffff", 0.82)
reading_bg = _mix(bg, "#e7eef5", 0.58)
reading_fg = fg
status_fg = _mix(fg, bg, 0.25)
reading_accent = _mix(colors.info, "#000000", 0.25)
xy_series = "#1f2a36"
d65_mark = "#253142"
xy_series = _mix(fg, bg, 0.18)
d65_mark = _mix(fg, bg, 0.28)
return {
"figure_bg": figure_bg,
@@ -166,31 +164,59 @@ def _get_calman_palette() -> dict[str, str]:
"tree_heading_bg": heading_bg,
"tree_heading_fg": reading_fg,
"tree_select": _mix(colors.info, figure_bg, 0.35),
"patch_border": theme_palette["border"],
"patch_border_alt": _mix(theme_palette["border"], theme_palette["fg"], 0.12),
"patch_focus": theme_palette["focus"],
"xy_series": xy_series,
"d65_mark": d65_mark,
}
def _xyY_to_rgb_balance(x: float, y: float, big_y: float) -> tuple[float, float, float]:
"""把 xyY 近似映射到 RGB 比例,并归一到平均值 100"""
"""按 D65 同亮度参考计算 RGB BalanceCalman 常见口径)"""
if y <= 0 or big_y <= 0:
return float("nan"), float("nan"), float("nan")
big_x = (x * big_y) / y
big_z = ((1.0 - x - y) * big_y) / y
def _xyY_to_xyz(cx: float, cy: float, cy_big: float) -> tuple[float, float, float]:
if cy <= 0:
return float("nan"), float("nan"), float("nan")
cx_big = (cx * cy_big) / cy
cz_big = ((1.0 - cx - cy) * cy_big) / cy
return cx_big, cy_big, cz_big
r = (3.2406 * big_x) + (-1.5372 * big_y) + (-0.4986 * big_z)
g = (-0.9689 * big_x) + (1.8758 * big_y) + (0.0415 * big_z)
b = (0.0557 * big_x) + (-0.2040 * big_y) + (1.0570 * big_z)
def _xyz_to_linear_rgb(cx_big: float, cy_big: float, cz_big: float) -> tuple[float, float, float]:
rr = (3.2406 * cx_big) + (-1.5372 * cy_big) + (-0.4986 * cz_big)
gg = (-0.9689 * cx_big) + (1.8758 * cy_big) + (0.0415 * cz_big)
bb = (0.0557 * cx_big) + (-0.2040 * cy_big) + (1.0570 * cz_big)
return rr, gg, bb
r = max(r, 0.0)
g = max(g, 0.0)
b = max(b, 0.0)
mx, my, mz = _xyY_to_xyz(x, y, big_y)
tx, ty, tz = _xyY_to_xyz(D65_X, D65_Y, big_y)
mr, mg, mb = _xyz_to_linear_rgb(mx, my, mz)
tr, tg, tb = _xyz_to_linear_rgb(tx, ty, tz)
avg = (r + g + b) / 3.0
if avg <= 0:
eps = 1e-9
if tr <= eps or tg <= eps or tb <= eps:
return float("nan"), float("nan"), float("nan")
return (r / avg) * 100.0, (g / avg) * 100.0, (b / avg) * 100.0
rr = (mr / tr) * 100.0
gg = (mg / tg) * 100.0
bb = (mb / tb) * 100.0
# 明显异常值视为无效,避免图表被离群点拉坏。
if not (math.isfinite(rr) and math.isfinite(gg) and math.isfinite(bb)):
return float("nan"), float("nan"), float("nan")
if rr < 0 or gg < 0 or bb < 0:
return float("nan"), float("nan"), float("nan")
return rr, gg, bb
def _target_gamma_loglog_curve(pct: int) -> float:
"""Calman风格目标曲线低灰阶从 1.8 过渡并逐步逼近 2.2。"""
if pct <= 0:
return 1.8
return TARGET_GAMMA - 0.4 * math.exp(-pct / 6.0)
def _style_axes(self: "PQAutomationApp", ax, title: str) -> None:
@@ -480,6 +506,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
self.calman_actual_patch_cells = []
self.calman_target_patch_canvases = []
self.calman_target_hexes = []
patch_palette = _get_calman_palette()
for idx, pct in enumerate(self.calman_levels):
rgb = _pct_to_gray_rgb(pct)
color = _rgb_to_hex(rgb)
@@ -493,7 +520,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
bd=1,
relief="solid",
highlightthickness=1,
highlightbackground="#808080",
highlightbackground=patch_palette["patch_border_alt"],
cursor="hand2",
)
actual_cell.grid(row=0, column=idx, padx=1, pady=1, sticky=tk.NSEW)
@@ -520,7 +547,7 @@ def create_calman_panel(self: "PQAutomationApp") -> None:
bd=1,
relief="solid",
highlightthickness=1,
highlightbackground="#9c9c9c",
highlightbackground=patch_palette["patch_border"],
cursor="hand2",
)
cell.grid(row=1, column=idx, padx=1, pady=1, sticky=tk.NSEW)
@@ -722,7 +749,7 @@ def send_patch(self: "PQAutomationApp", pct: int) -> None:
def _measure_once(self: "PQAutomationApp", pct: int) -> dict | None:
"""采集一次 CA410并组装一条记录含 CCT/Gamma/ΔE2000"""
try:
x, y, lv, X, Y, Z = self.ca.readAllDisplay()
x, y, lv, X, Y, Z = self.read_ca_xyLv()
except Exception as exc:
self._dispatch_ui(self.log_gui.log, f"CA410 采集异常: {exc}", "error")
return None
@@ -974,20 +1001,21 @@ def clear_results(self: "PQAutomationApp") -> None:
def _highlight_patch(self: "PQAutomationApp", pct: int) -> None:
"""高亮当前选中色块。"""
palette = _get_calman_palette()
try:
idx = self.calman_levels.index(pct)
except ValueError:
return
for i, cell in enumerate(self.calman_patch_cells):
if i == idx:
cell.configure(highlightbackground="#1f6fb2", highlightthickness=2)
cell.configure(highlightbackground=palette["patch_focus"], highlightthickness=2)
else:
cell.configure(highlightbackground="#9c9c9c", highlightthickness=1)
cell.configure(highlightbackground=palette["patch_border"], highlightthickness=1)
for i, cell in enumerate(self.calman_actual_cells):
if i == idx:
cell.configure(highlightbackground="#1f6fb2", highlightthickness=2)
cell.configure(highlightbackground=palette["patch_focus"], highlightthickness=2)
else:
cell.configure(highlightbackground="#808080", highlightthickness=1)
cell.configure(highlightbackground=palette["patch_border_alt"], highlightthickness=1)
total_cols = len(self.calman_levels) + 1 # 含 metric 列
col_index = idx + 1
@@ -1082,10 +1110,18 @@ def _redraw_calman_charts(self: "PQAutomationApp") -> None:
pcts = [r["pct"] for r in recs]
de_vals = [r["de2000"] if r["de2000"] == r["de2000"] else 0 for r in recs]
lum_vals = [r["Y"] if r["Y"] == r["Y"] else 0 for r in recs]
rgb_r = [r["rgb_r"] for r in recs if r["rgb_r"] == r["rgb_r"]]
rgb_g = [r["rgb_g"] for r in recs if r["rgb_g"] == r["rgb_g"]]
rgb_b = [r["rgb_b"] for r in recs if r["rgb_b"] == r["rgb_b"]]
rgb_pcts = [r["pct"] for r in recs if r["rgb_r"] == r["rgb_r"]]
rgb_recs = [
r for r in recs
if (
r.get("rgb_r") == r.get("rgb_r")
and r.get("rgb_g") == r.get("rgb_g")
and r.get("rgb_b") == r.get("rgb_b")
)
]
rgb_pcts = [r["pct"] for r in rgb_recs]
rgb_r = [r["rgb_r"] for r in rgb_recs]
rgb_g = [r["rgb_g"] for r in rgb_recs]
rgb_b = [r["rgb_b"] for r in rgb_recs]
gamma_pcts = [r["pct"] for r in recs if r["gamma"] == r["gamma"]]
gamma_vals = [r["gamma"] for r in recs if r["gamma"] == r["gamma"]]
cct_vals = [r["cct"] for r in recs if r["cct"] == r["cct"]]
@@ -1123,6 +1159,16 @@ def _redraw_calman_charts(self: "PQAutomationApp") -> None:
a1.set_ylim(bottom=0)
a1.set_xlabel("", fontsize=8)
rgb_ylim_low = 95.0
rgb_ylim_high = 105.0
if rgb_recs:
rgb_values = rgb_r + rgb_g + rgb_b
rgb_min = min(rgb_values + [100.0])
rgb_max = max(rgb_values + [100.0])
pad = max(0.8, (rgb_max - rgb_min) * 0.15)
rgb_ylim_low = min(95.0, rgb_min - pad)
rgb_ylim_high = max(105.0, rgb_max + pad)
# RGB Balance 线图
a2 = self.calman_ax_rgb_line
a2.clear()
@@ -1133,36 +1179,38 @@ def _redraw_calman_charts(self: "PQAutomationApp") -> None:
a2.plot(rgb_pcts, rgb_b, "-", color="#4a73ff", linewidth=1.2)
a2.axhline(100, color="#9e9e9e", lw=0.8, ls="--")
a2.set_xlim(-2, 102)
a2.set_ylim(95, 105)
a2.set_ylim(rgb_ylim_low, rgb_ylim_high)
a2.set_xlabel("", fontsize=8)
# RGB Balance 条图(用最后一个点)
a3 = self.calman_ax_rgb_bar
a3.clear()
_style_axes(self, a3, "RGB Balance")
if recs:
last = recs[-1]
if rgb_recs:
last = rgb_recs[-1]
bars = [
last["rgb_r"] if last["rgb_r"] == last["rgb_r"] else 100,
last["rgb_g"] if last["rgb_g"] == last["rgb_g"] else 100,
last["rgb_b"] if last["rgb_b"] == last["rgb_b"] else 100,
last["rgb_r"],
last["rgb_g"],
last["rgb_b"],
]
a3.bar([0, 1, 2], bars, color=["#ff4d4d", "#4caf50", "#4a73ff"], width=0.7)
a3.set_xticks([0, 1, 2], ["R", "G", "B"])
else:
a3.set_xticks([0, 1, 2], ["R", "G", "B"])
a3.set_ylim(95, 105)
a3.set_ylim(rgb_ylim_low, rgb_ylim_high)
a3.set_xlabel("", fontsize=8)
# Gamma
a4 = self.calman_ax_gamma
a4.clear()
_style_axes(self, a4, "Gamma Log/Log")
target_pcts = list(self.calman_levels)
target_vals = [_target_gamma_loglog_curve(p) for p in target_pcts]
a4.plot(target_pcts, target_vals, "-", color="#f4ff00", linewidth=1.8)
if gamma_pcts:
a4.plot(gamma_pcts, gamma_vals, "-", color="#ffe24d", linewidth=1.3)
a4.axhline(TARGET_GAMMA, color="#9e9e9e", lw=0.8, ls="--")
a4.plot(gamma_pcts, gamma_vals, "-", color="#8f8f8f", linewidth=2.0)
a4.set_xlim(-2, 102)
a4.set_ylim(1.6, 2.8)
a4.set_ylim(1.8, 2.8)
a4.set_xlabel("", fontsize=8)
self.calman_canvas.draw_idle()
@@ -1232,14 +1280,20 @@ def _refresh_metric_table(self: "PQAutomationApp") -> None:
"""重绘下方矩阵表。"""
_apply_calman_tree_style(self)
palette = _get_calman_palette()
ref_white_y = self.calman_results.get(100, {}).get("Y")
def _target_y_abs(pctx):
if pctx is None:
return "-"
if ref_white_y is None or ref_white_y != ref_white_y or ref_white_y <= 0:
return "-"
return _safe_float(ref_white_y * ((pctx / 100.0) ** TARGET_GAMMA), "{:.3f}")
metrics = [
("x CIE31", lambda r: _safe_float(r.get("x")) if r else "-"),
("y CIE31", lambda r: _safe_float(r.get("y")) if r else "-"),
("Y", lambda r: _safe_float(r.get("Y"), "{:.3f}") if r else "-"),
(
"Target Y",
lambda _r, pctx=None: _safe_float((pctx / 100.0) ** TARGET_GAMMA, "{:.4f}") if pctx and pctx > 0 else ("0.0000" if pctx == 0 else "-"),
),
("Target Y", lambda _r, pctx=None: _target_y_abs(pctx)),
("Gamma Log/Log", lambda r: _safe_float(r.get("gamma"), "{:.3f}") if r else "-"),
("CCT", lambda r: _safe_float(r.get("cct"), "{:.0f}") if r else "-"),
("dE 2000", lambda r: _safe_float(r.get("de2000"), "{:.3f}") if r else "-"),