Files
massive-cloud/alg/点云测距v1.1/train_measurement_system/src/excel_exporter.py

246 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Excel结果导出器"""
from pathlib import Path
from typing import List, Dict, Any
from datetime import datetime
from openpyxl import Workbook
from openpyxl.styles import Font, Alignment, PatternFill, Border, Side
from openpyxl.utils import get_column_letter
from measurements.base import MeasurementResult
class ExcelExporter:
"""将测量结果导出为Excel文件"""
def __init__(self, output_dir: str):
"""
初始化导出器
Args:
output_dir: 输出目录路径
"""
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
def export(self, results: List[MeasurementResult], filename: str = None, task_configs: List[Dict[str, Any]] = None) -> str:
"""
导出测量结果到Excel
Args:
results: 测量结果列表
filename: 输出文件名(不含扩展名),默认使用时间戳
task_configs: 任务配置列表(用于提取描述和参数)
Returns:
str: 导出的Excel文件路径
"""
if filename is None:
filename = f"measurement_results_{datetime.now().strftime('%Y%m%d_%H%M%S')}"
output_path = self.output_dir / f"{filename}.xlsx"
# 创建任务名称到配置的映射
task_config_map = {}
if task_configs:
for config in task_configs:
task_config_map[config.get('name')] = config
# 创建工作簿
wb = Workbook()
ws = wb.active
ws.title = "测量结果"
# 写入标题
self._write_header(ws)
# 写入数据
row = 2
for result in results:
task_config = task_config_map.get(result.task_name, {})
row = self._write_result(ws, result, row, task_config)
# 调整列宽
self._adjust_column_widths(ws)
# 保存文件
wb.save(output_path)
return str(output_path)
def _write_header(self, ws):
"""写入表头"""
headers = [
"任务名称",
"任务类型",
"任务描述",
"点位参数",
"状态",
"平均距离 (mm)",
"标准差 (mm)",
"最小距离 (mm)",
"最大距离 (mm)",
"采样点数",
"源ROI点数",
"目标ROI点数",
"源平面内点率",
"目标平面内点率",
"错误信息"
]
# 样式
header_font = Font(bold=True, color="FFFFFF")
header_fill = PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid")
header_alignment = Alignment(horizontal="center", vertical="center")
border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
for col, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col)
cell.value = header
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_alignment
cell.border = border
def _format_task_params(self, task_config: Dict[str, Any]) -> str:
"""
格式化任务参数为字符串
Args:
task_config: 任务配置字典
Returns:
str: 格式化后的参数字符串
"""
task_type = task_config.get('type', '')
params_parts = []
if task_type == 'plane_to_plane':
# 平面到平面:显示源ROI和目标ROI
source_roi = task_config.get('source_roi', {})
target_roi = task_config.get('target_roi', {})
if source_roi:
params_parts.append(f"源ROI: min{source_roi.get('bbox_min', [])} max{source_roi.get('bbox_max', [])}")
if target_roi:
params_parts.append(f"目标ROI: min{target_roi.get('bbox_min', [])} max{target_roi.get('bbox_max', [])}")
elif task_type == 'point_to_plane':
mode = task_config.get('mode', '')
if mode == 'exact_points':
# 精确点模式:显示目标点坐标
target_points = task_config.get('target_points', [])
if target_points:
params_parts.append(f"目标点({len(target_points)}个): {target_points}")
plane = task_config.get('plane', {})
if plane:
params_parts.append(f"平面: {plane.get('a', 0)}x+{plane.get('b', 0)}y+{plane.get('c', 0)}z+{plane.get('d', 0)}=0")
elif mode == 'bbox_filtered':
# 包围盒过滤模式:显示包围盒
bboxes = task_config.get('bboxes', [])
if bboxes:
bbox_strs = [f"{bbox.get('name', '')}: min{bbox.get('min', [])} max{bbox.get('max', [])}" for bbox in bboxes]
params_parts.append(f"包围盒: {'; '.join(bbox_strs)}")
filter_info = task_config.get('filter', {})
if filter_info:
params_parts.append(f"过滤: {filter_info.get('type', '')} axis={filter_info.get('axis', '')} top_n={filter_info.get('top_n', '')}")
elif task_type == 'point_to_point':
# 点到点:显示点对
point_pairs = task_config.get('point_pairs', [])
if point_pairs:
pair_strs = [f"{pair.get('name', '')}: {pair.get('point1', [])} <-> {pair.get('point2', [])}" for pair in point_pairs]
params_parts.append(f"点对({len(point_pairs)}对): {'; '.join(pair_strs)}")
return '\n'.join(params_parts) if params_parts else "无"
def _write_result(self, ws, result: MeasurementResult, row: int, task_config: Dict[str, Any]) -> int:
"""
写入单个测量结果
Args:
ws: 工作表
result: 测量结果
row: 起始行号
task_config: 任务配置
Returns:
int: 下一行号
"""
border = Border(
left=Side(style='thin'),
right=Side(style='thin'),
top=Side(style='thin'),
bottom=Side(style='thin')
)
# 基本信息
ws.cell(row=row, column=1).value = result.task_name
ws.cell(row=row, column=2).value = result.task_type
ws.cell(row=row, column=3).value = task_config.get('description', '')
ws.cell(row=row, column=4).value = self._format_task_params(task_config)
ws.cell(row=row, column=5).value = "成功" if result.success else "失败"
# 状态颜色
status_cell = ws.cell(row=row, column=5)
if result.success:
status_cell.fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid")
status_cell.font = Font(color="006100")
else:
status_cell.fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid")
status_cell.font = Font(color="9C0006")
# 测量数据
if result.success and result.data:
data = result.data
ws.cell(row=row, column=6).value = round(data.get('mean_distance', 0), 2)
ws.cell(row=row, column=7).value = round(data.get('std_distance', 0), 2)
ws.cell(row=row, column=8).value = round(data.get('min_distance', 0), 2)
ws.cell(row=row, column=9).value = round(data.get('max_distance', 0), 2)
ws.cell(row=row, column=10).value = data.get('sample_count', 0)
ws.cell(row=row, column=11).value = data.get('source_roi_points', 0)
ws.cell(row=row, column=12).value = data.get('target_roi_points', 0)
ws.cell(row=row, column=13).value = f"{data.get('source_inlier_ratio', 0) * 100:.1f}%"
ws.cell(row=row, column=14).value = f"{data.get('target_inlier_ratio', 0) * 100:.1f}%"
else:
# 失败时显示错误信息
ws.cell(row=row, column=15).value = result.error_message
# 应用边框和对齐
for col in range(1, 16):
cell = ws.cell(row=row, column=col)
cell.border = border
# 点位参数列使用左对齐,其他居中
if col == 4:
cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
else:
cell.alignment = Alignment(horizontal="center", vertical="center")
return row + 1
def _adjust_column_widths(self, ws):
"""自动调整列宽"""
column_widths = {
1: 25, # 任务名称
2: 18, # 任务类型
3: 30, # 任务描述
4: 60, # 点位参数
5: 10, # 状态
6: 15, # 平均距离
7: 15, # 标准差
8: 15, # 最小距离
9: 15, # 最大距离
10: 12, # 采样点数
11: 12, # 源ROI点数
12: 12, # 目标ROI点数
13: 15, # 源平面内点率
14: 15, # 目标平面内点率
15: 40 # 错误信息
}
for col, width in column_widths.items():
ws.column_dimensions[get_column_letter(col)].width = width