diff --git a/pqAutomationApp.py b/pqAutomationApp.py index 8d868ac..d6d2d7d 100644 --- a/pqAutomationApp.py +++ b/pqAutomationApp.py @@ -1136,13 +1136,6 @@ class PQAutomationApp: if hasattr(self, "cct_params_frame"): self.toggle_cct_params_frame() - # ========== 新增方法: 更新配置并同步Tab状态 ========== - def update_config_and_tabs(self): - """更新配置并同步图表Tab状态""" - self.update_config() - self.update_chart_tabs_state() - - update_chart_tabs_state = _cf_update_chart_tabs_state def get_test_type_display_name(self, test_type): """获取测试类型的显示名称""" display_names = { @@ -2871,6 +2864,35 @@ class PQAutomationApp: # ========== 延迟1秒后执行清理 ========== self.root.after(1000, cleanup_and_finish) + # ==================== Excel 导出配置 ==================== + _EXCEL_EXPORT_CONFIG = { + "screen_module": { + "title": "屏模组性能测试数据报告", + "type_label": "屏模组", + "log_prefix": "屏模组", + "curve_type": "gamma", + "has_accuracy": False, + "column_widths": {"A": 18, "B": 18, "C": 18, "D": 18, "E": 18, "F": 15, "G": 15}, + }, + "sdr_movie": { + "title": "SDR Movie 性能测试数据报告", + "type_label": "SDR Movie", + "log_prefix": "SDR Movie", + "curve_type": "gamma", + "has_accuracy": True, + "column_widths": {c: 18 for c in "ABCDEFG"}, + }, + "hdr_movie": { + "title": "HDR Movie 性能测试数据报告", + "type_label": "HDR Movie", + "log_prefix": "HDR Movie", + "curve_type": "eotf", + "has_accuracy": True, + "column_widths": {c: 18 for c in "ABCDEFG"}, + }, + } + + # ==================== 主入口 ==================== def save_results(self): """保存测试结果(图片 + Excel)""" save_dir = filedialog.askdirectory(title="选择保存测试结果的目录") @@ -2883,1744 +2905,27 @@ class PQAutomationApp: result_dir = os.path.join(save_dir, f"{test_type}_{timestamp}") os.makedirs(result_dir, exist_ok=True) - # ========== ✅ 获取当前测试类型和已选测试项 ========== current_test_type = self.test_type_var.get() selected_items = self.get_selected_test_items() self.log_gui.log(f"保存测试类型: {current_test_type}") self.log_gui.log(f"已选测试项: {selected_items}") - # ========== 保存图片 ========== - if "gamut" in selected_items and hasattr(self, "gamut_fig"): - gamut_path = os.path.join(result_dir, "色域测试结果.png") - self.gamut_fig.savefig(gamut_path, dpi=300) - self.log_gui.log(f"✓ 已保存: 色域测试结果.png") + # 1) 图片 + self._save_result_images(result_dir, current_test_type, selected_items) - if current_test_type in ["screen_module", "sdr_movie"]: - if "gamma" in selected_items and hasattr(self, "gamma_fig"): - gamma_path = os.path.join(result_dir, "Gamma曲线测试结果.png") - self.gamma_fig.savefig(gamma_path, dpi=300) - self.log_gui.log(f"✓ 已保存: Gamma曲线测试结果.png") - - if current_test_type == "hdr_movie": - if "eotf" in selected_items and hasattr(self, "eotf_fig"): - eotf_path = os.path.join(result_dir, "EOTF曲线测试结果.png") - self.eotf_fig.savefig(eotf_path, dpi=300) - self.log_gui.log(f"✓ 已保存: EOTF曲线测试结果.png") - - if "cct" in selected_items and hasattr(self, "cct_fig"): - cct_path = os.path.join(result_dir, "色度一致性测试结果.png") - self.cct_fig.savefig(cct_path, dpi=300) - self.log_gui.log(f"✓ 已保存: 色度一致性测试结果.png") - - if "contrast" in selected_items and hasattr(self, "contrast_fig"): - contrast_path = os.path.join(result_dir, "对比度测试结果.png") - self.contrast_fig.savefig(contrast_path, dpi=300, bbox_inches="tight") - self.log_gui.log(f"✓ 已保存: 对比度测试结果.png") - - if current_test_type in ["sdr_movie", "hdr_movie"]: - if "accuracy" in selected_items and hasattr(self, "accuracy_fig"): - accuracy_path = os.path.join(result_dir, "色准测试结果.png") - self.accuracy_fig.savefig(accuracy_path, dpi=300) - self.log_gui.log(f"✓ 已保存: 色准测试结果.png") - - # ========== ✅ 屏模组测试 Excel 导出 ========== + # 2) Excel if ( - current_test_type == "screen_module" + current_test_type in self._EXCEL_EXPORT_CONFIG and hasattr(self, "results") and self.results ): - try: - import openpyxl - from openpyxl.styles import ( - Font, - Alignment, - PatternFill, - Border, - Side, - ) + self._export_excel_report(result_dir, current_test_type, selected_items) - self.log_gui.log("=" * 60) - self.log_gui.log("开始生成屏模组 Excel 数据报告...") - - wb = openpyxl.Workbook() - ws = wb.active - ws.title = "测试数据" - - # ========== 样式定义 ========== - title_font = Font( - name="微软雅黑", size=16, bold=True, color="FFFFFF" - ) - title_fill = PatternFill( - start_color="4472C4", end_color="4472C4", fill_type="solid" - ) - title_alignment = Alignment(horizontal="center", vertical="center") - - section_font = Font( - name="微软雅黑", size=13, bold=True, color="FFFFFF" - ) - section_fill = PatternFill( - start_color="5B9BD5", end_color="5B9BD5", fill_type="solid" - ) - section_alignment = Alignment( - horizontal="center", vertical="center" - ) - - header_font = Font( - name="微软雅黑", size=10, bold=True, color="FFFFFF" - ) - header_fill = PatternFill( - start_color="70AD47", end_color="70AD47", fill_type="solid" - ) - header_alignment = Alignment( - horizontal="center", vertical="center", wrap_text=True - ) - - data_font = Font(name="微软雅黑", size=10) - data_alignment = Alignment(horizontal="center", vertical="center") - - label_font = Font(name="微软雅黑", size=10, bold=True) - - thin_border = Border( - left=Side(style="thin"), - right=Side(style="thin"), - top=Side(style="thin"), - bottom=Side(style="thin"), - ) - - # ========== 总标题 ========== - ws.merge_cells("A1:G1") - ws["A1"] = "屏模组性能测试数据报告" - ws["A1"].font = title_font - ws["A1"].fill = title_fill - ws["A1"].alignment = title_alignment - ws.row_dimensions[1].height = 35 - - # ========== 测试基本信息 ========== - row = 3 - ws.merge_cells(f"A{row}:B{row}") - ws[f"A{row}"] = "📋 测试基本信息" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - - row += 1 - info_items = [ - ( - "测试时间", - datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ), - ("测试类型", "屏模组"), - ] - - for label, value in info_items: - ws[f"A{row}"] = label - ws[f"B{row}"] = value - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - row += 1 # 空行 - - # ========== 1. 色域数据 ========== - if "gamut" in selected_items: - rgb_data = self.results.get_intermediate_data("gamut", "rgb") - gamut_final_result = None - if "gamut" in self.results.test_items: - gamut_final_result = self.results.test_items[ - "gamut" - ].final_result - - if rgb_data and len(rgb_data) >= 3: - # 分区标题 - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "🎨 色域测试数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - if gamut_final_result: - # 第一行:参考标准 - ws[f"A{row}"] = "参考标准" - ws[f"B{row}"] = gamut_final_result.get( - "reference", "DCI-P3" - ) - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - # 第二行:XY 覆盖率 | UV 覆盖率 - xy_coverage = gamut_final_result.get("coverage", 0) - uv_coverage = ( - gamut_final_result.get("uv_coverage", 0) - or gamut_final_result.get("uv_space_coverage", 0) - or gamut_final_result.get("coverage_uv", 0) - or 0 - ) - - ws[f"A{row}"] = "XY 色域覆盖率" - ws[f"B{row}"] = f"{xy_coverage:.2f}%" - ws[f"C{row}"] = "UV 色域覆盖率" - ws[f"D{row}"] = f"{uv_coverage:.2f}%" - - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"C{row}"].font = label_font - ws[f"D{row}"].font = data_font - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].border = thin_border - - row += 1 - - # RGB 数据表格 - headers = [ - "点位", - "x 坐标", - "y 坐标", - "亮度 (cd/m²)", - "", - "", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - rgb_labels = ["Red", "Green", "Blue"] - for i, result in enumerate(rgb_data[:3]): - x, y, lv = result[0], result[1], result[2] - - ws[f"A{row}"] = rgb_labels[i] - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - - ws[f"A{row}"].font = data_font - ws[f"A{row}"].alignment = data_alignment - ws[f"A{row}"].border = thin_border - - ws[f"B{row}"].number_format = "0.0000" - ws[f"B{row}"].font = data_font - ws[f"B{row}"].alignment = data_alignment - ws[f"B{row}"].border = thin_border - - ws[f"C{row}"].number_format = "0.0000" - ws[f"C{row}"].font = data_font - ws[f"C{row}"].alignment = data_alignment - ws[f"C{row}"].border = thin_border - - ws[f"D{row}"].number_format = "0.00" - ws[f"D{row}"].font = data_font - ws[f"D{row}"].alignment = data_alignment - ws[f"D{row}"].border = thin_border - - row += 1 - - row += 1 # 空行 - self.log_gui.log(" ✓ 添加色域数据") - - # ========== 2. Gamma 数据 ========== - if "gamma" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if not gray_data: - gray_data = self.results.get_intermediate_data( - "gamma", "gray" - ) - - gamma_final_result = None - if "gamma" in self.results.test_items: - gamma_final_result = self.results.test_items[ - "gamma" - ].final_result - - if gray_data and len(gray_data) > 0 and gamma_final_result: - gamma_list = gamma_final_result.get("gamma", []) - L_bar_list = gamma_final_result.get("L_bar", []) - - # 分区标题 - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "📊 Gamma 曲线数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - # Gamma 统计信息 - valid_gamma = [] - if gamma_list: - for item in gamma_list: - if ( - isinstance(item, (list, tuple)) - and len(item) >= 4 - ): - gamma_val = item[3] - if 0.5 < gamma_val < 5.0: - valid_gamma.append(gamma_val) - - if valid_gamma: - avg_gamma = sum(valid_gamma) / len(valid_gamma) - max_gamma = max(valid_gamma) - min_gamma = min(valid_gamma) - - ws[f"A{row}"] = "平均 Gamma" - ws[f"B{row}"] = f"{avg_gamma:.3f}" - ws[f"C{row}"] = "最大 Gamma" - ws[f"D{row}"] = f"{max_gamma:.3f}" - ws[f"E{row}"] = "最小 Gamma" - ws[f"F{row}"] = f"{min_gamma:.3f}" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = ( - label_font - if col in ["A", "C", "E"] - else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # Gamma 数据表格 - headers = [ - "灰阶 (%)", - "x 坐标", - "y 坐标", - "实测亮度\n(cd/m²)", - "归一化亮度\n(L_bar)", - "Gamma 值", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - total_points = len(gray_data) - for i in range(total_points - 1, -1, -1): - gray_level = ( - 100 - int(i * 100 / (total_points - 1)) - if total_points > 1 - else 0 - ) - - x, y, lv = ( - gray_data[i][0], - gray_data[i][1], - gray_data[i][2], - ) - - L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0 - - gamma_val = None - if ( - i < len(gamma_list) - and isinstance(gamma_list[i], (list, tuple)) - and len(gamma_list[i]) >= 4 - ): - gamma_val = gamma_list[i][3] - - ws[f"A{row}"] = gray_level - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - ws[f"E{row}"] = L_bar_val - - if gamma_val is not None and 0.5 < gamma_val < 5.0: - ws[f"F{row}"] = gamma_val - ws[f"F{row}"].number_format = "0.000" - else: - ws[f"F{row}"] = "N/A" - - ws[f"A{row}"].number_format = "0" - ws[f"B{row}"].number_format = "0.0000" - ws[f"C{row}"].number_format = "0.0000" - ws[f"D{row}"].number_format = "0.00" - ws[f"E{row}"].number_format = "0.0000" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加 Gamma 数据") - - # ========== 3. 色度一致性数据 ========== - if "cct" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if not gray_data: - gray_data = self.results.get_intermediate_data( - "cct", "gray" - ) - - if gray_data and len(gray_data) > 1: - gray_data_no_black = gray_data[:-1] - - # 分区标题 - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "🌈 色度一致性数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - # 色度波动信息 - x_coords = [d[0] for d in gray_data_no_black] - y_coords = [d[1] for d in gray_data_no_black] - - ws[f"A{row}"] = "x 坐标范围" - ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}" - ws[f"C{row}"] = "y 坐标范围" - ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # 数据表格 - headers = [ - "灰阶 (%)", - "x 坐标", - "y 坐标", - "亮度 (cd/m²)", - "", - "", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - total_points = len(gray_data) - for i in range(len(gray_data_no_black) - 1, -1, -1): - x, y, lv = ( - gray_data_no_black[i][0], - gray_data_no_black[i][1], - gray_data_no_black[i][2], - ) - gray_level = ( - 100 - int(i * 100 / (total_points - 1)) - if total_points > 1 - else 0 - ) - - ws[f"A{row}"] = gray_level - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - - ws[f"A{row}"].number_format = "0" - ws[f"B{row}"].number_format = "0.0000" - ws[f"C{row}"].number_format = "0.0000" - ws[f"D{row}"].number_format = "0.00" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加色度一致性数据") - - # ========== 4. 对比度数据 ========== - if "contrast" in selected_items: - contrast_final_result = None - if "contrast" in self.results.test_items: - contrast_final_result = self.results.test_items[ - "contrast" - ].final_result - - if contrast_final_result: - # 分区标题 - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "⚫⚪ 对比度测试数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - max_lv = contrast_final_result.get("max_luminance", 0) - min_lv = contrast_final_result.get("min_luminance", 0) - contrast_ratio = contrast_final_result.get( - "contrast_ratio", 0 - ) - - info_items = [ - ("最大亮度(白场)", f"{max_lv:.2f} cd/m²"), - ("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"), - ("对比度", f"{contrast_ratio:.0f}:1"), - ] - - for label, value in info_items: - ws[f"A{row}"] = label - ws[f"B{row}"] = value - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - self.log_gui.log(" ✓ 添加对比度数据") - - # ========== 调整列宽 ========== - ws.column_dimensions["A"].width = 18 - ws.column_dimensions["B"].width = 18 - ws.column_dimensions["C"].width = 18 - ws.column_dimensions["D"].width = 18 - ws.column_dimensions["E"].width = 18 - ws.column_dimensions["F"].width = 15 - ws.column_dimensions["G"].width = 15 - - # ========== 保存 Excel ========== - excel_path = os.path.join(result_dir, "测试数据.xlsx") - wb.save(excel_path) - - self.log_gui.log(f"✓ 已保存: 测试数据.xlsx") - self.log_gui.log("=" * 60) - - except ImportError: - self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出") - self.log_gui.log(" 安装方法: pip install openpyxl") - except Exception as e: - self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}") - import traceback - - self.log_gui.log(traceback.format_exc()) - - # ========== ✅ SDR Movie 测试 Excel 导出 ========== - elif ( - current_test_type == "sdr_movie" - and hasattr(self, "results") - and self.results - ): - try: - import openpyxl - from openpyxl.styles import ( - Font, - Alignment, - PatternFill, - Border, - Side, - ) - - self.log_gui.log("=" * 60) - self.log_gui.log("开始生成 SDR Movie Excel 数据报告...") - - wb = openpyxl.Workbook() - ws = wb.active - ws.title = "测试数据" - - # ========== 样式定义 ========== - title_font = Font( - name="微软雅黑", size=16, bold=True, color="FFFFFF" - ) - title_fill = PatternFill( - start_color="4472C4", end_color="4472C4", fill_type="solid" - ) - title_alignment = Alignment(horizontal="center", vertical="center") - - section_font = Font( - name="微软雅黑", size=13, bold=True, color="FFFFFF" - ) - section_fill = PatternFill( - start_color="5B9BD5", end_color="5B9BD5", fill_type="solid" - ) - section_alignment = Alignment( - horizontal="center", vertical="center" - ) - - header_font = Font( - name="微软雅黑", size=10, bold=True, color="FFFFFF" - ) - header_fill = PatternFill( - start_color="70AD47", end_color="70AD47", fill_type="solid" - ) - header_alignment = Alignment( - horizontal="center", vertical="center", wrap_text=True - ) - - data_font = Font(name="微软雅黑", size=10) - data_alignment = Alignment(horizontal="center", vertical="center") - label_font = Font(name="微软雅黑", size=10, bold=True) - - thin_border = Border( - left=Side(style="thin"), - right=Side(style="thin"), - top=Side(style="thin"), - bottom=Side(style="thin"), - ) - - # ========== 总标题 ========== - ws.merge_cells("A1:G1") - ws["A1"] = "SDR Movie 性能测试数据报告" - ws["A1"].font = title_font - ws["A1"].fill = title_fill - ws["A1"].alignment = title_alignment - ws.row_dimensions[1].height = 35 - - # ========== 测试基本信息 ========== - row = 3 - ws.merge_cells(f"A{row}:B{row}") - ws[f"A{row}"] = "📋 测试基本信息" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - - row += 1 - info_items = [ - ( - "测试时间", - datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ), - ("测试类型", "SDR Movie"), - ] - - for label, value in info_items: - ws[f"A{row}"] = label - ws[f"B{row}"] = value - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - row += 1 # 空行 - - # ========== 1. 色域数据 ========== - if "gamut" in selected_items: - rgb_data = self.results.get_intermediate_data("gamut", "rgb") - gamut_final_result = None - if "gamut" in self.results.test_items: - gamut_final_result = self.results.test_items[ - "gamut" - ].final_result - - if rgb_data and len(rgb_data) >= 3: - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "🎨 色域测试数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - if gamut_final_result: - xy_coverage = gamut_final_result.get("coverage", 0) - uv_coverage = ( - gamut_final_result.get("uv_coverage", 0) - or gamut_final_result.get("uv_space_coverage", 0) - or 0 - ) - - ws[f"A{row}"] = "参考标准" - ws[f"B{row}"] = gamut_final_result.get( - "reference", "DCI-P3" - ) - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - ws[f"A{row}"] = "XY 色域覆盖率" - ws[f"B{row}"] = f"{xy_coverage:.2f}%" - ws[f"C{row}"] = "UV 色域覆盖率" - ws[f"D{row}"] = f"{uv_coverage:.2f}%" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # RGB 数据表格 - headers = [ - "点位", - "x 坐标", - "y 坐标", - "亮度 (cd/m²)", - "", - "", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - rgb_labels = ["Red", "Green", "Blue"] - for i, result in enumerate(rgb_data[:3]): - x, y, lv = result[0], result[1], result[2] - ws[f"A{row}"] = rgb_labels[i] - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - - ws[f"B{row}"].number_format = "0.0000" - ws[f"C{row}"].number_format = "0.0000" - ws[f"D{row}"].number_format = "0.00" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加色域数据") - - # ========== 2. Gamma 数据 ========== - if "gamma" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if not gray_data: - gray_data = self.results.get_intermediate_data( - "gamma", "gray" - ) - - gamma_final_result = None - if "gamma" in self.results.test_items: - gamma_final_result = self.results.test_items[ - "gamma" - ].final_result - - if gray_data and gamma_final_result: - gamma_list = gamma_final_result.get("gamma", []) - L_bar_list = gamma_final_result.get("L_bar", []) - - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "📊 Gamma 曲线数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - # Gamma 统计 - valid_gamma = [ - item[3] - for item in gamma_list - if isinstance(item, (list, tuple)) - and len(item) >= 4 - and 0.5 < item[3] < 5.0 - ] - if valid_gamma: - avg_gamma = sum(valid_gamma) / len(valid_gamma) - ws[f"A{row}"] = "平均 Gamma" - ws[f"B{row}"] = f"{avg_gamma:.3f}" - ws[f"C{row}"] = "最大 Gamma" - ws[f"D{row}"] = f"{max(valid_gamma):.3f}" - ws[f"E{row}"] = "最小 Gamma" - ws[f"F{row}"] = f"{min(valid_gamma):.3f}" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = ( - label_font - if col in ["A", "C", "E"] - else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # Gamma 数据表格 - headers = [ - "灰阶 (%)", - "x 坐标", - "y 坐标", - "实测亮度\n(cd/m²)", - "归一化亮度\n(L_bar)", - "Gamma 值", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - total_points = len(gray_data) - for i in range(total_points - 1, -1, -1): - gray_level = ( - 100 - int(i * 100 / (total_points - 1)) - if total_points > 1 - else 0 - ) - x, y, lv = ( - gray_data[i][0], - gray_data[i][1], - gray_data[i][2], - ) - L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0 - - gamma_val = None - if ( - i < len(gamma_list) - and isinstance(gamma_list[i], (list, tuple)) - and len(gamma_list[i]) >= 4 - ): - gamma_val = gamma_list[i][3] - - ws[f"A{row}"] = gray_level - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - ws[f"E{row}"] = L_bar_val - - if gamma_val is not None and 0.5 < gamma_val < 5.0: - ws[f"F{row}"] = gamma_val - ws[f"F{row}"].number_format = "0.000" - else: - ws[f"F{row}"] = "N/A" - - ws[f"A{row}"].number_format = "0" - ws[f"B{row}"].number_format = "0.0000" - ws[f"C{row}"].number_format = "0.0000" - ws[f"D{row}"].number_format = "0.00" - ws[f"E{row}"].number_format = "0.0000" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加 Gamma 数据") - - # ========== 3. 色度一致性数据 ========== - if "cct" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if not gray_data: - gray_data = self.results.get_intermediate_data( - "cct", "gray" - ) - - if gray_data and len(gray_data) > 1: - gray_data_no_black = gray_data[:-1] - - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "🌈 色度一致性数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - x_coords = [d[0] for d in gray_data_no_black] - y_coords = [d[1] for d in gray_data_no_black] - - ws[f"A{row}"] = "x 坐标范围" - ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}" - ws[f"C{row}"] = "y 坐标范围" - ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - headers = [ - "灰阶 (%)", - "x 坐标", - "y 坐标", - "亮度 (cd/m²)", - "", - "", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - total_points = len(gray_data) - for i in range(len(gray_data_no_black) - 1, -1, -1): - x, y, lv = ( - gray_data_no_black[i][0], - gray_data_no_black[i][1], - gray_data_no_black[i][2], - ) - gray_level = ( - 100 - int(i * 100 / (total_points - 1)) - if total_points > 1 - else 0 - ) - - ws[f"A{row}"] = gray_level - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - - ws[f"A{row}"].number_format = "0" - ws[f"B{row}"].number_format = "0.0000" - ws[f"C{row}"].number_format = "0.0000" - ws[f"D{row}"].number_format = "0.00" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加色度一致性数据") - - # ========== 4. 对比度数据 ========== - if "contrast" in selected_items: - contrast_final_result = None - if "contrast" in self.results.test_items: - contrast_final_result = self.results.test_items[ - "contrast" - ].final_result - - if contrast_final_result: - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "⚫⚪ 对比度测试数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - max_lv = contrast_final_result.get("max_luminance", 0) - min_lv = contrast_final_result.get("min_luminance", 0) - contrast_ratio = contrast_final_result.get( - "contrast_ratio", 0 - ) - - info_items = [ - ("最大亮度(白场)", f"{max_lv:.2f} cd/m²"), - ("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"), - ("对比度", f"{contrast_ratio:.0f}:1"), - ] - - for label, value in info_items: - ws[f"A{row}"] = label - ws[f"B{row}"] = value - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加对比度数据") - - # ========== 5. 色准数据(SDR 特有)========== - if "accuracy" in selected_items: - accuracy_final_result = None - if "accuracy" in self.results.test_items: - accuracy_final_result = self.results.test_items[ - "accuracy" - ].final_result - - if accuracy_final_result: - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "🎯 色准测试数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - # 色准统计信息 - avg_delta_e = accuracy_final_result.get("avg_delta_e", 0) - max_delta_e = accuracy_final_result.get("max_delta_e", 0) - min_delta_e = accuracy_final_result.get("min_delta_e", 0) - excellent_count = accuracy_final_result.get( - "excellent_count", 0 - ) - good_count = accuracy_final_result.get("good_count", 0) - poor_count = accuracy_final_result.get("poor_count", 0) - - ws[f"A{row}"] = "平均 ΔE" - ws[f"B{row}"] = f"{avg_delta_e:.2f}" - ws[f"C{row}"] = "最大 ΔE" - ws[f"D{row}"] = f"{max_delta_e:.2f}" - ws[f"E{row}"] = "最小 ΔE" - ws[f"F{row}"] = f"{min_delta_e:.2f}" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C", "E"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # 第二行统计 - ws[f"A{row}"] = "优秀 (ΔE<3)" - ws[f"B{row}"] = f"{excellent_count} 个" - ws[f"C{row}"] = "良好 (3≤ΔE<5)" - ws[f"D{row}"] = f"{good_count} 个" - ws[f"E{row}"] = "偏差 (ΔE≥5)" - ws[f"F{row}"] = f"{poor_count} 个" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C", "E"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # ========== 色准详细数据表格(带 xy 坐标和亮度)========== - color_patches = accuracy_final_result.get( - "color_patches", [] - ) - delta_e_values = accuracy_final_result.get( - "delta_e_values", [] - ) - - # ✅ 获取原始测量数据(包含 xy 和亮度) - color_measurements = accuracy_final_result.get( - "color_measurements", [] - ) - - if color_patches and delta_e_values: - # 表头 - headers = [ - "序号", - "颜色名称", - "x 坐标", - "y 坐标", - "亮度 (cd/m²)", - "ΔE 2000", - "等级", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - # 数据行 - for idx, (color_name, delta_e) in enumerate( - zip(color_patches, delta_e_values), start=1 - ): - # 判断等级 - if delta_e < 3: - grade = "优秀" - elif delta_e < 5: - grade = "良好" - else: - grade = "偏差" - - # ✅ 获取测量数据(x, y, 亮度) - x_val = "N/A" - y_val = "N/A" - lv_val = "N/A" - - if color_measurements and idx - 1 < len( - color_measurements - ): - measurement = color_measurements[idx - 1] - if len(measurement) >= 3: - x_val = measurement[0] - y_val = measurement[1] - lv_val = measurement[2] - - ws[f"A{row}"] = idx - ws[f"B{row}"] = color_name - ws[f"C{row}"] = x_val - ws[f"D{row}"] = y_val - ws[f"E{row}"] = lv_val - ws[f"F{row}"] = delta_e - ws[f"G{row}"] = grade - - # 数字格式 - ws[f"A{row}"].number_format = "0" - if isinstance(x_val, (int, float)): - ws[f"C{row}"].number_format = "0.0000" - if isinstance(y_val, (int, float)): - ws[f"D{row}"].number_format = "0.0000" - if isinstance(lv_val, (int, float)): - ws[f"E{row}"].number_format = "0.00" - ws[f"F{row}"].number_format = "0.00" - - for col in ["A", "B", "C", "D", "E", "F", "G"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加色准数据(含 xy 坐标和亮度)") - - # ========== 调整列宽 ========== - for col in ["A", "B", "C", "D", "E", "F", "G"]: - ws.column_dimensions[col].width = 18 - - # ========== 保存 Excel ========== - excel_path = os.path.join(result_dir, "测试数据.xlsx") - wb.save(excel_path) - - self.log_gui.log(f"✓ 已保存: 测试数据.xlsx") - self.log_gui.log("=" * 60) - - except ImportError: - self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出") - except Exception as e: - self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}") - import traceback - - self.log_gui.log(traceback.format_exc()) - - # ========== ✅ HDR Movie 测试 Excel 导出 ========== - elif ( - current_test_type == "hdr_movie" - and hasattr(self, "results") - and self.results - ): - try: - import openpyxl - from openpyxl.styles import ( - Font, - Alignment, - PatternFill, - Border, - Side, - ) - - self.log_gui.log("=" * 60) - self.log_gui.log("开始生成 HDR Movie Excel 数据报告...") - - wb = openpyxl.Workbook() - ws = wb.active - ws.title = "测试数据" - - # ========== 样式定义 ========== - title_font = Font( - name="微软雅黑", size=16, bold=True, color="FFFFFF" - ) - title_fill = PatternFill( - start_color="4472C4", end_color="4472C4", fill_type="solid" - ) - title_alignment = Alignment(horizontal="center", vertical="center") - - section_font = Font( - name="微软雅黑", size=13, bold=True, color="FFFFFF" - ) - section_fill = PatternFill( - start_color="5B9BD5", end_color="5B9BD5", fill_type="solid" - ) - section_alignment = Alignment( - horizontal="center", vertical="center" - ) - - header_font = Font( - name="微软雅黑", size=10, bold=True, color="FFFFFF" - ) - header_fill = PatternFill( - start_color="70AD47", end_color="70AD47", fill_type="solid" - ) - header_alignment = Alignment( - horizontal="center", vertical="center", wrap_text=True - ) - - data_font = Font(name="微软雅黑", size=10) - data_alignment = Alignment(horizontal="center", vertical="center") - label_font = Font(name="微软雅黑", size=10, bold=True) - - thin_border = Border( - left=Side(style="thin"), - right=Side(style="thin"), - top=Side(style="thin"), - bottom=Side(style="thin"), - ) - - # ========== 总标题 ========== - ws.merge_cells("A1:G1") - ws["A1"] = "HDR Movie 性能测试数据报告" - ws["A1"].font = title_font - ws["A1"].fill = title_fill - ws["A1"].alignment = title_alignment - ws.row_dimensions[1].height = 35 - - # ========== 测试基本信息 ========== - row = 3 - ws.merge_cells(f"A{row}:B{row}") - ws[f"A{row}"] = "📋 测试基本信息" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - - row += 1 - info_items = [ - ( - "测试时间", - datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - ), - ("测试类型", "HDR Movie"), - ] - - for label, value in info_items: - ws[f"A{row}"] = label - ws[f"B{row}"] = value - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - row += 1 - - # ========== 1. 色域数据 ========== - if "gamut" in selected_items: - rgb_data = self.results.get_intermediate_data("gamut", "rgb") - gamut_final_result = None - if "gamut" in self.results.test_items: - gamut_final_result = self.results.test_items[ - "gamut" - ].final_result - - if rgb_data and len(rgb_data) >= 3: - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "🎨 色域测试数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - if gamut_final_result: - xy_coverage = gamut_final_result.get("coverage", 0) - uv_coverage = ( - gamut_final_result.get("uv_coverage", 0) - or gamut_final_result.get("uv_space_coverage", 0) - or 0 - ) - - ws[f"A{row}"] = "参考标准" - ws[f"B{row}"] = gamut_final_result.get( - "reference", "DCI-P3" - ) - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - ws[f"A{row}"] = "XY 色域覆盖率" - ws[f"B{row}"] = f"{xy_coverage:.2f}%" - ws[f"C{row}"] = "UV 色域覆盖率" - ws[f"D{row}"] = f"{uv_coverage:.2f}%" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # RGB 数据表格 - headers = [ - "点位", - "x 坐标", - "y 坐标", - "亮度 (cd/m²)", - "", - "", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - rgb_labels = ["Red", "Green", "Blue"] - for i, result in enumerate(rgb_data[:3]): - x, y, lv = result[0], result[1], result[2] - ws[f"A{row}"] = rgb_labels[i] - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - - ws[f"B{row}"].number_format = "0.0000" - ws[f"C{row}"].number_format = "0.0000" - ws[f"D{row}"].number_format = "0.00" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加色域数据") - - # ========== 2. EOTF 数据(HDR 特有)========== - if "eotf" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if not gray_data: - gray_data = self.results.get_intermediate_data( - "eotf", "gray" - ) - - eotf_final_result = None - if "eotf" in self.results.test_items: - eotf_final_result = self.results.test_items[ - "eotf" - ].final_result - - if gray_data and len(gray_data) > 0 and eotf_final_result: - eotf_list = eotf_final_result.get("eotf", []) - L_bar_list = eotf_final_result.get("L_bar", []) - - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "📊 EOTF 曲线数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - # ✅ EOTF 统计信息(类似 Gamma 统计) - valid_eotf = [] - if eotf_list: - for item in eotf_list: - if ( - isinstance(item, (list, tuple)) - and len(item) >= 4 - ): - eotf_val = item[3] - if 0.5 < eotf_val < 5.0: - valid_eotf.append(eotf_val) - - if valid_eotf: - avg_eotf = sum(valid_eotf) / len(valid_eotf) - max_eotf = max(valid_eotf) - min_eotf = min(valid_eotf) - - ws[f"A{row}"] = "平均 EOTF" - ws[f"B{row}"] = f"{avg_eotf:.3f}" - ws[f"C{row}"] = "最大 EOTF" - ws[f"D{row}"] = f"{max_eotf:.3f}" - ws[f"E{row}"] = "最小 EOTF" - ws[f"F{row}"] = f"{min_eotf:.3f}" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = ( - label_font - if col in ["A", "C", "E"] - else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # ✅ EOTF 数据表格(与 Gamma 表格完全一致) - headers = [ - "灰阶 (%)", - "x 坐标", - "y 坐标", - "实测亮度\n(cd/m²)", - "归一化亮度\n(L_bar)", - "EOTF 值", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - total_points = len(gray_data) - for i in range(total_points - 1, -1, -1): - gray_level = ( - 100 - int(i * 100 / (total_points - 1)) - if total_points > 1 - else 0 - ) - - x, y, lv = ( - gray_data[i][0], - gray_data[i][1], - gray_data[i][2], - ) - - L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0 - - eotf_val = None - if ( - i < len(eotf_list) - and isinstance(eotf_list[i], (list, tuple)) - and len(eotf_list[i]) >= 4 - ): - eotf_val = eotf_list[i][3] - - ws[f"A{row}"] = gray_level - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - ws[f"E{row}"] = L_bar_val - - if eotf_val is not None and 0.5 < eotf_val < 5.0: - ws[f"F{row}"] = eotf_val - ws[f"F{row}"].number_format = "0.000" - else: - ws[f"F{row}"] = "N/A" - - ws[f"A{row}"].number_format = "0" - ws[f"B{row}"].number_format = "0.0000" - ws[f"C{row}"].number_format = "0.0000" - ws[f"D{row}"].number_format = "0.00" - ws[f"E{row}"].number_format = "0.0000" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加 EOTF 数据") - - # ========== 3. 色度一致性数据 ========== - if "cct" in selected_items: - gray_data = self.results.get_intermediate_data("shared", "gray") - if not gray_data: - gray_data = self.results.get_intermediate_data( - "cct", "gray" - ) - - if gray_data and len(gray_data) > 1: - gray_data_no_black = gray_data[:-1] - - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "🌈 色度一致性数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - x_coords = [d[0] for d in gray_data_no_black] - y_coords = [d[1] for d in gray_data_no_black] - - ws[f"A{row}"] = "x 坐标范围" - ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}" - ws[f"C{row}"] = "y 坐标范围" - ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - headers = [ - "灰阶 (%)", - "x 坐标", - "y 坐标", - "亮度 (cd/m²)", - "", - "", - "", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - total_points = len(gray_data) - for i in range(len(gray_data_no_black) - 1, -1, -1): - x, y, lv = ( - gray_data_no_black[i][0], - gray_data_no_black[i][1], - gray_data_no_black[i][2], - ) - gray_level = ( - 100 - int(i * 100 / (total_points - 1)) - if total_points > 1 - else 0 - ) - - ws[f"A{row}"] = gray_level - ws[f"B{row}"] = x - ws[f"C{row}"] = y - ws[f"D{row}"] = lv - - ws[f"A{row}"].number_format = "0" - ws[f"B{row}"].number_format = "0.0000" - ws[f"C{row}"].number_format = "0.0000" - ws[f"D{row}"].number_format = "0.00" - - for col in ["A", "B", "C", "D"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加色度一致性数据") - - # ========== 4. 对比度数据 ========== - if "contrast" in selected_items: - contrast_final_result = None - if "contrast" in self.results.test_items: - contrast_final_result = self.results.test_items[ - "contrast" - ].final_result - - if contrast_final_result: - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "⚫⚪ 对比度测试数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - max_lv = contrast_final_result.get("max_luminance", 0) - min_lv = contrast_final_result.get("min_luminance", 0) - contrast_ratio = contrast_final_result.get( - "contrast_ratio", 0 - ) - - info_items = [ - ("最大亮度(白场)", f"{max_lv:.2f} cd/m²"), - ("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"), - ("对比度", f"{contrast_ratio:.0f}:1"), - ] - - for label, value in info_items: - ws[f"A{row}"] = label - ws[f"B{row}"] = value - ws[f"A{row}"].font = label_font - ws[f"B{row}"].font = data_font - ws[f"A{row}"].border = thin_border - ws[f"B{row}"].border = thin_border - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加对比度数据") - - # ========== 5. 色准数据(HDR 特有)========== - if "accuracy" in selected_items: - accuracy_final_result = None - if "accuracy" in self.results.test_items: - accuracy_final_result = self.results.test_items[ - "accuracy" - ].final_result - - if accuracy_final_result: - ws.merge_cells(f"A{row}:G{row}") - ws[f"A{row}"] = "🎯 色准测试数据" - ws[f"A{row}"].font = section_font - ws[f"A{row}"].fill = section_fill - ws[f"A{row}"].alignment = section_alignment - ws.row_dimensions[row].height = 25 - row += 1 - - # 色准统计信息 - avg_delta_e = accuracy_final_result.get("avg_delta_e", 0) - max_delta_e = accuracy_final_result.get("max_delta_e", 0) - min_delta_e = accuracy_final_result.get("min_delta_e", 0) - excellent_count = accuracy_final_result.get( - "excellent_count", 0 - ) - good_count = accuracy_final_result.get("good_count", 0) - poor_count = accuracy_final_result.get("poor_count", 0) - - ws[f"A{row}"] = "平均 ΔE" - ws[f"B{row}"] = f"{avg_delta_e:.2f}" - ws[f"C{row}"] = "最大 ΔE" - ws[f"D{row}"] = f"{max_delta_e:.2f}" - ws[f"E{row}"] = "最小 ΔE" - ws[f"F{row}"] = f"{min_delta_e:.2f}" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C", "E"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # 第二行统计 - ws[f"A{row}"] = "优秀 (ΔE<3)" - ws[f"B{row}"] = f"{excellent_count} 个" - ws[f"C{row}"] = "良好 (3≤ΔE<5)" - ws[f"D{row}"] = f"{good_count} 个" - ws[f"E{row}"] = "偏差 (ΔE≥5)" - ws[f"F{row}"] = f"{poor_count} 个" - - for col in ["A", "B", "C", "D", "E", "F"]: - ws[f"{col}{row}"].font = ( - label_font if col in ["A", "C", "E"] else data_font - ) - ws[f"{col}{row}"].border = thin_border - row += 1 - - # ========== 色准详细数据表格(带 xy 坐标和亮度)========== - color_patches = accuracy_final_result.get( - "color_patches", [] - ) - delta_e_values = accuracy_final_result.get( - "delta_e_values", [] - ) - - # ✅ 获取原始测量数据(包含 xy 和亮度) - color_measurements = accuracy_final_result.get( - "color_measurements", [] - ) - - if color_patches and delta_e_values: - # 表头 - headers = [ - "序号", - "颜色名称", - "x 坐标", - "y 坐标", - "亮度 (cd/m²)", - "ΔE 2000", - "等级", - ] - for col_idx, header in enumerate(headers, start=1): - cell = ws.cell(row=row, column=col_idx) - cell.value = header - cell.font = header_font - cell.fill = header_fill - cell.alignment = header_alignment - cell.border = thin_border - row += 1 - - # 数据行 - for idx, (color_name, delta_e) in enumerate( - zip(color_patches, delta_e_values), start=1 - ): - # 判断等级 - if delta_e < 3: - grade = "优秀" - elif delta_e < 5: - grade = "良好" - else: - grade = "偏差" - - # ✅ 获取测量数据(x, y, 亮度) - x_val = "N/A" - y_val = "N/A" - lv_val = "N/A" - - if color_measurements and idx - 1 < len( - color_measurements - ): - measurement = color_measurements[idx - 1] - if len(measurement) >= 3: - x_val = measurement[0] - y_val = measurement[1] - lv_val = measurement[2] - - ws[f"A{row}"] = idx - ws[f"B{row}"] = color_name - ws[f"C{row}"] = x_val - ws[f"D{row}"] = y_val - ws[f"E{row}"] = lv_val - ws[f"F{row}"] = delta_e - ws[f"G{row}"] = grade - - # 数字格式 - ws[f"A{row}"].number_format = "0" - if isinstance(x_val, (int, float)): - ws[f"C{row}"].number_format = "0.0000" - if isinstance(y_val, (int, float)): - ws[f"D{row}"].number_format = "0.0000" - if isinstance(lv_val, (int, float)): - ws[f"E{row}"].number_format = "0.00" - ws[f"F{row}"].number_format = "0.00" - - for col in ["A", "B", "C", "D", "E", "F", "G"]: - ws[f"{col}{row}"].font = data_font - ws[f"{col}{row}"].alignment = data_alignment - ws[f"{col}{row}"].border = thin_border - - row += 1 - - row += 1 - self.log_gui.log(" ✓ 添加色准数据(含 xy 坐标和亮度)") - - # ========== 调整列宽 ========== - for col in ["A", "B", "C", "D", "E", "F", "G"]: - ws.column_dimensions[col].width = 18 - - # ========== 保存 Excel ========== - excel_path = os.path.join(result_dir, "测试数据.xlsx") - wb.save(excel_path) - - self.log_gui.log(f"✓ 已保存: 测试数据.xlsx") - self.log_gui.log("=" * 60) - - except ImportError: - self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出") - except Exception as e: - self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}") - import traceback - - self.log_gui.log(traceback.format_exc()) - - # ========== ✅ 统一的成功提示(在所有 Excel 代码之后)========== - self.log_gui.log(f"=" * 50) + # 3) 成功提示 + self.log_gui.log("=" * 50) self.log_gui.log(f"✅ 测试结果已保存到目录: {result_dir}") - self.log_gui.log(f"=" * 50) + self.log_gui.log("=" * 50) messagebox.showinfo("成功", f"测试结果已保存到目录:\n{result_dir}") except Exception as e: @@ -4630,6 +2935,515 @@ class PQAutomationApp: self.log_gui.log(traceback.format_exc()) messagebox.showerror("错误", f"保存测试结果失败: {str(e)}") + # ==================== 图片保存 ==================== + def _save_result_images(self, result_dir, current_test_type, selected_items): + """根据测试类型和已选项保存各测试图表 PNG。""" + image_specs = [ + ("gamut", "gamut_fig", "色域测试结果.png", None, True), + ("gamma", "gamma_fig", "Gamma曲线测试结果.png", + {"screen_module", "sdr_movie"}, True), + ("eotf", "eotf_fig", "EOTF曲线测试结果.png", {"hdr_movie"}, True), + ("cct", "cct_fig", "色度一致性测试结果.png", None, True), + ("contrast", "contrast_fig", "对比度测试结果.png", None, False), + ("accuracy", "accuracy_fig", "色准测试结果.png", + {"sdr_movie", "hdr_movie"}, True), + ] + for item_key, fig_attr, filename, allowed_types, default_bbox in image_specs: + if item_key not in selected_items: + continue + if allowed_types is not None and current_test_type not in allowed_types: + continue + fig = getattr(self, fig_attr, None) + if fig is None: + continue + path = os.path.join(result_dir, filename) + if default_bbox: + fig.savefig(path, dpi=300) + else: + fig.savefig(path, dpi=300, bbox_inches="tight") + self.log_gui.log(f"✓ 已保存: {filename}") + + # ==================== Excel 导出总入口 ==================== + def _export_excel_report(self, result_dir, current_test_type, selected_items): + """根据测试类型生成统一格式的 Excel 报告。""" + cfg = self._EXCEL_EXPORT_CONFIG[current_test_type] + try: + import openpyxl + from openpyxl.styles import ( + Font, + Alignment, + PatternFill, + Border, + Side, + ) + + self.log_gui.log("=" * 60) + self.log_gui.log(f"开始生成{cfg['log_prefix']} Excel 数据报告...") + + wb = openpyxl.Workbook() + ws = wb.active + ws.title = "测试数据" + + styles = self._build_excel_styles(Font, Alignment, PatternFill, Border, Side) + + self._excel_write_title(ws, styles, cfg["title"]) + row = self._excel_write_basic_info(ws, styles, 3, cfg["type_label"]) + row += 1 # 空行 + + if "gamut" in selected_items: + row = self._excel_section_gamut(ws, styles, row) + if cfg["curve_type"] == "gamma" and "gamma" in selected_items: + row = self._excel_section_curve(ws, styles, row, "gamma") + if cfg["curve_type"] == "eotf" and "eotf" in selected_items: + row = self._excel_section_curve(ws, styles, row, "eotf") + if "cct" in selected_items: + row = self._excel_section_cct(ws, styles, row) + if "contrast" in selected_items: + row = self._excel_section_contrast(ws, styles, row) + if cfg["has_accuracy"] and "accuracy" in selected_items: + row = self._excel_section_accuracy(ws, styles, row) + + for col, width in cfg["column_widths"].items(): + ws.column_dimensions[col].width = width + + excel_path = os.path.join(result_dir, "测试数据.xlsx") + wb.save(excel_path) + + self.log_gui.log("✓ 已保存: 测试数据.xlsx") + self.log_gui.log("=" * 60) + + except ImportError: + self.log_gui.log("⚠️ 未安装 openpyxl 库,跳过 Excel 导出") + self.log_gui.log(" 安装方法: pip install openpyxl") + except Exception as e: + self.log_gui.log(f"⚠️ Excel 导出失败: {str(e)}") + import traceback + + self.log_gui.log(traceback.format_exc()) + + # ==================== 样式与通用写入 ==================== + @staticmethod + def _build_excel_styles(Font, Alignment, PatternFill, Border, Side): + thin = Side(style="thin") + return { + "title_font": Font(name="微软雅黑", size=16, bold=True, color="FFFFFF"), + "title_fill": PatternFill(start_color="4472C4", end_color="4472C4", fill_type="solid"), + "title_alignment": Alignment(horizontal="center", vertical="center"), + "section_font": Font(name="微软雅黑", size=13, bold=True, color="FFFFFF"), + "section_fill": PatternFill(start_color="5B9BD5", end_color="5B9BD5", fill_type="solid"), + "section_alignment": Alignment(horizontal="center", vertical="center"), + "header_font": Font(name="微软雅黑", size=10, bold=True, color="FFFFFF"), + "header_fill": PatternFill(start_color="70AD47", end_color="70AD47", fill_type="solid"), + "header_alignment": Alignment(horizontal="center", vertical="center", wrap_text=True), + "data_font": Font(name="微软雅黑", size=10), + "data_alignment": Alignment(horizontal="center", vertical="center"), + "label_font": Font(name="微软雅黑", size=10, bold=True), + "thin_border": Border(left=thin, right=thin, top=thin, bottom=thin), + } + + @staticmethod + def _excel_write_title(ws, styles, title_text): + ws.merge_cells("A1:G1") + ws["A1"] = title_text + ws["A1"].font = styles["title_font"] + ws["A1"].fill = styles["title_fill"] + ws["A1"].alignment = styles["title_alignment"] + ws.row_dimensions[1].height = 35 + + @staticmethod + def _excel_write_basic_info(ws, styles, row, type_label): + ws.merge_cells(f"A{row}:B{row}") + ws[f"A{row}"] = "📋 测试基本信息" + ws[f"A{row}"].font = styles["section_font"] + ws[f"A{row}"].fill = styles["section_fill"] + ws[f"A{row}"].alignment = styles["section_alignment"] + ws.row_dimensions[row].height = 25 + row += 1 + + info_items = [ + ("测试时间", datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")), + ("测试类型", type_label), + ] + for label, value in info_items: + ws[f"A{row}"] = label + ws[f"B{row}"] = value + ws[f"A{row}"].font = styles["label_font"] + ws[f"B{row}"].font = styles["data_font"] + ws[f"A{row}"].border = styles["thin_border"] + ws[f"B{row}"].border = styles["thin_border"] + row += 1 + return row + + @staticmethod + def _excel_write_section_header(ws, styles, row, text, span="A{r}:G{r}"): + ws.merge_cells(span.format(r=row)) + ws[f"A{row}"] = text + ws[f"A{row}"].font = styles["section_font"] + ws[f"A{row}"].fill = styles["section_fill"] + ws[f"A{row}"].alignment = styles["section_alignment"] + ws.row_dimensions[row].height = 25 + return row + 1 + + @staticmethod + def _excel_write_headers(ws, styles, row, headers): + for col_idx, header in enumerate(headers, start=1): + cell = ws.cell(row=row, column=col_idx) + cell.value = header + cell.font = styles["header_font"] + cell.fill = styles["header_fill"] + cell.alignment = styles["header_alignment"] + cell.border = styles["thin_border"] + return row + 1 + + @staticmethod + def _excel_apply_row_style(ws, row, cols, styles, label_cols=()): + """对一整行的指定列应用数据样式;label_cols 中的列使用粗体标签字体。""" + for col in cols: + cell = ws[f"{col}{row}"] + cell.font = styles["label_font"] if col in label_cols else styles["data_font"] + cell.border = styles["thin_border"] + + @staticmethod + def _excel_apply_data_row(ws, row, cols, styles): + for col in cols: + cell = ws[f"{col}{row}"] + cell.font = styles["data_font"] + cell.alignment = styles["data_alignment"] + cell.border = styles["thin_border"] + + # ==================== 分区:色域 ==================== + def _excel_section_gamut(self, ws, styles, row): + rgb_data = self.results.get_intermediate_data("gamut", "rgb") + gamut_final_result = None + if "gamut" in self.results.test_items: + gamut_final_result = self.results.test_items["gamut"].final_result + + if not (rgb_data and len(rgb_data) >= 3): + return row + + row = self._excel_write_section_header(ws, styles, row, "🎨 色域测试数据") + + if gamut_final_result: + xy_coverage = gamut_final_result.get("coverage", 0) + uv_coverage = ( + gamut_final_result.get("uv_coverage", 0) + or gamut_final_result.get("uv_space_coverage", 0) + or gamut_final_result.get("coverage_uv", 0) + or 0 + ) + + ws[f"A{row}"] = "参考标准" + ws[f"B{row}"] = gamut_final_result.get("reference", "DCI-P3") + ws[f"A{row}"].font = styles["label_font"] + ws[f"B{row}"].font = styles["data_font"] + ws[f"A{row}"].border = styles["thin_border"] + ws[f"B{row}"].border = styles["thin_border"] + row += 1 + + ws[f"A{row}"] = "XY 色域覆盖率" + ws[f"B{row}"] = f"{xy_coverage:.2f}%" + ws[f"C{row}"] = "UV 色域覆盖率" + ws[f"D{row}"] = f"{uv_coverage:.2f}%" + self._excel_apply_row_style( + ws, row, ["A", "B", "C", "D"], styles, label_cols=("A", "C") + ) + row += 1 + + row = self._excel_write_headers( + ws, styles, row, + ["点位", "x 坐标", "y 坐标", "亮度 (cd/m²)", "", "", ""], + ) + + rgb_labels = ["Red", "Green", "Blue"] + for i, result in enumerate(rgb_data[:3]): + x, y, lv = result[0], result[1], result[2] + ws[f"A{row}"] = rgb_labels[i] + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + self._excel_apply_data_row(ws, row, ["A", "B", "C", "D"], styles) + row += 1 + + self.log_gui.log(" ✓ 添加色域数据") + return row + 1 + + # ==================== 分区:Gamma / EOTF(曲线类) ==================== + def _excel_section_curve(self, ws, styles, row, curve_type): + """curve_type: 'gamma' 或 'eotf'。两者数据结构一致,仅 key/显示名不同。""" + if curve_type == "gamma": + section_title = "📊 Gamma 曲线数据" + label_prefix = "Gamma" + data_key = "gamma" + value_header = "Gamma 值" + else: + section_title = "📊 EOTF 曲线数据" + label_prefix = "EOTF" + data_key = "eotf" + value_header = "EOTF 值" + + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data(curve_type, "gray") + + curve_final_result = None + if curve_type in self.results.test_items: + curve_final_result = self.results.test_items[curve_type].final_result + + if not (gray_data and len(gray_data) > 0 and curve_final_result): + return row + + value_list = curve_final_result.get(data_key, []) + L_bar_list = curve_final_result.get("L_bar", []) + + row = self._excel_write_section_header(ws, styles, row, section_title) + + # 统计 + valid_values = [ + item[3] + for item in value_list + if isinstance(item, (list, tuple)) + and len(item) >= 4 + and 0.5 < item[3] < 5.0 + ] + if valid_values: + avg_v = sum(valid_values) / len(valid_values) + ws[f"A{row}"] = f"平均 {label_prefix}" + ws[f"B{row}"] = f"{avg_v:.3f}" + ws[f"C{row}"] = f"最大 {label_prefix}" + ws[f"D{row}"] = f"{max(valid_values):.3f}" + ws[f"E{row}"] = f"最小 {label_prefix}" + ws[f"F{row}"] = f"{min(valid_values):.3f}" + self._excel_apply_row_style( + ws, row, ["A", "B", "C", "D", "E", "F"], + styles, label_cols=("A", "C", "E"), + ) + row += 1 + + # 数据表 + row = self._excel_write_headers( + ws, styles, row, + ["灰阶 (%)", "x 坐标", "y 坐标", "实测亮度\n(cd/m²)", + "归一化亮度\n(L_bar)", value_header, ""], + ) + + total_points = len(gray_data) + for i in range(total_points - 1, -1, -1): + gray_level = ( + 100 - int(i * 100 / (total_points - 1)) if total_points > 1 else 0 + ) + x, y, lv = gray_data[i][0], gray_data[i][1], gray_data[i][2] + L_bar_val = L_bar_list[i] if i < len(L_bar_list) else 0 + + val = None + if ( + i < len(value_list) + and isinstance(value_list[i], (list, tuple)) + and len(value_list[i]) >= 4 + ): + val = value_list[i][3] + + ws[f"A{row}"] = gray_level + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + ws[f"E{row}"] = L_bar_val + + if val is not None and 0.5 < val < 5.0: + ws[f"F{row}"] = val + ws[f"F{row}"].number_format = "0.000" + else: + ws[f"F{row}"] = "N/A" + + ws[f"A{row}"].number_format = "0" + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + ws[f"E{row}"].number_format = "0.0000" + self._excel_apply_data_row( + ws, row, ["A", "B", "C", "D", "E", "F"], styles + ) + row += 1 + + self.log_gui.log(f" ✓ 添加 {label_prefix} 数据") + return row + 1 + + # ==================== 分区:色度一致性 ==================== + def _excel_section_cct(self, ws, styles, row): + gray_data = self.results.get_intermediate_data("shared", "gray") + if not gray_data: + gray_data = self.results.get_intermediate_data("cct", "gray") + if not (gray_data and len(gray_data) > 1): + return row + + gray_data_no_black = gray_data[:-1] + + row = self._excel_write_section_header(ws, styles, row, "🌈 色度一致性数据") + + x_coords = [d[0] for d in gray_data_no_black] + y_coords = [d[1] for d in gray_data_no_black] + ws[f"A{row}"] = "x 坐标范围" + ws[f"B{row}"] = f"{min(x_coords):.4f} ~ {max(x_coords):.4f}" + ws[f"C{row}"] = "y 坐标范围" + ws[f"D{row}"] = f"{min(y_coords):.4f} ~ {max(y_coords):.4f}" + self._excel_apply_row_style( + ws, row, ["A", "B", "C", "D"], styles, label_cols=("A", "C") + ) + row += 1 + + row = self._excel_write_headers( + ws, styles, row, + ["灰阶 (%)", "x 坐标", "y 坐标", "亮度 (cd/m²)", "", "", ""], + ) + + total_points = len(gray_data) + for i in range(len(gray_data_no_black) - 1, -1, -1): + x, y, lv = ( + gray_data_no_black[i][0], + gray_data_no_black[i][1], + gray_data_no_black[i][2], + ) + gray_level = ( + 100 - int(i * 100 / (total_points - 1)) if total_points > 1 else 0 + ) + ws[f"A{row}"] = gray_level + ws[f"B{row}"] = x + ws[f"C{row}"] = y + ws[f"D{row}"] = lv + ws[f"A{row}"].number_format = "0" + ws[f"B{row}"].number_format = "0.0000" + ws[f"C{row}"].number_format = "0.0000" + ws[f"D{row}"].number_format = "0.00" + self._excel_apply_data_row(ws, row, ["A", "B", "C", "D"], styles) + row += 1 + + self.log_gui.log(" ✓ 添加色度一致性数据") + return row + 1 + + # ==================== 分区:对比度 ==================== + def _excel_section_contrast(self, ws, styles, row): + contrast_final_result = None + if "contrast" in self.results.test_items: + contrast_final_result = self.results.test_items["contrast"].final_result + if not contrast_final_result: + return row + + row = self._excel_write_section_header(ws, styles, row, "⚫⚪ 对比度测试数据") + + max_lv = contrast_final_result.get("max_luminance", 0) + min_lv = contrast_final_result.get("min_luminance", 0) + contrast_ratio = contrast_final_result.get("contrast_ratio", 0) + info_items = [ + ("最大亮度(白场)", f"{max_lv:.2f} cd/m²"), + ("最小亮度(黑场)", f"{min_lv:.4f} cd/m²"), + ("对比度", f"{contrast_ratio:.0f}:1"), + ] + for label, value in info_items: + ws[f"A{row}"] = label + ws[f"B{row}"] = value + ws[f"A{row}"].font = styles["label_font"] + ws[f"B{row}"].font = styles["data_font"] + ws[f"A{row}"].border = styles["thin_border"] + ws[f"B{row}"].border = styles["thin_border"] + row += 1 + + self.log_gui.log(" ✓ 添加对比度数据") + return row + 1 + + # ==================== 分区:色准(仅 SDR/HDR) ==================== + def _excel_section_accuracy(self, ws, styles, row): + accuracy_final_result = None + if "accuracy" in self.results.test_items: + accuracy_final_result = self.results.test_items["accuracy"].final_result + if not accuracy_final_result: + return row + + row = self._excel_write_section_header(ws, styles, row, "🎯 色准测试数据") + + avg_delta_e = accuracy_final_result.get("avg_delta_e", 0) + max_delta_e = accuracy_final_result.get("max_delta_e", 0) + min_delta_e = accuracy_final_result.get("min_delta_e", 0) + excellent_count = accuracy_final_result.get("excellent_count", 0) + good_count = accuracy_final_result.get("good_count", 0) + poor_count = accuracy_final_result.get("poor_count", 0) + + ws[f"A{row}"] = "平均 ΔE" + ws[f"B{row}"] = f"{avg_delta_e:.2f}" + ws[f"C{row}"] = "最大 ΔE" + ws[f"D{row}"] = f"{max_delta_e:.2f}" + ws[f"E{row}"] = "最小 ΔE" + ws[f"F{row}"] = f"{min_delta_e:.2f}" + self._excel_apply_row_style( + ws, row, ["A", "B", "C", "D", "E", "F"], + styles, label_cols=("A", "C", "E"), + ) + row += 1 + + ws[f"A{row}"] = "优秀 (ΔE<3)" + ws[f"B{row}"] = f"{excellent_count} 个" + ws[f"C{row}"] = "良好 (3≤ΔE<5)" + ws[f"D{row}"] = f"{good_count} 个" + ws[f"E{row}"] = "偏差 (ΔE≥5)" + ws[f"F{row}"] = f"{poor_count} 个" + self._excel_apply_row_style( + ws, row, ["A", "B", "C", "D", "E", "F"], + styles, label_cols=("A", "C", "E"), + ) + row += 1 + + color_patches = accuracy_final_result.get("color_patches", []) + delta_e_values = accuracy_final_result.get("delta_e_values", []) + color_measurements = accuracy_final_result.get("color_measurements", []) + + if color_patches and delta_e_values: + row = self._excel_write_headers( + ws, styles, row, + ["序号", "颜色名称", "x 坐标", "y 坐标", + "亮度 (cd/m²)", "ΔE 2000", "等级"], + ) + for idx, (color_name, delta_e) in enumerate( + zip(color_patches, delta_e_values), start=1 + ): + if delta_e < 3: + grade = "优秀" + elif delta_e < 5: + grade = "良好" + else: + grade = "偏差" + + x_val, y_val, lv_val = "N/A", "N/A", "N/A" + if color_measurements and idx - 1 < len(color_measurements): + m = color_measurements[idx - 1] + if len(m) >= 3: + x_val, y_val, lv_val = m[0], m[1], m[2] + + ws[f"A{row}"] = idx + ws[f"B{row}"] = color_name + ws[f"C{row}"] = x_val + ws[f"D{row}"] = y_val + ws[f"E{row}"] = lv_val + ws[f"F{row}"] = delta_e + ws[f"G{row}"] = grade + + ws[f"A{row}"].number_format = "0" + if isinstance(x_val, (int, float)): + ws[f"C{row}"].number_format = "0.0000" + if isinstance(y_val, (int, float)): + ws[f"D{row}"].number_format = "0.0000" + if isinstance(lv_val, (int, float)): + ws[f"E{row}"].number_format = "0.00" + ws[f"F{row}"].number_format = "0.00" + + self._excel_apply_data_row( + ws, row, ["A", "B", "C", "D", "E", "F", "G"], styles + ) + row += 1 + + self.log_gui.log(" ✓ 添加色准数据(含 xy 坐标和亮度)") + return row + 1 + new_pq_results = _run_new_pq_results run_test = _run_run_test run_screen_module_test = _run_run_screen_module_test @@ -4666,6 +3480,8 @@ class PQAutomationApp: on_custom_template_test_completed = _run_on_custom_template_test_completed get_current_test_result = _run_get_current_test_result on_test_error = _run_on_test_error + + update_chart_tabs_state = _cf_update_chart_tabs_state def get_test_type_name(self, test_type): """获取测试类型的显示名称"""