""" 시각화 모듈 평가 결과를 시각화하고 대시보드 및 리포트를 생성합니다. """ import numpy as np import matplotlib.pyplot as plt import seaborn as sns import plotly.graph_objects as go import plotly.express as px from plotly.subplots import make_subplots import plotly.offline as pyo from typing import Dict, List, Optional, Tuple import os import json from datetime import datetime import trimesh import open3d as o3d class Visualizer: """시각화를 담당하는 클래스""" def __init__(self, output_dir: str = "results"): """ 시각화 도구 초기화 Args: output_dir (str): 출력 디렉토리 """ self.output_dir = output_dir self.images_dir = os.path.join(output_dir, "images") self.reports_dir = os.path.join(output_dir, "reports") # 디렉토리 생성 os.makedirs(self.images_dir, exist_ok=True) os.makedirs(self.reports_dir, exist_ok=True) # matplotlib 스타일 설정 plt.style.use('seaborn-v0_8') sns.set_palette("husl") def plot_metrics_comparison(self, results: Dict, save_path: Optional[str] = None) -> None: """ 평가 지표 비교 차트를 생성합니다. Args: results (Dict): 평가 결과 save_path (Optional[str]): 저장 경로 """ metrics = results['metrics'] score_details = results['score_details'] # 서브플롯 생성 fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 12)) # 1. 원본 지표 값 막대 차트 metric_names = list(metrics.keys()) metric_values = list(metrics.values()) bars1 = ax1.bar(metric_names, metric_values, color='skyblue', alpha=0.7) ax1.set_title('원본 지표 값', fontsize=14, fontweight='bold') ax1.set_ylabel('값') ax1.tick_params(axis='x', rotation=45) # 막대 위에 값 표시 for bar, value in zip(bars1, metric_values): ax1.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.01, f'{value:.3f}', ha='center', va='bottom') # 2. 정규화된 점수 막대 차트 normalized_scores = [details['normalized_score'] for details in score_details.values()] bars2 = ax2.bar(metric_names, normalized_scores, color='lightgreen', alpha=0.7) ax2.set_title('정규화된 점수 (0-100)', fontsize=14, fontweight='bold') ax2.set_ylabel('점수') ax2.set_ylim(0, 100) ax2.tick_params(axis='x', rotation=45) # 막대 위에 값 표시 for bar, score in zip(bars2, normalized_scores): ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 2, f'{score:.1f}', ha='center', va='bottom') # 3. 가중치 적용 점수 막대 차트 weighted_scores = [details['weighted_score'] for details in score_details.values()] weights = [details['weight'] for details in score_details.values()] bars3 = ax3.bar(metric_names, weighted_scores, color='orange', alpha=0.7) ax3.set_title('가중치 적용 점수', fontsize=14, fontweight='bold') ax3.set_ylabel('가중 점수') ax3.tick_params(axis='x', rotation=45) # 막대 위에 가중치 표시 for bar, score, weight in zip(bars3, weighted_scores, weights): ax3.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 0.5, f'{score:.1f}\n(w:{weight})', ha='center', va='bottom', fontsize=9) # 4. 종합 점수 파이 차트 comprehensive_score = results['comprehensive_score'] grade = results['grade'] # 등급별 색상 설정 grade_colors = {'A': '#2e8b57', 'B': '#4169e1', 'C': '#ffa500', 'D': '#ff6347', 'F': '#dc143c'} ax4.pie([comprehensive_score, 100 - comprehensive_score], labels=[f'{grade}등급\n{comprehensive_score:.1f}점', ''], colors=[grade_colors.get(grade, '#666666'), '#f0f0f0'], autopct='%1.1f%%', startangle=90) ax4.set_title('종합 점수', fontsize=14, fontweight='bold') plt.tight_layout() # 저장 if save_path is None: save_path = os.path.join(self.images_dir, f"metrics_comparison_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png") plt.savefig(save_path, dpi=300, bbox_inches='tight') plt.show() print(f"지표 비교 차트가 저장되었습니다: {save_path}") def visualize_3d_model(self, model: Dict, title: str = "3D Model", save_path: Optional[str] = None) -> None: """ 3D 모델을 시각화합니다. Args: model (Dict): 3D 모델 정보 title (str): 시각화 제목 save_path (Optional[str]): 저장 경로 """ try: vertices = model['vertices'] faces = model['faces'] if len(vertices) == 0: print("시각화할 정점이 없습니다.") return # trimesh를 사용한 3D 시각화 mesh = trimesh.Trimesh(vertices=vertices, faces=faces) # 3D 플롯 생성 fig = plt.figure(figsize=(12, 8)) ax = fig.add_subplot(111, projection='3d') # 메시 플롯 ax.plot_trisurf(vertices[:, 0], vertices[:, 1], vertices[:, 2], triangles=faces, alpha=0.8, cmap='viridis') # 바운딩 박스 표시 bbox = model.get('bounding_box', None) if bbox is not None: self._plot_bounding_box(ax, bbox) ax.set_title(title, fontsize=14, fontweight='bold') ax.set_xlabel('X') ax.set_ylabel('Y') ax.set_zlabel('Z') # 축 비율 동일하게 설정 self._set_equal_axes(ax, vertices) # 저장 if save_path is None: save_path = os.path.join(self.images_dir, f"3d_model_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png") plt.savefig(save_path, dpi=300, bbox_inches='tight') plt.show() print(f"3D 모델 시각화가 저장되었습니다: {save_path}") except Exception as e: print(f"3D 모델 시각화 중 오류 발생: {e}") def create_performance_dashboard(self, results: Dict) -> str: """ 성능 대시보드를 생성합니다. Args: results (Dict): 평가 결과 Returns: str: 대시보드 HTML 파일 경로 """ # Plotly 대시보드 생성 fig = make_subplots( rows=2, cols=2, subplot_titles=('종합 점수', '지표별 점수', '가중치 분포', '성능 등급'), specs=[[{"type": "indicator"}, {"type": "bar"}], [{"type": "pie"}, {"type": "indicator"}]] ) # 1. 종합 점수 게이지 comprehensive_score = results['comprehensive_score'] fig.add_trace( go.Indicator( mode="gauge+number+delta", value=comprehensive_score, domain={'x': [0, 1], 'y': [0, 1]}, title={'text': "종합 점수"}, gauge={ 'axis': {'range': [None, 100]}, 'bar': {'color': "darkblue"}, 'steps': [ {'range': [0, 60], 'color': "lightgray"}, {'range': [60, 80], 'color': "yellow"}, {'range': [80, 100], 'color': "green"} ], 'threshold': { 'line': {'color': "red", 'width': 4}, 'thickness': 0.75, 'value': 90 } } ), row=1, col=1 ) # 2. 지표별 점수 막대 차트 score_details = results['score_details'] metric_names = list(score_details.keys()) normalized_scores = [details['normalized_score'] for details in score_details.values()] fig.add_trace( go.Bar( x=metric_names, y=normalized_scores, name="정규화된 점수", marker_color='lightblue' ), row=1, col=2 ) # 3. 가중치 분포 파이 차트 weights = [details['weight'] for details in score_details.values()] fig.add_trace( go.Pie( labels=metric_names, values=weights, name="가중치 분포" ), row=2, col=1 ) # 4. 성능 등급 표시 grade = results['grade'] grade_colors = {'A': '#2e8b57', 'B': '#4169e1', 'C': '#ffa500', 'D': '#ff6347', 'F': '#dc143c'} fig.add_trace( go.Indicator( mode="number", value=0, # 등급은 텍스트로 표시 title={'text': f"성능 등급: {grade}"}, number={'font': {'size': 50, 'color': grade_colors.get(grade, '#666666')}} ), row=2, col=2 ) # 레이아웃 업데이트 fig.update_layout( title_text="3D 객체인식 평가 대시보드", title_x=0.5, height=800, showlegend=False ) # HTML 파일로 저장 html_file = os.path.join(self.reports_dir, f"dashboard_{datetime.now().strftime('%Y%m%d_%H%M%S')}.html") fig.write_html(html_file) print(f"성능 대시보드가 생성되었습니다: {html_file}") return html_file def generate_html_report(self, results: Dict) -> str: """ HTML 형식의 상세 리포트를 생성합니다. Args: results (Dict): 평가 결과 Returns: str: HTML 리포트 파일 경로 """ html_content = f"""
평가 시간: {results['evaluation_timestamp']}
모델 파일: {os.path.basename(results['model_path'])}
참조 파일: {os.path.basename(results['reference_path'])}
| 지표 | 원본 값 | 정규화 점수 | 가중치 | 가중 점수 |
|---|