Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -4,9 +4,8 @@ import base64
|
|
| 4 |
import json
|
| 5 |
from io import BytesIO
|
| 6 |
from PIL import Image, ImageDraw
|
| 7 |
-
from mistralai
|
| 8 |
|
| 9 |
-
# 在參數中加入 progress=gr.Progress()
|
| 10 |
def process_document(file, api_key, progress=gr.Progress()):
|
| 11 |
if not api_key:
|
| 12 |
return None, "錯誤:請提供 Mistral API 金鑰。"
|
|
@@ -25,6 +24,7 @@ def process_document(file, api_key, progress=gr.Progress()):
|
|
| 25 |
doc = fitz.open(file.name)
|
| 26 |
for i in range(len(doc)):
|
| 27 |
page = doc.load_page(i)
|
|
|
|
| 28 |
pix = page.get_pixmap(dpi=150)
|
| 29 |
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
|
| 30 |
images.append(img)
|
|
@@ -37,28 +37,35 @@ def process_document(file, api_key, progress=gr.Progress()):
|
|
| 37 |
drawn_images = []
|
| 38 |
all_json_results = []
|
| 39 |
|
|
|
|
| 40 |
prompt = """
|
| 41 |
-
你是一個專
|
| 42 |
-
|
| 43 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
{
|
| 45 |
"handwriting": [
|
| 46 |
-
{"text": "
|
| 47 |
-
{"text": "
|
| 48 |
]
|
| 49 |
}
|
| 50 |
"""
|
| 51 |
|
| 52 |
for page_num, img in enumerate(images):
|
| 53 |
-
progress(0.3, desc=f"處理第 {page_num + 1} 頁:準備
|
| 54 |
buffered = BytesIO()
|
| 55 |
img.save(buffered, format="JPEG")
|
| 56 |
img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
| 57 |
base64_image = f"data:image/jpeg;base64,{img_b64}"
|
| 58 |
|
| 59 |
try:
|
| 60 |
-
|
| 61 |
-
progress(0.4, desc=f"第 {page_num + 1} 頁:正在等待 Pixtral 大模型運算與分析 (大約需要 30~60 秒,請耐心等候)...")
|
| 62 |
response = client.chat.complete(
|
| 63 |
model="pixtral-large-latest",
|
| 64 |
messages=[
|
|
@@ -73,12 +80,15 @@ def process_document(file, api_key, progress=gr.Progress()):
|
|
| 73 |
response_format={"type": "json_object"}
|
| 74 |
)
|
| 75 |
|
| 76 |
-
progress(0.8, desc=f"第 {page_num + 1} 頁:
|
| 77 |
content = response.choices[0].message.content
|
| 78 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 79 |
all_json_results.append({f"Page {page_num + 1}": data})
|
| 80 |
|
| 81 |
-
progress(0.9, desc=f"第 {page_num + 1} 頁:正在圖片上繪製紅色邊界框...")
|
| 82 |
draw = ImageDraw.Draw(img)
|
| 83 |
width, height = img.size
|
| 84 |
|
|
@@ -90,27 +100,23 @@ def process_document(file, api_key, progress=gr.Progress()):
|
|
| 90 |
abs_ymin = ymin * height
|
| 91 |
abs_xmax = xmax * width
|
| 92 |
abs_ymax = ymax * height
|
|
|
|
| 93 |
draw.rectangle([abs_xmin, abs_ymin, abs_xmax, abs_ymax], outline="red", width=3)
|
| 94 |
|
| 95 |
drawn_images.append(img)
|
| 96 |
|
| 97 |
except Exception as e:
|
| 98 |
-
|
| 99 |
-
error_msg = f"API 發生錯誤: {str(e)}"
|
| 100 |
-
print(error_msg) # 顯示在終端機
|
| 101 |
all_json_results.append({f"Page {page_num + 1} Error": error_msg})
|
| 102 |
drawn_images.append(img)
|
| 103 |
|
| 104 |
progress(1.0, desc="處理完成!")
|
| 105 |
return drawn_images, json.dumps(all_json_results, ensure_ascii=False, indent=2)
|
| 106 |
|
| 107 |
-
# --- Gradio 介面設計 (保持不變) ---
|
| 108 |
-
# ... 後面的程式碼不用動 ...
|
| 109 |
-
|
| 110 |
# --- Gradio 介面設計 ---
|
| 111 |
with gr.Blocks(title="手寫字偵測與畫框 Demo") as demo:
|
| 112 |
gr.Markdown("## 📝 手寫字偵測與畫框 Demo (Pixtral-large-latest)")
|
| 113 |
-
gr.Markdown("上傳考卷 PDF 或圖片,系統會找出所有的「手寫文字」並用紅框標示出來。")
|
| 114 |
|
| 115 |
with gr.Row():
|
| 116 |
with gr.Column():
|
|
|
|
| 4 |
import json
|
| 5 |
from io import BytesIO
|
| 6 |
from PIL import Image, ImageDraw
|
| 7 |
+
from mistralai import Mistral
|
| 8 |
|
|
|
|
| 9 |
def process_document(file, api_key, progress=gr.Progress()):
|
| 10 |
if not api_key:
|
| 11 |
return None, "錯誤:請提供 Mistral API 金鑰。"
|
|
|
|
| 24 |
doc = fitz.open(file.name)
|
| 25 |
for i in range(len(doc)):
|
| 26 |
page = doc.load_page(i)
|
| 27 |
+
# 設定 DPI,保持清晰度
|
| 28 |
pix = page.get_pixmap(dpi=150)
|
| 29 |
img = Image.frombytes("RGB", [pix.width, pix.height], pix.samples)
|
| 30 |
images.append(img)
|
|
|
|
| 37 |
drawn_images = []
|
| 38 |
all_json_results = []
|
| 39 |
|
| 40 |
+
# 2. 升級版:極度嚴格的 Prompt (防幻覺、防抓印刷體)
|
| 41 |
prompt = """
|
| 42 |
+
你是一個專精於「中文手寫字跡辨識」的 AI 專家。
|
| 43 |
+
這張圖片是一份學生的考卷,上面同時包含「電腦打字的印刷體題目」以及「學生手寫的作答字跡」。
|
| 44 |
+
學生的字跡混合了繁體中文與簡體中文,且字體較為潦草,通常寫在橫線上或空白處。
|
| 45 |
+
|
| 46 |
+
你的任務是:
|
| 47 |
+
1. **絕對忽略印刷體**:只尋找並提取「手寫字」。絕對不要轉錄任何電腦印刷體的題目內容!
|
| 48 |
+
2. **精準辨識(拒絕幻覺)**:請盡最大努力辨識學生的手寫字(包含簡體與繁體)。如果你遇到真的看不懂的草字,請嚴格使用「[無法辨識]」四個字代替。絕對不要自己發明、猜測或聯想詞彙。
|
| 49 |
+
3. **輸出邊界框**:給出該段手寫文字在圖片上的相對邊界框 [ymin, xmin, ymax, xmax](範圍 0.0 到 1.0)。
|
| 50 |
+
|
| 51 |
+
請嚴格以 JSON 格式輸出:
|
| 52 |
{
|
| 53 |
"handwriting": [
|
| 54 |
+
{"text": "第一段手寫內容", "box": [0.12, 0.34, 0.15, 0.55]},
|
| 55 |
+
{"text": "第二段手寫內容", "box": [0.60, 0.10, 0.65, 0.80]}
|
| 56 |
]
|
| 57 |
}
|
| 58 |
"""
|
| 59 |
|
| 60 |
for page_num, img in enumerate(images):
|
| 61 |
+
progress(0.3, desc=f"處理第 {page_num + 1} 頁:準備圖片資料...")
|
| 62 |
buffered = BytesIO()
|
| 63 |
img.save(buffered, format="JPEG")
|
| 64 |
img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
|
| 65 |
base64_image = f"data:image/jpeg;base64,{img_b64}"
|
| 66 |
|
| 67 |
try:
|
| 68 |
+
progress(0.4, desc=f"第 {page_num + 1} 頁:Pixtral-large-latest 運算中 (約 30~60 秒)...")
|
|
|
|
| 69 |
response = client.chat.complete(
|
| 70 |
model="pixtral-large-latest",
|
| 71 |
messages=[
|
|
|
|
| 80 |
response_format={"type": "json_object"}
|
| 81 |
)
|
| 82 |
|
| 83 |
+
progress(0.8, desc=f"第 {page_num + 1} 頁:正在解析 JSON 並畫框...")
|
| 84 |
content = response.choices[0].message.content
|
| 85 |
+
|
| 86 |
+
# 清理模型可能多加的 Markdown JSON 標記 (防錯機制)
|
| 87 |
+
clean_content = content.replace("```json", "").replace("```", "").strip()
|
| 88 |
+
data = json.loads(clean_content)
|
| 89 |
+
|
| 90 |
all_json_results.append({f"Page {page_num + 1}": data})
|
| 91 |
|
|
|
|
| 92 |
draw = ImageDraw.Draw(img)
|
| 93 |
width, height = img.size
|
| 94 |
|
|
|
|
| 100 |
abs_ymin = ymin * height
|
| 101 |
abs_xmax = xmax * width
|
| 102 |
abs_ymax = ymax * height
|
| 103 |
+
# 畫紅框
|
| 104 |
draw.rectangle([abs_xmin, abs_ymin, abs_xmax, abs_ymax], outline="red", width=3)
|
| 105 |
|
| 106 |
drawn_images.append(img)
|
| 107 |
|
| 108 |
except Exception as e:
|
| 109 |
+
error_msg = f"API 或解析發生錯誤: {str(e)}"
|
|
|
|
|
|
|
| 110 |
all_json_results.append({f"Page {page_num + 1} Error": error_msg})
|
| 111 |
drawn_images.append(img)
|
| 112 |
|
| 113 |
progress(1.0, desc="處理完成!")
|
| 114 |
return drawn_images, json.dumps(all_json_results, ensure_ascii=False, indent=2)
|
| 115 |
|
|
|
|
|
|
|
|
|
|
| 116 |
# --- Gradio 介面設計 ---
|
| 117 |
with gr.Blocks(title="手寫字偵測與畫框 Demo") as demo:
|
| 118 |
gr.Markdown("## 📝 手寫字偵測與畫框 Demo (Pixtral-large-latest)")
|
| 119 |
+
gr.Markdown("上傳考卷 PDF 或圖片,系統會自動排除印刷體,找出所有的「手寫文字」並用紅框標示出來。")
|
| 120 |
|
| 121 |
with gr.Row():
|
| 122 |
with gr.Column():
|