修改色准测试结果显示
This commit is contained in:
@@ -1,98 +1,33 @@
|
|||||||
"""色准测试结果绘制。
|
"""色准测试结果绘制。
|
||||||
|
|
||||||
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_accuracy 原样搬迁。
|
布局:
|
||||||
|
- 左侧:大尺寸 ColorChecker 条形图(每个条形使用对应颜色)。
|
||||||
|
- 右侧:CIE 1976 u'v' 色度图(目标点/实测点/偏移连线)。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from matplotlib.patches import Rectangle
|
|
||||||
|
|
||||||
from typing import TYPE_CHECKING
|
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:
|
if TYPE_CHECKING:
|
||||||
from pqAutomationApp import PQAutomationApp
|
from pqAutomationApp import PQAutomationApp
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# 常量
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type):
|
_COLOR_MAP = {
|
||||||
"""绘制色准测试结果 - 29色显示 - 简洁版布局(显示 Gamma)"""
|
|
||||||
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 获取色准数据
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 获取 Gamma 值
|
|
||||||
target_gamma = accuracy_data.get("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
|
|
||||||
title = f"{test_type_name} - 色准测试(全 29色 | Gamma {target_gamma})"
|
|
||||||
|
|
||||||
self.accuracy_fig.suptitle(
|
|
||||||
title,
|
|
||||||
fontsize=11,
|
|
||||||
y=0.98,
|
|
||||||
fontweight="bold",
|
|
||||||
color="#111111",
|
|
||||||
)
|
|
||||||
|
|
||||||
# ========== 29色:6行5列布局 ==========
|
|
||||||
cols = 5
|
|
||||||
rows = 6
|
|
||||||
|
|
||||||
patch_width = 0.135
|
|
||||||
patch_height = 0.085
|
|
||||||
x_start = 0.08
|
|
||||||
y_start = 0.90
|
|
||||||
x_gap = 0.035
|
|
||||||
y_gap = 0.050
|
|
||||||
|
|
||||||
# ========== 绘制色块 ==========
|
|
||||||
for i, (color_name, delta_e) in enumerate(zip(color_patches, delta_e_values)):
|
|
||||||
row = i // cols
|
|
||||||
col = i % cols
|
|
||||||
|
|
||||||
x = x_start + col * (patch_width + x_gap)
|
|
||||||
y = y_start - row * (patch_height + y_gap)
|
|
||||||
|
|
||||||
# 颜色映射
|
|
||||||
color_map = {
|
|
||||||
# 灰阶
|
|
||||||
"White": "#FFFFFF",
|
"White": "#FFFFFF",
|
||||||
"Gray 80": "#E6E6E6",
|
"Gray 80": "#E6E6E6",
|
||||||
"Gray 65": "#D1D1D1",
|
"Gray 65": "#D1D1D1",
|
||||||
"Gray 50": "#BABABA",
|
"Gray 50": "#BABABA",
|
||||||
"Gray 35": "#9E9E9E",
|
"Gray 35": "#9E9E9E",
|
||||||
# 饱和色
|
"Black": "#000000",
|
||||||
"100% Red": "#FF0000",
|
|
||||||
"100% Green": "#00FF00",
|
|
||||||
"100% Blue": "#0000FF",
|
|
||||||
"100% Cyan": "#00FFFF",
|
|
||||||
"100% Magenta": "#FF00FF",
|
|
||||||
"100% Yellow": "#FFFF00",
|
|
||||||
# ColorChecker 颜色
|
|
||||||
"Dark Skin": "#735242",
|
"Dark Skin": "#735242",
|
||||||
"Light Skin": "#C29682",
|
"Light Skin": "#C29682",
|
||||||
"Blue Sky": "#5E7A9C",
|
"Blue Sky": "#5E7A9C",
|
||||||
@@ -111,217 +46,245 @@ def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type):
|
|||||||
"Yellow (Legacy)": "#EDC721",
|
"Yellow (Legacy)": "#EDC721",
|
||||||
"Magenta (Legacy)": "#BA5491",
|
"Magenta (Legacy)": "#BA5491",
|
||||||
"Cyan (Legacy)": "#0085A3",
|
"Cyan (Legacy)": "#0085A3",
|
||||||
|
"100% Red": "#FF0000",
|
||||||
|
"100% Green": "#00FF00",
|
||||||
|
"100% Blue": "#0000FF",
|
||||||
|
"100% Cyan": "#00FFFF",
|
||||||
|
"100% Magenta": "#FF00FF",
|
||||||
|
"100% Yellow": "#FFFF00",
|
||||||
}
|
}
|
||||||
|
|
||||||
patch_color = color_map.get(color_name, "#808080")
|
|
||||||
|
|
||||||
# ΔE 等级颜色
|
def _grade_color(delta_e: float) -> str:
|
||||||
if delta_e < 3:
|
if delta_e < 3:
|
||||||
edge_color = "green"
|
return "#1FAE45" # 绿
|
||||||
elif delta_e < 5:
|
if delta_e < 5:
|
||||||
edge_color = "orange"
|
return "#E08A00" # 橙
|
||||||
else:
|
return "#D81B1B" # 红
|
||||||
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)
|
|
||||||
|
|
||||||
# ========== 标注色块名称(上方)==========
|
def _xy_to_uv(x: float, y: float):
|
||||||
self.accuracy_ax.text(
|
"""CIE 1931 xy → CIE 1976 u'v'"""
|
||||||
x + patch_width / 2,
|
denom = -2.0 * x + 12.0 * y + 3.0
|
||||||
y + patch_height + 0.015,
|
if abs(denom) < 1e-10:
|
||||||
color_name,
|
return 0.0, 0.0
|
||||||
ha="center",
|
return (4.0 * x) / denom, (9.0 * y) / denom
|
||||||
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"
|
# ============================================================
|
||||||
|
# 子图:左侧 Calman 风格面板
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
self.accuracy_ax.text(
|
def _draw_left_panel(ax, color_patches, delta_e_values):
|
||||||
x + patch_width / 2,
|
"""左侧仅保留大条形图。"""
|
||||||
y + patch_height / 2,
|
ax.clear()
|
||||||
f"ΔE\n{delta_e:.2f}",
|
|
||||||
ha="center",
|
n = len(color_patches)
|
||||||
va="center",
|
if n == 0:
|
||||||
fontsize=5.2,
|
ax.set_axis_off()
|
||||||
fontweight="bold",
|
return
|
||||||
color=text_color,
|
|
||||||
transform=self.accuracy_ax.transAxes,
|
y_pos = list(range(n))
|
||||||
bbox=dict(
|
bar_colors = [_COLOR_MAP.get(name, "#888888") for name in color_patches]
|
||||||
boxstyle="round,pad=0.22",
|
edge_colors = [_grade_color(dE) for dE in delta_e_values]
|
||||||
facecolor="white" if text_color == "black" else "black",
|
|
||||||
alpha=0.75,
|
ax.barh(
|
||||||
edgecolor=edge_color,
|
y_pos,
|
||||||
|
delta_e_values,
|
||||||
|
height=0.72,
|
||||||
|
color=bar_colors,
|
||||||
|
edgecolor=edge_colors,
|
||||||
linewidth=1.0,
|
linewidth=1.0,
|
||||||
),
|
zorder=3,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ========== 统计信息卡片(只保留外框)==========
|
ax.set_yticks(y_pos)
|
||||||
card_width = 0.84
|
ax.set_yticklabels(color_patches, fontsize=7)
|
||||||
card_height = 0.15
|
ax.invert_yaxis()
|
||||||
card_x = 0.08
|
|
||||||
card_y = 0.01
|
|
||||||
|
|
||||||
info_card = Rectangle(
|
x_max = max(15.0, max(delta_e_values) * 1.15)
|
||||||
(card_x, card_y),
|
ax.set_xlim(0, x_max)
|
||||||
card_width,
|
ax.grid(axis="x", linestyle="-", linewidth=0.6, alpha=0.3, zorder=0)
|
||||||
card_height,
|
ax.grid(axis="y", linestyle=":", linewidth=0.35, alpha=0.15, zorder=0)
|
||||||
transform=self.accuracy_ax.transAxes,
|
|
||||||
facecolor="#F0F0F0",
|
ax.set_facecolor("#FFFFFF")
|
||||||
edgecolor="black",
|
for spine in ax.spines.values():
|
||||||
linewidth=1.5,
|
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",
|
||||||
)
|
)
|
||||||
self.accuracy_ax.add_patch(info_card)
|
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")
|
||||||
self.accuracy_ax.text(
|
ax.set_aspect("equal", adjustable="box")
|
||||||
card_x + card_width / 2,
|
ax.set_title("CIE 1976 u'v'", fontsize=11, fontweight="bold",
|
||||||
card_y + card_height - 0.008,
|
color="#111", pad=4)
|
||||||
"色准统计(5灰阶 + 18 ColorChecker + 6饱和色 | ΔE 2000 标准)",
|
ax.set_xlabel("u'", fontsize=9, color="#222", labelpad=1)
|
||||||
ha="center",
|
ax.set_ylabel("v'", fontsize=9, color="#222", labelpad=1)
|
||||||
va="top",
|
ax.tick_params(axis="both", labelsize=8, colors="#222")
|
||||||
fontsize=7.5,
|
for sp in ax.spines.values():
|
||||||
fontweight="bold",
|
sp.set_color("#666")
|
||||||
color="#111111",
|
sp.set_linewidth(0.9)
|
||||||
transform=self.accuracy_ax.transAxes,
|
|
||||||
|
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 = [
|
||||||
stats_y = card_y + card_height * 0.55
|
Line2D([0], [0], marker="s", linestyle="none",
|
||||||
|
markerfacecolor="#CCCCCC", markeredgecolor="#FFFFFF",
|
||||||
# 左侧:ΔE 统计
|
markersize=7, label="目标 (Target)"),
|
||||||
left_x = card_x + 0.02
|
Line2D([0], [0], marker="o", linestyle="none",
|
||||||
stats_text = [
|
markerfacecolor="#CCCCCC", markeredgecolor="#000000",
|
||||||
f"平均 ΔE: {avg_delta_e:.2f}",
|
markersize=7, label="实测 (Actual)"),
|
||||||
f"最大 ΔE: {max_delta_e:.2f}",
|
|
||||||
f"最小 ΔE: {min_delta_e:.2f}",
|
|
||||||
]
|
]
|
||||||
|
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)
|
||||||
|
|
||||||
for i, text in enumerate(stats_text):
|
|
||||||
self.accuracy_ax.text(
|
def _draw_result_judgement(ax, accuracy_data):
|
||||||
left_x,
|
"""底部结果条"""
|
||||||
stats_y - i * 0.030,
|
ax.clear()
|
||||||
text,
|
ax.set_xlim(0, 1)
|
||||||
ha="left",
|
ax.set_ylim(0, 1)
|
||||||
va="center",
|
ax.axis("off")
|
||||||
fontsize=7,
|
|
||||||
fontweight="bold",
|
avg = accuracy_data.get("avg_delta_e", 0.0)
|
||||||
color="#111111",
|
mx = accuracy_data.get("max_delta_e", 0.0)
|
||||||
transform=self.accuracy_ax.transAxes,
|
|
||||||
|
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,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 中间:色块统计
|
|
||||||
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(
|
def plot_accuracy(self: "PQAutomationApp", accuracy_data, test_type):
|
||||||
middle_x,
|
"""绘制色准测试结果 - Calman 风格(色块 + CIE 1976 u'v' + 统计)。"""
|
||||||
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(
|
fig = self.accuracy_fig
|
||||||
middle_x,
|
fig.clear()
|
||||||
stats_y - 0.060,
|
|
||||||
f"偏差 (ΔE≥5): {poor_count} 个",
|
|
||||||
ha="left",
|
|
||||||
va="center",
|
|
||||||
fontsize=7,
|
|
||||||
color="red",
|
|
||||||
fontweight="bold",
|
|
||||||
transform=self.accuracy_ax.transAxes,
|
|
||||||
)
|
|
||||||
|
|
||||||
# 右侧:总体评价
|
color_patches = accuracy_data.get("color_patches", []) or []
|
||||||
right_x = card_x + card_width - 0.02
|
delta_e_values = accuracy_data.get("delta_e_values", []) or []
|
||||||
|
measurements = accuracy_data.get("color_measurements", []) or []
|
||||||
|
|
||||||
if avg_delta_e < 2:
|
try:
|
||||||
grade = "专业级"
|
target_gamma = float(accuracy_data.get("target_gamma", 2.2))
|
||||||
grade_icon = "★★★"
|
except (TypeError, ValueError):
|
||||||
grade_color = "darkgreen"
|
target_gamma = 2.2
|
||||||
elif avg_delta_e < 3:
|
|
||||||
grade = "优秀"
|
test_type_name = self.get_test_type_name(test_type)
|
||||||
grade_icon = "OK"
|
|
||||||
grade_color = "green"
|
if test_type == "sdr_movie":
|
||||||
elif avg_delta_e < 5:
|
title = f"{test_type_name} - 色准测试(全 29色 | Gamma {target_gamma})"
|
||||||
grade = "良好"
|
elif test_type == "hdr_movie":
|
||||||
grade_icon = "PASS"
|
title = f"{test_type_name} - 色准测试(全 29色 | PQ EOTF)"
|
||||||
grade_color = "orange"
|
|
||||||
else:
|
else:
|
||||||
grade = "需要校准"
|
title = f"{test_type_name} - 色准测试(全 29色 | Gamma {target_gamma})"
|
||||||
grade_icon = "[Error]"
|
|
||||||
grade_color = "red"
|
|
||||||
|
|
||||||
self.accuracy_ax.text(
|
fig.suptitle(title, fontsize=11, y=0.975, fontweight="bold", color="#111")
|
||||||
right_x,
|
|
||||||
stats_y + 0.020,
|
gs = fig.add_gridspec(
|
||||||
"总体评价:",
|
2, 2,
|
||||||
ha="right",
|
width_ratios=[1.12, 1.0],
|
||||||
va="bottom",
|
height_ratios=[4.0, 0.62],
|
||||||
fontsize=7,
|
left=0.08, right=0.985,
|
||||||
fontweight="bold",
|
top=0.91, bottom=0.06,
|
||||||
color="#111111",
|
wspace=0.14, hspace=0.10,
|
||||||
transform=self.accuracy_ax.transAxes,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
self.accuracy_ax.text(
|
ax_left = fig.add_subplot(gs[0, 0])
|
||||||
right_x,
|
ax_uv = fig.add_subplot(gs[0, 1])
|
||||||
stats_y - 0.025,
|
ax_judge = fig.add_subplot(gs[1, :])
|
||||||
f"{grade} {grade_icon}",
|
|
||||||
ha="right",
|
# 兼容外部对 self.accuracy_ax 的引用
|
||||||
va="top",
|
self.accuracy_ax = ax_judge
|
||||||
fontsize=11,
|
|
||||||
fontweight="bold",
|
_draw_left_panel(ax_left, color_patches, delta_e_values)
|
||||||
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.accuracy_canvas.draw()
|
||||||
self.chart_notebook.select(self.accuracy_chart_frame)
|
self.chart_notebook.select(self.accuracy_chart_frame)
|
||||||
|
|||||||
188
tools/demo_accuracy_plot.py
Normal file
188
tools/demo_accuracy_plot.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
"""离线色准图 Demo。
|
||||||
|
|
||||||
|
运行后会在 tools/demo_outputs/ 下生成一张 PNG,
|
||||||
|
用于在没有 UCD 设备时预览当前色准图表的 Calman 风格布局。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import math
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
|
||||||
|
matplotlib.use("Agg")
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
plt.rcParams["font.family"] = ["sans-serif"]
|
||||||
|
plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "DejaVu Sans"]
|
||||||
|
plt.rcParams["axes.unicode_minus"] = False
|
||||||
|
|
||||||
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||||||
|
if str(REPO_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(REPO_ROOT))
|
||||||
|
|
||||||
|
from app.plots.plot_accuracy import plot_accuracy
|
||||||
|
from app.tests.color_accuracy import (
|
||||||
|
calculate_delta_e_2000,
|
||||||
|
get_accuracy_color_standards,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
COLOR_NAMES = [
|
||||||
|
"White",
|
||||||
|
"Gray 80",
|
||||||
|
"Gray 65",
|
||||||
|
"Gray 50",
|
||||||
|
"Gray 35",
|
||||||
|
"Dark Skin",
|
||||||
|
"Light Skin",
|
||||||
|
"Blue Sky",
|
||||||
|
"Foliage",
|
||||||
|
"Blue Flower",
|
||||||
|
"Bluish Green",
|
||||||
|
"Orange",
|
||||||
|
"Purplish Blue",
|
||||||
|
"Moderate Red",
|
||||||
|
"Purple",
|
||||||
|
"Yellow Green",
|
||||||
|
"Orange Yellow",
|
||||||
|
"Blue (Legacy)",
|
||||||
|
"Green (Legacy)",
|
||||||
|
"Red (Legacy)",
|
||||||
|
"Yellow (Legacy)",
|
||||||
|
"Magenta (Legacy)",
|
||||||
|
"Cyan (Legacy)",
|
||||||
|
"100% Red",
|
||||||
|
"100% Green",
|
||||||
|
"100% Blue",
|
||||||
|
"100% Cyan",
|
||||||
|
"100% Magenta",
|
||||||
|
"100% Yellow",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class _DummyNotebook:
|
||||||
|
def select(self, *_args, **_kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class _DummyCanvas:
|
||||||
|
def draw(self):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class _DemoApp:
|
||||||
|
def __init__(self, fig):
|
||||||
|
self.accuracy_fig = fig
|
||||||
|
self.accuracy_canvas = _DummyCanvas()
|
||||||
|
self.chart_notebook = _DummyNotebook()
|
||||||
|
self.accuracy_chart_frame = object()
|
||||||
|
|
||||||
|
def get_test_type_name(self, test_type):
|
||||||
|
mapping = {
|
||||||
|
"sdr_movie": "SDR Movie",
|
||||||
|
"hdr_movie": "HDR Movie",
|
||||||
|
"screen_module": "屏模组",
|
||||||
|
}
|
||||||
|
return mapping.get(test_type, str(test_type))
|
||||||
|
|
||||||
|
|
||||||
|
def _build_demo_data(test_type: str = "sdr_movie"):
|
||||||
|
standards = get_accuracy_color_standards(test_type)
|
||||||
|
rng = np.random.default_rng(20260527)
|
||||||
|
|
||||||
|
measured = []
|
||||||
|
color_patches = []
|
||||||
|
delta_e_values = []
|
||||||
|
|
||||||
|
for idx, name in enumerate(COLOR_NAMES):
|
||||||
|
sx, sy = standards[name]
|
||||||
|
|
||||||
|
# 构造一些“看起来像真实测量”的偏移:
|
||||||
|
# 大部分点轻微偏移,少数点更明显,便于看出方向和等级差异。
|
||||||
|
if idx < 5:
|
||||||
|
offset_scale = 0.0012
|
||||||
|
elif idx < 23:
|
||||||
|
offset_scale = 0.0028
|
||||||
|
else:
|
||||||
|
offset_scale = 0.0045
|
||||||
|
|
||||||
|
angle = rng.uniform(0, 2 * math.pi)
|
||||||
|
radius = offset_scale * (0.55 + 0.85 * rng.random())
|
||||||
|
dx = math.cos(angle) * radius
|
||||||
|
dy = math.sin(angle) * radius
|
||||||
|
|
||||||
|
# 为了让图上连线不完全随机,给部分饱和色再加一点定向偏移。
|
||||||
|
if idx >= 23:
|
||||||
|
dx += 0.002 * (1 if idx % 2 == 0 else -1)
|
||||||
|
dy += 0.0015 * (1 if idx % 3 == 0 else -1)
|
||||||
|
|
||||||
|
mx = min(max(sx + dx, 0.0), 0.8)
|
||||||
|
my = min(max(sy + dy, 0.0), 0.9)
|
||||||
|
|
||||||
|
# 亮度也做一点微小变化,避免所有点完全同一层。
|
||||||
|
measured_lv = 70.0 + rng.normal(0, 4.0)
|
||||||
|
measured_lv = max(measured_lv, 1.0)
|
||||||
|
|
||||||
|
delta_e = calculate_delta_e_2000(mx, my, measured_lv, sx, sy)
|
||||||
|
|
||||||
|
measured.append((mx, my, measured_lv))
|
||||||
|
color_patches.append(name)
|
||||||
|
delta_e_values.append(delta_e)
|
||||||
|
|
||||||
|
avg_delta_e = float(np.mean(delta_e_values))
|
||||||
|
max_delta_e = float(np.max(delta_e_values))
|
||||||
|
min_delta_e = float(np.min(delta_e_values))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"color_patches": color_patches,
|
||||||
|
"delta_e_values": delta_e_values,
|
||||||
|
"color_measurements": measured,
|
||||||
|
"avg_delta_e": avg_delta_e,
|
||||||
|
"max_delta_e": max_delta_e,
|
||||||
|
"min_delta_e": min_delta_e,
|
||||||
|
"excellent_count": sum(1 for value in delta_e_values if value < 3),
|
||||||
|
"good_count": sum(1 for value in delta_e_values if 3 <= value < 5),
|
||||||
|
"poor_count": sum(1 for value in delta_e_values if value >= 5),
|
||||||
|
"avg_delta_e_gray": float(np.mean(delta_e_values[0:5])),
|
||||||
|
"avg_delta_e_colorchecker": float(np.mean(delta_e_values[5:23])),
|
||||||
|
"avg_delta_e_saturated": float(np.mean(delta_e_values[23:29])),
|
||||||
|
"target_gamma": 2.2,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description="Generate an offline color accuracy demo PNG.")
|
||||||
|
parser.add_argument(
|
||||||
|
"--output",
|
||||||
|
type=Path,
|
||||||
|
default=Path(__file__).resolve().parent / "demo_outputs" / "accuracy_demo.png",
|
||||||
|
help="Output PNG path.",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--test-type",
|
||||||
|
choices=["sdr_movie", "hdr_movie", "screen_module"],
|
||||||
|
default="sdr_movie",
|
||||||
|
help="Test type used for the title and standard color set.",
|
||||||
|
)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
args.output.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
fig = plt.Figure(figsize=(14, 8), dpi=120, tight_layout=False)
|
||||||
|
app = _DemoApp(fig)
|
||||||
|
accuracy_data = _build_demo_data(args.test_type)
|
||||||
|
|
||||||
|
plot_accuracy(app, accuracy_data, args.test_type)
|
||||||
|
fig.savefig(args.output, dpi=220)
|
||||||
|
|
||||||
|
print(f"Saved demo image to: {args.output}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
BIN
tools/demo_outputs/accuracy_demo.png
Normal file
BIN
tools/demo_outputs/accuracy_demo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 545 KiB |
Reference in New Issue
Block a user