修改色准测试结果显示

This commit is contained in:
xinzhu.yin
2026-05-27 14:58:44 +08:00
parent dff4e0df4d
commit 59c9424218
3 changed files with 445 additions and 294 deletions

View File

@@ -1,327 +1,290 @@
"""色准测试结果绘制。
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_accuracy 原样搬迁。
布局:
- 左侧:大尺寸 ColorChecker 条形图(每个条形使用对应颜色)。
- 右侧CIE 1976 u'v' 色度图(目标点/实测点/偏移连线)。
"""
from matplotlib.patches import Rectangle
from typing import TYPE_CHECKING
from matplotlib.patches import Rectangle
from matplotlib.lines import Line2D
from app.plots.gamut_background import get_cie1976_background
from app.tests.color_accuracy import get_accuracy_color_standards
if TYPE_CHECKING:
from pqAutomationApp import PQAutomationApp
# ============================================================
# 常量
# ============================================================
def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type):
"""绘制色准测试结果 - 29色显示 - 简洁版布局(显示 Gamma"""
_COLOR_MAP = {
"White": "#FFFFFF",
"Gray 80": "#E6E6E6",
"Gray 65": "#D1D1D1",
"Gray 50": "#BABABA",
"Gray 35": "#9E9E9E",
"Black": "#000000",
"Dark Skin": "#735242",
"Light Skin": "#C29682",
"Blue Sky": "#5E7A9C",
"Foliage": "#596B42",
"Blue Flower": "#8280B0",
"Bluish Green": "#63BDA8",
"Orange": "#D97829",
"Purplish Blue": "#4A5CA3",
"Moderate Red": "#C25461",
"Purple": "#5C3D6B",
"Yellow Green": "#9EBA40",
"Orange Yellow": "#E6A12E",
"Blue (Legacy)": "#333D96",
"Green (Legacy)": "#479447",
"Red (Legacy)": "#B0303B",
"Yellow (Legacy)": "#EDC721",
"Magenta (Legacy)": "#BA5491",
"Cyan (Legacy)": "#0085A3",
"100% Red": "#FF0000",
"100% Green": "#00FF00",
"100% Blue": "#0000FF",
"100% Cyan": "#00FFFF",
"100% Magenta": "#FF00FF",
"100% Yellow": "#FFFF00",
}
self.accuracy_ax.clear()
self.accuracy_ax.set_xlim(0, 1)
self.accuracy_ax.set_ylim(0, 1)
self.accuracy_ax.axis("off")
self.accuracy_fig.subplots_adjust(
left=0.05,
right=0.95,
top=0.95,
bottom=0.02,
def _grade_color(delta_e: float) -> str:
if delta_e < 3:
return "#1FAE45" # 绿
if delta_e < 5:
return "#E08A00" # 橙
return "#D81B1B" # 红
def _xy_to_uv(x: float, y: float):
"""CIE 1931 xy → CIE 1976 u'v'"""
denom = -2.0 * x + 12.0 * y + 3.0
if abs(denom) < 1e-10:
return 0.0, 0.0
return (4.0 * x) / denom, (9.0 * y) / denom
# ============================================================
# 子图:左侧 Calman 风格面板
# ============================================================
def _draw_left_panel(ax, color_patches, delta_e_values):
"""左侧仅保留大条形图。"""
ax.clear()
n = len(color_patches)
if n == 0:
ax.set_axis_off()
return
y_pos = list(range(n))
bar_colors = [_COLOR_MAP.get(name, "#888888") for name in color_patches]
edge_colors = [_grade_color(dE) for dE in delta_e_values]
ax.barh(
y_pos,
delta_e_values,
height=0.72,
color=bar_colors,
edgecolor=edge_colors,
linewidth=1.0,
zorder=3,
)
# 获取色准数据
color_patches = accuracy_data.get("color_patches", [])
delta_e_values = accuracy_data.get("delta_e_values", [])
avg_delta_e = accuracy_data.get("avg_delta_e", 0)
max_delta_e = accuracy_data.get("max_delta_e", 0)
min_delta_e = accuracy_data.get("min_delta_e", 0)
excellent_count = accuracy_data.get("excellent_count", 0)
good_count = accuracy_data.get("good_count", 0)
poor_count = accuracy_data.get("poor_count", 0)
ax.set_yticks(y_pos)
ax.set_yticklabels(color_patches, fontsize=7)
ax.invert_yaxis()
# 获取 Gamma 值
target_gamma = accuracy_data.get("target_gamma", 2.2)
x_max = max(15.0, max(delta_e_values) * 1.15)
ax.set_xlim(0, x_max)
ax.grid(axis="x", linestyle="-", linewidth=0.6, alpha=0.3, zorder=0)
ax.grid(axis="y", linestyle=":", linewidth=0.35, alpha=0.15, zorder=0)
ax.set_facecolor("#FFFFFF")
for spine in ax.spines.values():
spine.set_color("#9A9A9A")
spine.set_linewidth(0.9)
# ============================================================
# 子图CIE 1976 u'v' 色度图(目标 vs 实测)
# ============================================================
def _draw_uv_diagram(ax, color_patches, measurements, standards):
"""绘制 CIE 1976 u'v' 上的色准对比。"""
ax.clear()
try:
bg, bbox = get_cie1976_background()
xmin, xmax, ymin, ymax = bbox
ax.imshow(
bg, extent=(xmin, xmax, ymin, ymax),
origin="upper", interpolation="bicubic",
zorder=0, aspect="auto",
)
ax.set_xlim(xmin, xmax)
ax.set_ylim(ymin, ymax)
except Exception:
ax.set_xlim(0.0, 0.65)
ax.set_ylim(0.0, 0.60)
ax.set_facecolor("#000")
ax.set_aspect("equal", adjustable="box")
ax.set_title("CIE 1976 u'v'", fontsize=11, fontweight="bold",
color="#111", pad=4)
ax.set_xlabel("u'", fontsize=9, color="#222", labelpad=1)
ax.set_ylabel("v'", fontsize=9, color="#222", labelpad=1)
ax.tick_params(axis="both", labelsize=8, colors="#222")
for sp in ax.spines.values():
sp.set_color("#666")
sp.set_linewidth(0.9)
for name, meas in zip(color_patches, measurements):
if meas is None or len(meas) < 2:
continue
mx, my = meas[0], meas[1]
sxy = standards.get(name)
if sxy is None:
continue
sx, sy = sxy
m_u, m_v = _xy_to_uv(mx, my)
s_u, s_v = _xy_to_uv(sx, sy)
face = _COLOR_MAP.get(name, "#FFFFFF")
# 目标点:仅空心方框(不填充标准颜色)
ax.scatter(
[s_u], [s_v],
s=56, marker="s",
facecolors="none", edgecolors="#FFFFFF",
linewidths=1.25, zorder=18,
)
# 实测点:白色外圈 + 内层圆点
ax.scatter(
[m_u], [m_v],
s=52, marker="o",
facecolors="none", edgecolors="#FFFFFF",
linewidths=1.0, zorder=19,
)
ax.scatter(
[m_u], [m_v],
s=24, marker="o",
facecolors=face, edgecolors="#111111",
linewidths=0.85, zorder=20,
)
legend_handles = [
Line2D([0], [0], marker="s", linestyle="none",
markerfacecolor="#CCCCCC", markeredgecolor="#FFFFFF",
markersize=7, label="目标 (Target)"),
Line2D([0], [0], marker="o", linestyle="none",
markerfacecolor="#CCCCCC", markeredgecolor="#000000",
markersize=7, label="实测 (Actual)"),
]
leg = ax.legend(
handles=legend_handles,
loc="lower right", fontsize=8,
framealpha=0.88, labelcolor="#FFF",
)
if leg is not None:
leg.get_frame().set_facecolor("#111")
leg.get_frame().set_edgecolor("#FFF")
leg.set_zorder(50)
def _draw_result_judgement(ax, accuracy_data):
"""底部结果条"""
ax.clear()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.axis("off")
avg = accuracy_data.get("avg_delta_e", 0.0)
mx = accuracy_data.get("max_delta_e", 0.0)
ax.add_patch(Rectangle(
(0.0, 0.10), 1.0, 0.80,
transform=ax.transAxes,
facecolor="#FFFFFF", edgecolor="#C6C6C6", linewidth=1.0,
))
ax.text(
0.03, 0.50,
f"Avg dE2000: {avg:.2f}",
ha="left", va="center",
fontsize=20, fontweight="normal", color="#111111",
transform=ax.transAxes,
)
ax.text(
0.52, 0.50,
f"Max dE2000: {mx:.2f}",
ha="left", va="center",
fontsize=20, fontweight="normal", color="#111111",
transform=ax.transAxes,
)
# ============================================================
# 主入口
# ============================================================
def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type):
"""绘制色准测试结果 - Calman 风格(色块 + CIE 1976 u'v' + 统计)。"""
fig = self.accuracy_fig
fig.clear()
color_patches = accuracy_data.get("color_patches", []) or []
delta_e_values = accuracy_data.get("delta_e_values", []) or []
measurements = accuracy_data.get("color_measurements", []) or []
try:
target_gamma = float(accuracy_data.get("target_gamma", 2.2))
except (TypeError, ValueError):
target_gamma = 2.2
test_type_name = self.get_test_type_name(test_type)
# ========== 标题(动态显示 Gamma==========
if test_type == "sdr_movie":
title = f"{test_type_name} - 色准测试(全 29色 | Gamma {target_gamma}"
elif test_type == "hdr_movie":
title = f"{test_type_name} - 色准测试(全 29色 | PQ EOTF"
else: # screen_module
else:
title = f"{test_type_name} - 色准测试(全 29色 | Gamma {target_gamma}"
self.accuracy_fig.suptitle(
title,
fontsize=11,
y=0.98,
fontweight="bold",
color="#111111",
fig.suptitle(title, fontsize=11, y=0.975, fontweight="bold", color="#111")
gs = fig.add_gridspec(
2, 2,
width_ratios=[1.12, 1.0],
height_ratios=[4.0, 0.62],
left=0.08, right=0.985,
top=0.91, bottom=0.06,
wspace=0.14, hspace=0.10,
)
# ========== 29色6行5列布局 ==========
cols = 5
rows = 6
ax_left = fig.add_subplot(gs[0, 0])
ax_uv = fig.add_subplot(gs[0, 1])
ax_judge = fig.add_subplot(gs[1, :])
patch_width = 0.135
patch_height = 0.085
x_start = 0.08
y_start = 0.90
x_gap = 0.035
y_gap = 0.050
# 兼容外部对 self.accuracy_ax 的引用
self.accuracy_ax = ax_judge
# ========== 绘制色块 ==========
for i, (color_name, delta_e) in enumerate(zip(color_patches, delta_e_values)):
row = i // cols
col = i % cols
_draw_left_panel(ax_left, color_patches, delta_e_values)
x = x_start + col * (patch_width + x_gap)
y = y_start - row * (patch_height + y_gap)
# 颜色映射
color_map = {
# 灰阶
"White": "#FFFFFF",
"Gray 80": "#E6E6E6",
"Gray 65": "#D1D1D1",
"Gray 50": "#BABABA",
"Gray 35": "#9E9E9E",
# 饱和色
"100% Red": "#FF0000",
"100% Green": "#00FF00",
"100% Blue": "#0000FF",
"100% Cyan": "#00FFFF",
"100% Magenta": "#FF00FF",
"100% Yellow": "#FFFF00",
# ColorChecker 颜色
"Dark Skin": "#735242",
"Light Skin": "#C29682",
"Blue Sky": "#5E7A9C",
"Foliage": "#596B42",
"Blue Flower": "#8280B0",
"Bluish Green": "#63BDA8",
"Orange": "#D97829",
"Purplish Blue": "#4A5CA3",
"Moderate Red": "#C25461",
"Purple": "#5C3D6B",
"Yellow Green": "#9EBA40",
"Orange Yellow": "#E6A12E",
"Blue (Legacy)": "#333D96",
"Green (Legacy)": "#479447",
"Red (Legacy)": "#B0303B",
"Yellow (Legacy)": "#EDC721",
"Magenta (Legacy)": "#BA5491",
"Cyan (Legacy)": "#0085A3",
}
patch_color = color_map.get(color_name, "#808080")
# ΔE 等级颜色
if delta_e < 3:
edge_color = "green"
elif delta_e < 5:
edge_color = "orange"
else:
edge_color = "red"
# 绘制色块
rect = Rectangle(
(x, y),
patch_width,
patch_height,
transform=self.accuracy_ax.transAxes,
facecolor=patch_color,
edgecolor=edge_color,
linewidth=1.8,
)
self.accuracy_ax.add_patch(rect)
# ========== 标注色块名称(上方)==========
self.accuracy_ax.text(
x + patch_width / 2,
y + patch_height + 0.015,
color_name,
ha="center",
va="bottom",
fontsize=5.5,
fontweight="bold",
transform=self.accuracy_ax.transAxes,
clip_on=False,
)
# ========== 标注 ΔE 值(中心)==========
dark_colors = [
"100% Red",
"100% Green",
"100% Blue",
"Gray 35",
"Dark Skin",
"Foliage",
"Purple",
"Purplish Blue",
"Blue (Legacy)",
"Green (Legacy)",
"Red (Legacy)",
"Magenta (Legacy)",
"Cyan (Legacy)",
]
text_color = "white" if color_name in dark_colors else "black"
self.accuracy_ax.text(
x + patch_width / 2,
y + patch_height / 2,
f"ΔE\n{delta_e:.2f}",
ha="center",
va="center",
fontsize=5.2,
fontweight="bold",
color=text_color,
transform=self.accuracy_ax.transAxes,
bbox=dict(
boxstyle="round,pad=0.22",
facecolor="white" if text_color == "black" else "black",
alpha=0.75,
edgecolor=edge_color,
linewidth=1.0,
),
)
# ========== 统计信息卡片(只保留外框)==========
card_width = 0.84
card_height = 0.15
card_x = 0.08
card_y = 0.01
info_card = Rectangle(
(card_x, card_y),
card_width,
card_height,
transform=self.accuracy_ax.transAxes,
facecolor="#F0F0F0",
edgecolor="black",
linewidth=1.5,
)
self.accuracy_ax.add_patch(info_card)
# ========== 标题(带说明)==========
self.accuracy_ax.text(
card_x + card_width / 2,
card_y + card_height - 0.008,
"色准统计5灰阶 + 18 ColorChecker + 6饱和色 | ΔE 2000 标准)",
ha="center",
va="top",
fontsize=7.5,
fontweight="bold",
color="#111111",
transform=self.accuracy_ax.transAxes,
)
# ========== 统计内容(无内部框)==========
stats_y = card_y + card_height * 0.55
# 左侧ΔE 统计
left_x = card_x + 0.02
stats_text = [
f"平均 ΔE: {avg_delta_e:.2f}",
f"最大 ΔE: {max_delta_e:.2f}",
f"最小 ΔE: {min_delta_e:.2f}",
]
for i, text in enumerate(stats_text):
self.accuracy_ax.text(
left_x,
stats_y - i * 0.030,
text,
ha="left",
va="center",
fontsize=7,
fontweight="bold",
color="#111111",
transform=self.accuracy_ax.transAxes,
)
# 中间:色块统计
middle_x = card_x + card_width * 0.32
self.accuracy_ax.text(
middle_x,
stats_y,
f"优秀 (ΔE<3): {excellent_count}",
ha="left",
va="center",
fontsize=7,
color="green",
fontweight="bold",
transform=self.accuracy_ax.transAxes,
)
self.accuracy_ax.text(
middle_x,
stats_y - 0.030,
f"良好 (3≤ΔE<5): {good_count}",
ha="left",
va="center",
fontsize=7,
color="orange",
fontweight="bold",
transform=self.accuracy_ax.transAxes,
)
self.accuracy_ax.text(
middle_x,
stats_y - 0.060,
f"偏差 (ΔE≥5): {poor_count}",
ha="left",
va="center",
fontsize=7,
color="red",
fontweight="bold",
transform=self.accuracy_ax.transAxes,
)
# 右侧:总体评价
right_x = card_x + card_width - 0.02
if avg_delta_e < 2:
grade = "专业级"
grade_icon = "★★★"
grade_color = "darkgreen"
elif avg_delta_e < 3:
grade = "优秀"
grade_icon = "OK"
grade_color = "green"
elif avg_delta_e < 5:
grade = "良好"
grade_icon = "PASS"
grade_color = "orange"
else:
grade = "需要校准"
grade_icon = "[Error]"
grade_color = "red"
self.accuracy_ax.text(
right_x,
stats_y + 0.020,
"总体评价:",
ha="right",
va="bottom",
fontsize=7,
fontweight="bold",
color="#111111",
transform=self.accuracy_ax.transAxes,
)
self.accuracy_ax.text(
right_x,
stats_y - 0.025,
f"{grade} {grade_icon}",
ha="right",
va="top",
fontsize=11,
fontweight="bold",
color=grade_color,
transform=self.accuracy_ax.transAxes,
)
try:
standards = get_accuracy_color_standards(test_type)
except Exception:
standards = {}
_draw_uv_diagram(ax_uv, color_patches, measurements, standards)
_draw_result_judgement(ax_judge, accuracy_data)
self.accuracy_canvas.draw()
self.chart_notebook.select(self.accuracy_chart_frame)