Files
pqAutomationApp/app/plots/plot_gamut.py
2026-04-20 10:00:44 +08:00

540 lines
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""色域图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("色域图绘制完成")