ForcePilot/scripts/paddlestructure/paddlex_layout_parser.py

294 lines
10 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.

import requests
import json
import base64
import os
import time
from typing import Optional, Dict, Any
class PaddleXLayoutParser:
"""PaddleX 版面解析服务客户端"""
def __init__(self, base_url: str = "http://localhost:8080"):
self.base_url = base_url.rstrip('/')
self.endpoint = f"{self.base_url}/layout-parsing"
def encode_file_to_base64(self, file_path: str) -> str:
with open(file_path, 'rb') as file:
encoded = base64.b64encode(file.read()).decode('utf-8')
return encoded
def _process_file_input(self, file_input: str) -> str:
# 检查是否为本地文件路径
if os.path.exists(file_input):
print(f"📁 检测到本地文件: {file_input}")
print(f"📏 文件大小: {os.path.getsize(file_input) / 1024 / 1024:.2f} MB")
try:
# 将本地文件编码为Base64
encoded_content = self.encode_file_to_base64(file_input)
print(f"✅ 文件已编码为Base64长度: {len(encoded_content)} 字符")
return encoded_content
except Exception as e:
print(f"❌ 文件编码失败: {e}")
raise
# 检查是否为URL
elif file_input.startswith(('http://', 'https://')):
print(f"🌐 检测到URL: {file_input}")
return file_input
# 否则假设为Base64编码内容
else:
print(f"📝 假设为Base64编码内容长度: {len(file_input)} 字符")
return file_input
def layout_parsing(self,
file_input: str,
file_type: Optional[int] = None,
use_textline_orientation: Optional[bool] = None,
use_seal_recognition: Optional[bool] = None,
use_table_recognition: Optional[bool] = None,
use_formula_recognition: Optional[bool] = None,
use_chart_recognition: Optional[bool] = None,
use_region_detection: Optional[bool] = None,
layout_threshold: Optional[float] = None,
layout_nms: Optional[bool] = None,
use_doc_orientation_classify: Optional[bool] = True,
use_doc_unwarping: Optional[bool] = False,
use_wired_table_cells_trans_to_html: Optional[bool] = True, # 是否启用无有线表单元格检测结果直转HTML默认False启用则直接基于有线表单元格检测结果的几何关系构建HTML。
**kwargs) -> Dict[str, Any]:
"""
调用版面解析APIhttps://paddlepaddle.github.io/PaddleX/latest/pipeline_usage/tutorials/ocr_pipelines/PP-StructureV3.html#22-python
"""
# 处理文件输入:检测是否为本地文件路径
processed_file_input = self._process_file_input(file_input)
payload = {"file": processed_file_input}
# 添加可选参数
optional_params = {
"fileType": file_type,
"useDocOrientationClassify": use_doc_orientation_classify,
"useDocUnwarping": use_doc_unwarping,
"useTextlineOrientation": use_textline_orientation,
"useSealRecognition": use_seal_recognition,
"useTableRecognition": use_table_recognition,
"useFormulaRecognition": use_formula_recognition,
"useChartRecognition": use_chart_recognition,
"useRegionDetection": use_region_detection,
"layoutThreshold": layout_threshold,
"layoutNms": layout_nms,
"useWiredTableCellsTransToHtml": use_wired_table_cells_trans_to_html,
}
# 添加非空参数
for key, value in optional_params.items():
if value is not None:
payload[key] = value
# 添加其他kwargs参数
for key, value in kwargs.items():
if value is not None:
payload[key] = value
try:
response = requests.post(
self.endpoint,
json=payload,
headers={"Content-Type": "application/json"},
timeout=300
)
if response.status_code == 200:
result = response.json()
print("✅ 请求成功!")
return result
else:
print("❌ 请求失败!")
try:
error_result = response.json()
print(f"错误信息: {json.dumps(error_result, indent=2, ensure_ascii=False)}")
return error_result
except:
print(f"响应内容: {response.text}")
return {"error": response.text, "status_code": response.status_code}
except requests.exceptions.RequestException as e:
print(f"❌ 网络请求异常: {e}")
return {"error": str(e)}
except Exception as e:
print(f"❌ 其他异常: {e}")
return {"error": str(e)}
def _parse_recognition_result(api_result: Dict[str, Any], file_path: str) -> Dict[str, Any]:
# 基本信息
parsed_result = {
"success": True,
"file_path": file_path,
"file_name": os.path.basename(file_path),
"log_id": api_result.get("logId"),
"total_pages": 0,
"pages": [],
"full_text": "",
"summary": {}
}
result_data = api_result.get("result", {})
layout_results = result_data.get("layoutParsingResults", [])
data_info = result_data.get("dataInfo", {})
# 数据信息
parsed_result["total_pages"] = len(layout_results)
parsed_result["document_info"] = {
"type": data_info.get("type", "unknown"),
"total_pages": data_info.get("numPages", len(layout_results)),
"page_dimensions": data_info.get("pages", [])
}
# 统计信息
total_elements = 0
total_tables = 0
total_formulas = 0
total_charts = 0
total_seals = 0
all_text_content = []
# 解析每页结果
for page_index, page_result in enumerate(layout_results):
page_info = {
"page_number": page_index + 1,
"content": {},
"statistics": {}
}
# Markdown内容
if "markdown" in page_result:
markdown = page_result["markdown"]
page_info["content"]["markdown_text"] = markdown.get("text", "")
page_info["content"]["images"] = list(markdown.get("images", {}).keys())
page_info["content"]["is_paragraph_start"] = markdown.get("isStart", False)
page_info["content"]["is_paragraph_end"] = markdown.get("isEnd", False)
# 收集文本内容
if markdown.get("text"):
all_text_content.append(markdown["text"])
# 详细识别结果
if "prunedResult" in page_result:
pruned = page_result["prunedResult"]
# 版面检测
layout_detection = pruned.get("layout_detection", [])
page_info["statistics"]["layout_elements"] = len(layout_detection)
total_elements += len(layout_detection)
# OCR结果
ocr_result = pruned.get("ocr_result", [])
page_info["statistics"]["ocr_elements"] = len(ocr_result)
# 表格识别
table_result = pruned.get("table_result", [])
page_info["statistics"]["tables"] = len(table_result)
total_tables += len(table_result)
# 公式识别
formula_result = pruned.get("formula_result", [])
page_info["statistics"]["formulas"] = len(formula_result)
total_formulas += len(formula_result)
# 图表识别
chart_result = pruned.get("chart_result", [])
page_info["statistics"]["charts"] = len(chart_result)
total_charts += len(chart_result)
# 印章识别
seal_result = pruned.get("seal_result", [])
page_info["statistics"]["seals"] = len(seal_result)
total_seals += len(seal_result)
# 详细元素信息
page_info["content"]["layout_elements"] = layout_detection
page_info["content"]["ocr_elements"] = ocr_result
page_info["content"]["tables"] = table_result
page_info["content"]["formulas"] = formula_result
page_info["content"]["charts"] = chart_result
page_info["content"]["seals"] = seal_result
parsed_result["pages"].append(page_info)
# 汇总全文内容
parsed_result["full_text"] = "\n\n".join(all_text_content)
# 汇总统计信息
parsed_result["summary"] = {
"total_elements": total_elements,
"total_tables": total_tables,
"total_formulas": total_formulas,
"total_charts": total_charts,
"total_seals": total_seals,
"total_characters": len(parsed_result["full_text"]),
"average_elements_per_page": round(total_elements / max(1, len(layout_results)), 2)
}
return parsed_result
def analyze_document(file_path: str) -> Dict[str, Any]:
# 检查文件是否存在
if not os.path.exists(file_path):
return {
"success": False,
"error": f"文件不存在: {file_path}",
"file_path": file_path
}
# 初始化客户端
client = PaddleXLayoutParser()
# 判断文件类型
file_ext = os.path.splitext(file_path)[1].lower()
if file_ext == '.pdf':
file_type = 0
elif file_ext in ['.jpg', '.jpeg', '.png', '.bmp', '.tiff', '.tif']:
file_type = 1
else:
return {
"success": False,
"error": f"不支持的文件类型: {file_ext}",
"file_path": file_path
}
print(f"📄 开始分析文档: {os.path.basename(file_path)}")
print(f"📏 文件大小: {os.path.getsize(file_path) / 1024 / 1024:.2f} MB")
print(f"📋 文件类型: {'PDF' if file_type == 0 else '图片'}")
try:
# 调用API进行识别
result = client.layout_parsing(file_input=file_path, file_type=file_type)
# 检查API调用是否成功
if result.get("errorCode") != 0:
return {
"success": False,
"error": result.get("errorMsg", "API调用失败"),
"file_path": file_path,
"raw_result": result
}
# 解析结果
analysis_result = _parse_recognition_result(result, file_path)
return analysis_result
except Exception as e:
return {
"success": False,
"error": f"处理异常: {str(e)}",
"file_path": file_path
}