1.1.0版本

This commit is contained in:
xinzhu.yin
2026-04-16 16:51:05 +08:00
commit c157e774e5
333 changed files with 70759 additions and 0 deletions

601
utils/pq/pq_config.py Normal file
View 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
View 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())