1.1.0版本
This commit is contained in:
601
utils/pq/pq_config.py
Normal file
601
utils/pq/pq_config.py
Normal file
@@ -0,0 +1,601 @@
|
||||
# PQ自动化测试配置模块
|
||||
import json
|
||||
import copy
|
||||
|
||||
|
||||
class PQConfig:
|
||||
def __init__(self, current_test_type="screen_module", device_config={}, pattern={}):
|
||||
self.default_test_types = {
|
||||
"screen_module": {
|
||||
"name": "屏模组性能测试",
|
||||
"test_items": ["gamut", "gamma", "cct", "contrast"],
|
||||
"timing": "DMT 1920x 1080 @ 60Hz",
|
||||
"color_format": "RGB",
|
||||
"bpc": 8,
|
||||
"colorimetry": "sRGB",
|
||||
},
|
||||
"sdr_movie": {
|
||||
"name": "SDR Movie测试",
|
||||
"test_items": ["gamut", "gamma", "cct", "contrast", "accuracy"],
|
||||
"timing": "DMT 1920x 1080 @ 60Hz",
|
||||
"color_format": "RGB",
|
||||
"bpc": 8,
|
||||
"colorimetry": "sRGB",
|
||||
},
|
||||
"hdr_movie": {
|
||||
"name": "HDR Movie测试",
|
||||
"test_items": ["gamut", "eotf", "cct", "contrast", "accuracy"],
|
||||
"timing": "DMT 1920x 1080 @ 60Hz",
|
||||
"color_format": "RGB",
|
||||
"bpc": 8,
|
||||
"colorimetry": "sRGB",
|
||||
},
|
||||
}
|
||||
|
||||
# 设备连接配置
|
||||
self.device_config = {
|
||||
"ca_com": "COM1",
|
||||
"ucd_list": "0: UCD-323 [2128C209]",
|
||||
"ca_channel": "0",
|
||||
}
|
||||
|
||||
# ========== RGB Pattern 配置 ==========
|
||||
self.default_pattern_rgb = {
|
||||
"pattern_mode": "SolidColor",
|
||||
"measurement_bit_depth": 8,
|
||||
"measurement_max_value": 2,
|
||||
"pattern_params": [
|
||||
[255, 0, 0], # 红色
|
||||
[0, 255, 0], # 绿色
|
||||
[0, 0, 255], # 蓝色
|
||||
],
|
||||
}
|
||||
|
||||
# ========== 灰阶 Pattern 配置 ==========
|
||||
self.default_pattern_gray = {
|
||||
"pattern_mode": "SolidColor",
|
||||
"measurement_bit_depth": 8,
|
||||
"measurement_max_value": 10,
|
||||
"pattern_params": [
|
||||
[255, 255, 255], # 100% 白色
|
||||
[230, 230, 230], # 90%
|
||||
[205, 205, 205], # 80%
|
||||
[179, 179, 179], # 70%
|
||||
[154, 154, 154], # 60%
|
||||
[128, 128, 128], # 50%
|
||||
[102, 102, 102], # 40%
|
||||
[78, 78, 78], # 30%
|
||||
[52, 52, 52], # 20%
|
||||
[26, 26, 26], # 10%
|
||||
[0, 0, 0], # 0% 黑色
|
||||
],
|
||||
}
|
||||
|
||||
# ========== 色准 Pattern 配置(29色 - SDR 和 HDR 通用)==========
|
||||
self.default_pattern_accuracy = {
|
||||
"pattern_mode": "SolidColor",
|
||||
"measurement_bit_depth": 8,
|
||||
"measurement_max_value": 28, # 29个颜色,最大索引是28
|
||||
"pattern_params": [
|
||||
# ========== 灰阶 (5个) ==========
|
||||
[255, 255, 255], # 0: White
|
||||
[230, 230, 230], # 1: Gray 80
|
||||
[209, 209, 209], # 2: Gray 65
|
||||
[186, 186, 186], # 3: Gray 50
|
||||
[158, 158, 158], # 4: Gray 35
|
||||
# ========== ColorChecker 24色 (18个) ==========
|
||||
[115, 82, 66], # 5: Dark Skin
|
||||
[194, 150, 130], # 6: Light Skin
|
||||
[94, 122, 156], # 7: Blue Sky
|
||||
[89, 107, 66], # 8: Foliage
|
||||
[130, 128, 176], # 9: Blue Flower
|
||||
[99, 189, 168], # 10: Bluish Green
|
||||
[217, 120, 41], # 11: Orange
|
||||
[74, 92, 163], # 12: Purplish Blue
|
||||
[194, 84, 97], # 13: Moderate Red
|
||||
[92, 61, 107], # 14: Purple
|
||||
[158, 186, 64], # 15: Yellow Green
|
||||
[230, 161, 46], # 16: Orange Yellow
|
||||
[51, 61, 150], # 17: Blue (Legacy)
|
||||
[71, 148, 71], # 18: Green (Legacy)
|
||||
[176, 48, 59], # 19: Red (Legacy)
|
||||
[237, 199, 33], # 20: Yellow (Legacy)
|
||||
[186, 84, 145], # 21: Magenta (Legacy)
|
||||
[0, 133, 163], # 22: Cyan (Legacy)
|
||||
# ========== 100% 饱和色 (6个) ==========
|
||||
[255, 0, 0], # 23: 100% Red
|
||||
[0, 255, 0], # 24: 100% Green
|
||||
[0, 0, 255], # 25: 100% Blue
|
||||
[0, 255, 255], # 26: 100% Cyan
|
||||
[255, 0, 255], # 27: 100% Magenta
|
||||
[255, 255, 0], # 28: 100% Yellow
|
||||
],
|
||||
}
|
||||
|
||||
self.default_pattern_temp = {
|
||||
"pattern_mode": "SolidColor",
|
||||
"measurement_bit_depth": 8,
|
||||
"measurement_max_value": 146,
|
||||
"pattern_params": [
|
||||
[255, 255, 255],
|
||||
[242, 242, 242],
|
||||
[230, 230, 230],
|
||||
[217, 217, 217],
|
||||
[204, 204, 204],
|
||||
[191, 191, 191],
|
||||
[179, 179, 179],
|
||||
[166, 166, 166],
|
||||
[153, 153, 153],
|
||||
[140, 140, 140],
|
||||
[128, 128, 128],
|
||||
[115, 115, 115],
|
||||
[102, 102, 102],
|
||||
[89, 89, 89],
|
||||
[77, 77, 77],
|
||||
[64, 64, 64],
|
||||
[51, 51, 51],
|
||||
[38, 38, 38],
|
||||
[26, 26, 26],
|
||||
[13, 13, 13],
|
||||
[0, 0, 0],
|
||||
|
||||
[255, 0, 0],
|
||||
[242, 0, 0],
|
||||
[230, 0, 0],
|
||||
[217, 0, 0],
|
||||
[204, 0, 0],
|
||||
[191, 0, 0],
|
||||
[179, 0, 0],
|
||||
[166, 0, 0],
|
||||
[153, 0, 0],
|
||||
[140, 0, 0],
|
||||
[128, 0, 0],
|
||||
[115, 0, 0],
|
||||
[102, 0, 0],
|
||||
[89, 0, 0],
|
||||
[77, 0, 0],
|
||||
[64, 0, 0],
|
||||
[51, 0, 0],
|
||||
[38, 0, 0],
|
||||
[26, 0, 0],
|
||||
[13, 0, 0],
|
||||
[0, 0, 0],
|
||||
|
||||
[0, 255, 0],
|
||||
[0, 242, 0],
|
||||
[0, 230, 0],
|
||||
[0, 217, 0],
|
||||
[0, 204, 0],
|
||||
[0, 191, 0],
|
||||
[0, 179, 0],
|
||||
[0, 166, 0],
|
||||
[0, 153, 0],
|
||||
[0, 140, 0],
|
||||
[0, 128, 0],
|
||||
[0, 115, 0],
|
||||
[0, 102, 0],
|
||||
[0, 89, 0],
|
||||
[0, 77, 0],
|
||||
[0, 64, 0],
|
||||
[0, 51, 0],
|
||||
[0, 38, 0],
|
||||
[0, 26, 0],
|
||||
[0, 13, 0],
|
||||
[0, 0, 0],
|
||||
|
||||
[0, 0, 255],
|
||||
[0, 0, 242],
|
||||
[0, 0, 230],
|
||||
[0, 0, 217],
|
||||
[0, 0, 204],
|
||||
[0, 0, 191],
|
||||
[0, 0, 179],
|
||||
[0, 0, 166],
|
||||
[0, 0, 153],
|
||||
[0, 0, 140],
|
||||
[0, 0, 128],
|
||||
[0, 0, 115],
|
||||
[0, 0, 102],
|
||||
[0, 0, 89],
|
||||
[0, 0, 77],
|
||||
[0, 0, 64],
|
||||
[0, 0, 51],
|
||||
[0, 0, 38],
|
||||
[0, 0, 26],
|
||||
[0, 0, 13],
|
||||
[0, 0, 0],
|
||||
|
||||
[255, 255, 0],
|
||||
[242, 242, 0],
|
||||
[230, 230, 0],
|
||||
[217, 217, 0],
|
||||
[204, 204, 0],
|
||||
[191, 191, 0],
|
||||
[179, 179, 0],
|
||||
[166, 166, 0],
|
||||
[153, 153, 0],
|
||||
[140, 140, 0],
|
||||
[128, 128, 0],
|
||||
[115, 115, 0],
|
||||
[102, 102, 0],
|
||||
[89, 89, 0],
|
||||
[77, 77, 0],
|
||||
[64, 64, 0],
|
||||
[51, 51, 0],
|
||||
[38, 38, 0],
|
||||
[26, 26, 0],
|
||||
[13, 13, 0],
|
||||
[0, 0, 0],
|
||||
|
||||
[0, 255, 255],
|
||||
[0, 242, 242],
|
||||
[0, 230, 230],
|
||||
[0, 217, 217],
|
||||
[0, 204, 204],
|
||||
[0, 191, 191],
|
||||
[0, 179, 179],
|
||||
[0, 166, 166],
|
||||
[0, 153, 153],
|
||||
[0, 140, 140],
|
||||
[0, 128, 128],
|
||||
[0, 115, 115],
|
||||
[0, 102, 102],
|
||||
[0, 89, 89],
|
||||
[0, 77, 77],
|
||||
[0, 64, 64],
|
||||
[0, 51, 51],
|
||||
[0, 38, 38],
|
||||
[0, 26, 26],
|
||||
[0, 13, 13],
|
||||
[0, 0, 0],
|
||||
|
||||
[255, 0, 255],
|
||||
[242, 0, 242],
|
||||
[230, 0, 230],
|
||||
[217, 0, 217],
|
||||
[204, 0, 204],
|
||||
[191, 0, 191],
|
||||
[179, 0, 179],
|
||||
[166, 0, 166],
|
||||
[153, 0, 153],
|
||||
[140, 0, 140],
|
||||
[128, 0, 128],
|
||||
[115, 0, 115],
|
||||
[102, 0, 102],
|
||||
[89, 0, 89],
|
||||
[77, 0, 77],
|
||||
[64, 0, 64],
|
||||
[51, 0, 51],
|
||||
[38, 0, 38],
|
||||
[26, 0, 26],
|
||||
[13, 0, 13],
|
||||
[0, 0, 0],
|
||||
]
|
||||
}
|
||||
|
||||
# 自定义图案
|
||||
self.custom_pattern = {
|
||||
"pattern_mode": "SolidColor",
|
||||
"measurement_bit_depth": 8,
|
||||
"measurement_max_value": 0,
|
||||
"pattern_params": [],
|
||||
}
|
||||
|
||||
self.current_test_types = self.default_test_types
|
||||
self.current_test_type = current_test_type
|
||||
self.current_pattern = self.default_pattern_rgb
|
||||
|
||||
# ========== 获取临时配置(用于 Full/Limited 转换)==========
|
||||
def get_temp_config_with_converted_params(self, mode, converted_params):
|
||||
"""
|
||||
创建一个临时配置对象,包含转换后的 pattern 参数
|
||||
|
||||
Args:
|
||||
mode: "rgb" | "gray" | "accuracy"
|
||||
converted_params: 转换后的参数列表(Full 或 Limited Range)
|
||||
|
||||
Returns:
|
||||
PQConfig: 临时配置对象(深拷贝,不影响原始配置)
|
||||
"""
|
||||
# 1. 深拷贝整个配置对象
|
||||
temp_config = copy.deepcopy(self)
|
||||
|
||||
# 2. 设置正确的 pattern 模式
|
||||
if mode == "rgb":
|
||||
temp_config.current_pattern = copy.deepcopy(self.default_pattern_rgb)
|
||||
elif mode == "gray":
|
||||
temp_config.current_pattern = copy.deepcopy(self.default_pattern_gray)
|
||||
elif mode == "accuracy":
|
||||
temp_config.current_pattern = copy.deepcopy(self.default_pattern_accuracy)
|
||||
|
||||
# 3. 替换为转换后的参数
|
||||
temp_config.current_pattern["pattern_params"] = converted_params
|
||||
|
||||
return temp_config
|
||||
|
||||
def to_dict(self):
|
||||
"""将配置转换为字典格式"""
|
||||
return {
|
||||
"current_test_type": self.current_test_type,
|
||||
"test_types": self.current_test_types,
|
||||
"device_config": self.device_config,
|
||||
"default_pattern_rgb": self.default_pattern_rgb,
|
||||
"default_pattern_gray": self.default_pattern_gray,
|
||||
"default_pattern_accuracy": self.default_pattern_accuracy,
|
||||
"custom_pattern": self.custom_pattern,
|
||||
}
|
||||
|
||||
def from_dict(self, config_dict):
|
||||
"""从字典加载配置"""
|
||||
self.current_test_type = config_dict.get("current_test_type", "screen_module")
|
||||
self.current_test_types = config_dict.get("test_types", self.current_test_types)
|
||||
self.device_config = config_dict.get("device_config", self.device_config)
|
||||
|
||||
self.default_pattern_rgb = config_dict.get(
|
||||
"default_pattern_rgb", self.default_pattern_rgb
|
||||
)
|
||||
self.default_pattern_gray = config_dict.get(
|
||||
"default_pattern_gray", self.default_pattern_gray
|
||||
)
|
||||
|
||||
# ========== ✅ 强制使用新的 29色配置 ==========
|
||||
loaded_accuracy = config_dict.get("default_pattern_accuracy", None)
|
||||
|
||||
# 检查加载的配置是否是旧的 10色
|
||||
if loaded_accuracy and len(loaded_accuracy.get("pattern_params", [])) != 29:
|
||||
print(
|
||||
f"⚠️ 检测到旧的配置({len(loaded_accuracy.get('pattern_params', []))}色),强制使用新的 29色配置"
|
||||
)
|
||||
# 使用 __init__ 中定义的新配置
|
||||
self.default_pattern_accuracy = self.default_pattern_accuracy
|
||||
else:
|
||||
self.default_pattern_accuracy = config_dict.get(
|
||||
"default_pattern_accuracy", self.default_pattern_accuracy
|
||||
)
|
||||
# ==========================================
|
||||
|
||||
self.custom_pattern = config_dict.get("custom_pattern", self.custom_pattern)
|
||||
|
||||
def save_to_file(self, filename):
|
||||
"""将配置保存到文件"""
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
json.dump(self.to_dict(), f, indent=4, ensure_ascii=False)
|
||||
|
||||
def set_current_test_type(self, test_type):
|
||||
"""设置当前测试类型"""
|
||||
if test_type in self.current_test_types:
|
||||
self.current_test_type = test_type
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_current_test_items(self, test_items):
|
||||
"""设置当前测试类型的测试项"""
|
||||
if self.current_test_type in self.current_test_types:
|
||||
self.current_test_types[self.current_test_type]["test_items"] = test_items
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_current_timing(self, timing):
|
||||
if self.current_test_type in self.current_test_types:
|
||||
self.current_test_types[self.current_test_type]["timing"] = timing
|
||||
return True
|
||||
return False
|
||||
|
||||
def set_device_config(self, ca_com, ucd_list, ca_channel):
|
||||
"""设置设备连接配置"""
|
||||
self.device_config["ca_com"] = ca_com
|
||||
self.device_config["ucd_list"] = ucd_list
|
||||
self.device_config["ca_channel"] = ca_channel
|
||||
return True
|
||||
|
||||
def set_current_pattern(self, mode):
|
||||
"""设置当前模式的测试图案"""
|
||||
if mode == "rgb":
|
||||
self.current_pattern = self.default_pattern_rgb
|
||||
elif mode == "gray":
|
||||
self.current_pattern = self.default_pattern_gray
|
||||
elif mode == "accuracy": # ✅ 色准模式(SDR 和 HDR 通用 29色)
|
||||
self.current_pattern = self.default_pattern_accuracy
|
||||
elif mode == "custom":
|
||||
# self.current_pattern = self.custom_pattern
|
||||
self.current_pattern = self.default_pattern_temp
|
||||
else:
|
||||
return False
|
||||
|
||||
# 确保 measurement_max_value 是整数
|
||||
if "measurement_max_value" in self.current_pattern:
|
||||
value = self.current_pattern["measurement_max_value"]
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
self.current_pattern["measurement_max_value"] = int(value)
|
||||
except ValueError:
|
||||
self.current_pattern["measurement_max_value"] = (
|
||||
len(self.current_pattern["pattern_params"]) - 1
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def set_custom_pattern(self, pattern_mode, pattern_params):
|
||||
"""设置自定义模式的测试项"""
|
||||
self.custom_pattern["pattern_mode"] = pattern_mode
|
||||
self.custom_pattern["pattern_params"] = pattern_params
|
||||
self.custom_pattern["measurement_max_value"] = len(pattern_params) - 1
|
||||
return True
|
||||
|
||||
# ========== ✅ 获取 29色名称列表 ==========
|
||||
def get_accuracy_color_names(self):
|
||||
"""
|
||||
获取色准测试的 29个颜色名称(SDR 和 HDR 通用)
|
||||
|
||||
Returns:
|
||||
list: 29个颜色名称
|
||||
"""
|
||||
return [
|
||||
# 灰阶 (5个)
|
||||
"White",
|
||||
"Gray 80",
|
||||
"Gray 65",
|
||||
"Gray 50",
|
||||
"Gray 35",
|
||||
# ColorChecker 24色 (18个)
|
||||
"Dark Skin",
|
||||
"Light Skin",
|
||||
"Blue Sky",
|
||||
"Foliage",
|
||||
"Blue Flower",
|
||||
"Bluish Green",
|
||||
"Orange",
|
||||
"Purplish Blue",
|
||||
"Moderate Red",
|
||||
"Purple",
|
||||
"Yellow Green",
|
||||
"Orange Yellow",
|
||||
"Blue (Legacy)",
|
||||
"Green (Legacy)",
|
||||
"Red (Legacy)",
|
||||
"Yellow (Legacy)",
|
||||
"Magenta (Legacy)",
|
||||
"Cyan (Legacy)",
|
||||
# 100% 饱和色 (6个)
|
||||
"100% Red",
|
||||
"100% Green",
|
||||
"100% Blue",
|
||||
"100% Cyan",
|
||||
"100% Magenta",
|
||||
"100% Yellow",
|
||||
]
|
||||
|
||||
# ========== ✅ 获取 29色的 RGB 值 ==========
|
||||
def get_accuracy_color_rgb(self):
|
||||
"""
|
||||
获取色准测试的 RGB 值(用于标准值计算)
|
||||
|
||||
Returns:
|
||||
list: [(name, r, g, b), ...]
|
||||
"""
|
||||
names = self.get_accuracy_color_names()
|
||||
rgb_values = self.default_pattern_accuracy["pattern_params"]
|
||||
|
||||
return [(name, r, g, b) for name, (r, g, b) in zip(names, rgb_values)]
|
||||
|
||||
def get_temp_pattern_names(self):
|
||||
"""获取客户模板测试(default_pattern_temp)的固定 pattern 名称列表"""
|
||||
percentages = list(range(100, -1, -5))
|
||||
color_prefixes = ["W", "R", "G", "B", "Y", "C", "M"]
|
||||
|
||||
names = []
|
||||
for prefix in color_prefixes:
|
||||
for value in percentages:
|
||||
names.append(f"{prefix} {value}%")
|
||||
|
||||
pattern_count = len(self.default_pattern_temp.get("pattern_params", []))
|
||||
|
||||
if pattern_count <= len(names):
|
||||
return names[:pattern_count]
|
||||
|
||||
# 兜底:如果后续扩展了 pattern 数量,补充通用名称,避免索引越界。
|
||||
for i in range(len(names), pattern_count):
|
||||
names.append(f"P {i + 1}")
|
||||
|
||||
return names
|
||||
|
||||
def get_test_item_chinese_names(self, test_items):
|
||||
"""获取测试项目的显示名称"""
|
||||
item_names = []
|
||||
for item in test_items:
|
||||
if item == "gamut":
|
||||
item_names.append("色域")
|
||||
elif item == "gamma":
|
||||
item_names.append("Gamma")
|
||||
elif item == "eotf":
|
||||
item_names.append("EOTF")
|
||||
elif item == "cct":
|
||||
item_names.append("色度一致性")
|
||||
elif item == "contrast":
|
||||
item_names.append("对比度")
|
||||
elif item == "accuracy":
|
||||
item_names.append("色准")
|
||||
else:
|
||||
item_names.append(item)
|
||||
return item_names
|
||||
|
||||
def get_current_config(self):
|
||||
"""返回当前测试类型相关的所有配置信息"""
|
||||
if self.current_test_type not in self.current_test_types:
|
||||
return {}
|
||||
|
||||
current_test = self.current_test_types[self.current_test_type]
|
||||
|
||||
config_info = {
|
||||
"test_type": self.current_test_type,
|
||||
"test_name": current_test.get("name", "未知测试"),
|
||||
"test_items": current_test.get("test_items", []),
|
||||
"test_items_chinese": self.get_test_item_chinese_names(
|
||||
current_test.get("test_items", [])
|
||||
),
|
||||
"timing": current_test.get("timing", "DMT 1920x 1080 @ 60Hz"),
|
||||
"color_format": current_test.get("color_format", "RGB"),
|
||||
"bpc": current_test.get("bpc", 8),
|
||||
"colorimetry": current_test.get("colorimetry", "sRGB"),
|
||||
}
|
||||
|
||||
return config_info
|
||||
|
||||
|
||||
# ========== ✅ 验证代码(测试完成后可删除)==========
|
||||
if __name__ == "__main__":
|
||||
print("=" * 60)
|
||||
print("验证 pq_config.py 配置")
|
||||
print("=" * 60)
|
||||
|
||||
config = PQConfig()
|
||||
|
||||
# 检查 default_pattern_accuracy
|
||||
pattern_count = len(config.default_pattern_accuracy["pattern_params"])
|
||||
max_value = config.default_pattern_accuracy["measurement_max_value"]
|
||||
|
||||
print(f"\ndefault_pattern_accuracy:")
|
||||
print(f" 图案数量: {pattern_count}")
|
||||
print(f" measurement_max_value: {max_value}")
|
||||
|
||||
if pattern_count == 29 and max_value == 28:
|
||||
print("\n✅ 配置正确(29色)")
|
||||
|
||||
# 显示前 5 个图案
|
||||
print("\n前5个图案:")
|
||||
for i in range(5):
|
||||
rgb = config.default_pattern_accuracy["pattern_params"][i]
|
||||
names = config.get_accuracy_color_names()
|
||||
print(f" [{i}] {names[i]:15s} RGB{rgb}")
|
||||
|
||||
# 显示后 5 个图案
|
||||
print("\n后5个图案:")
|
||||
for i in range(24, 29):
|
||||
rgb = config.default_pattern_accuracy["pattern_params"][i]
|
||||
names = config.get_accuracy_color_names()
|
||||
print(f" [{i}] {names[i]:15s} RGB{rgb}")
|
||||
|
||||
# 测试 set_current_pattern
|
||||
print("\n测试 set_current_pattern('accuracy'):")
|
||||
config.set_current_pattern("accuracy")
|
||||
current_count = len(config.current_pattern["pattern_params"])
|
||||
print(f" current_pattern 图案数量: {current_count}")
|
||||
|
||||
if current_count == 29:
|
||||
print(" ✅ set_current_pattern 工作正常")
|
||||
else:
|
||||
print(f" ❌ set_current_pattern 失败!只有 {current_count} 个图案")
|
||||
|
||||
else:
|
||||
print(f"\n❌ 配置错误!")
|
||||
print(f" 期望: 29 个图案, measurement_max_value=28")
|
||||
print(f" 实际: {pattern_count} 个图案, measurement_max_value={max_value}")
|
||||
|
||||
print("\n❌ 请检查 default_pattern_accuracy 定义!")
|
||||
print(" 应该包含:")
|
||||
print(" - 5个灰阶")
|
||||
print(" - 18个 ColorChecker 色块")
|
||||
print(" - 6个 100% 饱和色")
|
||||
print(" - 总计 29 个 RGB 数组")
|
||||
|
||||
print("=" * 60)
|
||||
469
utils/pq/pq_result.py
Normal file
469
utils/pq/pq_result.py
Normal file
@@ -0,0 +1,469 @@
|
||||
import json
|
||||
import os
|
||||
import datetime
|
||||
from typing import Dict, List, Any, Optional
|
||||
from dataclasses import dataclass, asdict
|
||||
|
||||
|
||||
@dataclass
|
||||
class TestItemResult:
|
||||
"""单个测试项的结果数据"""
|
||||
|
||||
item_name: str # 测试项名称 (如 "gamut", "gamma", "cct" 等)
|
||||
item_display_name: str # 测试项显示名称 (如 "色域", "Gamma", "色温一致性" 等)
|
||||
status: str # 测试状态: "completed", "failed", "skipped"
|
||||
start_time: Optional[datetime.datetime] = None
|
||||
end_time: Optional[datetime.datetime] = None
|
||||
intermediate_data: Dict[str, Any] = None # 中间过程数据
|
||||
final_result: Dict[str, Any] = None # 最终测试结果
|
||||
error_message: Optional[str] = None # 错误信息
|
||||
|
||||
def __post_init__(self):
|
||||
if self.intermediate_data is None:
|
||||
self.intermediate_data = {}
|
||||
if self.final_result is None:
|
||||
self.final_result = {}
|
||||
|
||||
def to_dict(self):
|
||||
"""转换为字典格式"""
|
||||
data = asdict(self)
|
||||
if self.start_time:
|
||||
data["start_time"] = self.start_time.isoformat()
|
||||
if self.end_time:
|
||||
data["end_time"] = self.end_time.isoformat()
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data):
|
||||
"""从字典创建对象"""
|
||||
if "start_time" in data and data["start_time"]:
|
||||
data["start_time"] = datetime.datetime.fromisoformat(data["start_time"])
|
||||
if "end_time" in data and data["end_time"]:
|
||||
data["end_time"] = datetime.datetime.fromisoformat(data["end_time"])
|
||||
return cls(**data)
|
||||
|
||||
|
||||
class PQResult:
|
||||
"""PQ测试结果管理类"""
|
||||
|
||||
def __init__(
|
||||
self, test_type: str = "", test_name: str = "", output_dir: str = "results"
|
||||
):
|
||||
"""
|
||||
初始化PQ测试结果管理器
|
||||
|
||||
Args:
|
||||
test_type: 测试类型 ("screen_module", "sdr_movie", "hdr_movie")
|
||||
test_name: 测试名称显示
|
||||
output_dir: 结果输出目录
|
||||
"""
|
||||
self.test_id = self._generate_test_id()
|
||||
self.test_type = test_type
|
||||
self.test_name = test_name
|
||||
self.output_dir = output_dir
|
||||
|
||||
# 测试基本信息
|
||||
self.start_time = datetime.datetime.now()
|
||||
self.end_time = None
|
||||
self.status = "running" # "running", "completed", "failed", "stopped"
|
||||
|
||||
# 测试配置信息
|
||||
self.test_config = {}
|
||||
|
||||
# 测试项结果
|
||||
self.test_items: Dict[str, TestItemResult] = {}
|
||||
|
||||
# 全局测试数据
|
||||
self.global_data = {
|
||||
"device_info": {},
|
||||
"environment_info": {},
|
||||
"measurement_settings": {},
|
||||
}
|
||||
|
||||
# 确保输出目录存在
|
||||
self._ensure_output_dir()
|
||||
|
||||
# =============================================================================
|
||||
# 存放当次测试的中间数据,方便调用
|
||||
# =============================================================================
|
||||
self.fix_pattern_rgb = None
|
||||
self.fix_pattern_gray = None
|
||||
|
||||
def _generate_test_id(self) -> str:
|
||||
"""生成唯一的测试ID"""
|
||||
return datetime.datetime.now().strftime("PQ_%Y%m%d_%H%M%S_%f")
|
||||
|
||||
def _ensure_output_dir(self):
|
||||
pass
|
||||
|
||||
def set_test_config(self, config: Dict[str, Any]):
|
||||
"""设置测试配置信息"""
|
||||
self.test_config = config.copy()
|
||||
|
||||
def set_global_data(
|
||||
self,
|
||||
device_info: Dict = None,
|
||||
environment_info: Dict = None,
|
||||
measurement_settings: Dict = None,
|
||||
):
|
||||
"""设置全局测试数据"""
|
||||
if device_info:
|
||||
self.global_data["device_info"].update(device_info)
|
||||
if environment_info:
|
||||
self.global_data["environment_info"].update(environment_info)
|
||||
if measurement_settings:
|
||||
self.global_data["measurement_settings"].update(measurement_settings)
|
||||
|
||||
def add_test_item(self, item_name: str, item_display_name: str) -> TestItemResult:
|
||||
"""添加测试项"""
|
||||
test_item = TestItemResult(
|
||||
item_name=item_name, item_display_name=item_display_name, status="pending"
|
||||
)
|
||||
self.test_items[item_name] = test_item
|
||||
return test_item
|
||||
|
||||
def start_test_item(self, item_name: str):
|
||||
"""开始测试项"""
|
||||
if item_name in self.test_items:
|
||||
self.test_items[item_name].status = "running"
|
||||
self.test_items[item_name].start_time = datetime.datetime.now()
|
||||
|
||||
def add_intermediate_data(self, item_name: str, data_key: str, data_value: Any):
|
||||
"""添加测试项的中间过程数据"""
|
||||
if item_name in self.test_items:
|
||||
self.test_items[item_name].intermediate_data[data_key] = data_value
|
||||
if data_key == "rgb":
|
||||
self.fix_pattern_rgb = data_value
|
||||
if data_key == "gray":
|
||||
self.fix_pattern_gray = data_value
|
||||
|
||||
def get_intermediate_data(self, item_name: str, data_key: str) -> Any:
|
||||
"""
|
||||
获取测试项的中间过程数据
|
||||
|
||||
Args:
|
||||
item_name: 测试项名称 (如 "gamut", "gamma", "cct", "shared")
|
||||
data_key: 数据键名 (如 "rgb", "gray", "measurement_points")
|
||||
|
||||
Returns:
|
||||
对应的数据,如果不存在则返回 None
|
||||
|
||||
Examples:
|
||||
>>> pq_result.get_intermediate_data("gamut", "rgb")
|
||||
[[0.64, 0.33, 100.5, ...], ...]
|
||||
|
||||
>>> pq_result.get_intermediate_data("shared", "gray")
|
||||
[[0.31, 0.33, 50.2, ...], ...]
|
||||
"""
|
||||
# 方式1: 从 test_items 中获取
|
||||
if item_name in self.test_items:
|
||||
intermediate_data = self.test_items[item_name].intermediate_data
|
||||
if data_key in intermediate_data:
|
||||
return intermediate_data[data_key]
|
||||
|
||||
# 方式2: 从快捷属性中获取(用于 "shared" 数据)
|
||||
if item_name == "shared":
|
||||
if data_key == "rgb" and self.fix_pattern_rgb is not None:
|
||||
return self.fix_pattern_rgb
|
||||
if data_key == "gray" and self.fix_pattern_gray is not None:
|
||||
return self.fix_pattern_gray
|
||||
|
||||
# 未找到数据
|
||||
return None
|
||||
|
||||
def has_intermediate_data(self, item_name: str, data_key: str) -> bool:
|
||||
"""
|
||||
检查是否存在指定的中间数据
|
||||
|
||||
Args:
|
||||
item_name: 测试项名称
|
||||
data_key: 数据键名
|
||||
|
||||
Returns:
|
||||
bool: 数据是否存在
|
||||
"""
|
||||
return self.get_intermediate_data(item_name, data_key) is not None
|
||||
|
||||
def get_all_intermediate_data(self, item_name: str) -> Dict[str, Any]:
|
||||
"""
|
||||
获取测试项的所有中间数据
|
||||
|
||||
Args:
|
||||
item_name: 测试项名称
|
||||
|
||||
Returns:
|
||||
包含所有中间数据的字典,如果测试项不存在则返回空字典
|
||||
"""
|
||||
if item_name in self.test_items:
|
||||
return self.test_items[item_name].intermediate_data.copy()
|
||||
|
||||
if item_name == "shared":
|
||||
return {
|
||||
"rgb": self.fix_pattern_rgb,
|
||||
"gray": self.fix_pattern_gray,
|
||||
}
|
||||
|
||||
return {}
|
||||
|
||||
def clear_intermediate_data(self, item_name: str = None):
|
||||
"""
|
||||
清除中间数据
|
||||
|
||||
Args:
|
||||
item_name: 测试项名称,如果为 None 则清除所有中间数据
|
||||
"""
|
||||
if item_name is None:
|
||||
# 清除所有测试项的中间数据
|
||||
for item in self.test_items.values():
|
||||
item.intermediate_data.clear()
|
||||
# 清除快捷属性
|
||||
self.fix_pattern_rgb = None
|
||||
self.fix_pattern_gray = None
|
||||
elif item_name in self.test_items:
|
||||
# 清除指定测试项的中间数据
|
||||
self.test_items[item_name].intermediate_data.clear()
|
||||
|
||||
def set_test_item_result(
|
||||
self,
|
||||
item_name: str,
|
||||
result_data: Dict[str, Any],
|
||||
status: str = "completed",
|
||||
error_message: str = None,
|
||||
):
|
||||
"""设置测试项的最终结果"""
|
||||
if item_name in self.test_items:
|
||||
self.test_items[item_name].final_result = result_data
|
||||
self.test_items[item_name].status = status
|
||||
self.test_items[item_name].end_time = datetime.datetime.now()
|
||||
if error_message:
|
||||
self.test_items[item_name].error_message = error_message
|
||||
|
||||
def complete_test(self, status: str = "completed"):
|
||||
"""完成整个测试"""
|
||||
self.end_time = datetime.datetime.now()
|
||||
self.status = status
|
||||
|
||||
def get_test_summary(self) -> Dict[str, Any]:
|
||||
"""获取测试摘要信息"""
|
||||
completed_items = len(
|
||||
[item for item in self.test_items.values() if item.status == "completed"]
|
||||
)
|
||||
failed_items = len(
|
||||
[item for item in self.test_items.values() if item.status == "failed"]
|
||||
)
|
||||
total_items = len(self.test_items)
|
||||
|
||||
duration = None
|
||||
if self.start_time and self.end_time:
|
||||
duration = (self.end_time - self.start_time).total_seconds()
|
||||
|
||||
return {
|
||||
"test_id": self.test_id,
|
||||
"test_type": self.test_type,
|
||||
"test_name": self.test_name,
|
||||
"status": self.status,
|
||||
"start_time": self.start_time.isoformat() if self.start_time else None,
|
||||
"end_time": self.end_time.isoformat() if self.end_time else None,
|
||||
"duration_seconds": duration,
|
||||
"total_items": total_items,
|
||||
"completed_items": completed_items,
|
||||
"failed_items": failed_items,
|
||||
}
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""转换为完整的字典格式"""
|
||||
return {
|
||||
"test_summary": self.get_test_summary(),
|
||||
"test_config": self.test_config,
|
||||
"global_data": self.global_data,
|
||||
"test_items": {
|
||||
name: item.to_dict() for name, item in self.test_items.items()
|
||||
},
|
||||
"export_timestamp": datetime.datetime.now().isoformat(),
|
||||
"format_version": "1.0",
|
||||
}
|
||||
|
||||
def save_to_file(self, file_path: str) -> bool:
|
||||
return False
|
||||
|
||||
def save_to_json(self, filename: str = None) -> str:
|
||||
return ""
|
||||
|
||||
@classmethod
|
||||
def load_from_json(cls, file_path: str) -> "PQResult":
|
||||
"""从JSON文件加载测试结果"""
|
||||
with open(file_path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
# 创建PQResult实例
|
||||
test_summary = data.get("test_summary", {})
|
||||
pq_result = cls(
|
||||
test_type=test_summary.get("test_type", ""),
|
||||
test_name=test_summary.get("test_name", ""),
|
||||
)
|
||||
|
||||
# 恢复基本信息
|
||||
pq_result.test_id = test_summary.get("test_id", pq_result.test_id)
|
||||
pq_result.status = test_summary.get("status", "unknown")
|
||||
|
||||
if test_summary.get("start_time"):
|
||||
pq_result.start_time = datetime.datetime.fromisoformat(
|
||||
test_summary["start_time"]
|
||||
)
|
||||
if test_summary.get("end_time"):
|
||||
pq_result.end_time = datetime.datetime.fromisoformat(
|
||||
test_summary["end_time"]
|
||||
)
|
||||
|
||||
# 恢复配置和全局数据
|
||||
pq_result.test_config = data.get("test_config", {})
|
||||
pq_result.global_data = data.get("global_data", {})
|
||||
|
||||
# 恢复测试项
|
||||
test_items_data = data.get("test_items", {})
|
||||
for item_name, item_data in test_items_data.items():
|
||||
pq_result.test_items[item_name] = TestItemResult.from_dict(item_data)
|
||||
|
||||
return pq_result
|
||||
|
||||
def export_item_data(self, item_name: str, export_format: str = "json") -> str:
|
||||
"""
|
||||
导出单个测试项的数据
|
||||
|
||||
Args:
|
||||
item_name: 测试项名称
|
||||
export_format: 导出格式 ("json", "csv")
|
||||
|
||||
Returns:
|
||||
导出文件路径
|
||||
"""
|
||||
if item_name not in self.test_items:
|
||||
raise ValueError(f"测试项 {item_name} 不存在")
|
||||
|
||||
item = self.test_items[item_name]
|
||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
|
||||
if export_format == "json":
|
||||
filename = f"{self.test_id}_{item_name}_{timestamp}.json"
|
||||
if self.test_type:
|
||||
file_path = os.path.join(self.output_dir, self.test_type, filename)
|
||||
else:
|
||||
file_path = os.path.join(self.output_dir, filename)
|
||||
|
||||
with open(file_path, "w", encoding="utf-8") as f:
|
||||
json.dump(item.to_dict(), f, ensure_ascii=False, indent=2)
|
||||
|
||||
elif export_format == "csv":
|
||||
import csv
|
||||
|
||||
filename = f"{self.test_id}_{item_name}_{timestamp}.csv"
|
||||
if self.test_type:
|
||||
file_path = os.path.join(self.output_dir, self.test_type, filename)
|
||||
else:
|
||||
file_path = os.path.join(self.output_dir, filename)
|
||||
|
||||
with open(file_path, "w", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
|
||||
# 写入基本信息
|
||||
writer.writerow(["测试项", item.item_display_name])
|
||||
writer.writerow(["状态", item.status])
|
||||
writer.writerow(
|
||||
["开始时间", item.start_time.isoformat() if item.start_time else ""]
|
||||
)
|
||||
writer.writerow(
|
||||
["结束时间", item.end_time.isoformat() if item.end_time else ""]
|
||||
)
|
||||
writer.writerow([])
|
||||
|
||||
# 写入最终结果数据
|
||||
writer.writerow(["最终结果"])
|
||||
for key, value in item.final_result.items():
|
||||
writer.writerow([key, str(value)])
|
||||
|
||||
else:
|
||||
raise ValueError(f"不支持的导出格式: {export_format}")
|
||||
|
||||
return file_path
|
||||
|
||||
def get_progress_info(self) -> Dict[str, Any]:
|
||||
"""获取测试进度信息"""
|
||||
total_items = len(self.test_items)
|
||||
completed_items = len(
|
||||
[
|
||||
item
|
||||
for item in self.test_items.values()
|
||||
if item.status in ["completed", "failed"]
|
||||
]
|
||||
)
|
||||
running_items = len(
|
||||
[item for item in self.test_items.values() if item.status == "running"]
|
||||
)
|
||||
|
||||
progress_percentage = (
|
||||
(completed_items / total_items * 100) if total_items > 0 else 0
|
||||
)
|
||||
|
||||
return {
|
||||
"total_items": total_items,
|
||||
"completed_items": completed_items,
|
||||
"running_items": running_items,
|
||||
"pending_items": total_items - completed_items - running_items,
|
||||
"progress_percentage": progress_percentage,
|
||||
"current_status": self.status,
|
||||
}
|
||||
|
||||
|
||||
# 使用示例和工具函数
|
||||
def create_pq_result_from_config(config: Dict[str, Any]) -> PQResult:
|
||||
"""根据配置创建PQResult实例"""
|
||||
test_type = config.get("test_type", "")
|
||||
test_name = config.get("test_name", "")
|
||||
|
||||
pq_result = PQResult(test_type=test_type, test_name=test_name)
|
||||
pq_result.set_test_config(config)
|
||||
|
||||
# 添加测试项
|
||||
test_items = config.get("test_items", [])
|
||||
test_items_names = config.get("test_items_chinese", [])
|
||||
|
||||
for i, item in enumerate(test_items):
|
||||
display_name = test_items_names[i] if i < len(test_items_names) else item
|
||||
pq_result.add_test_item(item, display_name)
|
||||
|
||||
return pq_result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
# 测试代码
|
||||
print("PQResult类测试")
|
||||
|
||||
# 创建测试实例
|
||||
pq_result = PQResult("screen_module", "屏模组性能测试")
|
||||
|
||||
# 设置配置
|
||||
config = {
|
||||
"test_type": "screen_module",
|
||||
"test_name": "屏模组性能测试",
|
||||
"test_items": ["gamut", "gamma", "cct"],
|
||||
"test_items_chinese": ["色域", "Gamma", "色温一致性"],
|
||||
}
|
||||
pq_result.set_test_config(config)
|
||||
|
||||
# 添加测试项
|
||||
pq_result.add_test_item("gamut", "色域")
|
||||
pq_result.add_test_item("gamma", "Gamma")
|
||||
pq_result.add_test_item("cct", "色温一致性")
|
||||
|
||||
# 模拟测试过程
|
||||
pq_result.start_test_item("gamut")
|
||||
pq_result.add_intermediate_data(
|
||||
"gamut", "measurement_points", [[0.64, 0.33], [0.30, 0.60]]
|
||||
)
|
||||
pq_result.set_test_item_result("gamut", {"coverage": 95.2, "accuracy": 98.5})
|
||||
|
||||
# 完成测试
|
||||
pq_result.complete_test()
|
||||
|
||||
print(f"测试结果已保存到: {pq_result.save_to_json()}")
|
||||
print("测试摘要:", pq_result.get_test_summary())
|
||||
Reference in New Issue
Block a user