Spaces:
Sleeping
Sleeping
with gr.Tab("計算紙分析"):
Browse files
app.py
CHANGED
|
@@ -11,6 +11,8 @@ from storage_service import GoogleCloudStorage
|
|
| 11 |
import csv
|
| 12 |
import io
|
| 13 |
import fitz # PyMuPDF
|
|
|
|
|
|
|
| 14 |
|
| 15 |
|
| 16 |
|
|
@@ -46,18 +48,22 @@ sheets_client = gspread.service_account_from_dict(GSHEET_KEY_DICT)
|
|
| 46 |
CSV_DATA = []
|
| 47 |
|
| 48 |
# 函数定义
|
| 49 |
-
def upload_image_to_gcs(
|
| 50 |
-
#
|
| 51 |
-
|
| 52 |
-
unique_filename = f"{int(time.time())}_{original_filename}"
|
| 53 |
blob = bucket.blob(unique_filename)
|
| 54 |
|
| 55 |
-
#
|
| 56 |
-
|
| 57 |
-
blob.upload_from_file(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 58 |
blob.make_public()
|
| 59 |
print("======upload_image_to_gcs=====")
|
| 60 |
-
print(f"File
|
| 61 |
return blob.public_url
|
| 62 |
|
| 63 |
def process_image(image_url):
|
|
@@ -226,7 +232,7 @@ def create_csv(processed_data):
|
|
| 226 |
# 设定一个可写的目录路径
|
| 227 |
writable_directory = "/tmp/csv_files"
|
| 228 |
if not os.path.exists(writable_directory):
|
| 229 |
-
os.makedirs(writable_directory) #
|
| 230 |
|
| 231 |
timestamp = int(time.time())
|
| 232 |
file_name = f"csv_{timestamp}.csv"
|
|
@@ -285,7 +291,7 @@ def process_image_to_data(password, images):
|
|
| 285 |
print("image_url:", image_url)
|
| 286 |
|
| 287 |
question_count = len(processed_data)
|
| 288 |
-
result = f"
|
| 289 |
csv_file_path = create_csv(processed_data)
|
| 290 |
|
| 291 |
return processed_data, result, csv_file_path
|
|
@@ -521,11 +527,85 @@ def process_qid_to_data(password, q_id):
|
|
| 521 |
|
| 522 |
return processed_data, result, csv_file_path
|
| 523 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 524 |
# Gradio界面
|
| 525 |
with gr.Blocks() as demo:
|
| 526 |
with gr.Row():
|
| 527 |
password_input = gr.Textbox(label="密碼", type="password", elem_id="password_input")
|
| 528 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 529 |
with gr.Tab("Junyi_Q_ID", elem_id="junyi_q_id_tab"):
|
| 530 |
with gr.Row():
|
| 531 |
gr.Markdown("## Junyi Q_ID")
|
|
@@ -599,12 +679,13 @@ with gr.Blocks() as demo:
|
|
| 599 |
with gr.Accordion(open=False):
|
| 600 |
with gr.Row():
|
| 601 |
pdf_result_table = gr.Dataframe(
|
| 602 |
-
headers=["圖片URL", "文字", "題號", "題目", "選項1", "
|
| 603 |
column_widths=[10, 10, 5, 20, 4, 4, 4, 4, 4,4,4,4,4,4, 10],
|
| 604 |
wrap=True
|
| 605 |
)
|
| 606 |
|
| 607 |
|
|
|
|
| 608 |
submit_button.click(
|
| 609 |
fn=process_image_to_data,
|
| 610 |
inputs=[password_input, image_input],
|
|
@@ -653,6 +734,12 @@ with gr.Blocks() as demo:
|
|
| 653 |
outputs=[junyi_q_id_question_markdown]
|
| 654 |
)
|
| 655 |
|
| 656 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 657 |
|
| 658 |
|
|
|
|
| 11 |
import csv
|
| 12 |
import io
|
| 13 |
import fitz # PyMuPDF
|
| 14 |
+
import base64
|
| 15 |
+
from PIL import Image
|
| 16 |
|
| 17 |
|
| 18 |
|
|
|
|
| 48 |
CSV_DATA = []
|
| 49 |
|
| 50 |
# 函数定义
|
| 51 |
+
def upload_image_to_gcs(image_data, bucket):
|
| 52 |
+
# Generate a unique filename
|
| 53 |
+
unique_filename = f"{int(time.time())}_image.jpg"
|
|
|
|
| 54 |
blob = bucket.blob(unique_filename)
|
| 55 |
|
| 56 |
+
# If image_data is a BytesIO object, upload directly
|
| 57 |
+
if isinstance(image_data, io.BytesIO):
|
| 58 |
+
blob.upload_from_file(image_data, content_type='image/jpeg')
|
| 59 |
+
else:
|
| 60 |
+
# If it's a file path, open and upload
|
| 61 |
+
with open(image_data, "rb") as image_file:
|
| 62 |
+
blob.upload_from_file(image_file)
|
| 63 |
+
|
| 64 |
blob.make_public()
|
| 65 |
print("======upload_image_to_gcs=====")
|
| 66 |
+
print(f"File uploaded to {unique_filename} in GCS.")
|
| 67 |
return blob.public_url
|
| 68 |
|
| 69 |
def process_image(image_url):
|
|
|
|
| 232 |
# 设定一个可写的目录路径
|
| 233 |
writable_directory = "/tmp/csv_files"
|
| 234 |
if not os.path.exists(writable_directory):
|
| 235 |
+
os.makedirs(writable_directory) # 如果目录不存在,创它
|
| 236 |
|
| 237 |
timestamp = int(time.time())
|
| 238 |
file_name = f"csv_{timestamp}.csv"
|
|
|
|
| 291 |
print("image_url:", image_url)
|
| 292 |
|
| 293 |
question_count = len(processed_data)
|
| 294 |
+
result = f"圖片處理完成總共完成 {question_count} 道題目"
|
| 295 |
csv_file_path = create_csv(processed_data)
|
| 296 |
|
| 297 |
return processed_data, result, csv_file_path
|
|
|
|
| 527 |
|
| 528 |
return processed_data, result, csv_file_path
|
| 529 |
|
| 530 |
+
# 新增函數來處理計算紙截圖
|
| 531 |
+
def process_calculation_image(image_input, base64_input):
|
| 532 |
+
if base64_input:
|
| 533 |
+
# 處理 base64 編碼的圖片
|
| 534 |
+
image_data = base64.b64decode(base64_input.split(',')[1])
|
| 535 |
+
image = Image.open(io.BytesIO(image_data))
|
| 536 |
+
elif image_input:
|
| 537 |
+
if isinstance(image_input, str):
|
| 538 |
+
# 處理圖片文件路徑
|
| 539 |
+
image = Image.open(image_input)
|
| 540 |
+
else:
|
| 541 |
+
# 處理上傳的圖片文件
|
| 542 |
+
image = Image.open(image_input.name)
|
| 543 |
+
else:
|
| 544 |
+
return None, "請上傳圖片或輸入 base64 圖片字串"
|
| 545 |
+
|
| 546 |
+
# 將圖片轉換為 base64 字串
|
| 547 |
+
buffered = io.BytesIO()
|
| 548 |
+
image.save(buffered, format="PNG")
|
| 549 |
+
img_str = base64.b64encode(buffered.getvalue()).decode()
|
| 550 |
+
|
| 551 |
+
# 直接使用 OpenAI API 分析圖片
|
| 552 |
+
analysis = analyze_calculation_image(img_str)
|
| 553 |
+
|
| 554 |
+
return image, analysis
|
| 555 |
+
|
| 556 |
+
def analyze_calculation_image(image_base64):
|
| 557 |
+
user_prompt = """
|
| 558 |
+
請分析這張學生計算紙的截圖:
|
| 559 |
+
1. 如果有計算式,請解釋學生的解題步驟,並指出可能的錯誤或改進點。使用 LaTeX 格式表示數學公式。
|
| 560 |
+
2. 如果沒有計算式,請提供這道題目的標準教學步驟,但不要直接給出答案。同樣使用 LaTeX 格式表示數學公式。
|
| 561 |
+
3. 無論哪種情況,都請給出鼓勵性的回饋。
|
| 562 |
+
4. 請使用 Markdown 格式輸出,數學公式請用 $...$ 包裹。
|
| 563 |
+
5. 請使用繁體中文輸出 ZH-TW
|
| 564 |
+
"""
|
| 565 |
+
|
| 566 |
+
response = OPEN_AI_CLIENT.chat.completions.create(
|
| 567 |
+
model="gpt-4o",
|
| 568 |
+
messages=[
|
| 569 |
+
{
|
| 570 |
+
"role": "user",
|
| 571 |
+
"content": [
|
| 572 |
+
{
|
| 573 |
+
"type": "text",
|
| 574 |
+
"text": user_prompt
|
| 575 |
+
},
|
| 576 |
+
{
|
| 577 |
+
"type": "image_url",
|
| 578 |
+
"image_url": {
|
| 579 |
+
"url": f"data:image/png;base64,{image_base64}",
|
| 580 |
+
},
|
| 581 |
+
},
|
| 582 |
+
],
|
| 583 |
+
}
|
| 584 |
+
],
|
| 585 |
+
max_tokens=1000,
|
| 586 |
+
)
|
| 587 |
+
return response.choices[0].message.content
|
| 588 |
+
|
| 589 |
# Gradio界面
|
| 590 |
with gr.Blocks() as demo:
|
| 591 |
with gr.Row():
|
| 592 |
password_input = gr.Textbox(label="密碼", type="password", elem_id="password_input")
|
| 593 |
|
| 594 |
+
with gr.Tab("計算紙分析"):
|
| 595 |
+
with gr.Row():
|
| 596 |
+
gr.Markdown("## 學生計算紙分析")
|
| 597 |
+
with gr.Accordion(open=False):
|
| 598 |
+
with gr.Row():
|
| 599 |
+
calculation_image_input = gr.Image(label="上傳計算紙截圖", type="filepath")
|
| 600 |
+
with gr.Row():
|
| 601 |
+
calculation_base64_input = gr.Textbox(label="或輸入 base64 圖片字串", lines=3, elem_id="calculation_base64_input")
|
| 602 |
+
with gr.Row():
|
| 603 |
+
calculation_submit_button = gr.Button("分析計算紙", elem_id="calculation_submit_button")
|
| 604 |
+
with gr.Row():
|
| 605 |
+
calculation_image_display = gr.Image(label="上傳的圖片")
|
| 606 |
+
with gr.Row():
|
| 607 |
+
calculation_result = gr.Markdown(label="分析結果", latex_delimiters=[{"left": "$", "right": "$", "display": False}])
|
| 608 |
+
|
| 609 |
with gr.Tab("Junyi_Q_ID", elem_id="junyi_q_id_tab"):
|
| 610 |
with gr.Row():
|
| 611 |
gr.Markdown("## Junyi Q_ID")
|
|
|
|
| 679 |
with gr.Accordion(open=False):
|
| 680 |
with gr.Row():
|
| 681 |
pdf_result_table = gr.Dataframe(
|
| 682 |
+
headers=["圖片URL", "文字", "題號", "題目", "選項1", "選���2", "選項3", "選項4", "答案", "提示1", "提示2", "提示3", "提示4", "提示5", "Perseus JSON"],
|
| 683 |
column_widths=[10, 10, 5, 20, 4, 4, 4, 4, 4,4,4,4,4,4, 10],
|
| 684 |
wrap=True
|
| 685 |
)
|
| 686 |
|
| 687 |
|
| 688 |
+
|
| 689 |
submit_button.click(
|
| 690 |
fn=process_image_to_data,
|
| 691 |
inputs=[password_input, image_input],
|
|
|
|
| 734 |
outputs=[junyi_q_id_question_markdown]
|
| 735 |
)
|
| 736 |
|
| 737 |
+
calculation_submit_button.click(
|
| 738 |
+
fn=process_calculation_image,
|
| 739 |
+
inputs=[calculation_image_input, calculation_base64_input],
|
| 740 |
+
outputs=[calculation_image_display, calculation_result]
|
| 741 |
+
)
|
| 742 |
+
|
| 743 |
+
demo.launch(share=True)
|
| 744 |
|
| 745 |
|