pdfchecking / app.py
felixkky's picture
Update app.py
174dfc7 verified
Raw
History Blame
5.9 kB
import gradio as gr
import fitz # PyMuPDF
import base64
import json
from io import BytesIO
from PIL import Image, ImageDraw
from mistralai import Mistral
def process_document(file, api_key, progress=gr.Progress()):
if not api_key:
return None, "錯誤:請提供 Mistral API 金鑰。"
if not file:
return None, "錯誤:請上傳檔案。"
progress(0.05, desc="正在初始化 Mistral 客戶端...")
client = Mistral(api_key=api_key)
images = []
# 1. 處理檔案上傳
file_name = file.name.lower()
try:
progress(0.1, desc="正在讀取與轉換圖片...")
if file_name.endswith('.pdf'):
doc = fitz.open(file.name)
for i in range(len(doc)):
page = doc.load_page(i)
# 設定 DPI,保持清晰度
pix = page.get_pixmap(dpi=150)
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
images.append(img)
else:
img = Image.open(file.name).convert("RGB")
images.append(img)
except Exception as e:
return None, f"檔案讀取錯誤:{str(e)}"
drawn_images = []
all_json_results = []
# 2. 升級版:極度嚴格的 Prompt (防幻覺、防抓印刷體)
prompt = """
你是一個專精於「中文手寫字跡辨識」的 AI 專家。
這張圖片是一份學生的考卷,上面同時包含「電腦打字的印刷體題目」以及「學生手寫的作答字跡」。
學生的字跡混合了繁體中文與簡體中文,且字體較為潦草,通常寫在橫線上或空白處。
你的任務是:
1. **絕對忽略印刷體**:只尋找並提取「手寫字」。絕對不要轉錄任何電腦印刷體的題目內容!
2. **精準辨識(拒絕幻覺)**:請盡最大努力辨識學生的手寫字(包含簡體與繁體)。如果你遇到真的看不懂的草字,請嚴格使用「[無法辨識]」四個字代替。絕對不要自己發明、猜測或聯想詞彙。
3. **輸出邊界框**:給出該段手寫文字在圖片上的相對邊界框 [ymin, xmin, ymax, xmax](範圍 0.0 到 1.0)。
請嚴格以 JSON 格式輸出:
{
"handwriting": [
{"text": "第一段手寫內容", "box": [0.12, 0.34, 0.15, 0.55]},
{"text": "第二段手寫內容", "box": [0.60, 0.10, 0.65, 0.80]}
]
}
"""
for page_num, img in enumerate(images):
progress(0.3, desc=f"處理第 {page_num + 1} 頁:準備圖片資料...")
buffered = BytesIO()
img.save(buffered, format="JPEG")
img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
base64_image = f"data:image/jpeg;base64,{img_b64}"
try:
progress(0.4, desc=f"第 {page_num + 1} 頁:Pixtral-large-latest 運算中 (約 30~60 秒)...")
response = client.chat.complete(
model="pixtral-large-latest",
messages=[
{
"role": "user",
"content": [
{"type": "text", "text": prompt},
{"type": "image_url", "image_url": base64_image}
]
}
],
response_format={"type": "json_object"}
)
progress(0.8, desc=f"第 {page_num + 1} 頁:正在解析 JSON 並畫框...")
content = response.choices[0].message.content
# 清理模型可能多加的 Markdown JSON 標記 (防錯機制)
clean_content = content.replace("```json", "").replace("```", "").strip()
data = json.loads(clean_content)
all_json_results.append({f"Page {page_num + 1}": data})
draw = ImageDraw.Draw(img)
width, height = img.size
for item in data.get("handwriting", []):
box = item.get("box")
if box and len(box) == 4:
ymin, xmin, ymax, xmax = box
abs_xmin = xmin * width
abs_ymin = ymin * height
abs_xmax = xmax * width
abs_ymax = ymax * height
# 畫紅框
draw.rectangle([abs_xmin, abs_ymin, abs_xmax, abs_ymax], outline="red", width=3)
drawn_images.append(img)
except Exception as e:
error_msg = f"API 或解析發生錯誤: {str(e)}"
all_json_results.append({f"Page {page_num + 1} Error": error_msg})
drawn_images.append(img)
progress(1.0, desc="處理完成!")
return drawn_images, json.dumps(all_json_results, ensure_ascii=False, indent=2)
# --- Gradio 介面設計 ---
with gr.Blocks(title="手寫字偵測與畫框 Demo") as demo:
gr.Markdown("## 📝 手寫字偵測與畫框 Demo (Pixtral-large-latest)")
gr.Markdown("上傳考卷 PDF 或圖片,系統會自動排除印刷體,找出所有的「手寫文字」並用紅框標示出來。")
with gr.Row():
with gr.Column():
api_key_input = gr.Textbox(label="Mistral API Key", type="password")
file_input = gr.File(label="上傳 PDF 或圖片", file_types=[".pdf", ".jpg", ".png", ".jpeg"])
submit_btn = gr.Button("開始偵測畫框", variant="primary")
with gr.Column():
output_gallery = gr.Gallery(label="畫框結果預覽", columns=1, height="auto")
output_json = gr.JSON(label="模型輸出的 JSON (文字與座標)")
submit_btn.click(
fn=process_document,
inputs=[file_input, api_key_input],
outputs=[output_gallery, output_json]
)
if __name__ == "__main__":
demo.launch()