重构提取plot函数
This commit is contained in:
0
app/plots/__init__.py
Normal file
0
app/plots/__init__.py
Normal file
318
app/plots/plot_accuracy.py
Normal file
318
app/plots/plot_accuracy.py
Normal file
@@ -0,0 +1,318 @@
|
|||||||
|
"""色准测试结果绘制。
|
||||||
|
|
||||||
|
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_accuracy 原样搬迁。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from matplotlib.patches import Rectangle
|
||||||
|
|
||||||
|
|
||||||
|
def plot_accuracy(app, accuracy_data, test_type):
|
||||||
|
"""绘制色准测试结果 - 29色显示 - 简洁版布局(显示 Gamma)"""
|
||||||
|
self = app
|
||||||
|
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 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",
|
||||||
|
"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",
|
||||||
|
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",
|
||||||
|
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 = "✓✓"
|
||||||
|
grade_color = "green"
|
||||||
|
elif avg_delta_e < 5:
|
||||||
|
grade = "良好"
|
||||||
|
grade_icon = "✓"
|
||||||
|
grade_color = "orange"
|
||||||
|
else:
|
||||||
|
grade = "需要校准"
|
||||||
|
grade_icon = "✗"
|
||||||
|
grade_color = "red"
|
||||||
|
|
||||||
|
self.accuracy_ax.text(
|
||||||
|
right_x,
|
||||||
|
stats_y + 0.020,
|
||||||
|
"总体评价:",
|
||||||
|
ha="right",
|
||||||
|
va="bottom",
|
||||||
|
fontsize=7,
|
||||||
|
fontweight="bold",
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.accuracy_canvas.draw()
|
||||||
|
self.chart_notebook.select(4)
|
||||||
325
app/plots/plot_cct.py
Normal file
325
app/plots/plot_cct.py
Normal file
@@ -0,0 +1,325 @@
|
|||||||
|
"""CCT / 色度一致性绘制。
|
||||||
|
|
||||||
|
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_cct 原样搬迁。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def plot_cct(app, test_type):
|
||||||
|
"""绘制 x 和 y 坐标分离图 - 每个点标注纵坐标值"""
|
||||||
|
self = app
|
||||||
|
|
||||||
|
self.cct_fig.clear()
|
||||||
|
|
||||||
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
||||||
|
if not gray_data:
|
||||||
|
gray_data = self.results.get_intermediate_data("cct", "gray")
|
||||||
|
|
||||||
|
if not gray_data or len(gray_data) < 2:
|
||||||
|
self.log_gui.log("⚠️ 无 xy 数据可用")
|
||||||
|
ax = self.cct_fig.add_subplot(111)
|
||||||
|
ax.text(
|
||||||
|
0.5,
|
||||||
|
0.5,
|
||||||
|
"无可用数据",
|
||||||
|
ha="center",
|
||||||
|
va="center",
|
||||||
|
fontsize=14,
|
||||||
|
color="red",
|
||||||
|
)
|
||||||
|
ax.axis("off")
|
||||||
|
self.cct_canvas.draw()
|
||||||
|
return
|
||||||
|
|
||||||
|
x_measured = [data[0] for data in gray_data]
|
||||||
|
y_measured = [data[1] for data in gray_data]
|
||||||
|
|
||||||
|
# 反转数据顺序(从暗到亮)
|
||||||
|
x_measured = x_measured[::-1]
|
||||||
|
y_measured = y_measured[::-1]
|
||||||
|
|
||||||
|
# 去掉第一个点
|
||||||
|
x_measured = x_measured[1:]
|
||||||
|
y_measured = y_measured[1:]
|
||||||
|
|
||||||
|
# 重新生成灰阶坐标
|
||||||
|
total_points = len(gray_data)
|
||||||
|
grayscale = np.linspace(100 / total_points, 100, len(x_measured))
|
||||||
|
|
||||||
|
self.log_gui.log(f"✓ 已移除第一个数据点,当前数据点数: {len(x_measured)}")
|
||||||
|
self.log_gui.log(f" x范围: {min(x_measured):.6f} - {max(x_measured):.6f}")
|
||||||
|
self.log_gui.log(f" y范围: {min(y_measured):.6f} - {max(y_measured):.6f}")
|
||||||
|
|
||||||
|
# ========== 根据测试类型读取对应参数 ==========
|
||||||
|
if test_type == "sdr_movie":
|
||||||
|
try:
|
||||||
|
x_ideal = float(self.sdr_cct_x_ideal_var.get())
|
||||||
|
x_tolerance = float(self.sdr_cct_x_tolerance_var.get())
|
||||||
|
y_ideal = float(self.sdr_cct_y_ideal_var.get())
|
||||||
|
y_tolerance = float(self.sdr_cct_y_tolerance_var.get())
|
||||||
|
self.log_gui.log("✓ 使用 SDR 色度参数")
|
||||||
|
except:
|
||||||
|
x_ideal = 0.3127
|
||||||
|
x_tolerance = 0.003
|
||||||
|
y_ideal = 0.3290
|
||||||
|
y_tolerance = 0.003
|
||||||
|
self.log_gui.log("⚠️ SDR 参数读取失败,使用默认值")
|
||||||
|
elif test_type == "hdr_movie":
|
||||||
|
try:
|
||||||
|
x_ideal = float(self.hdr_cct_x_ideal_var.get())
|
||||||
|
x_tolerance = float(self.hdr_cct_x_tolerance_var.get())
|
||||||
|
y_ideal = float(self.hdr_cct_y_ideal_var.get())
|
||||||
|
y_tolerance = float(self.hdr_cct_y_tolerance_var.get())
|
||||||
|
self.log_gui.log("✓ 使用 HDR 色度参数")
|
||||||
|
except:
|
||||||
|
x_ideal = 0.3127
|
||||||
|
x_tolerance = 0.003
|
||||||
|
y_ideal = 0.3290
|
||||||
|
y_tolerance = 0.003
|
||||||
|
self.log_gui.log("⚠️ HDR 参数读取失败,使用默认值")
|
||||||
|
else: # screen_module
|
||||||
|
try:
|
||||||
|
x_ideal = float(self.cct_x_ideal_var.get())
|
||||||
|
x_tolerance = float(self.cct_x_tolerance_var.get())
|
||||||
|
y_ideal = float(self.cct_y_ideal_var.get())
|
||||||
|
y_tolerance = float(self.cct_y_tolerance_var.get())
|
||||||
|
self.log_gui.log("✓ 使用屏模组色度参数")
|
||||||
|
except:
|
||||||
|
x_ideal = 0.306
|
||||||
|
x_tolerance = 0.003
|
||||||
|
y_ideal = 0.318
|
||||||
|
y_tolerance = 0.003
|
||||||
|
self.log_gui.log("⚠️ 屏模组参数读取失败,使用默认值")
|
||||||
|
|
||||||
|
x_low = x_ideal - x_tolerance
|
||||||
|
x_high = x_ideal + x_tolerance
|
||||||
|
y_low = y_ideal - y_tolerance
|
||||||
|
y_high = y_ideal + y_tolerance
|
||||||
|
|
||||||
|
self.log_gui.log(f"✓ 用户设置参数:")
|
||||||
|
self.log_gui.log(f" x-ideal={x_ideal:.4f}, tolerance={x_tolerance:.4f}")
|
||||||
|
self.log_gui.log(f" x范围: [{x_low:.4f}, {x_high:.4f}]")
|
||||||
|
self.log_gui.log(f" y-ideal={y_ideal:.4f}, tolerance={y_tolerance:.4f}")
|
||||||
|
self.log_gui.log(f" y范围: [{y_low:.4f}, {y_high:.4f}]")
|
||||||
|
|
||||||
|
# 为所有测试类型创建子图
|
||||||
|
ax1 = self.cct_fig.add_subplot(211)
|
||||||
|
ax2 = self.cct_fig.add_subplot(212)
|
||||||
|
|
||||||
|
# ========== 上图:x coordinates ==========
|
||||||
|
ax1.plot(
|
||||||
|
grayscale,
|
||||||
|
x_measured,
|
||||||
|
"b-o",
|
||||||
|
label="屏本体",
|
||||||
|
linewidth=2,
|
||||||
|
markersize=4,
|
||||||
|
zorder=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 为每个点添加数值标注(x 坐标)
|
||||||
|
for i, (gs, x_val) in enumerate(zip(grayscale, x_measured)):
|
||||||
|
ax1.annotate(
|
||||||
|
f"{x_val:.5f}",
|
||||||
|
xy=(gs, x_val),
|
||||||
|
xytext=(0, 8),
|
||||||
|
textcoords="offset points",
|
||||||
|
ha="center",
|
||||||
|
va="bottom",
|
||||||
|
fontsize=7,
|
||||||
|
color="blue",
|
||||||
|
bbox=dict(
|
||||||
|
boxstyle="round,pad=0.2",
|
||||||
|
facecolor="white",
|
||||||
|
edgecolor="blue",
|
||||||
|
alpha=0.8,
|
||||||
|
linewidth=0.5,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
# 绘制完整的参考线
|
||||||
|
full_grayscale = np.linspace(0, 100, 100)
|
||||||
|
ax1.axhline(
|
||||||
|
y=x_ideal,
|
||||||
|
color="green",
|
||||||
|
linestyle="--",
|
||||||
|
linewidth=1.5,
|
||||||
|
label=f"x-ideal ({x_ideal:.4f})",
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
ax1.axhline(
|
||||||
|
y=x_low,
|
||||||
|
color="red",
|
||||||
|
linestyle=":",
|
||||||
|
linewidth=1,
|
||||||
|
alpha=0.7,
|
||||||
|
label=f"x-low ({x_low:.4f})",
|
||||||
|
zorder=2,
|
||||||
|
)
|
||||||
|
ax1.axhline(
|
||||||
|
y=x_high,
|
||||||
|
color="red",
|
||||||
|
linestyle=":",
|
||||||
|
linewidth=1,
|
||||||
|
alpha=0.7,
|
||||||
|
label=f"x-high ({x_high:.4f})",
|
||||||
|
zorder=2,
|
||||||
|
)
|
||||||
|
ax1.fill_between(
|
||||||
|
full_grayscale, x_low, x_high, alpha=0.15, color="blue", zorder=1
|
||||||
|
)
|
||||||
|
|
||||||
|
ax1.set_xlabel("灰阶 (%)", fontsize=9)
|
||||||
|
ax1.set_ylabel("CIE x", fontsize=9)
|
||||||
|
ax1.grid(True, linestyle="--", alpha=0.3)
|
||||||
|
ax1.tick_params(labelsize=8)
|
||||||
|
ax1.set_xlim(0, 105)
|
||||||
|
|
||||||
|
# 纵坐标范围由用户参数控制
|
||||||
|
x_min_data = min(x_measured)
|
||||||
|
x_max_data = max(x_measured)
|
||||||
|
data_range_x = x_max_data - x_min_data
|
||||||
|
|
||||||
|
self.log_gui.log(f" x数据波动: {data_range_x:.6f}")
|
||||||
|
|
||||||
|
range_span = x_tolerance * 2
|
||||||
|
margin_ratio = 0.20
|
||||||
|
extra_margin = range_span * margin_ratio
|
||||||
|
|
||||||
|
final_y_min = min(x_min_data, x_low) - extra_margin
|
||||||
|
final_y_max = max(x_max_data, x_high) + extra_margin
|
||||||
|
|
||||||
|
if x_min_data >= x_low and x_max_data <= x_high:
|
||||||
|
self.log_gui.log(f" x数据在tolerance范围内,使用tolerance范围显示")
|
||||||
|
final_y_min = x_low - extra_margin
|
||||||
|
final_y_max = x_high + extra_margin
|
||||||
|
else:
|
||||||
|
self.log_gui.log(f" x数据超出tolerance范围,扩展显示范围")
|
||||||
|
|
||||||
|
ax1.set_ylim(final_y_min, final_y_max)
|
||||||
|
self.log_gui.log(
|
||||||
|
f" x轴显示范围: {final_y_min:.6f} - {final_y_max:.6f} (跨度: {final_y_max - final_y_min:.6f})"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 下图:y coordinates ==========
|
||||||
|
ax2.plot(
|
||||||
|
grayscale,
|
||||||
|
y_measured,
|
||||||
|
"r-o",
|
||||||
|
label="屏本体",
|
||||||
|
linewidth=2,
|
||||||
|
markersize=4,
|
||||||
|
zorder=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 为每个点添加数值标注(y 坐标)
|
||||||
|
for i, (gs, y_val) in enumerate(zip(grayscale, y_measured)):
|
||||||
|
ax2.annotate(
|
||||||
|
f"{y_val:.5f}",
|
||||||
|
xy=(gs, y_val),
|
||||||
|
xytext=(0, 8),
|
||||||
|
textcoords="offset points",
|
||||||
|
ha="center",
|
||||||
|
va="bottom",
|
||||||
|
fontsize=7,
|
||||||
|
color="red",
|
||||||
|
bbox=dict(
|
||||||
|
boxstyle="round,pad=0.2",
|
||||||
|
facecolor="white",
|
||||||
|
edgecolor="red",
|
||||||
|
alpha=0.8,
|
||||||
|
linewidth=0.5,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
ax2.axhline(
|
||||||
|
y=y_ideal,
|
||||||
|
color="green",
|
||||||
|
linestyle="--",
|
||||||
|
linewidth=1.5,
|
||||||
|
label=f"y-ideal ({y_ideal:.4f})",
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
ax2.axhline(
|
||||||
|
y=y_low,
|
||||||
|
color="orange",
|
||||||
|
linestyle=":",
|
||||||
|
linewidth=1,
|
||||||
|
alpha=0.7,
|
||||||
|
label=f"y-low ({y_low:.4f})",
|
||||||
|
zorder=2,
|
||||||
|
)
|
||||||
|
ax2.axhline(
|
||||||
|
y=y_high,
|
||||||
|
color="orange",
|
||||||
|
linestyle=":",
|
||||||
|
linewidth=1,
|
||||||
|
alpha=0.7,
|
||||||
|
label=f"y-high ({y_high:.4f})",
|
||||||
|
zorder=2,
|
||||||
|
)
|
||||||
|
ax2.fill_between(
|
||||||
|
full_grayscale, y_low, y_high, alpha=0.15, color="orange", zorder=1
|
||||||
|
)
|
||||||
|
|
||||||
|
ax2.set_xlabel("灰阶 (%)", fontsize=9)
|
||||||
|
ax2.set_ylabel("CIE y", fontsize=9)
|
||||||
|
ax2.grid(True, linestyle="--", alpha=0.3)
|
||||||
|
ax2.tick_params(labelsize=8)
|
||||||
|
ax2.set_xlim(0, 105)
|
||||||
|
|
||||||
|
# 纵坐标范围由用户参数控制
|
||||||
|
y_min_data = min(y_measured)
|
||||||
|
y_max_data = max(y_measured)
|
||||||
|
data_range_y = y_max_data - y_min_data
|
||||||
|
|
||||||
|
self.log_gui.log(f" y数据波动: {data_range_y:.6f}")
|
||||||
|
|
||||||
|
range_span = y_tolerance * 2
|
||||||
|
extra_margin = range_span * margin_ratio
|
||||||
|
|
||||||
|
final_y_min = min(y_min_data, y_low) - extra_margin
|
||||||
|
final_y_max = max(y_max_data, y_high) + extra_margin
|
||||||
|
|
||||||
|
if y_min_data >= y_low and y_max_data <= y_high:
|
||||||
|
self.log_gui.log(f" y数据在tolerance范围内,使用tolerance范围显示")
|
||||||
|
final_y_min = y_low - extra_margin
|
||||||
|
final_y_max = y_high + extra_margin
|
||||||
|
else:
|
||||||
|
self.log_gui.log(f" y数据超出tolerance范围,扩展显示范围")
|
||||||
|
|
||||||
|
ax2.set_ylim(final_y_min, final_y_max)
|
||||||
|
self.log_gui.log(
|
||||||
|
f" y轴显示范围: {final_y_min:.6f} - {final_y_max:.6f} (跨度: {final_y_max - final_y_min:.6f})"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 总标题 - 统一格式(去掉统计信息)==========
|
||||||
|
test_type_name = self.get_test_type_name(test_type)
|
||||||
|
|
||||||
|
self.cct_fig.suptitle(
|
||||||
|
f"{test_type_name} - 色度一致性测试",
|
||||||
|
fontsize=12,
|
||||||
|
y=0.98,
|
||||||
|
fontweight="bold",
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cct_fig.subplots_adjust(
|
||||||
|
left=0.12,
|
||||||
|
right=0.82,
|
||||||
|
top=0.92,
|
||||||
|
bottom=0.08,
|
||||||
|
hspace=0.30,
|
||||||
|
)
|
||||||
|
|
||||||
|
ax1.legend(
|
||||||
|
fontsize=7, loc="center left", bbox_to_anchor=(1.05, 0.5), framealpha=1.0
|
||||||
|
)
|
||||||
|
ax2.legend(
|
||||||
|
fontsize=7, loc="center left", bbox_to_anchor=(1.05, 0.5), framealpha=1.0
|
||||||
|
)
|
||||||
|
|
||||||
|
self.cct_canvas.draw()
|
||||||
|
self.chart_notebook.select(2)
|
||||||
|
|
||||||
|
self.log_gui.log("✓ xy 色度坐标图绘制完成")
|
||||||
168
app/plots/plot_contrast.py
Normal file
168
app/plots/plot_contrast.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
"""对比度测试结果绘制。
|
||||||
|
|
||||||
|
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_contrast 原样搬迁。
|
||||||
|
"""
|
||||||
|
|
||||||
|
from matplotlib.patches import Rectangle
|
||||||
|
|
||||||
|
|
||||||
|
def plot_contrast(app, contrast_data, test_type):
|
||||||
|
"""绘制对比度测试结果 - 固定布局版本"""
|
||||||
|
self = app
|
||||||
|
|
||||||
|
# 清空并重置
|
||||||
|
self.contrast_ax.clear()
|
||||||
|
self.contrast_ax.set_xlim(0, 1)
|
||||||
|
self.contrast_ax.set_ylim(0, 1)
|
||||||
|
self.contrast_ax.axis("off")
|
||||||
|
|
||||||
|
# 强制重置布局
|
||||||
|
self.contrast_fig.subplots_adjust(
|
||||||
|
left=0.02,
|
||||||
|
right=0.98,
|
||||||
|
top=0.90,
|
||||||
|
bottom=0.02,
|
||||||
|
)
|
||||||
|
|
||||||
|
max_lum = contrast_data["max_luminance"]
|
||||||
|
min_lum = contrast_data["min_luminance"]
|
||||||
|
contrast = contrast_data["contrast_ratio"]
|
||||||
|
|
||||||
|
# 确定等级和颜色
|
||||||
|
if contrast >= 5000:
|
||||||
|
grade, grade_color = "优秀", "#4CAF50"
|
||||||
|
elif contrast >= 3000:
|
||||||
|
grade, grade_color = "良好", "#8BC34A"
|
||||||
|
elif contrast >= 1000:
|
||||||
|
grade, grade_color = "合格", "#FFC107"
|
||||||
|
else:
|
||||||
|
grade, grade_color = "不合格", "#F44336"
|
||||||
|
|
||||||
|
test_type_name = self.get_test_type_name(test_type)
|
||||||
|
|
||||||
|
# ========== 顶部标题 - 统一格式 ==========
|
||||||
|
self.contrast_fig.suptitle(
|
||||||
|
f"{test_type_name} - 对比度测试",
|
||||||
|
fontsize=12,
|
||||||
|
y=0.98,
|
||||||
|
fontweight="bold",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 中央大对比度卡片 ==========
|
||||||
|
center_card = Rectangle(
|
||||||
|
(0.15, 0.48),
|
||||||
|
0.70,
|
||||||
|
0.32,
|
||||||
|
transform=self.contrast_ax.transAxes,
|
||||||
|
facecolor=grade_color,
|
||||||
|
edgecolor="black",
|
||||||
|
linewidth=2.5,
|
||||||
|
alpha=0.15,
|
||||||
|
)
|
||||||
|
self.contrast_ax.add_patch(center_card)
|
||||||
|
|
||||||
|
# 对比度数值
|
||||||
|
self.contrast_ax.text(
|
||||||
|
0.5,
|
||||||
|
0.65,
|
||||||
|
f"{contrast:.0f} : 1",
|
||||||
|
ha="center",
|
||||||
|
va="center",
|
||||||
|
fontsize=36,
|
||||||
|
fontweight="bold",
|
||||||
|
color=grade_color,
|
||||||
|
transform=self.contrast_ax.transAxes,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 等级标签
|
||||||
|
self.contrast_ax.text(
|
||||||
|
0.5,
|
||||||
|
0.51,
|
||||||
|
f"等级: {grade}",
|
||||||
|
ha="center",
|
||||||
|
va="center",
|
||||||
|
fontsize=12,
|
||||||
|
fontweight="bold",
|
||||||
|
color=grade_color,
|
||||||
|
transform=self.contrast_ax.transAxes,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 两个信息卡片(缩小)==========
|
||||||
|
card_width = 0.32
|
||||||
|
card_height = 0.22
|
||||||
|
card_y = 0.12
|
||||||
|
|
||||||
|
gap = 0.05
|
||||||
|
total_width = card_width * 2 + gap
|
||||||
|
start_x = (1 - total_width) / 2
|
||||||
|
|
||||||
|
cards_data = [
|
||||||
|
{
|
||||||
|
"x": start_x,
|
||||||
|
"title": "白场亮度",
|
||||||
|
"value": f"{max_lum:.2f}",
|
||||||
|
"unit": "cd/m²",
|
||||||
|
"color": "#E3F2FD",
|
||||||
|
"edge_color": "#2196F3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"x": start_x + card_width + gap,
|
||||||
|
"title": "黑场亮度",
|
||||||
|
"value": f"{min_lum:.4f}",
|
||||||
|
"unit": "cd/m²",
|
||||||
|
"color": "#F3E5F5",
|
||||||
|
"edge_color": "#9C27B0",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
for card in cards_data:
|
||||||
|
# 绘制卡片背景
|
||||||
|
rect = Rectangle(
|
||||||
|
(card["x"], card_y),
|
||||||
|
card_width,
|
||||||
|
card_height,
|
||||||
|
transform=self.contrast_ax.transAxes,
|
||||||
|
facecolor=card["color"],
|
||||||
|
edgecolor=card["edge_color"],
|
||||||
|
linewidth=2,
|
||||||
|
)
|
||||||
|
self.contrast_ax.add_patch(rect)
|
||||||
|
|
||||||
|
# 标题
|
||||||
|
self.contrast_ax.text(
|
||||||
|
card["x"] + card_width / 2,
|
||||||
|
card_y + card_height - 0.03,
|
||||||
|
card["title"],
|
||||||
|
ha="center",
|
||||||
|
va="top",
|
||||||
|
fontsize=10,
|
||||||
|
fontweight="bold",
|
||||||
|
transform=self.contrast_ax.transAxes,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 数值
|
||||||
|
self.contrast_ax.text(
|
||||||
|
card["x"] + card_width / 2,
|
||||||
|
card_y + card_height / 2,
|
||||||
|
card["value"],
|
||||||
|
ha="center",
|
||||||
|
va="center",
|
||||||
|
fontsize=16,
|
||||||
|
fontweight="bold",
|
||||||
|
transform=self.contrast_ax.transAxes,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 单位
|
||||||
|
self.contrast_ax.text(
|
||||||
|
card["x"] + card_width / 2,
|
||||||
|
card_y + 0.03,
|
||||||
|
card["unit"],
|
||||||
|
ha="center",
|
||||||
|
va="bottom",
|
||||||
|
fontsize=9,
|
||||||
|
color="gray",
|
||||||
|
transform=self.contrast_ax.transAxes,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.contrast_canvas.draw()
|
||||||
|
self.chart_notebook.select(3)
|
||||||
149
app/plots/plot_eotf.py
Normal file
149
app/plots/plot_eotf.py
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
"""EOTF 曲线绘制(HDR)。
|
||||||
|
|
||||||
|
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_eotf 原样搬迁。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def plot_eotf(app, L_bar, results_with_eotf_list, test_type):
|
||||||
|
"""绘制 EOTF 曲线 + 数据表格(HDR 专用,包含实测亮度)"""
|
||||||
|
self = app
|
||||||
|
|
||||||
|
# ========== 1. 清空并重置左侧曲线 ==========
|
||||||
|
self.eotf_ax.clear()
|
||||||
|
self.eotf_ax.set_xlim(0, 105)
|
||||||
|
self.eotf_ax.set_ylim(0, 1.1)
|
||||||
|
self.eotf_ax.set_xlabel("灰阶 (%)", fontsize=10)
|
||||||
|
self.eotf_ax.set_ylabel("L_bar", fontsize=10)
|
||||||
|
self.eotf_ax.grid(True, linestyle="--", alpha=0.3)
|
||||||
|
self.eotf_ax.tick_params(labelsize=9)
|
||||||
|
|
||||||
|
# 生成横坐标(灰阶百分比)
|
||||||
|
x_values = np.linspace(0, 100, len(L_bar))
|
||||||
|
|
||||||
|
# 反转 L_bar
|
||||||
|
if len(L_bar) > 1 and L_bar[0] > L_bar[-1]:
|
||||||
|
L_bar = L_bar[::-1]
|
||||||
|
results_with_eotf_list = results_with_eotf_list[::-1]
|
||||||
|
|
||||||
|
# 计算平均 EOTF Gamma
|
||||||
|
eotf_values = []
|
||||||
|
for item in results_with_eotf_list:
|
||||||
|
if isinstance(item, (list, tuple)) and len(item) >= 4:
|
||||||
|
eotf = item[3]
|
||||||
|
if 0.5 < eotf < 5.0:
|
||||||
|
eotf_values.append(eotf)
|
||||||
|
|
||||||
|
avg_eotf = np.mean(eotf_values) if eotf_values else 2.2
|
||||||
|
|
||||||
|
# 绘制实测曲线
|
||||||
|
self.eotf_ax.plot(
|
||||||
|
x_values,
|
||||||
|
L_bar,
|
||||||
|
"b-o",
|
||||||
|
label=f"实测 (平均γ={avg_eotf:.2f})",
|
||||||
|
linewidth=2,
|
||||||
|
markersize=4,
|
||||||
|
zorder=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 绘制 PQ (ST.2084) 理想曲线
|
||||||
|
pq_L_bar = self.calculate_pq_curve(x_values)
|
||||||
|
self.eotf_ax.plot(
|
||||||
|
x_values,
|
||||||
|
pq_L_bar,
|
||||||
|
"r--",
|
||||||
|
label="理想 PQ (ST.2084)",
|
||||||
|
linewidth=2,
|
||||||
|
alpha=0.7,
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 图例
|
||||||
|
self.eotf_ax.legend(fontsize=9, loc="upper left", framealpha=0.95)
|
||||||
|
|
||||||
|
# ========== 2. 清空并绘制右侧表格 ==========
|
||||||
|
self.eotf_table_ax.clear()
|
||||||
|
self.eotf_table_ax.axis("off")
|
||||||
|
|
||||||
|
# 构建表格数据(4列)
|
||||||
|
table_data = [["灰阶", "实测亮度\n(cd/m²)", "L_bar", "EOTF γ"]]
|
||||||
|
|
||||||
|
for i, (x_val, L_val, result) in enumerate(
|
||||||
|
zip(x_values, L_bar, results_with_eotf_list)
|
||||||
|
):
|
||||||
|
# 提取实测亮度
|
||||||
|
if isinstance(result, (list, tuple)) and len(result) >= 3:
|
||||||
|
measured_lv = result[2]
|
||||||
|
measured_lv_str = f"{measured_lv:.2f}"
|
||||||
|
else:
|
||||||
|
measured_lv_str = "--"
|
||||||
|
|
||||||
|
# 提取 EOTF
|
||||||
|
if isinstance(result, (list, tuple)) and len(result) >= 4:
|
||||||
|
eotf = result[3]
|
||||||
|
if eotf < 0.5 or eotf > 5.0:
|
||||||
|
eotf_str = "--"
|
||||||
|
else:
|
||||||
|
eotf_str = f"{eotf:.2f}"
|
||||||
|
else:
|
||||||
|
eotf_str = "--"
|
||||||
|
|
||||||
|
table_data.append(
|
||||||
|
[
|
||||||
|
f"{x_val:.0f}%",
|
||||||
|
measured_lv_str,
|
||||||
|
f"{L_val:.3f}",
|
||||||
|
eotf_str,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 绘制表格(4列)
|
||||||
|
table = self.eotf_table_ax.table(
|
||||||
|
cellText=table_data,
|
||||||
|
cellLoc="center",
|
||||||
|
loc="center",
|
||||||
|
colWidths=[0.18, 0.28, 0.27, 0.27],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 美化表格
|
||||||
|
table.auto_set_font_size(False)
|
||||||
|
table.set_fontsize(7.5)
|
||||||
|
table.scale(1, 1.5)
|
||||||
|
|
||||||
|
# 表头样式
|
||||||
|
for i in range(4):
|
||||||
|
cell = table[(0, i)]
|
||||||
|
cell.set_facecolor("#4472C4")
|
||||||
|
cell.set_text_props(weight="bold", color="white")
|
||||||
|
|
||||||
|
# 数据行交替颜色
|
||||||
|
for i in range(1, len(table_data)):
|
||||||
|
for j in range(4):
|
||||||
|
cell = table[(i, j)]
|
||||||
|
if i % 2 == 0:
|
||||||
|
cell.set_facecolor("#E7E6E6")
|
||||||
|
else:
|
||||||
|
cell.set_facecolor("#FFFFFF")
|
||||||
|
|
||||||
|
# ========== 3. 总标题 ==========
|
||||||
|
test_type_name = self.get_test_type_name(test_type)
|
||||||
|
self.eotf_fig.suptitle(
|
||||||
|
f"{test_type_name} - EOTF 曲线(PQ ST.2084)",
|
||||||
|
fontsize=12,
|
||||||
|
y=0.98,
|
||||||
|
fontweight="bold",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 选中 EOTF Tab
|
||||||
|
try:
|
||||||
|
eotf_tab_id = str(self.eotf_chart_frame)
|
||||||
|
current_tabs = list(self.chart_notebook.tabs())
|
||||||
|
if eotf_tab_id in current_tabs:
|
||||||
|
eotf_index = current_tabs.index(eotf_tab_id)
|
||||||
|
self.chart_notebook.select(eotf_index)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.log_gui.log("EOTF 曲线 + 数据表格绘制完成")
|
||||||
143
app/plots/plot_gamma.py
Normal file
143
app/plots/plot_gamma.py
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
"""Gamma 曲线绘制。
|
||||||
|
|
||||||
|
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_gamma 原样搬迁。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
|
def plot_gamma(app, L_bar, results_with_gamma_list, target_gamma, test_type):
|
||||||
|
"""绘制Gamma曲线 + 数据表格(包含实测亮度)"""
|
||||||
|
self = app
|
||||||
|
|
||||||
|
# ========== 1. 清空并重置左侧曲线 ==========
|
||||||
|
self.gamma_ax.clear()
|
||||||
|
self.gamma_ax.set_xlim(0, 105)
|
||||||
|
self.gamma_ax.set_ylim(0, 1.1)
|
||||||
|
self.gamma_ax.set_xlabel("灰阶 (%)", fontsize=10)
|
||||||
|
self.gamma_ax.set_ylabel("L_bar", fontsize=10)
|
||||||
|
self.gamma_ax.grid(True, linestyle="--", alpha=0.3)
|
||||||
|
self.gamma_ax.tick_params(labelsize=9)
|
||||||
|
|
||||||
|
# 生成横坐标(灰阶百分比)
|
||||||
|
x_values = np.linspace(0, 100, len(L_bar))
|
||||||
|
|
||||||
|
# 反转 L_bar(确保从左到右是 0% → 100%)
|
||||||
|
if len(L_bar) > 1 and L_bar[0] > L_bar[-1]:
|
||||||
|
L_bar = L_bar[::-1]
|
||||||
|
results_with_gamma_list = results_with_gamma_list[::-1]
|
||||||
|
|
||||||
|
# 计算平均Gamma
|
||||||
|
gamma_values = []
|
||||||
|
for item in results_with_gamma_list:
|
||||||
|
if isinstance(item, (list, tuple)) and len(item) >= 4:
|
||||||
|
gamma = item[3]
|
||||||
|
if 0.5 < gamma < 5.0:
|
||||||
|
gamma_values.append(gamma)
|
||||||
|
|
||||||
|
avg_gamma = np.mean(gamma_values) if gamma_values else target_gamma
|
||||||
|
|
||||||
|
# 绘制实测曲线
|
||||||
|
self.gamma_ax.plot(
|
||||||
|
x_values,
|
||||||
|
L_bar,
|
||||||
|
"b-o",
|
||||||
|
label=f"实测 (平均γ={avg_gamma:.2f})",
|
||||||
|
linewidth=2,
|
||||||
|
markersize=4,
|
||||||
|
zorder=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 绘制理想曲线(使用 target_gamma)
|
||||||
|
ideal_L_bar = [(x / 100) ** target_gamma for x in x_values]
|
||||||
|
self.gamma_ax.plot(
|
||||||
|
x_values,
|
||||||
|
ideal_L_bar,
|
||||||
|
"r--",
|
||||||
|
label=f"理想 (γ={target_gamma})",
|
||||||
|
linewidth=2,
|
||||||
|
alpha=0.7,
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 图例
|
||||||
|
self.gamma_ax.legend(fontsize=9, loc="upper left", framealpha=0.95)
|
||||||
|
|
||||||
|
# ========== 2. 清空并绘制右侧表格 ==========
|
||||||
|
self.gamma_table_ax.clear()
|
||||||
|
self.gamma_table_ax.axis("off")
|
||||||
|
|
||||||
|
# 构建表格数据(4列)
|
||||||
|
table_data = [["灰阶", "实测亮度\n(cd/m²)", "L_bar", "Gamma"]]
|
||||||
|
|
||||||
|
for i, (x_val, L_val, result) in enumerate(
|
||||||
|
zip(x_values, L_bar, results_with_gamma_list)
|
||||||
|
):
|
||||||
|
# 提取实测亮度
|
||||||
|
if isinstance(result, (list, tuple)) and len(result) >= 3:
|
||||||
|
measured_lv = result[2]
|
||||||
|
measured_lv_str = f"{measured_lv:.2f}"
|
||||||
|
else:
|
||||||
|
measured_lv_str = "--"
|
||||||
|
|
||||||
|
# 提取 Gamma
|
||||||
|
if isinstance(result, (list, tuple)) and len(result) >= 4:
|
||||||
|
gamma = result[3]
|
||||||
|
if gamma < 0.5 or gamma > 5.0:
|
||||||
|
gamma_str = "--"
|
||||||
|
else:
|
||||||
|
gamma_str = f"{gamma:.2f}"
|
||||||
|
else:
|
||||||
|
gamma_str = "--"
|
||||||
|
|
||||||
|
table_data.append(
|
||||||
|
[
|
||||||
|
f"{x_val:.0f}%",
|
||||||
|
measured_lv_str,
|
||||||
|
f"{L_val:.3f}",
|
||||||
|
gamma_str,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
# 绘制表格(4列)
|
||||||
|
table = self.gamma_table_ax.table(
|
||||||
|
cellText=table_data,
|
||||||
|
cellLoc="center",
|
||||||
|
loc="center",
|
||||||
|
colWidths=[0.18, 0.28, 0.27, 0.27],
|
||||||
|
)
|
||||||
|
|
||||||
|
# 美化表格
|
||||||
|
table.auto_set_font_size(False)
|
||||||
|
table.set_fontsize(7.5)
|
||||||
|
table.scale(1, 1.5)
|
||||||
|
|
||||||
|
# 表头样式
|
||||||
|
for i in range(4):
|
||||||
|
cell = table[(0, i)]
|
||||||
|
cell.set_facecolor("#4472C4")
|
||||||
|
cell.set_text_props(weight="bold", color="white")
|
||||||
|
|
||||||
|
# 数据行交替颜色
|
||||||
|
for i in range(1, len(table_data)):
|
||||||
|
for j in range(4):
|
||||||
|
cell = table[(i, j)]
|
||||||
|
if i % 2 == 0:
|
||||||
|
cell.set_facecolor("#E7E6E6")
|
||||||
|
else:
|
||||||
|
cell.set_facecolor("#FFFFFF")
|
||||||
|
|
||||||
|
# ========== 3. 总标题 ==========
|
||||||
|
test_type_name = self.get_test_type_name(test_type)
|
||||||
|
self.gamma_fig.suptitle(
|
||||||
|
f"{test_type_name} - Gamma曲线",
|
||||||
|
fontsize=12,
|
||||||
|
y=0.98,
|
||||||
|
fontweight="bold",
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 4. 绘制到画布 ==========
|
||||||
|
self.gamma_canvas.draw()
|
||||||
|
self.chart_notebook.select(1)
|
||||||
|
|
||||||
|
self.log_gui.log("Gamma曲线 + 数据表格绘制完成")
|
||||||
539
app/plots/plot_gamut.py
Normal file
539
app/plots/plot_gamut.py
Normal file
@@ -0,0 +1,539 @@
|
|||||||
|
"""色域图(Gamut)绘制。
|
||||||
|
|
||||||
|
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_gamut 整体搬迁,
|
||||||
|
实现与原方法完全一致;原方法仅保留为一行转发。
|
||||||
|
"""
|
||||||
|
|
||||||
|
import matplotlib.image as mpimg
|
||||||
|
|
||||||
|
import algorithm.pq_algorithm as pq_algorithm
|
||||||
|
from app.resources import get_resource_path
|
||||||
|
|
||||||
|
|
||||||
|
def plot_gamut(app, results, coverage, test_type):
|
||||||
|
"""绘制色域图 - 根据用户选择的参考标准动态计算覆盖率"""
|
||||||
|
# 实现从原 PQAutomationApp 方法体原样搬迁,为减少修改面
|
||||||
|
# 范围、保持行为一致,给 self 赋值为传入的 app 实例。
|
||||||
|
self = app
|
||||||
|
|
||||||
|
self.gamut_ax_xy.clear()
|
||||||
|
self.gamut_ax_uv.clear()
|
||||||
|
|
||||||
|
# ==================== XY 图校准参数 ====================
|
||||||
|
XY_ORIGIN_X = 20.55
|
||||||
|
XY_ORIGIN_Y = 378.00
|
||||||
|
XY_PIXELS_PER_X = 510.6818
|
||||||
|
XY_PIXELS_PER_Y = 429.8844
|
||||||
|
|
||||||
|
# ==================== UV 图校准参数 ====================
|
||||||
|
UV_ORIGIN_U = 26.91
|
||||||
|
UV_ORIGIN_V = 377.16
|
||||||
|
UV_PIXELS_PER_U = 615.7260
|
||||||
|
UV_PIXELS_PER_V = 599.8432
|
||||||
|
|
||||||
|
# ========== ✅ 读取用户选择的参考标准 ==========
|
||||||
|
if test_type == "screen_module":
|
||||||
|
current_ref = self.screen_gamut_ref_var.get()
|
||||||
|
elif test_type == "sdr_movie":
|
||||||
|
current_ref = self.sdr_gamut_ref_var.get()
|
||||||
|
elif test_type == "hdr_movie":
|
||||||
|
current_ref = self.hdr_gamut_ref_var.get()
|
||||||
|
else:
|
||||||
|
current_ref = "DCI-P3"
|
||||||
|
|
||||||
|
# ========== ✅✅✅ 根据参考标准重新计算覆盖率(XY 空间)==========
|
||||||
|
xy_coverage = coverage # 默认使用传入的值
|
||||||
|
uv_coverage = 0.0
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 提取前 3 个 RGB 点的 xy 坐标
|
||||||
|
if len(results) >= 3:
|
||||||
|
xy_points = [[result[0], result[1]] for result in results[:3]]
|
||||||
|
|
||||||
|
# 根据参考标准计算 XY 覆盖率
|
||||||
|
if current_ref == "BT.2020":
|
||||||
|
_, xy_coverage = pq_algorithm.calculate_gamut_coverage_BT2020(
|
||||||
|
xy_points
|
||||||
|
)
|
||||||
|
elif current_ref == "BT.709":
|
||||||
|
_, xy_coverage = pq_algorithm.calculate_gamut_coverage_BT709(
|
||||||
|
xy_points
|
||||||
|
)
|
||||||
|
elif current_ref == "DCI-P3":
|
||||||
|
_, xy_coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(
|
||||||
|
xy_points
|
||||||
|
)
|
||||||
|
elif current_ref == "BT.601":
|
||||||
|
_, xy_coverage = pq_algorithm.calculate_gamut_coverage_BT601(
|
||||||
|
xy_points
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
self.log_gui.log(f"⚠️ 未知参考标准 '{current_ref}',使用 DCI-P3")
|
||||||
|
_, xy_coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(
|
||||||
|
xy_points
|
||||||
|
)
|
||||||
|
current_ref = "DCI-P3"
|
||||||
|
|
||||||
|
self.log_gui.log(
|
||||||
|
f"✓ XY 空间覆盖率({current_ref}): {xy_coverage:.1f}%"
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_gui.log(f"⚠️ 重新计算 XY 覆盖率失败: {str(e)}")
|
||||||
|
xy_coverage = coverage # 回退到传入值
|
||||||
|
# =================================================
|
||||||
|
|
||||||
|
# ========== 左图:CIE 1931 xy ==========
|
||||||
|
try:
|
||||||
|
img_xy = mpimg.imread(get_resource_path("assets/cie.png"))
|
||||||
|
h_xy, w_xy = img_xy.shape[:2]
|
||||||
|
|
||||||
|
self.log_gui.log(f"加载 XY 色域图: {w_xy}x{h_xy}")
|
||||||
|
|
||||||
|
self.gamut_ax_xy.imshow(img_xy, extent=[0, w_xy, h_xy, 0], aspect="equal")
|
||||||
|
self.gamut_ax_xy.set_xlim(0, w_xy)
|
||||||
|
self.gamut_ax_xy.set_ylim(h_xy, 0)
|
||||||
|
self.gamut_ax_xy.axis("off")
|
||||||
|
self.gamut_ax_xy.set_clip_on(False)
|
||||||
|
|
||||||
|
def cie_xy_to_pixel(x, y):
|
||||||
|
"""CIE xy → 像素坐标"""
|
||||||
|
px = XY_ORIGIN_X + x * XY_PIXELS_PER_X
|
||||||
|
py = XY_ORIGIN_Y - y * XY_PIXELS_PER_Y
|
||||||
|
return px, py
|
||||||
|
|
||||||
|
if len(results) >= 3:
|
||||||
|
red_x, red_y = results[0][0], results[0][1]
|
||||||
|
green_x, green_y = results[1][0], results[1][1]
|
||||||
|
blue_x, blue_y = results[2][0], results[2][1]
|
||||||
|
|
||||||
|
self.log_gui.log(
|
||||||
|
f"测量色域: R({red_x:.4f},{red_y:.4f}) "
|
||||||
|
f"G({green_x:.4f},{green_y:.4f}) B({blue_x:.4f},{blue_y:.4f})"
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 绘制测量三角形 ==========
|
||||||
|
points = [
|
||||||
|
cie_xy_to_pixel(red_x, red_y),
|
||||||
|
cie_xy_to_pixel(green_x, green_y),
|
||||||
|
cie_xy_to_pixel(blue_x, blue_y),
|
||||||
|
cie_xy_to_pixel(red_x, red_y),
|
||||||
|
]
|
||||||
|
|
||||||
|
xs = [p[0] for p in points]
|
||||||
|
ys = [p[1] for p in points]
|
||||||
|
|
||||||
|
self.gamut_ax_xy.plot(
|
||||||
|
xs,
|
||||||
|
ys,
|
||||||
|
color="red",
|
||||||
|
linewidth=2.5,
|
||||||
|
marker="o",
|
||||||
|
markersize=10,
|
||||||
|
markerfacecolor="red",
|
||||||
|
markeredgecolor="white",
|
||||||
|
markeredgewidth=2,
|
||||||
|
label="测量色域",
|
||||||
|
zorder=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 标注 RGB 点 ==========
|
||||||
|
labels = ["R", "G", "B"]
|
||||||
|
coords = [(red_x, red_y), (green_x, green_y), (blue_x, blue_y)]
|
||||||
|
|
||||||
|
for (x_cie, y_cie), label in zip(coords, labels):
|
||||||
|
px, py = cie_xy_to_pixel(x_cie, y_cie)
|
||||||
|
|
||||||
|
# 自适应偏移
|
||||||
|
if label == "R":
|
||||||
|
offset = (-60, -40) if x_cie > 0.6 else (0, -60)
|
||||||
|
elif label == "G":
|
||||||
|
offset = (0, -60)
|
||||||
|
else: # B
|
||||||
|
offset = (60, 40)
|
||||||
|
|
||||||
|
self.gamut_ax_xy.annotate(
|
||||||
|
f"{label}\n({x_cie:.3f},{y_cie:.3f})",
|
||||||
|
xy=(px, py),
|
||||||
|
xytext=offset,
|
||||||
|
textcoords="offset points",
|
||||||
|
fontsize=9,
|
||||||
|
color="white",
|
||||||
|
fontweight="bold",
|
||||||
|
bbox=dict(
|
||||||
|
boxstyle="round,pad=0.5",
|
||||||
|
facecolor="red",
|
||||||
|
alpha=0.9,
|
||||||
|
edgecolor="white",
|
||||||
|
linewidth=2,
|
||||||
|
),
|
||||||
|
arrowprops=dict(arrowstyle="->", color="red", lw=2),
|
||||||
|
zorder=11,
|
||||||
|
clip_on=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 绘制所有参考标准 ==========
|
||||||
|
# DCI-P3
|
||||||
|
dcip3 = [
|
||||||
|
(0.6800, 0.3200),
|
||||||
|
(0.2650, 0.6900),
|
||||||
|
(0.1500, 0.0600),
|
||||||
|
(0.6800, 0.3200),
|
||||||
|
]
|
||||||
|
dcip3_px = [cie_xy_to_pixel(x, y) for x, y in dcip3]
|
||||||
|
self.gamut_ax_xy.plot(
|
||||||
|
[p[0] for p in dcip3_px],
|
||||||
|
[p[1] for p in dcip3_px],
|
||||||
|
color="blue",
|
||||||
|
linewidth=1.5,
|
||||||
|
linestyle="--",
|
||||||
|
marker="s",
|
||||||
|
markersize=6,
|
||||||
|
alpha=0.7,
|
||||||
|
label="DCI-P3",
|
||||||
|
zorder=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# BT.2020
|
||||||
|
bt2020 = [
|
||||||
|
(0.7080, 0.2920),
|
||||||
|
(0.1700, 0.7970),
|
||||||
|
(0.1310, 0.0460),
|
||||||
|
(0.7080, 0.2920),
|
||||||
|
]
|
||||||
|
bt2020_px = [cie_xy_to_pixel(x, y) for x, y in bt2020]
|
||||||
|
self.gamut_ax_xy.plot(
|
||||||
|
[p[0] for p in bt2020_px],
|
||||||
|
[p[1] for p in bt2020_px],
|
||||||
|
color="green",
|
||||||
|
linewidth=1.5,
|
||||||
|
linestyle="-.",
|
||||||
|
marker="D",
|
||||||
|
markersize=5,
|
||||||
|
alpha=0.7,
|
||||||
|
label="BT.2020",
|
||||||
|
zorder=4,
|
||||||
|
)
|
||||||
|
|
||||||
|
# BT.709
|
||||||
|
bt709 = [
|
||||||
|
(0.6400, 0.3300),
|
||||||
|
(0.3000, 0.6000),
|
||||||
|
(0.1500, 0.0600),
|
||||||
|
(0.6400, 0.3300),
|
||||||
|
]
|
||||||
|
bt709_px = [cie_xy_to_pixel(x, y) for x, y in bt709]
|
||||||
|
self.gamut_ax_xy.plot(
|
||||||
|
[p[0] for p in bt709_px],
|
||||||
|
[p[1] for p in bt709_px],
|
||||||
|
color="gray",
|
||||||
|
linewidth=1.2,
|
||||||
|
linestyle=":",
|
||||||
|
marker="^",
|
||||||
|
markersize=5,
|
||||||
|
alpha=0.6,
|
||||||
|
label="BT.709",
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
# BT.601(仅 SDR 测试)
|
||||||
|
if test_type == "sdr_movie":
|
||||||
|
bt601 = [
|
||||||
|
(0.6300, 0.3400),
|
||||||
|
(0.3100, 0.5950),
|
||||||
|
(0.1550, 0.0700),
|
||||||
|
(0.6300, 0.3400),
|
||||||
|
]
|
||||||
|
bt601_px = [cie_xy_to_pixel(x, y) for x, y in bt601]
|
||||||
|
self.gamut_ax_xy.plot(
|
||||||
|
[p[0] for p in bt601_px],
|
||||||
|
[p[1] for p in bt601_px],
|
||||||
|
color="purple",
|
||||||
|
linewidth=1.2,
|
||||||
|
linestyle="-",
|
||||||
|
marker="o",
|
||||||
|
markersize=5,
|
||||||
|
alpha=0.6,
|
||||||
|
label="BT.601",
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== ✅ XY 覆盖率标注(使用重新计算的值)==========
|
||||||
|
self.gamut_ax_xy.text(
|
||||||
|
w_xy * 0.85,
|
||||||
|
h_xy * 0.92,
|
||||||
|
f"参考: {current_ref}\n覆盖率: {xy_coverage:.1f}%",
|
||||||
|
ha="right",
|
||||||
|
va="bottom",
|
||||||
|
fontsize=11,
|
||||||
|
fontweight="bold",
|
||||||
|
color="red",
|
||||||
|
bbox=dict(
|
||||||
|
boxstyle="round,pad=0.5",
|
||||||
|
facecolor="white",
|
||||||
|
alpha=0.95,
|
||||||
|
edgecolor="red",
|
||||||
|
linewidth=2,
|
||||||
|
),
|
||||||
|
zorder=12,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 图例
|
||||||
|
self.gamut_ax_xy.legend(
|
||||||
|
loc="upper right",
|
||||||
|
fontsize=7,
|
||||||
|
framealpha=0.95,
|
||||||
|
edgecolor="black",
|
||||||
|
fancybox=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_gui.log(f"XY 图绘制失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
self.log_gui.log(traceback.format_exc())
|
||||||
|
|
||||||
|
# ========== 右图:CIE 1976 u'v' ==========
|
||||||
|
try:
|
||||||
|
img_uv = mpimg.imread(get_resource_path("assets/cie_uv.png"))
|
||||||
|
h_uv, w_uv = img_uv.shape[:2]
|
||||||
|
|
||||||
|
self.log_gui.log(f"加载 UV 色域图: {w_uv}x{h_uv}")
|
||||||
|
|
||||||
|
self.gamut_ax_uv.imshow(img_uv, extent=[0, w_uv, h_uv, 0], aspect="equal")
|
||||||
|
self.gamut_ax_uv.set_xlim(0, w_uv)
|
||||||
|
self.gamut_ax_uv.set_ylim(h_uv, 0)
|
||||||
|
self.gamut_ax_uv.axis("off")
|
||||||
|
self.gamut_ax_uv.set_clip_on(False)
|
||||||
|
|
||||||
|
def cie_uv_to_pixel(u, v):
|
||||||
|
"""CIE u'v' → 像素坐标"""
|
||||||
|
px = UV_ORIGIN_U + u * UV_PIXELS_PER_U
|
||||||
|
py = UV_ORIGIN_V - v * UV_PIXELS_PER_V
|
||||||
|
return px, py
|
||||||
|
|
||||||
|
if len(results) >= 3:
|
||||||
|
# 只取前 3 个 RGB 点
|
||||||
|
rgb_results = results[:3]
|
||||||
|
|
||||||
|
# 转换为 u'v' 坐标
|
||||||
|
def xy_to_uv(x, y):
|
||||||
|
"""xy → u'v' 转换"""
|
||||||
|
denom = -2 * x + 12 * y + 3
|
||||||
|
if abs(denom) < 1e-10:
|
||||||
|
return 0, 0
|
||||||
|
u = (4 * x) / denom
|
||||||
|
v = (9 * y) / denom
|
||||||
|
return u, v
|
||||||
|
|
||||||
|
uv_coords = [
|
||||||
|
[u, v] for u, v in [xy_to_uv(r[0], r[1]) for r in rgb_results]
|
||||||
|
]
|
||||||
|
|
||||||
|
self.log_gui.log(f"UV 坐标: {uv_coords}")
|
||||||
|
|
||||||
|
# ========== ✅✅✅ 计算 u'v' 覆盖率(使用参考标准)==========
|
||||||
|
try:
|
||||||
|
uv_coverage = pq_algorithm.calculate_uv_gamut_coverage(
|
||||||
|
uv_coords, reference=current_ref
|
||||||
|
)
|
||||||
|
self.log_gui.log(
|
||||||
|
f"✓ UV 空间覆盖率({current_ref}): {uv_coverage:.1f}%"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
self.log_gui.log(f"⚠️ 计算 UV 覆盖率失败: {str(e)}")
|
||||||
|
uv_coverage = 0.0
|
||||||
|
# =================================================
|
||||||
|
|
||||||
|
# ========== 绘制测量三角形 ==========
|
||||||
|
uv_coords_plot = uv_coords + [uv_coords[0]]
|
||||||
|
points_uv = [cie_uv_to_pixel(u, v) for u, v in uv_coords_plot]
|
||||||
|
xs_uv = [p[0] for p in points_uv]
|
||||||
|
ys_uv = [p[1] for p in points_uv]
|
||||||
|
|
||||||
|
self.gamut_ax_uv.plot(
|
||||||
|
xs_uv,
|
||||||
|
ys_uv,
|
||||||
|
color="red",
|
||||||
|
linewidth=2.5,
|
||||||
|
marker="o",
|
||||||
|
markersize=10,
|
||||||
|
markerfacecolor="red",
|
||||||
|
markeredgecolor="white",
|
||||||
|
markeredgewidth=2,
|
||||||
|
label="测量色域",
|
||||||
|
zorder=10,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== 标注 RGB 点 ==========
|
||||||
|
labels = ["R", "G", "B"]
|
||||||
|
for (u, v), label in zip(uv_coords, labels):
|
||||||
|
px, py = cie_uv_to_pixel(u, v)
|
||||||
|
|
||||||
|
# 自适应偏移
|
||||||
|
if label == "R":
|
||||||
|
if u > 0.42 and v > 0.50:
|
||||||
|
offset = (-70, 20)
|
||||||
|
elif u > 0.45:
|
||||||
|
offset = (30, 50)
|
||||||
|
else:
|
||||||
|
offset = (50, 45)
|
||||||
|
elif label == "G":
|
||||||
|
offset = (0, -60)
|
||||||
|
else: # B
|
||||||
|
offset = (60, 40)
|
||||||
|
|
||||||
|
self.gamut_ax_uv.annotate(
|
||||||
|
f"{label}\n({u:.3f},{v:.3f})",
|
||||||
|
xy=(px, py),
|
||||||
|
xytext=offset,
|
||||||
|
textcoords="offset points",
|
||||||
|
fontsize=9,
|
||||||
|
color="white",
|
||||||
|
fontweight="bold",
|
||||||
|
bbox=dict(
|
||||||
|
boxstyle="round,pad=0.5",
|
||||||
|
facecolor="red",
|
||||||
|
alpha=0.9,
|
||||||
|
edgecolor="white",
|
||||||
|
linewidth=2,
|
||||||
|
),
|
||||||
|
arrowprops=dict(arrowstyle="->", color="red", lw=2),
|
||||||
|
zorder=11,
|
||||||
|
clip_on=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== DCI-P3 参考(蓝色)==========
|
||||||
|
dcip3_uv = [
|
||||||
|
[0.4970, 0.5260],
|
||||||
|
[0.0999, 0.5780],
|
||||||
|
[0.1754, 0.1576],
|
||||||
|
[0.4970, 0.5260],
|
||||||
|
]
|
||||||
|
dcip3_uv_px = [cie_uv_to_pixel(u, v) for u, v in dcip3_uv]
|
||||||
|
|
||||||
|
self.gamut_ax_uv.plot(
|
||||||
|
[p[0] for p in dcip3_uv_px],
|
||||||
|
[p[1] for p in dcip3_uv_px],
|
||||||
|
color="blue",
|
||||||
|
linewidth=1.5,
|
||||||
|
linestyle="--",
|
||||||
|
marker="s",
|
||||||
|
markersize=6,
|
||||||
|
alpha=0.7,
|
||||||
|
label="DCI-P3",
|
||||||
|
zorder=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== BT.2020 参考(绿色)==========
|
||||||
|
bt2020_uv = [
|
||||||
|
[0.5566, 0.5165],
|
||||||
|
[0.0556, 0.5868],
|
||||||
|
[0.1593, 0.1258],
|
||||||
|
[0.5566, 0.5165],
|
||||||
|
]
|
||||||
|
bt2020_uv_px = [cie_uv_to_pixel(u, v) for u, v in bt2020_uv]
|
||||||
|
|
||||||
|
self.gamut_ax_uv.plot(
|
||||||
|
[p[0] for p in bt2020_uv_px],
|
||||||
|
[p[1] for p in bt2020_uv_px],
|
||||||
|
color="green",
|
||||||
|
linewidth=1.5,
|
||||||
|
linestyle="-.",
|
||||||
|
marker="D",
|
||||||
|
markersize=5,
|
||||||
|
alpha=0.7,
|
||||||
|
label="BT.2020",
|
||||||
|
zorder=4,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== BT.709 参考(灰色)==========
|
||||||
|
bt709_uv = [
|
||||||
|
[0.4507, 0.5229],
|
||||||
|
[0.1250, 0.5625],
|
||||||
|
[0.1754, 0.1576],
|
||||||
|
[0.4507, 0.5229],
|
||||||
|
]
|
||||||
|
bt709_uv_px = [cie_uv_to_pixel(u, v) for u, v in bt709_uv]
|
||||||
|
|
||||||
|
self.gamut_ax_uv.plot(
|
||||||
|
[p[0] for p in bt709_uv_px],
|
||||||
|
[p[1] for p in bt709_uv_px],
|
||||||
|
color="gray",
|
||||||
|
linewidth=1.2,
|
||||||
|
linestyle=":",
|
||||||
|
marker="^",
|
||||||
|
markersize=5,
|
||||||
|
alpha=0.6,
|
||||||
|
label="BT.709",
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== BT.601 参考(紫色)- 仅 SDR 测试显示 ==========
|
||||||
|
if test_type == "sdr_movie":
|
||||||
|
bt601_uv = [
|
||||||
|
[0.4510, 0.5236],
|
||||||
|
[0.1291, 0.5606],
|
||||||
|
[0.1787, 0.1610],
|
||||||
|
[0.4510, 0.5236],
|
||||||
|
]
|
||||||
|
bt601_uv_px = [cie_uv_to_pixel(u, v) for u, v in bt601_uv]
|
||||||
|
|
||||||
|
self.gamut_ax_uv.plot(
|
||||||
|
[p[0] for p in bt601_uv_px],
|
||||||
|
[p[1] for p in bt601_uv_px],
|
||||||
|
color="purple",
|
||||||
|
linewidth=1.2,
|
||||||
|
linestyle="-",
|
||||||
|
marker="o",
|
||||||
|
markersize=5,
|
||||||
|
alpha=0.6,
|
||||||
|
label="BT.601",
|
||||||
|
zorder=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ========== ✅ UV 覆盖率标注(使用动态计算的值)==========
|
||||||
|
self.gamut_ax_uv.text(
|
||||||
|
w_uv * 0.85,
|
||||||
|
h_uv * 0.92,
|
||||||
|
f"参考: {current_ref}\n覆盖率: {uv_coverage:.1f}%",
|
||||||
|
ha="right",
|
||||||
|
va="bottom",
|
||||||
|
fontsize=11,
|
||||||
|
fontweight="bold",
|
||||||
|
color="red",
|
||||||
|
bbox=dict(
|
||||||
|
boxstyle="round,pad=0.5",
|
||||||
|
facecolor="white",
|
||||||
|
alpha=0.95,
|
||||||
|
edgecolor="red",
|
||||||
|
linewidth=2,
|
||||||
|
),
|
||||||
|
zorder=12,
|
||||||
|
)
|
||||||
|
|
||||||
|
# 图例
|
||||||
|
self.gamut_ax_uv.legend(
|
||||||
|
loc="upper right",
|
||||||
|
fontsize=7,
|
||||||
|
framealpha=0.95,
|
||||||
|
edgecolor="black",
|
||||||
|
fancybox=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.log_gui.log(f"UV 图绘制失败: {str(e)}")
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
self.log_gui.log(traceback.format_exc())
|
||||||
|
|
||||||
|
# ========== 总标题 ==========
|
||||||
|
test_type_name = self.get_test_type_name(test_type)
|
||||||
|
self.gamut_fig.suptitle(
|
||||||
|
f"{test_type_name} - 色域测试", fontsize=12, y=0.98, fontweight="bold"
|
||||||
|
)
|
||||||
|
|
||||||
|
self.gamut_canvas.draw()
|
||||||
|
self.chart_notebook.select(0)
|
||||||
|
|
||||||
|
self.log_gui.log("色域图绘制完成")
|
||||||
1602
pqAutomationApp.py
1602
pqAutomationApp.py
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user