Files
pqAutomationApp/app/plots/plot_cct.py
2026-06-05 16:58:46 +08:00

395 lines
12 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.
"""CCT / 色度一致性绘制。
Step 2 重构:从 pqAutomationApp.PQAutomationApp.plot_cct 原样搬迁。
"""
import numpy as np
from app.views.modern_styles import get_theme_palette
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from pqAutomationApp import PQAutomationApp
def _is_dark_palette(palette: dict[str, str]) -> bool:
"""根据主题背景色亮度判断是否深色主题。"""
bg = palette.get("bg", "#FFFFFF").lstrip("#")
try:
r = int(bg[0:2], 16)
g = int(bg[2:4], 16)
b = int(bg[4:6], 16)
except Exception:
return False
return (r * 299 + g * 587 + b * 114) / 1000 < 128
def plot_cct(self: "PQAutomationApp", test_type):
"""绘制 x 和 y 坐标分离图 - 每个点标注纵坐标值"""
palette = get_theme_palette()
dark_mode = _is_dark_palette(palette)
x_line_color = "#2F8BFF" if dark_mode else "#0A4BFF"
y_line_color = "#FF4D4D" if dark_mode else "#D90429"
ideal_line_color = "#00C853" if dark_mode else "#198754"
x_tol_color = "#FF7070" if dark_mode else "#C0392B"
y_tol_color = "#FFB74D" if dark_mode else "#D68910"
grid_color = "#566070" if dark_mode else "#B8BDC3"
axis_text = "#EDF2FA" if dark_mode else palette["fg"]
axis_sub_text = "#C8D2E0" if dark_mode else "#222222"
legend_bg = "#131821" if dark_mode else "#FFFFFF"
legend_edge = "#A4B2C6" if dark_mode else palette["border"]
self.cct_fig.clear()
self.cct_fig.patch.set_facecolor(palette["bg"])
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 数据可用", level="error")
ax = self.cct_fig.add_subplot(111)
ax.text(
0.5,
0.5,
"无可用数据",
ha="center",
va="center",
fontsize=14,
color=palette["danger"],
)
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)}", level="success")
self.log_gui.log(f" x范围: {min(x_measured):.6f} - {max(x_measured):.6f}", level="info")
self.log_gui.log(f" y范围: {min(y_measured):.6f} - {max(y_measured):.6f}", level="info")
# ========== 根据测试类型读取对应参数 ==========
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 色度参数", level="success")
except:
x_ideal = 0.3127
x_tolerance = 0.003
y_ideal = 0.3290
y_tolerance = 0.003
self.log_gui.log("SDR 参数读取失败,使用默认值", level="error")
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 色度参数", level="success")
except:
x_ideal = 0.3127
x_tolerance = 0.003
y_ideal = 0.3290
y_tolerance = 0.003
self.log_gui.log("HDR 参数读取失败,使用默认值", level="error")
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("使用屏模组色度参数", level="success")
except:
x_ideal = 0.306
x_tolerance = 0.003
y_ideal = 0.318
y_tolerance = 0.003
self.log_gui.log("屏模组参数读取失败,使用默认值", level="error")
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"用户设置参数:", level="success")
self.log_gui.log(f" x-ideal={x_ideal:.4f}, tolerance={x_tolerance:.4f}", level="info")
self.log_gui.log(f" x范围: [{x_low:.4f}, {x_high:.4f}]", level="info")
self.log_gui.log(f" y-ideal={y_ideal:.4f}, tolerance={y_tolerance:.4f}", level="info")
self.log_gui.log(f" y范围: [{y_low:.4f}, {y_high:.4f}]", level="info")
# 为所有测试类型创建子图
ax1 = self.cct_fig.add_subplot(211)
ax2 = self.cct_fig.add_subplot(212)
for ax in (ax1, ax2):
ax.set_facecolor(palette["card_bg"])
for spine in ax.spines.values():
spine.set_color(palette["border"])
ax.tick_params(labelsize=8, colors=axis_sub_text)
ax.xaxis.label.set_color(axis_text)
ax.yaxis.label.set_color(axis_text)
# ========== 上图x coordinates ==========
ax1.plot(
grayscale,
x_measured,
color=x_line_color,
marker="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=x_line_color,
bbox=dict(
boxstyle="round,pad=0.2",
facecolor=palette["card_bg"],
edgecolor=x_line_color,
alpha=0.92 if dark_mode else 0.85,
linewidth=0.8,
),
)
# 绘制完整的参考线
full_grayscale = np.linspace(0, 100, 100)
ax1.axhline(
y=x_ideal,
color=ideal_line_color,
linestyle="--",
linewidth=1.5,
label=f"x-ideal ({x_ideal:.4f})",
zorder=3,
)
ax1.axhline(
y=x_low,
color=x_tol_color,
linestyle=":",
linewidth=1,
alpha=0.95 if dark_mode else 0.7,
label=f"x-low ({x_low:.4f})",
zorder=2,
)
ax1.axhline(
y=x_high,
color=x_tol_color,
linestyle=":",
linewidth=1,
alpha=0.95 if dark_mode else 0.7,
label=f"x-high ({x_high:.4f})",
zorder=2,
)
ax1.fill_between(
full_grayscale,
x_low,
x_high,
alpha=0.22 if dark_mode else 0.15,
color=x_line_color,
zorder=1,
)
ax1.set_xlabel("灰阶 (%)", fontsize=9, color=axis_text)
ax1.set_ylabel("CIE x", fontsize=9, color=axis_text)
ax1.grid(True, linestyle="--", alpha=0.45 if dark_mode else 0.3, color=grid_color)
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}", level="info")
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范围显示", level="info")
final_y_min = x_low - extra_margin
final_y_max = x_high + extra_margin
else:
self.log_gui.log(f" x数据超出tolerance范围扩展显示范围", level="info")
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})"
, level="info")
# ========== 下图y coordinates ==========
ax2.plot(
grayscale,
y_measured,
color=y_line_color,
marker="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=y_line_color,
bbox=dict(
boxstyle="round,pad=0.2",
facecolor=palette["card_bg"],
edgecolor=y_line_color,
alpha=0.92 if dark_mode else 0.85,
linewidth=0.8,
),
)
ax2.axhline(
y=y_ideal,
color=ideal_line_color,
linestyle="--",
linewidth=1.5,
label=f"y-ideal ({y_ideal:.4f})",
zorder=3,
)
ax2.axhline(
y=y_low,
color=y_tol_color,
linestyle=":",
linewidth=1,
alpha=0.95 if dark_mode else 0.7,
label=f"y-low ({y_low:.4f})",
zorder=2,
)
ax2.axhline(
y=y_high,
color=y_tol_color,
linestyle=":",
linewidth=1,
alpha=0.95 if dark_mode else 0.7,
label=f"y-high ({y_high:.4f})",
zorder=2,
)
ax2.fill_between(
full_grayscale,
y_low,
y_high,
alpha=0.22 if dark_mode else 0.15,
color=y_tol_color,
zorder=1,
)
ax2.set_xlabel("灰阶 (%)", fontsize=9, color=axis_text)
ax2.set_ylabel("CIE y", fontsize=9, color=axis_text)
ax2.grid(True, linestyle="--", alpha=0.45 if dark_mode else 0.3, color=grid_color)
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}", level="info")
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范围显示", level="info")
final_y_min = y_low - extra_margin
final_y_max = y_high + extra_margin
else:
self.log_gui.log(f" y数据超出tolerance范围扩展显示范围", level="info")
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})"
, level="info")
# ========== 总标题 - 统一格式(去掉统计信息)==========
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",
color=palette["fg"],
)
self.cct_fig.subplots_adjust(
left=0.12,
right=0.82,
top=0.92,
bottom=0.08,
hspace=0.30,
)
legend1 = ax1.legend(
fontsize=7,
loc="center left",
bbox_to_anchor=(1.05, 0.5),
framealpha=0.92 if dark_mode else 1.0,
facecolor=legend_bg,
edgecolor=legend_edge,
)
legend2 = ax2.legend(
fontsize=7,
loc="center left",
bbox_to_anchor=(1.05, 0.5),
framealpha=0.92 if dark_mode else 1.0,
facecolor=legend_bg,
edgecolor=legend_edge,
)
for legend in (legend1, legend2):
for text in legend.get_texts():
text.set_color(axis_text)
self.cct_canvas.draw()
self.chart_notebook.select(self.cct_chart_frame)
self.log_gui.log("xy 色度坐标图绘制完成", level="success")
class PlotCctMixin:
"""由 tools/refactor_to_mixins.py 自动生成。
把本模块的自由函数挂到 PQAutomationApp 上,便于 F12 跳转与类型推断。
"""
plot_cct = plot_cct