2026-05-27 11:26:28 +08:00
|
|
|
|
"""测试执行(runner)相关逻辑(Step 5 重构)。
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
从 pqAutomationApp.PQAutomationApp 中搬迁。每个函数第一行 `self = app`
|
|
|
|
|
|
以保留原有 `self.xxx` 属性访问不变。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
|
|
import time
|
|
|
|
|
|
from tkinter import messagebox
|
|
|
|
|
|
import tkinter as tk
|
|
|
|
|
|
|
|
|
|
|
|
import colour
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
|
|
|
|
|
|
|
import algorithm.pq_algorithm as pq_algorithm
|
2026-04-20 11:48:38 +08:00
|
|
|
|
from app.pq.pq_result import PQResult
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
|
|
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
|
|
|
|
from pqAutomationApp import PQAutomationApp
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def new_pq_results(self: "PQAutomationApp", test_type, test_name):
|
2026-04-21 16:03:11 +08:00
|
|
|
|
# 通过 PQResultStore 创建/替换指定 test_type 的结果,并设为当前活跃
|
|
|
|
|
|
self.results.new(test_type, test_name)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
# 设置配置
|
|
|
|
|
|
config = {
|
|
|
|
|
|
"test_type": test_type,
|
|
|
|
|
|
"test_name": test_name,
|
|
|
|
|
|
"test_items": self.config.current_test_types[test_type]["test_items"],
|
|
|
|
|
|
"test_items_chinese": self.config.get_test_item_chinese_names(
|
|
|
|
|
|
self.config.current_test_types[test_type]["test_items"]
|
|
|
|
|
|
),
|
|
|
|
|
|
}
|
|
|
|
|
|
self.results.set_test_config(config)
|
|
|
|
|
|
|
|
|
|
|
|
# 添加测试项
|
|
|
|
|
|
for item in config["test_items"]:
|
|
|
|
|
|
self.results.add_test_item(
|
|
|
|
|
|
item, config["test_items_chinese"][config["test_items"].index(item)]
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def run_test(self: "PQAutomationApp", test_type, test_items):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""执行测试"""
|
|
|
|
|
|
try:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"开始执行{self.get_test_type_name(test_type)}测试", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.log_gui.log(
|
|
|
|
|
|
f"测试项目: {', '.join(self.config.get_test_item_chinese_names(test_items))}"
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 根据测试类型执行不同的测试流程
|
|
|
|
|
|
if test_type == "screen_module":
|
|
|
|
|
|
self.run_screen_module_test(test_items)
|
|
|
|
|
|
elif test_type == "sdr_movie":
|
|
|
|
|
|
self.run_sdr_movie_test(test_items)
|
|
|
|
|
|
elif test_type == "hdr_movie":
|
|
|
|
|
|
self.run_hdr_movie_test(test_items)
|
2026-05-29 14:40:39 +08:00
|
|
|
|
elif test_type == "local_dimming":
|
|
|
|
|
|
self.log_gui.log(
|
|
|
|
|
|
"Local Dimming 为手动模式,请在 Local Dimming 面板发送图案并采集亮度",
|
|
|
|
|
|
level="info",
|
|
|
|
|
|
)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 测试完成后更新UI状态
|
|
|
|
|
|
if self.testing: # 如果没有被中途停止
|
2026-04-20 15:34:45 +08:00
|
|
|
|
self._dispatch_ui(self.on_test_completed)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"测试过程中发生错误: {str(e)}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(traceback.format_exc(), level="error")
|
2026-04-20 15:34:45 +08:00
|
|
|
|
self._dispatch_ui(self.on_test_error)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def run_screen_module_test(self: "PQAutomationApp", test_items):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""执行屏模组性能测试 - 优化版"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("执行屏模组性能测试...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
if test_items:
|
|
|
|
|
|
self.new_pq_results("screen_module", "屏模组性能测试")
|
|
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("未选择任何测试项目", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 判断是否需要灰阶数据
|
|
|
|
|
|
needs_gray_data = any(
|
|
|
|
|
|
item in test_items for item in ["gamma", "cct", "contrast"]
|
|
|
|
|
|
)
|
|
|
|
|
|
shared_gray_data = None # 共享的灰阶数据
|
|
|
|
|
|
|
|
|
|
|
|
# 计算总测试项数量
|
|
|
|
|
|
total_items = len(test_items)
|
|
|
|
|
|
current_item = 0
|
|
|
|
|
|
|
|
|
|
|
|
for item in test_items:
|
|
|
|
|
|
if not self.testing: # 检查是否被停止
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
current_item += 1
|
|
|
|
|
|
self.status_var.set(f"测试进行中... ({current_item}/{total_items})")
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 色域测试 ====================
|
|
|
|
|
|
if item == "gamut":
|
|
|
|
|
|
self.test_gamut("screen_module")
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 灰阶数据采集 ====================
|
|
|
|
|
|
# 如果是第一个需要灰阶数据的测试项,统一采集数据
|
|
|
|
|
|
elif (
|
|
|
|
|
|
item in ["gamma", "cct", "contrast"]
|
|
|
|
|
|
and shared_gray_data is None
|
|
|
|
|
|
and needs_gray_data
|
|
|
|
|
|
):
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
|
|
|
|
|
self.log_gui.log("开始统一采集灰阶数据(用于 Gamma/CCT/对比度测试)", level="info")
|
|
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
shared_gray_data = self.send_fix_pattern("gray")
|
|
|
|
|
|
|
|
|
|
|
|
if not shared_gray_data or len(shared_gray_data) < 2:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("灰阶数据采集失败或数据不足,跳过相关测试", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f"灰阶数据采集完成,共 {len(shared_gray_data)} 个数据点"
|
|
|
|
|
|
, level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 保存到 results 对象,供所有灰阶测试使用
|
|
|
|
|
|
self.results.add_intermediate_data("shared", "gray", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
# 执行当前测试项
|
|
|
|
|
|
if item == "gamma":
|
|
|
|
|
|
self.test_gamma("screen_module", shared_gray_data)
|
|
|
|
|
|
elif item == "cct":
|
|
|
|
|
|
self.test_cct("screen_module", shared_gray_data)
|
|
|
|
|
|
elif item == "contrast":
|
|
|
|
|
|
self.test_contrast("screen_module", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 后续灰阶测试(复用数据) ====================
|
|
|
|
|
|
elif item in ["gamma", "cct", "contrast"] and shared_gray_data is not None:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"复用已采集的灰阶数据进行 {item} 测试", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
if item == "gamma":
|
|
|
|
|
|
self.test_gamma("screen_module", shared_gray_data)
|
|
|
|
|
|
elif item == "cct":
|
|
|
|
|
|
self.test_cct("screen_module", shared_gray_data)
|
|
|
|
|
|
elif item == "contrast":
|
|
|
|
|
|
self.test_contrast("screen_module", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def run_custom_sdr_test(self: "PQAutomationApp", test_items):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""执行客户定制 SDR 测试 - 升级版"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("执行客户定制 SDR 测试...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
# 获取信号格式设置
|
|
|
|
|
|
color_space = self.sdr_color_space_var.get() # BT.709/BT.601/BT.2020
|
|
|
|
|
|
data_range = self.sdr_data_range_var.get() # Full/Limited
|
|
|
|
|
|
bit_depth = self.sdr_bit_depth_var.get() # 8bit/10bit/12bit
|
|
|
|
|
|
|
2026-05-22 11:31:36 +08:00
|
|
|
|
self.log_gui.log(f"信号格式: 色彩空间={color_space}, 数据范围={data_range}, 编码位深={bit_depth}", level="info")
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("开始统一采集灰阶数据(用于 Gamma/CCT/对比度测试)", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.test_custom_sdr()
|
|
|
|
|
|
|
|
|
|
|
|
if self.testing:
|
2026-04-20 15:34:45 +08:00
|
|
|
|
self._dispatch_ui(self.on_custom_template_test_completed)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def run_sdr_movie_test(self: "PQAutomationApp", test_items):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""执行SDR Movie测试"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("执行SDR Movie测试...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
if test_items:
|
|
|
|
|
|
self.new_pq_results("sdr_movie", "SDR Movie测试")
|
|
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("未选择任何测试项目", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 获取信号格式设置
|
|
|
|
|
|
color_space = self.sdr_color_space_var.get() # BT.709/BT.601/BT.2020
|
|
|
|
|
|
data_range = self.sdr_data_range_var.get() # Full/Limited
|
|
|
|
|
|
bit_depth = self.sdr_bit_depth_var.get() # 8bit/10bit/12bit
|
|
|
|
|
|
|
2026-05-22 11:31:36 +08:00
|
|
|
|
self.log_gui.log(f"信号格式: 色彩空间={color_space}, 数据范围={data_range}, 编码位深={bit_depth}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 判断是否需要灰阶数据
|
|
|
|
|
|
needs_gray_data = any(
|
|
|
|
|
|
item in test_items for item in ["gamma", "cct", "contrast"]
|
|
|
|
|
|
)
|
|
|
|
|
|
shared_gray_data = None
|
|
|
|
|
|
|
|
|
|
|
|
# 计算总测试项数量
|
|
|
|
|
|
total_items = len(test_items)
|
|
|
|
|
|
current_item = 0
|
|
|
|
|
|
|
|
|
|
|
|
for item in test_items:
|
|
|
|
|
|
if not self.testing:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
current_item += 1
|
|
|
|
|
|
self.status_var.set(f"测试进行中... ({current_item}/{total_items})")
|
|
|
|
|
|
|
|
|
|
|
|
if item == "gamut":
|
|
|
|
|
|
self.test_gamut("sdr_movie")
|
|
|
|
|
|
|
|
|
|
|
|
elif (
|
|
|
|
|
|
item in ["gamma", "cct", "contrast"]
|
|
|
|
|
|
and shared_gray_data is None
|
|
|
|
|
|
and needs_gray_data
|
|
|
|
|
|
):
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("开始统一采集灰阶数据(用于 Gamma/CCT/对比度测试)", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
shared_gray_data = self.send_fix_pattern("gray")
|
|
|
|
|
|
|
|
|
|
|
|
if not shared_gray_data or len(shared_gray_data) < 2:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("灰阶数据采集失败或数据不足", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.results.add_intermediate_data("shared", "gray", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
if item == "gamma":
|
|
|
|
|
|
self.test_gamma("sdr_movie", shared_gray_data)
|
|
|
|
|
|
elif item == "cct":
|
|
|
|
|
|
self.test_cct("sdr_movie", shared_gray_data)
|
|
|
|
|
|
elif item == "contrast":
|
|
|
|
|
|
self.test_contrast("sdr_movie", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
elif item in ["gamma", "cct", "contrast"] and shared_gray_data is not None:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"复用已采集的灰阶数据进行 {item} 测试", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if item == "gamma":
|
|
|
|
|
|
self.test_gamma("sdr_movie", shared_gray_data)
|
|
|
|
|
|
elif item == "cct":
|
|
|
|
|
|
self.test_cct("sdr_movie", shared_gray_data)
|
|
|
|
|
|
elif item == "contrast":
|
|
|
|
|
|
self.test_contrast("sdr_movie", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
elif item == "accuracy":
|
|
|
|
|
|
self.test_color_accuracy("sdr_movie")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def run_hdr_movie_test(self: "PQAutomationApp", test_items):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""执行HDR Movie测试"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("执行HDR Movie测试...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
if test_items:
|
|
|
|
|
|
self.new_pq_results("hdr_movie", "HDR Movie测试")
|
|
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("未选择任何测试项目", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 获取信号格式设置
|
|
|
|
|
|
color_space = self.hdr_color_space_var.get()
|
|
|
|
|
|
max_cll = self.hdr_maxcll_var.get()
|
|
|
|
|
|
max_fall = self.hdr_maxfall_var.get()
|
|
|
|
|
|
data_range = self.hdr_data_range_var.get()
|
|
|
|
|
|
bit_depth = self.hdr_bit_depth_var.get()
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"信号格式: 色彩空间={color_space}", level="info")
|
|
|
|
|
|
self.log_gui.log(f" MaxCLL={max_cll}, MaxFALL={max_fall}", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 数据范围={data_range}, 编码位深={bit_depth}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 判断是否需要灰阶数据
|
|
|
|
|
|
needs_gray_data = any(
|
|
|
|
|
|
item in test_items for item in ["eotf", "cct", "contrast"]
|
|
|
|
|
|
)
|
|
|
|
|
|
shared_gray_data = None
|
|
|
|
|
|
|
|
|
|
|
|
# 计算总测试项数量
|
|
|
|
|
|
total_items = len(test_items)
|
|
|
|
|
|
current_item = 0
|
|
|
|
|
|
|
|
|
|
|
|
for item in test_items:
|
|
|
|
|
|
if not self.testing:
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
current_item += 1
|
|
|
|
|
|
self.status_var.set(f"测试进行中... ({current_item}/{total_items})")
|
|
|
|
|
|
|
|
|
|
|
|
if item == "gamut":
|
|
|
|
|
|
self.test_gamut("hdr_movie")
|
|
|
|
|
|
|
|
|
|
|
|
elif (
|
|
|
|
|
|
item in ["eotf", "cct", "contrast"]
|
|
|
|
|
|
and shared_gray_data is None
|
|
|
|
|
|
and needs_gray_data
|
|
|
|
|
|
):
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("开始统一采集灰阶数据(用于 EOTF/CCT/对比度测试)", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
shared_gray_data = self.send_fix_pattern("gray")
|
|
|
|
|
|
|
|
|
|
|
|
if not shared_gray_data or len(shared_gray_data) < 2:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("灰阶数据采集失败或数据不足", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.results.add_intermediate_data("shared", "gray", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
if item == "eotf":
|
|
|
|
|
|
self.test_eotf("hdr_movie", shared_gray_data)
|
|
|
|
|
|
elif item == "cct":
|
|
|
|
|
|
self.test_cct("hdr_movie", shared_gray_data)
|
|
|
|
|
|
elif item == "contrast":
|
|
|
|
|
|
self.test_contrast("hdr_movie", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
elif item in ["eotf", "cct", "contrast"] and shared_gray_data is not None:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"复用已采集的灰阶数据进行 {item} 测试", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if item == "eotf":
|
|
|
|
|
|
self.test_eotf("hdr_movie", shared_gray_data)
|
|
|
|
|
|
elif item == "cct":
|
|
|
|
|
|
self.test_cct("hdr_movie", shared_gray_data)
|
|
|
|
|
|
elif item == "contrast":
|
|
|
|
|
|
self.test_contrast("hdr_movie", shared_gray_data)
|
|
|
|
|
|
|
|
|
|
|
|
elif item == "accuracy":
|
|
|
|
|
|
self.test_color_accuracy("hdr_movie")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def send_fix_pattern(self: "PQAutomationApp", mode):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""发送固定图案并采集数据 - 支持不同测试类型的信号格式"""
|
|
|
|
|
|
results = []
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
2026-05-13 17:17:13 +08:00
|
|
|
|
session = self.pattern_service.prepare_session(mode, log_details=True)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-05-24 11:37:45 +08:00
|
|
|
|
# 判定信号是否变化(决定 settle 长度)。
|
|
|
|
|
|
# - SDR/HDR:prepare_session 内部已调用 ``apply_signal_format`` → ``set_video_mode``,
|
|
|
|
|
|
# 此时 ``format_changed`` 反映本次 vs 上次的差异,可直接读取。
|
|
|
|
|
|
# - screen_module:prepare_session 只 stage 了 color_info/timing,未调用
|
|
|
|
|
|
# ``set_video_mode``,``format_changed`` 仍是上次测试的陈旧值,按"潜在变化"处理。
|
|
|
|
|
|
# 注意:必须在 prime 提交之前读取,否则 prime 的 set_video_mode 会拿当次
|
|
|
|
|
|
# 参数与刚写入的 ``_last_sent_config`` 比对,得到 False,把这个标志覆盖掉。
|
|
|
|
|
|
if mode == "screen_module":
|
|
|
|
|
|
format_changed = True
|
|
|
|
|
|
else:
|
|
|
|
|
|
format_changed = bool(
|
|
|
|
|
|
getattr(getattr(self, "ucd", None), "format_changed", True)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 预热提交:prepare_session 仅 stage 了新的 color/timing/pattern,
|
|
|
|
|
|
# 真正的 ``pg.apply()`` 要到第一次发图时才发生。提前发送首个 pattern,
|
|
|
|
|
|
# 让 TV 在 signal_settle 期间就开始重新锁定信号;
|
|
|
|
|
|
# 否则前 1~2 个 pattern 会落在 TV 锁定窗口里导致测量错误。
|
|
|
|
|
|
self.pattern_service.send_session_pattern(session, 0)
|
|
|
|
|
|
|
2026-05-22 11:31:36 +08:00
|
|
|
|
if format_changed:
|
|
|
|
|
|
signal_settle = max(1.0, float(getattr(self, "signal_settle_time", 5.0)))
|
|
|
|
|
|
self.log_gui.log(
|
|
|
|
|
|
f"信号格式已变化,等待电视重新锁定: {signal_settle:.1f}s(可通过 signal_settle_time 调整)",
|
|
|
|
|
|
level="info",
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
signal_settle = 0.5
|
|
|
|
|
|
self.log_gui.log("信号格式未变化,短暂等待: 0.5s", level="info")
|
|
|
|
|
|
time.sleep(signal_settle)
|
|
|
|
|
|
|
2026-05-13 17:17:13 +08:00
|
|
|
|
total_patterns = session.total_patterns
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"开始采集数据,共 {total_patterns} 个图案", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
settle_time = max(0.2, float(getattr(self, "pattern_settle_time", 1.0)))
|
|
|
|
|
|
progress_step = max(
|
|
|
|
|
|
1, int(getattr(self, "pattern_progress_log_step", 5))
|
|
|
|
|
|
)
|
|
|
|
|
|
self.log_gui.log(
|
|
|
|
|
|
f"采集等待时间: {settle_time:.2f}s(可通过 pattern_settle_time 调整)"
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-05-13 17:17:13 +08:00
|
|
|
|
display_names = session.display_names
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
for i in range(total_patterns):
|
|
|
|
|
|
if not self.testing:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("测试已停止", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
should_log_detail = (
|
2026-05-22 11:31:36 +08:00
|
|
|
|
total_patterns <= progress_step
|
|
|
|
|
|
or i == 0
|
2026-04-20 11:13:57 +08:00
|
|
|
|
or (i + 1) == total_patterns
|
|
|
|
|
|
or ((i + 1) % progress_step == 0)
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if should_log_detail:
|
2026-05-13 17:17:13 +08:00
|
|
|
|
if display_names and i < len(display_names):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.log_gui.log(
|
2026-05-13 17:17:13 +08:00
|
|
|
|
f"发送第 {i+1}/{total_patterns} 个图案: {display_names[i]}..."
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"发送第 {i+1}/{total_patterns} 个图案...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-05-24 11:37:45 +08:00
|
|
|
|
# 首图已在 prime 阶段发送并经 signal_settle 稳定,无需重发也无需再等
|
|
|
|
|
|
# settle_time;后续 pattern 走正常发图 + 等待。
|
|
|
|
|
|
if i == 0:
|
|
|
|
|
|
pass
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.pattern_service.send_session_pattern(session, i)
|
|
|
|
|
|
time.sleep(settle_time)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 测量数据
|
|
|
|
|
|
if mode == "custom":
|
|
|
|
|
|
result = []
|
2026-06-04 10:36:15 +08:00
|
|
|
|
tcp, duv, lv, X, Y, Z = self.read_ca_tcp_duv()
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
if should_log_detail:
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f" 测量完成: TCP={tcp:.4f}, DUV={duv:.4f}, lv={lv:.2f}, "
|
2026-04-20 11:13:57 +08:00
|
|
|
|
f"X={X:.4f}, Y={Y:.4f}, Z={Z:.4f}"
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-06-04 10:36:15 +08:00
|
|
|
|
lambda_, Pe, lv, X, Y, Z = self.read_ca_lambda_pe()
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
if should_log_detail:
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f" 测量完成: λ={lambda_:.4f}, Pe={Pe:.4f}, lv={lv:.2f}, "
|
2026-04-20 11:13:57 +08:00
|
|
|
|
f"X={X:.4f}, Y={Y:.4f}, Z={Z:.4f}"
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
result = [tcp, duv, lv, lambda_, Pe, lv, X, Y, Z]
|
|
|
|
|
|
results.append(result)
|
|
|
|
|
|
|
|
|
|
|
|
# 每完成一个 pattern,实时写入客户模板结果表。
|
|
|
|
|
|
try:
|
|
|
|
|
|
xy = colour.XYZ_to_xy(np.array([X, Y, Z]))
|
|
|
|
|
|
u_prime, v_prime, _ = colour.XYZ_to_CIE1976UCS(
|
|
|
|
|
|
np.array([X, Y, Z])
|
|
|
|
|
|
)
|
|
|
|
|
|
row_data = {
|
|
|
|
|
|
"pattern_name": (
|
2026-05-13 17:17:13 +08:00
|
|
|
|
display_names[i]
|
|
|
|
|
|
if i < len(display_names)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
else f"P {i + 1}"
|
|
|
|
|
|
),
|
|
|
|
|
|
"X": X,
|
|
|
|
|
|
"Y": Y,
|
|
|
|
|
|
"Z": Z,
|
|
|
|
|
|
"x": xy[0],
|
|
|
|
|
|
"y": xy[1],
|
|
|
|
|
|
"Lv": lv,
|
|
|
|
|
|
"u_prime": u_prime,
|
|
|
|
|
|
"v_prime": v_prime,
|
|
|
|
|
|
"Tcp": tcp,
|
|
|
|
|
|
"duv": duv,
|
|
|
|
|
|
"lambda_d": lambda_,
|
|
|
|
|
|
"Pe": Pe,
|
|
|
|
|
|
}
|
|
|
|
|
|
self.root.after(
|
|
|
|
|
|
0,
|
|
|
|
|
|
lambda row_no=i + 1, data=row_data: self.append_custom_template_result(
|
|
|
|
|
|
row_no, data
|
|
|
|
|
|
),
|
|
|
|
|
|
)
|
|
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"第 {i+1} 行实时结果写入失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
else:
|
2026-06-04 10:36:15 +08:00
|
|
|
|
x, y, lv, X, Y, Z = self.read_ca_xyLv()
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results.append([x, y, lv, X, Y, Z])
|
|
|
|
|
|
|
|
|
|
|
|
if should_log_detail:
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f" 测量完成: x={x:.4f}, y={y:.4f}, lv={lv:.2f}, "
|
2026-04-20 11:13:57 +08:00
|
|
|
|
f"X={X:.4f}, Y={Y:.4f}, Z={Z:.4f}"
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"数据采集完成,共 {len(results)} 组数据", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return results
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-04-21 16:03:11 +08:00
|
|
|
|
self.log_gui.log(f"发送图案失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(traceback.format_exc(), level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def test_custom_sdr(self: "PQAutomationApp"):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""执行客户定制 SDR 测试 - 升级版"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("执行客户定制 SDR 测试...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = self.send_fix_pattern("custom")
|
|
|
|
|
|
if not results:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("客户模板SDR测试被中断", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"客户模板采集完成,共 {len(results)} 组数据", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def test_gamut(self: "PQAutomationApp", test_type):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""测试色域"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("开始测试色域...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.results.start_test_item("gamut")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 存储测量结果
|
|
|
|
|
|
results = self.send_fix_pattern("rgb")
|
|
|
|
|
|
|
|
|
|
|
|
# 检查结果是否为空
|
|
|
|
|
|
if not results:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("色域测试被中断", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.results.add_intermediate_data("gamut", "rgb", results)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算色域覆盖率
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("计算色域覆盖率...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 提取 x, y 坐标用于计算色域
|
|
|
|
|
|
xy_points = [[result[0], result[1]] for result in results]
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# ========== 测试时:使用色彩空间的值作为参考标准 ==========
|
2026-04-20 11:13:57 +08:00
|
|
|
|
reference_standard = None
|
|
|
|
|
|
area = None
|
|
|
|
|
|
coverage = None
|
|
|
|
|
|
|
|
|
|
|
|
if test_type == "screen_module":
|
|
|
|
|
|
# 屏模组测试:固定使用 DCI-P3(因为没有色彩空间设置)
|
|
|
|
|
|
reference_standard = "DCI-P3"
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# 同步更新到色域参考标准变量(供后续重绘使用)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.screen_gamut_ref_var.set(reference_standard)
|
|
|
|
|
|
|
|
|
|
|
|
elif test_type == "sdr_movie":
|
|
|
|
|
|
# SDR 测试:使用色彩空间设置
|
|
|
|
|
|
color_space = self.sdr_color_space_var.get()
|
|
|
|
|
|
|
2026-05-22 11:31:36 +08:00
|
|
|
|
if color_space in ("sRGB", "BT.709"):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
reference_standard = "BT.709"
|
|
|
|
|
|
elif color_space == "BT.601":
|
|
|
|
|
|
reference_standard = "BT.601"
|
|
|
|
|
|
elif color_space == "BT.2020":
|
|
|
|
|
|
reference_standard = "BT.2020"
|
2026-05-22 11:31:36 +08:00
|
|
|
|
elif color_space == "DCI-P3":
|
|
|
|
|
|
reference_standard = "DCI-P3"
|
2026-04-20 11:13:57 +08:00
|
|
|
|
else:
|
|
|
|
|
|
reference_standard = "BT.709"
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f"未识别的色彩空间 '{color_space}',使用默认标准 BT.709"
|
|
|
|
|
|
, level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# 同步更新到色域参考标准变量
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.sdr_gamut_ref_var.set(reference_standard)
|
|
|
|
|
|
|
|
|
|
|
|
elif test_type == "hdr_movie":
|
|
|
|
|
|
# HDR 测试:使用色彩空间设置
|
|
|
|
|
|
color_space = self.hdr_color_space_var.get()
|
|
|
|
|
|
|
|
|
|
|
|
if color_space == "BT.2020":
|
|
|
|
|
|
reference_standard = "BT.2020"
|
|
|
|
|
|
elif color_space == "DCI-P3":
|
|
|
|
|
|
reference_standard = "DCI-P3"
|
|
|
|
|
|
else:
|
|
|
|
|
|
reference_standard = "BT.2020"
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f"未识别的色彩空间 '{color_space}',使用默认标准 BT.2020"
|
|
|
|
|
|
, level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# 同步更新到色域参考标准变量
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.hdr_gamut_ref_var.set(reference_standard)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 未知测试类型,使用 DCI-P3 作为后备
|
|
|
|
|
|
reference_standard = "DCI-P3"
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f"未识别的测试类型 '{test_type}',使用默认标准 DCI-P3"
|
|
|
|
|
|
, level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# ========== 根据参考标准计算 XY 覆盖率 ==========
|
|
|
|
|
|
if reference_standard == "BT.2020":
|
|
|
|
|
|
area, coverage = pq_algorithm.calculate_gamut_coverage_BT2020(xy_points)
|
|
|
|
|
|
elif reference_standard == "BT.709":
|
|
|
|
|
|
area, coverage = pq_algorithm.calculate_gamut_coverage_BT709(xy_points)
|
|
|
|
|
|
elif reference_standard == "DCI-P3":
|
|
|
|
|
|
area, coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(xy_points)
|
|
|
|
|
|
elif reference_standard == "BT.601":
|
|
|
|
|
|
area, coverage = pq_algorithm.calculate_gamut_coverage_BT601(xy_points)
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 默认使用 DCI-P3
|
|
|
|
|
|
area, coverage = pq_algorithm.calculate_gamut_coverage_DCIP3(xy_points)
|
|
|
|
|
|
reference_standard = "DCI-P3"
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f"未识别的参考标准 '{reference_standard}',使用默认标准 DCI-P3"
|
|
|
|
|
|
, level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# ========== ✅✅新增:计算 UV 覆盖率 ==========
|
2026-04-20 11:13:57 +08:00
|
|
|
|
uv_coverage = 0
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 将 XY 转换为 UV
|
|
|
|
|
|
uv_points = []
|
|
|
|
|
|
for x, y in xy_points:
|
|
|
|
|
|
u, v = pq_algorithm.xy_to_uv_1976(x, y)
|
|
|
|
|
|
uv_points.append([u, v])
|
|
|
|
|
|
|
|
|
|
|
|
# 根据参考标准计算 UV 覆盖率
|
|
|
|
|
|
if len(uv_points) >= 3:
|
|
|
|
|
|
if reference_standard == "BT.2020":
|
|
|
|
|
|
_, uv_coverage = (
|
|
|
|
|
|
pq_algorithm.calculate_gamut_coverage_BT2020_uv(uv_points)
|
|
|
|
|
|
)
|
|
|
|
|
|
elif reference_standard == "BT.709":
|
|
|
|
|
|
_, uv_coverage = pq_algorithm.calculate_gamut_coverage_BT709_uv(
|
|
|
|
|
|
uv_points
|
|
|
|
|
|
)
|
|
|
|
|
|
elif reference_standard == "DCI-P3":
|
|
|
|
|
|
_, uv_coverage = pq_algorithm.calculate_gamut_coverage_DCIP3_uv(
|
|
|
|
|
|
uv_points
|
|
|
|
|
|
)
|
|
|
|
|
|
elif reference_standard == "BT.601":
|
|
|
|
|
|
_, uv_coverage = pq_algorithm.calculate_gamut_coverage_BT601_uv(
|
|
|
|
|
|
uv_points
|
|
|
|
|
|
)
|
|
|
|
|
|
else:
|
|
|
|
|
|
_, uv_coverage = pq_algorithm.calculate_gamut_coverage_DCIP3_uv(
|
|
|
|
|
|
uv_points
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f"XY 覆盖率: {coverage:.1f}% | UV 覆盖率: {uv_coverage:.1f}%"
|
|
|
|
|
|
, level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
except:
|
|
|
|
|
|
uv_coverage = 0
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 保存结果时包含 XY 和 UV 覆盖率 ==========
|
|
|
|
|
|
self.results.set_test_item_result(
|
|
|
|
|
|
"gamut",
|
|
|
|
|
|
{
|
|
|
|
|
|
"area": area,
|
|
|
|
|
|
"coverage": coverage,
|
2026-04-21 15:31:48 +08:00
|
|
|
|
"uv_coverage": uv_coverage, # 新增 UV 覆盖率
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"reference": reference_standard,
|
|
|
|
|
|
},
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 传递完整的 results 用于绘图
|
|
|
|
|
|
self.plot_gamut(results, coverage, test_type)
|
2026-05-19 11:50:53 +08:00
|
|
|
|
self._save_chart_snapshot(test_type, "gamut", (results, coverage, test_type))
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("色域测试完成", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"色域测试失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(traceback.format_exc(), level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def test_gamma(self: "PQAutomationApp", test_type, gray_data=None):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""测试Gamma曲线
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
test_type: 测阶数据,如果提供则使用,否则重新采集
|
|
|
|
|
|
"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("开始测试Gamma曲线...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.results.start_test_item("gamma")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 使用传入的灰阶数据或独立采集
|
|
|
|
|
|
if gray_data is not None:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("使用共享的灰阶数据", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = gray_data
|
|
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("独立采集灰阶数据", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = self.send_fix_pattern("gray")
|
|
|
|
|
|
|
|
|
|
|
|
if not results or len(results) < 2:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("Gamma测试被中断或数据不足", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.results.add_intermediate_data("gamma", "gray", results)
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"使用 {len(results)} 个灰阶数据点进行计算", level="info")
|
|
|
|
|
|
self.log_gui.log("计算Gamma值...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# ========== 修复:正确获取 max_index_fix ==========
|
2026-04-20 11:13:57 +08:00
|
|
|
|
# 获取配置中的值
|
|
|
|
|
|
config_max_value = self.config.current_pattern.get(
|
|
|
|
|
|
"measurement_max_value", 10
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 强制转换为整数
|
|
|
|
|
|
try:
|
|
|
|
|
|
max_index_fix = int(config_max_value)
|
|
|
|
|
|
except (ValueError, TypeError):
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"警告: measurement_max_value 转换失败,使用默认值 10", level="warning")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
max_index_fix = 10
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"配置中的 max_index_fix = {max_index_fix}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 关键修复:验证并调整 max_index_fix
|
|
|
|
|
|
# max_index_fix 应该是数据点的最大索引(从0开始,所以是 len - 1)
|
|
|
|
|
|
actual_max_index = len(results) - 1
|
|
|
|
|
|
|
|
|
|
|
|
if max_index_fix > actual_max_index:
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f"警告: 配置的 max_index_fix({max_index_fix}) > 实际最大索引({actual_max_index})",
|
|
|
|
|
|
level="warning"
|
2026-04-20 11:13:57 +08:00
|
|
|
|
)
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"自动调整为: {actual_max_index}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
max_index_fix = actual_max_index
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"最终使用的 max_index_fix = {max_index_fix}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
# ========================================================
|
|
|
|
|
|
|
|
|
|
|
|
# 获取灰阶 pattern 参数(用于22293 Gamma数据对齐)
|
|
|
|
|
|
pattern_params = self.config.default_pattern_gray.get(
|
|
|
|
|
|
"pattern_params", None
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 计算Gamma值(使用修正后的 max_index_fix 和 8bit pattern参数)
|
|
|
|
|
|
results_with_gamma_list, L_bar = self.calculate_gamma(
|
|
|
|
|
|
results, max_index_fix, pattern_params
|
|
|
|
|
|
)
|
|
|
|
|
|
self.results.set_test_item_result(
|
|
|
|
|
|
"gamma", {"gamma": results_with_gamma_list, "L_bar": L_bar}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-05-22 11:31:36 +08:00
|
|
|
|
# 绘制Gamma曲线(SDR 使用用户选择的参考值)
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if test_type == "sdr_movie":
|
|
|
|
|
|
try:
|
|
|
|
|
|
target_gamma = float(self.sdr_gamma_type_var.get())
|
|
|
|
|
|
except (ValueError, AttributeError):
|
|
|
|
|
|
target_gamma = 2.2
|
|
|
|
|
|
else:
|
|
|
|
|
|
target_gamma = 2.2
|
|
|
|
|
|
self.plot_gamma(L_bar, results_with_gamma_list, target_gamma, test_type)
|
2026-05-19 11:50:53 +08:00
|
|
|
|
self._save_chart_snapshot(test_type, "gamma", (L_bar, results_with_gamma_list, target_gamma, test_type))
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("Gamma测试完成", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"Gamma测试失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(traceback.format_exc(), level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def test_eotf(self: "PQAutomationApp", test_type, gray_data=None):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""测试 EOTF 曲线(HDR 专用)
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
test_type: 测试类型阶数据,如果提供则使用,否则重新采集
|
|
|
|
|
|
"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("开始测试 EOTF 曲线(HDR)...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.results.start_test_item("eotf")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 使用传入的灰阶数据或独立采集
|
|
|
|
|
|
if gray_data is not None:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("使用共享的灰阶数据", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = gray_data
|
|
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("独立采集灰阶数据", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = self.send_fix_pattern("gray")
|
|
|
|
|
|
|
|
|
|
|
|
if not results or len(results) < 2:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("EOTF 测试被中断或数据不足", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.results.add_intermediate_data("eotf", "gray", results)
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"使用 {len(results)} 个灰阶数据点进行计算", level="info")
|
|
|
|
|
|
self.log_gui.log("计算 EOTF 值...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# ========== 获取 max_index_fix ==========
|
|
|
|
|
|
config_max_value = self.config.current_pattern.get(
|
|
|
|
|
|
"measurement_max_value", 10
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
max_index_fix = int(config_max_value)
|
|
|
|
|
|
except (ValueError, TypeError):
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"警告: measurement_max_value 转换失败,使用默认值 10", level="warning")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
max_index_fix = 10
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"配置中的 max_index_fix = {max_index_fix}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 验证并调整 max_index_fix
|
|
|
|
|
|
actual_max_index = len(results) - 1
|
|
|
|
|
|
|
|
|
|
|
|
if max_index_fix > actual_max_index:
|
|
|
|
|
|
self.log_gui.log(
|
2026-04-21 15:31:48 +08:00
|
|
|
|
f"警告: 配置的 max_index_fix({max_index_fix}) > 实际最大索引({actual_max_index})",
|
|
|
|
|
|
level="warning"
|
2026-04-20 11:13:57 +08:00
|
|
|
|
)
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"自动调整为: {actual_max_index}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
max_index_fix = actual_max_index
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"最终使用的 max_index_fix = {max_index_fix}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取灰阶 pattern 参数(用于22293 Gamma数据对齐)
|
|
|
|
|
|
pattern_params = self.config.default_pattern_gray.get(
|
|
|
|
|
|
"pattern_params", None
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 计算 EOTF(复用 Gamma 计算逻辑,使用8bit pattern参数)==========
|
|
|
|
|
|
results_with_eotf_list, L_bar = self.calculate_gamma(
|
|
|
|
|
|
results, max_index_fix, pattern_params
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存结果
|
|
|
|
|
|
self.results.set_test_item_result(
|
|
|
|
|
|
"eotf", {"eotf": results_with_eotf_list, "L_bar": L_bar}
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 绘制 EOTF 曲线 ==========
|
|
|
|
|
|
# HDR 使用 PQ 曲线,目标 gamma 设为 None(不使用传统 gamma)
|
|
|
|
|
|
self.plot_eotf(L_bar, results_with_eotf_list, test_type)
|
2026-05-19 11:50:53 +08:00
|
|
|
|
self._save_chart_snapshot(test_type, "eotf", (L_bar, results_with_eotf_list, test_type))
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("EOTF 测试完成", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"EOTF 测试失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(traceback.format_exc(), level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def test_cct(self: "PQAutomationApp", test_type, gray_data=None):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""测试色度一致性"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("开始测试色度一致性...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.results.start_test_item("cct")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
|
if gray_data is not None:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("使用共享的灰阶数据", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = gray_data
|
|
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("独立采集灰阶数据", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = self.send_fix_pattern("gray")
|
|
|
|
|
|
|
|
|
|
|
|
if not results:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("色度一致性测试被中断", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.results.add_intermediate_data("cct", "gray", results)
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"使用 {len(results)} 个灰阶数据点进行色度计算", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 提取色度坐标
|
|
|
|
|
|
cct_values = pq_algorithm.calculate_cct_from_results(results)
|
|
|
|
|
|
|
|
|
|
|
|
# 保存到结果
|
|
|
|
|
|
self.results.set_test_item_result("cct", {"cct_values": cct_values})
|
|
|
|
|
|
|
|
|
|
|
|
# 绘制图表
|
|
|
|
|
|
self.plot_cct(test_type)
|
2026-05-19 11:50:53 +08:00
|
|
|
|
self._save_chart_snapshot(test_type, "cct", (test_type,))
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("色度一致性测试完成", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"色度一致性测试失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(traceback.format_exc(), level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def test_contrast(self: "PQAutomationApp", test_type, gray_data=None):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""测试对比度
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
test_type: 阶数据,如果提供则使用,否则重新采集
|
|
|
|
|
|
"""
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("开始测试对比度...", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.results.start_test_item("contrast")
|
|
|
|
|
|
|
|
|
|
|
|
try:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# 优先使用传入的灰阶数据
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if gray_data is not None:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("使用共享的灰阶数据", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = gray_data
|
|
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("独立采集灰阶数据", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
results = self.send_fix_pattern("gray")
|
|
|
|
|
|
|
|
|
|
|
|
if not results:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("对比度测试被中断", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
self.results.add_intermediate_data("contrast", "gray", results)
|
|
|
|
|
|
|
|
|
|
|
|
# 获取最亮和最暗的亮度值
|
|
|
|
|
|
luminance_values = [result[2] for result in results] # 提取lv值
|
|
|
|
|
|
|
|
|
|
|
|
max_luminance = max(luminance_values) # 最大亮度(白)
|
|
|
|
|
|
min_luminance = min(luminance_values) # 最小亮度(黑)
|
|
|
|
|
|
|
|
|
|
|
|
# 防止除以0
|
|
|
|
|
|
if min_luminance < 0.001:
|
|
|
|
|
|
min_luminance = 0.001
|
|
|
|
|
|
|
|
|
|
|
|
# 计算对比度
|
|
|
|
|
|
contrast_ratio = max_luminance / min_luminance
|
|
|
|
|
|
|
|
|
|
|
|
# 保存结果
|
|
|
|
|
|
contrast_data = {
|
|
|
|
|
|
"max_luminance": max_luminance,
|
|
|
|
|
|
"min_luminance": min_luminance,
|
|
|
|
|
|
"contrast_ratio": contrast_ratio,
|
|
|
|
|
|
"luminance_values": luminance_values,
|
|
|
|
|
|
}
|
|
|
|
|
|
self.results.set_test_item_result("contrast", contrast_data)
|
|
|
|
|
|
|
|
|
|
|
|
# 显示对比度结果到日志
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"最大亮度 (白场): {max_luminance:.2f} cd/m²", level="info")
|
|
|
|
|
|
self.log_gui.log(f"最小亮度 (黑场): {min_luminance:.4f} cd/m²", level="info")
|
|
|
|
|
|
self.log_gui.log(f"对比度: {contrast_ratio:.0f}:1", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 绘制对比度图表
|
|
|
|
|
|
self.plot_contrast(contrast_data, test_type)
|
2026-05-19 11:50:53 +08:00
|
|
|
|
self._save_chart_snapshot(test_type, "contrast", (contrast_data, test_type))
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("对比度测试完成", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"对比度测试失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
import traceback
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(traceback.format_exc(), level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def test_color_accuracy(self: "PQAutomationApp", test_type):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""测试色准 - 使用手工实现的 ΔE 2000(应用 Gamma)"""
|
|
|
|
|
|
|
2026-05-22 11:31:36 +08:00
|
|
|
|
# ========== Gamma 参考值 ==========
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if test_type == "sdr_movie":
|
|
|
|
|
|
try:
|
|
|
|
|
|
target_gamma = float(self.sdr_gamma_type_var.get())
|
|
|
|
|
|
except (ValueError, AttributeError):
|
|
|
|
|
|
target_gamma = 2.2
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
|
|
|
|
|
self.log_gui.log(f"开始测试色准(SDR Movie 标准 - 29色)", level="info")
|
2026-05-22 11:31:36 +08:00
|
|
|
|
self.log_gui.log(f"使用 Gamma: {target_gamma}", level="success")
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
elif test_type == "hdr_movie":
|
|
|
|
|
|
target_gamma = 2.4 # HDR 使用 PQ,但保留参考值
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
|
|
|
|
|
self.log_gui.log(f"开始测试色准(HDR Movie 标准 - 29色)", level="info")
|
|
|
|
|
|
self.log_gui.log(f"使用 Gamma: PQ (参考γ={target_gamma})", level="success") # ← 新增
|
|
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
else: # screen_module
|
|
|
|
|
|
target_gamma = 2.2
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
|
|
|
|
|
self.log_gui.log(f"开始测试色准(屏模组 标准 - 29色)", level="info")
|
|
|
|
|
|
self.log_gui.log(f"使用 Gamma: {target_gamma}", level="success")
|
|
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取 29色名称
|
|
|
|
|
|
color_names = self.config.get_accuracy_color_names()
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"将测试 {len(color_names)} 个色块", level="success")
|
|
|
|
|
|
self.log_gui.log(f" 色块分组:", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 灰阶 (5个): {', '.join(color_names[:5])}", level="info")
|
|
|
|
|
|
self.log_gui.log(f" ColorChecker (18个): {', '.join(color_names[5:23])}", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 饱和色 (6个): {', '.join(color_names[23:])}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
|
|
|
|
|
self.log_gui.log("开始发送色准图案并采集数据...", level="info")
|
|
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 发送 29色图案
|
|
|
|
|
|
measured_data_list = self.send_fix_pattern("accuracy")
|
|
|
|
|
|
|
|
|
|
|
|
if measured_data_list is None or len(measured_data_list) != 29:
|
2026-04-21 16:03:11 +08:00
|
|
|
|
self.log_gui.log(f"数据数量不匹配", level="error")
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f" 期望: 29 个", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.log_gui.log(
|
|
|
|
|
|
f" 实际: {len(measured_data_list) if measured_data_list else 0} 个"
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
# 保存原始测量数据供单步调试使用
|
|
|
|
|
|
self.results.add_intermediate_data("accuracy", "measured", measured_data_list)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 计算 ΔE 2000(显示 Gamma)==========
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
|
|
|
|
|
self.log_gui.log(f"计算色准(ΔE 2000,Gamma {target_gamma})...", level="info")
|
|
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取标准 xy 坐标
|
|
|
|
|
|
standards = self.get_accuracy_color_standards(test_type)
|
|
|
|
|
|
|
|
|
|
|
|
delta_e_values = []
|
|
|
|
|
|
color_patches = []
|
|
|
|
|
|
|
|
|
|
|
|
for i, (name, measured_data) in enumerate(zip(color_names, measured_data_list)):
|
|
|
|
|
|
measured_x = measured_data[0]
|
|
|
|
|
|
measured_y = measured_data[1]
|
|
|
|
|
|
measured_lv = measured_data[2]
|
|
|
|
|
|
|
|
|
|
|
|
standard_x, standard_y = standards.get(name, (0.3127, 0.3290))
|
|
|
|
|
|
|
|
|
|
|
|
delta_e = self.calculate_delta_e_2000(
|
|
|
|
|
|
measured_x,
|
|
|
|
|
|
measured_y,
|
|
|
|
|
|
measured_lv,
|
|
|
|
|
|
standard_x,
|
|
|
|
|
|
standard_y,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
delta_e_values.append(delta_e)
|
|
|
|
|
|
color_patches.append(name)
|
|
|
|
|
|
|
|
|
|
|
|
if delta_e < 3:
|
2026-05-22 11:31:36 +08:00
|
|
|
|
grade, icon = "优秀", "OK"
|
2026-04-20 11:13:57 +08:00
|
|
|
|
elif delta_e < 5:
|
2026-05-22 11:31:36 +08:00
|
|
|
|
grade, icon = "良好", "WARN"
|
2026-04-20 11:13:57 +08:00
|
|
|
|
else:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
grade, icon = "偏差", "[Error]"
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
self.log_gui.log(
|
|
|
|
|
|
f" [{i+1:2d}] {name:20s} ΔE={delta_e:5.2f} {icon} {grade}"
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# ========== 统计 ==========
|
|
|
|
|
|
avg_delta_e_all = sum(delta_e_values) / len(delta_e_values)
|
|
|
|
|
|
max_delta_e_all = max(delta_e_values)
|
|
|
|
|
|
min_delta_e_all = min(delta_e_values)
|
|
|
|
|
|
|
|
|
|
|
|
excellent_count_all = sum(1 for de in delta_e_values if de < 3)
|
|
|
|
|
|
good_count_all = sum(1 for de in delta_e_values if 3 <= de < 5)
|
|
|
|
|
|
poor_count_all = sum(1 for de in delta_e_values if de >= 5)
|
|
|
|
|
|
|
|
|
|
|
|
delta_e_gray = delta_e_values[0:5]
|
|
|
|
|
|
avg_delta_e_gray = sum(delta_e_gray) / len(delta_e_gray)
|
|
|
|
|
|
|
|
|
|
|
|
delta_e_colorchecker = delta_e_values[5:23]
|
|
|
|
|
|
avg_delta_e_colorchecker = sum(delta_e_colorchecker) / len(delta_e_colorchecker)
|
|
|
|
|
|
|
|
|
|
|
|
delta_e_saturated = delta_e_values[23:29]
|
|
|
|
|
|
avg_delta_e_saturated = sum(delta_e_saturated) / len(delta_e_saturated)
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
|
|
|
|
|
self.log_gui.log("色准统计(全 29色):", level="info")
|
|
|
|
|
|
self.log_gui.log("=" * 50, level="separator")
|
|
|
|
|
|
self.log_gui.log(f" 平均 ΔE: {avg_delta_e_all:.2f}", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 最大 ΔE: {max_delta_e_all:.2f}", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 最小 ΔE: {min_delta_e_all:.2f}", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 优秀 (ΔE<3): {excellent_count_all} 个", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 良好 (3≤ΔE<5): {good_count_all} 个", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 偏差 (ΔE≥5): {poor_count_all} 个", level="info")
|
|
|
|
|
|
|
|
|
|
|
|
self.log_gui.log("", level="blank")
|
|
|
|
|
|
self.log_gui.log("分组统计:", level="info")
|
|
|
|
|
|
self.log_gui.log(f" 灰阶 (5个): 平均 ΔE = {avg_delta_e_gray:.2f}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
self.log_gui.log(
|
|
|
|
|
|
f" ColorChecker (18个): 平均 ΔE = {avg_delta_e_colorchecker:.2f}"
|
2026-04-21 15:31:48 +08:00
|
|
|
|
, level="info")
|
|
|
|
|
|
self.log_gui.log(f" 饱和色 (6个): 平均 ΔE = {avg_delta_e_saturated:.2f}", level="info")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# ========== 保存测试结果 ==========
|
|
|
|
|
|
accuracy_data = {
|
|
|
|
|
|
"color_patches": color_patches,
|
|
|
|
|
|
"delta_e_values": delta_e_values,
|
|
|
|
|
|
"color_measurements": measured_data_list,
|
|
|
|
|
|
"avg_delta_e": avg_delta_e_all,
|
|
|
|
|
|
"max_delta_e": max_delta_e_all,
|
|
|
|
|
|
"min_delta_e": min_delta_e_all,
|
|
|
|
|
|
"excellent_count": excellent_count_all,
|
|
|
|
|
|
"good_count": good_count_all,
|
|
|
|
|
|
"poor_count": poor_count_all,
|
|
|
|
|
|
"avg_delta_e_gray": avg_delta_e_gray,
|
|
|
|
|
|
"avg_delta_e_colorchecker": avg_delta_e_colorchecker,
|
|
|
|
|
|
"avg_delta_e_saturated": avg_delta_e_saturated,
|
|
|
|
|
|
"target_gamma": target_gamma,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
self.results.set_test_item_result("accuracy", accuracy_data)
|
|
|
|
|
|
|
|
|
|
|
|
# ========== 绘制图表 ==========
|
|
|
|
|
|
self.plot_accuracy(accuracy_data, test_type)
|
2026-05-19 11:50:53 +08:00
|
|
|
|
self._save_chart_snapshot(test_type, "accuracy", (accuracy_data, test_type))
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("色准测试完成", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def on_test_completed(self: "PQAutomationApp"):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""测试完成后的UI更新"""
|
|
|
|
|
|
self.testing = False
|
|
|
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.stop_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.save_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.clear_config_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.status_var.set("测试完成")
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("测试完成", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 恢复配置项按钮
|
|
|
|
|
|
if hasattr(self, "config_panel_frame"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.config_panel_frame.btn.configure(state="normal")
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
# 启用色域参考标准下拉框
|
|
|
|
|
|
try:
|
|
|
|
|
|
test_type = self.config.current_test_type
|
|
|
|
|
|
|
|
|
|
|
|
if test_type == "screen_module" and hasattr(self, "screen_gamut_combo"):
|
|
|
|
|
|
self.screen_gamut_combo.configure(state="readonly")
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("屏模组色域参考标准已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
elif test_type == "sdr_movie" and hasattr(self, "sdr_gamut_combo"):
|
|
|
|
|
|
self.sdr_gamut_combo.configure(state="readonly")
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("SDR 色域参考标准已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
elif test_type == "hdr_movie" and hasattr(self, "hdr_gamut_combo"):
|
|
|
|
|
|
self.hdr_gamut_combo.configure(state="readonly")
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("HDR 色域参考标准已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"启用色域参考标准失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取当前测试类型和选中的测试项
|
|
|
|
|
|
selected_items = self.get_selected_test_items()
|
|
|
|
|
|
test_type = self.config.current_test_type
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# ==================== 启用单步调试按钮 ====================
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if hasattr(self, "debug_panel"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 屏模组:启用 Gamma 和 RGB 单步调试
|
|
|
|
|
|
if test_type == "screen_module":
|
|
|
|
|
|
if "gamma" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if gray_data:
|
|
|
|
|
|
self.debug_panel.enable_debug(
|
|
|
|
|
|
"screen_module", "gamma", gray_data
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 启用 RGB 单步调试(色域测试完成后)
|
|
|
|
|
|
if "gamut" in selected_items:
|
|
|
|
|
|
rgb_data = self.results.get_intermediate_data("gamut", "rgb")
|
|
|
|
|
|
if rgb_data:
|
|
|
|
|
|
self.debug_panel.enable_debug(
|
|
|
|
|
|
"screen_module", "rgb", rgb_data
|
|
|
|
|
|
)
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# 启用单步调试按钮
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if hasattr(self, "screen_debug_btn"):
|
|
|
|
|
|
self.screen_debug_btn.config(state=tk.NORMAL)
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("屏模组单步调试按钮已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# SDR:启用 Gamma、色准和 RGB 单步调试
|
|
|
|
|
|
elif test_type == "sdr_movie":
|
|
|
|
|
|
if "gamma" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if gray_data:
|
|
|
|
|
|
self.debug_panel.enable_debug(
|
|
|
|
|
|
"sdr_movie", "gamma", gray_data
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if "accuracy" in selected_items:
|
|
|
|
|
|
accuracy_data = self.results.get_intermediate_data(
|
|
|
|
|
|
"accuracy", "measured"
|
|
|
|
|
|
)
|
|
|
|
|
|
if accuracy_data:
|
|
|
|
|
|
self.debug_panel.enable_debug(
|
|
|
|
|
|
"sdr_movie", "accuracy", accuracy_data
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 启用 RGB 单步调试(色域测试完成后)
|
|
|
|
|
|
if "gamut" in selected_items:
|
|
|
|
|
|
rgb_data = self.results.get_intermediate_data("gamut", "rgb")
|
|
|
|
|
|
if rgb_data:
|
|
|
|
|
|
self.debug_panel.enable_debug("sdr_movie", "rgb", rgb_data)
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# 启用单步调试按钮
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if hasattr(self, "sdr_debug_btn"):
|
|
|
|
|
|
self.sdr_debug_btn.config(state=tk.NORMAL)
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("SDR 单步调试按钮已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# HDR:启用 EOTF、色准和 RGB 单步调试
|
|
|
|
|
|
elif test_type == "hdr_movie":
|
|
|
|
|
|
if "eotf" in selected_items:
|
|
|
|
|
|
gray_data = self.results.get_intermediate_data("shared", "gray")
|
|
|
|
|
|
if gray_data:
|
|
|
|
|
|
self.debug_panel.enable_debug(
|
|
|
|
|
|
"hdr_movie", "eotf", gray_data
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if "accuracy" in selected_items:
|
|
|
|
|
|
accuracy_data = self.results.get_intermediate_data(
|
|
|
|
|
|
"accuracy", "measured"
|
|
|
|
|
|
)
|
|
|
|
|
|
if accuracy_data:
|
|
|
|
|
|
self.debug_panel.enable_debug(
|
|
|
|
|
|
"hdr_movie", "accuracy", accuracy_data
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
# 启用 RGB 单步调试(色域测试完成后)
|
|
|
|
|
|
if "gamut" in selected_items:
|
|
|
|
|
|
rgb_data = self.results.get_intermediate_data("gamut", "rgb")
|
|
|
|
|
|
if rgb_data:
|
|
|
|
|
|
self.debug_panel.enable_debug("hdr_movie", "rgb", rgb_data)
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
# 启用单步调试按钮
|
2026-04-20 11:13:57 +08:00
|
|
|
|
if hasattr(self, "hdr_debug_btn"):
|
|
|
|
|
|
self.hdr_debug_btn.config(state=tk.NORMAL)
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("HDR 单步调试按钮已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"启用单步调试失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
# ==================== 显示色度/色域重新计算按钮 ====================
|
|
|
|
|
|
if "cct" in selected_items:
|
|
|
|
|
|
try:
|
|
|
|
|
|
if test_type == "screen_module" and hasattr(self, "recalc_cct_btn"):
|
|
|
|
|
|
self.recalc_cct_btn.grid()
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("屏模组色度参数调整按钮已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
elif test_type == "sdr_movie" and hasattr(self, "sdr_recalc_cct_btn"):
|
|
|
|
|
|
self.sdr_recalc_cct_btn.grid()
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("SDR 色度参数调整按钮已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
elif test_type == "hdr_movie" and hasattr(self, "hdr_recalc_cct_btn"):
|
|
|
|
|
|
self.hdr_recalc_cct_btn.grid()
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("HDR 色度参数调整按钮已启用", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
except Exception as e:
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log(f"显示色度重新计算按钮失败: {str(e)}", level="error")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
|
|
|
|
|
|
messagebox.showinfo("完成", "测试已完成!")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def on_custom_template_test_completed(self: "PQAutomationApp"):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""客户模板测试完成后的UI更新"""
|
|
|
|
|
|
self.testing = False
|
|
|
|
|
|
self.set_custom_result_table_locked(False)
|
|
|
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.stop_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.save_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.clear_config_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.custom_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.status_var.set("客户模板测试完成")
|
|
|
|
|
|
|
|
|
|
|
|
if hasattr(self, "config_panel_frame"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.config_panel_frame.btn.configure(state="normal")
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
2026-04-21 15:31:48 +08:00
|
|
|
|
self.log_gui.log("客户模板测试完成", level="success")
|
2026-04-20 11:13:57 +08:00
|
|
|
|
messagebox.showinfo("完成", "客户模板测试已完成!")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def get_current_test_result(self: "PQAutomationApp"):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""获取当前测试结果"""
|
|
|
|
|
|
test_type = self.test_type_var.get()
|
|
|
|
|
|
test_items = self.get_selected_test_items()
|
|
|
|
|
|
|
|
|
|
|
|
# 构建测试结果字典
|
|
|
|
|
|
result = {
|
|
|
|
|
|
"test_type": test_type,
|
|
|
|
|
|
"test_type_name": self.get_test_type_name(test_type),
|
|
|
|
|
|
"test_items": test_items,
|
|
|
|
|
|
"test_items_names": self.config.get_test_item_chinese_names(test_items),
|
|
|
|
|
|
"timestamp": datetime.datetime.now(),
|
|
|
|
|
|
"status": "完成",
|
|
|
|
|
|
"results": {},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# 根据测试项目收集结果数据
|
|
|
|
|
|
for item in test_items:
|
|
|
|
|
|
if item == "gamut" and hasattr(self, "gamut_results"):
|
|
|
|
|
|
result["results"]["gamut"] = getattr(self, "gamut_results", {})
|
|
|
|
|
|
elif item in ["gamma", "eotf"] and hasattr(self, "gamma_results"):
|
|
|
|
|
|
result["results"][item] = getattr(self, "gamma_results", {})
|
|
|
|
|
|
elif item == "cct" and hasattr(self, "cct_results"):
|
|
|
|
|
|
result["results"]["cct"] = getattr(self, "cct_results", {})
|
|
|
|
|
|
elif item == "contrast" and hasattr(self, "contrast_results"):
|
|
|
|
|
|
result["results"]["contrast"] = getattr(self, "contrast_results", {})
|
|
|
|
|
|
elif item == "accuracy" and hasattr(self, "accuracy_results"):
|
|
|
|
|
|
result["results"]["accuracy"] = getattr(self, "accuracy_results", {})
|
|
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
def on_test_error(self: "PQAutomationApp"):
|
2026-04-20 11:13:57 +08:00
|
|
|
|
"""测试出错后的UI更新"""
|
|
|
|
|
|
self.testing = False
|
|
|
|
|
|
self.set_custom_result_table_locked(False)
|
|
|
|
|
|
self.start_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.stop_btn.config(state=tk.DISABLED)
|
|
|
|
|
|
self.clear_config_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
if hasattr(self, "custom_btn"):
|
|
|
|
|
|
self.custom_btn.config(state=tk.NORMAL)
|
|
|
|
|
|
self.status_var.set("测试出错")
|
|
|
|
|
|
|
|
|
|
|
|
# 恢复配置项按钮
|
|
|
|
|
|
if hasattr(self, "config_panel_frame"):
|
|
|
|
|
|
try:
|
|
|
|
|
|
self.config_panel_frame.btn.configure(state="normal")
|
|
|
|
|
|
except:
|
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
messagebox.showerror("错误", "测试过程中发生错误,请查看日志")
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-27 11:26:28 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class TestRunnerMixin:
|
|
|
|
|
|
"""由 tools/refactor_to_mixins.py 自动生成。
|
|
|
|
|
|
把本模块的自由函数挂到 PQAutomationApp 上,便于 F12 跳转与类型推断。
|
|
|
|
|
|
"""
|
|
|
|
|
|
new_pq_results = new_pq_results
|
|
|
|
|
|
run_test = run_test
|
|
|
|
|
|
run_screen_module_test = run_screen_module_test
|
|
|
|
|
|
run_custom_sdr_test = run_custom_sdr_test
|
|
|
|
|
|
run_sdr_movie_test = run_sdr_movie_test
|
|
|
|
|
|
run_hdr_movie_test = run_hdr_movie_test
|
|
|
|
|
|
send_fix_pattern = send_fix_pattern
|
|
|
|
|
|
test_custom_sdr = test_custom_sdr
|
|
|
|
|
|
test_gamut = test_gamut
|
|
|
|
|
|
test_gamma = test_gamma
|
|
|
|
|
|
test_eotf = test_eotf
|
|
|
|
|
|
test_cct = test_cct
|
|
|
|
|
|
test_contrast = test_contrast
|
|
|
|
|
|
test_color_accuracy = test_color_accuracy
|
|
|
|
|
|
on_test_completed = on_test_completed
|
|
|
|
|
|
on_custom_template_test_completed = on_custom_template_test_completed
|
|
|
|
|
|
get_current_test_result = get_current_test_result
|
|
|
|
|
|
on_test_error = on_test_error
|