294 lines
10 KiB
Python
294 lines
10 KiB
Python
|
|
|
|||
|
|
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]:
|
|||
|
|
"""
|
|||
|
|
调用版面解析API:https://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
|
|||
|
|
}
|