import sys
import xml.etree.ElementTree as ET
import re
import os
import subprocess
from colorama import init, Fore, Style
init(autoreset=True)
class Color:
"""ANSI颜色代码"""
RED = '\033[91m'
YELLOW = '\033[93m'
GREEN = '\033[92m'
BLUE = '\033[94m'
CYAN = '\033[96m'
BOLD = '\033[1m'
UNDERLINE = '\033[4m'
RESET = '\033[0m'
def colorize(text, color):
"""为文本添加颜色"""
return f"{color}{text}{Color.RESET}"
def find_all_ccmatrix_nodes(root):
"""查找所有 Awb_TblCCMatrix 开头的节点"""
pattern = re.compile(r"Awb_TblCCMatrix_\w+_0")
nodes = []
for node in root.iter():
tag = node.tag.split('}')[-1] # 移除命名空间
if pattern.match(tag):
nodes.append((tag, node))
return nodes
def is_numeric(value):
"""检查是否是数值类型(整数或浮点数)"""
try:
float(value)
return True
except (ValueError, TypeError):
return False
def parse_multi_value(text):
"""解析包含多个数值的文本"""
if not text:
return []
return [float(x) for x in text.split() if is_numeric(x)]
def check_row_sum(values, tolerance=1e-3):
"""检查矩阵每行求和是否为1(允许一定误差)"""
if len(values) != 9:
return []
results = []
# 第一行: 0,1,2
row1_sum = sum(values[0:3])
results.append({
"row": 1,
"values": values[0:3],
"sum": row1_sum,
"valid": abs(row1_sum - 1) <= tolerance
})
# 第二行: 3,4,5
row2_sum = sum(values[3:6])
results.append({
"row": 2,
"values": values[3:6],
"sum": row2_sum,
"valid": abs(row2_sum - 1) <= tolerance
})
# 第三行: 6,7,8
row3_sum = sum(values[6:9])
results.append({
"row": 3,
"values": values[6:9],
"sum": row3_sum,
"valid": abs(row3_sum - 1) <= tolerance
})
return results
def get_warning_level(diff, yellow_threshold=0.05, red_threshold=0.1):
"""根据差异值确定警告级别"""
if diff > red_threshold:
return "red"
elif diff > yellow_threshold:
return "yellow"
return "none"
def compare_multi_values(val1, val2, path, yellow_threshold=0.05, red_threshold=0.1):
"""比较多个数值的变化"""
values1 = parse_multi_value(val1)
values2 = parse_multi_value(val2)
if not values1 or not values2 or len(values1) != len(values2):
# 无法进行元素级比较,返回整体比较
return compare_text_value(val1, val2, path, yellow_threshold, red_threshold), []
changes = []
warnings = []
max_diff = 0.0
for i, (v1, v2) in enumerate(zip(values1, values2)):
diff = v1 - v2
abs_diff = abs(diff)
max_diff = max(max_diff, abs_diff)
warning_level = get_warning_level(abs_diff, yellow_threshold, red_threshold)
change = {
"path": f"{path}[{i}]",
"change": f"{v1:.6f} → {v2:.6f}",
"type": "数值",
"diff": abs_diff,
"delta": diff,
"warning_level": warning_level
}
changes.append(change)
if warning_level != "none" and "ctemp" not in path.lower():
level_text = colorize("红色警告", Color.RED) if warning_level == "red" else colorize("黄色警告", Color.YELLOW)
warnings.append(f"{level_text}: {path}[{i}] 变化 ({v1:.6f} → {v2:.6f}, 差值={abs_diff:.6f})")
# 添加汇总行
summary_warning = get_warning_level(max_diff, yellow_threshold, red_threshold)
changes.append({
"path": f"{path}/汇总",
"change": f"最大差值: {max_diff:.6f}",
"type": "汇总",
"diff": max_diff,
"warning_level": summary_warning
})
if summary_warning != "none" and "ctemp" not in path.lower():
level_text = colorize("红色警告", Color.RED) if summary_warning == "red" else colorize("黄色警告", Color.YELLOW)
warnings.append(f"{level_text}: {path} 最大变化 (最大差值={max_diff:.6f})")
return changes, warnings
def compare_text_value(val1, val2, path, yellow_threshold=0.05, red_threshold=0.1):
"""比较单个文本值"""
changes = []
warnings = []
# 数值类型的变化检查
if is_numeric(val1) and is_numeric(val2):
v1 = float(val1)
v2 = float(val2)
diff = abs(v1 - v2)
delta = v1 - v2
change_type = "数值"
warning_level = get_warning_level(diff, yellow_threshold, red_threshold)
if warning_level != "none" and "ctemp" not in path.lower():
level_text = colorize("红色警告", Color.RED) if warning_level == "red" else colorize("黄色警告", Color.YELLOW)
warnings.append(f"{level_text}: {path} 变化 ({v1:.6f} → {v2:.6f}, 差值={diff:.6f})")
else:
diff = 0
delta = 0
change_type = "非数值"
warning_level = "none"
changes.append({
"path": path,
"change": f"{val1} → {val2}",
"type": change_type,
"diff": diff,
"delta": delta,
"warning_level": warning_level
})
return changes, warnings
def process_ccmatrix_node(node, path):
"""处理单个CCMatrix节点,检查矩阵行和"""
results = []
for child in node.iter():
if child.text and "ccmatrix" in child.tag.lower():
values = parse_multi_value(child.text)
if len(values) == 9:
row_checks = check_row_sum(values)
for check in row_checks:
status = "✅" if check["valid"] else "❌"
result = {
"path": f"{path}/{child.tag}$row{check['row']}",
"status": status,
"values": check["values"],
"sum": check["sum"],
"valid": check["valid"]
}
results.append(result)
return results
def compare_nodes(n1, n2, path="", yellow_threshold=0.05, red_threshold=0.1):
"""比较两个节点的差异,忽略ctemp的警告但输出差异"""
changes = []
warnings = []
row_checks = []
# 处理第一个节点的矩阵行和检查
row_checks.extend(process_ccmatrix_node(n1, f"{path}/基准值"))
# 处理第二个节点的矩阵行和检查
row_checks.extend(process_ccmatrix_node(n2, f"{path}/新值"))
# 比较属性
for attr in set(n1.attrib.keys()) | set(n2.attrib.keys()):
val1 = n1.attrib.get(attr, "")
val2 = n2.attrib.get(attr, "")
if val1 != val2:
# 数值类型的变化检查
if is_numeric(val1) and is_numeric(val2):
v1 = float(val1)
v2 = float(val2)
diff = abs(v1 - v2)
delta = v1 - v2
change_type = "数值"
warning_level = get_warning_level(diff, yellow_threshold, red_threshold)
if warning_level != "none" and "ctemp" not in attr.lower():
level_text = colorize("红色警告", Color.RED) if warning_level == "red" else colorize("黄色警告", Color.YELLOW)
warnings.append(f"{level_text}: {path}@{attr} 变化 ({v1:.6f} → {v2:.6f}, 差值={diff:.6f})")
else:
diff = 0
delta = 0
change_type = "非数值"
warning_level = "none"
changes.append({
"path": f"{path}@{attr}",
"change": f"{val1} → {val2}",
"type": change_type,
"diff": diff,
"delta": delta,
"warning_level": warning_level
})
# 比较文本内容
text1 = (n1.text or "").strip()
text2 = (n2.text or "").strip()
if text1 != text2:
# 检查是否是多个数值
values1 = text1.split()
values2 = text2.split()
if len(values1) > 1 and len(values2) > 1 and len(values1) == len(values2):
# 处理多个数值的情况
multi_changes, multi_warnings = compare_multi_values(
text1, text2, f"{path}$text", yellow_threshold, red_threshold
)
changes.extend(multi_changes)
warnings.extend(multi_warnings)
else:
# 处理单个值或无法比较的情况
single_changes, single_warnings = compare_text_value(
text1, text2, f"{path}$text", yellow_threshold, red_threshold
)
changes.extend(single_changes)
warnings.extend(single_warnings)
# 比较子节点
children1 = {child.tag: child for child in n1}
children2 = {child.tag: child for child in n2}
# 共同子节点比较
for tag in set(children1.keys()) | set(children2.keys()):
if tag in children1 and tag in children2:
child_changes, child_warnings, child_row_checks = compare_nodes(
children1[tag], children2[tag],
f"{path}{tag}/", yellow_threshold, red_threshold
)
changes.extend(child_changes)
warnings.extend(child_warnings)
row_checks.extend(child_row_checks)
elif tag in children2:
# 新增子节点
changes.append({
"path": f"{path}+{tag}",
"change": "[新增]",
"type": "结构",
"diff": 0,
"delta": 0,
"warning_level": "none"
})
else:
# 删除子节点
changes.append({
"path": f"{path}-{tag}",
"change": "[删除]",
"type": "结构",
"diff": 0,
"delta": 0,
"warning_level": "none"
})
return changes, warnings, row_checks
def compare_ccmatrix_across_files(file1, file2, yellow_threshold=0.05, red_threshold=0.1):
try:
# 解析XML
tree1 = ET.parse(file1)
root1 = tree1.getroot()
tree2 = ET.parse(file2)
root2 = tree2.getroot()
# 查找所有CCMatrix节点
nodes1 = find_all_ccmatrix_nodes(root1)
nodes2 = find_all_ccmatrix_nodes(root2)
# 按场景分组
scenes1 = {scene for scene, _ in nodes1}
scenes2 = {scene for scene, _ in nodes2}
all_scenes = sorted(scenes1 | scenes2)
results = []
global_warnings = [] # 收集所有警告
global_row_checks = [] # 收集所有矩阵行和检查
# 对比每个场景
for scene in all_scenes:
node1 = next((node for s, node in nodes1 if s == scene), None)
node2 = next((node for s, node in nodes2 if s == scene), None)
exists1 = node1 is not None
exists2 = node2 is not None
changes = []
warnings = []
row_checks = []
if exists1 and exists2:
changes, warnings, row_checks = compare_nodes(
node1, node2, f"{scene}/", yellow_threshold, red_threshold
)
global_warnings.extend(warnings)
global_row_checks.extend(row_checks)
results.append({
"scene": scene,
"exists_in_file1": exists1,
"exists_in_file2": exists2,
"changes": changes,
"warnings": warnings,
"row_checks": row_checks
})
return results, global_warnings, global_row_checks
except ET.ParseError as e:
return None, [f"XML解析错误: {str(e)}"], []
except FileNotFoundError as e:
return None, [f"文件未找到: {str(e)}"], []
except Exception as e:
return None, [f"处理错误: {str(e)}"], []
def print_colored_header(text, width=80, color=Color.BLUE):
"""打印带颜色的标题"""
padding = (width - len(text)) // 2
print(colorize("=" * width, Color.CYAN))
print(colorize(" " * padding + text + " " * padding, color + Color.BOLD))
print(colorize("=" * width, Color.CYAN))
def main():
if len(sys.argv) < 3:
print("用法: python awb_diff.py <文件1.xml> <文件2.xml> [黄色阈值=0.05] [红色阈值=0.1]")
print("示例: python awb_diff.py config_v1.xml config_v2.xml")
print(" python awb_diff.py config_v1.xml config_v2.xml 0.03 0.08")
sys.exit(1)
# 解析阈值参数
yellow_threshold = 0.05 # 默认黄色阈值
red_threshold = 0.1 # 默认红色阈值
if len(sys.argv) >= 4:
try:
yellow_threshold = float(sys.argv[3])
if yellow_threshold <= 0:
print("错误: 黄色阈值必须大于0")
sys.exit(1)
except ValueError:
print("错误: 黄色阈值必须是数值类型")
sys.exit(1)
if len(sys.argv) >= 5:
try:
red_threshold = float(sys.argv[4])
if red_threshold <= 0 or red_threshold <= yellow_threshold:
print("错误: 红色阈值必须大于0且大于黄色阈值")
sys.exit(1)
except ValueError:
print("错误: 红色阈值必须是数值类型")
sys.exit(1)
file1 = sys.argv[1]
file2 = sys.argv[2]
results, global_warnings, global_row_checks = compare_ccmatrix_across_files(
file1, file2, yellow_threshold, red_threshold
)
# 打印报告头
print_colored_header(f" Awb_TblCCMatrix 参数对比报告 ", 80, Color.BLUE)
print(f" 文件1: {colorize(file1, Color.CYAN)}")
print(f" 文件2: {colorize(file2, Color.CYAN)}")
print(f" 黄色阈值: {colorize(f'{yellow_threshold:.6f}', Color.YELLOW)}")
print(f" 红色阈值: {colorize(f'{red_threshold:.6f}', Color.RED)}")
print(colorize("-" * 80, Color.CYAN))
# 输出全局警告
if global_warnings:
print_colored_header(" 警告: 检测到超过阈值的参数变更 ", 80, Color.RED)
for warning in global_warnings:
print(warning)
print(colorize("!" * 80, Color.RED))
if results is None:
print(colorize("\n无法生成报告: " + global_warnings[0], Color.RED))
return
# 输出每个场景的详情
for result in results:
scene = result["scene"]
exists1 = result["exists_in_file1"]
exists2 = result["exists_in_file2"]
changes = result["changes"]
row_checks = result["row_checks"]
# 打印场景头
scene_header = f" 场景: {scene} "
print_colored_header(scene_header, 80, Color.BLUE)
print(f" 文件1中存在: {colorize('是', Color.GREEN) if exists1 else colorize('否', Color.RED)}")
print(f" 文件2中存在: {colorize('是', Color.GREEN) if exists2 else colorize('否', Color.RED)}")
if exists1 and exists2:
if changes:
print(f" 检测到 {len(changes)} 处变化:")
# 按变化类型分组
num_changes = [c for c in changes if c["type"] in ["数值", "汇总"]]
non_num_changes = [c for c in changes if c["type"] == "非数值"]
struct_changes = [c for c in changes if c["type"] == "结构"]
if num_changes:
print(colorize("\n [数值变化]:", Color.BOLD))
for change in num_changes:
# 确定颜色
if change["warning_level"] == "red":
color = Color.RED
elif change["warning_level"] == "yellow":
color = Color.YELLOW
else:
color = Color.GREEN
# 格式化输出
path_display = colorize(change["path"], color)
# 为ctemp相关的变化添加特殊标记
ctemp_note = colorize(" (ctemp变化)", Color.CYAN) if "ctemp" in change["path"].lower() else ""
# 格式化输出差值
if "数值" in change["type"]:
sign = "+" if change["delta"] >= 0 else "-"
diff_str = colorize(f" | 变化: {sign}{abs(change['diff']):.6f}", color)
else:
diff_str = ""
print(f" {path_display}: {change['change']}{diff_str}{ctemp_note}")
if non_num_changes:
print(colorize("\n [非数值变化]:", Color.BOLD))
for change in non_num_changes:
print(f" {change['path']}: {change['change']}")
if struct_changes:
print(colorize("\n [结构变化]:", Color.BOLD))
for change in struct_changes:
print(f" {change['path']}: {change['change']}")
else:
print(colorize("\n ✅ 无变化", Color.GREEN))
# 输出该场景的矩阵行和检查
if row_checks:
# 检查是否所有行都通过
all_passed = all(check["valid"] for check in row_checks)
if all_passed:
print(colorize("\n [矩阵行和检查]:", Color.BOLD))
print(colorize(f" ✅ 所有行和检查通过 (每行和≈1.000000)", Color.GREEN))
else:
print(colorize("\n [矩阵行和检查]:", Color.BOLD))
print(colorize(f" ❌ 以下行和检查失败:", Color.RED))
for check in row_checks:
if not check["valid"]:
print(colorize(f" {check['path']}: 和={check['sum']:.6f} ≠ 1.000000", Color.RED))
print(colorize(f" 值: {' '.join(f'{v:.6f}' for v in check['values'])}", Color.YELLOW))
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print(colorize("\n程序被用户中断", Color.RED))
sys.exit(1)我想要在py文件运行完之后输出一个带有颜色的文本文件,在同目录下,并自动打开,win系统