feat: initial commit - massive-cloud point cloud processing system
This commit is contained in:
245
alg/点云测距v1.1/train_measurement_system/src/excel_exporter.py
Normal file
245
alg/点云测距v1.1/train_measurement_system/src/excel_exporter.py
Normal file
@@ -0,0 +1,245 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user