Spaces:
Running
Running
Commit
·
e404781
1
Parent(s):
95eb984
Add application file
Browse files
app.py
CHANGED
|
@@ -5,24 +5,25 @@ import onnxruntime as rt
|
|
| 5 |
import os
|
| 6 |
import spaces
|
| 7 |
import torch
|
|
|
|
| 8 |
from transformers import AutoImageProcessor
|
| 9 |
-
from scipy.special import softmax
|
| 10 |
-
import requests #
|
| 11 |
|
| 12 |
-
# 1.
|
| 13 |
# ----------------------------------------------------
|
| 14 |
ONNX_MODEL_PATH = "model.onnx"
|
| 15 |
CLASS_LABELS_FILE = "class_labels.txt"
|
| 16 |
-
MODEL_ID = 'facebook/convnext-tiny-224' # ID
|
| 17 |
|
| 18 |
-
# **LLM CONFIGURATION (
|
| 19 |
-
MODEL_NAME = "scb10x/typhoon2.5-qwen3-4b" #
|
| 20 |
-
#
|
| 21 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 22 |
API_URL = f"https://api-inference.huggingface.co/models/{MODEL_NAME}"
|
| 23 |
|
| 24 |
|
| 25 |
-
#
|
| 26 |
try:
|
| 27 |
with open(CLASS_LABELS_FILE, 'r', encoding='utf-8') as f:
|
| 28 |
CHARACTER_LABELS = [line.strip() for line in f.readlines()]
|
|
@@ -30,45 +31,49 @@ except FileNotFoundError:
|
|
| 30 |
CHARACTER_LABELS = ['Luffy', 'Zoro', 'Nami', 'Sanji', 'Chopper', 'Franky', 'Brook', 'Usopp', 'Jinbei', 'Robin', 'Ace', 'Law', 'Shanks', 'Kurohige', 'Mihawk', 'Rayleigh']
|
| 31 |
print(f"⚠️ WARNING: {CLASS_LABELS_FILE} not found. Using default labels.")
|
| 32 |
|
| 33 |
-
#
|
| 34 |
try:
|
|
|
|
| 35 |
sess = rt.InferenceSession(ONNX_MODEL_PATH)
|
| 36 |
onnx_input_name = sess.get_inputs()[0].name
|
| 37 |
onnx_output_name = sess.get_outputs()[0].name
|
|
|
|
| 38 |
processor = AutoImageProcessor.from_pretrained(MODEL_ID)
|
| 39 |
print("ONNX model and Image Processor loaded successfully.")
|
| 40 |
except Exception as e:
|
| 41 |
-
|
|
|
|
|
|
|
| 42 |
sess = None
|
| 43 |
|
| 44 |
|
| 45 |
-
# 2.
|
| 46 |
# ----------------------------------------------------
|
| 47 |
def query_typhoon_api(payload):
|
| 48 |
-
"""
|
| 49 |
if not HF_TOKEN:
|
| 50 |
return "Error: HF_TOKEN is not set in Hugging Face Space secrets."
|
| 51 |
|
| 52 |
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
|
| 53 |
response = requests.post(API_URL, headers=headers, json=payload)
|
| 54 |
|
| 55 |
-
#
|
| 56 |
if response.status_code != 200:
|
| 57 |
return f"Error {response.status_code}: API call failed. {response.text}"
|
| 58 |
|
| 59 |
-
# ดึงผลลัพธ์
|
| 60 |
try:
|
| 61 |
-
#
|
| 62 |
result = response.json()[0]['generated_text']
|
| 63 |
-
#
|
| 64 |
return result.split(payload['inputs'])[-1].strip()
|
| 65 |
except Exception as e:
|
| 66 |
return f"Error processing API response: {e}"
|
| 67 |
|
| 68 |
|
| 69 |
-
#
|
|
|
|
|
|
|
| 70 |
CHARACTER_INFO = {
|
| 71 |
-
# ... (ข้อมูลตัวละครยังคงเหมือนเดิม) ...
|
| 72 |
"Ace": "โพโทกัส ดี เอส พี่ชายบุญธรรม���องลูฟี่ ผู้ใช้พลังผลปีศาจเมระ เมระ",
|
| 73 |
"Luffy": "มังกี้ ดี ลูฟี่ กัปตันกลุ่มโจรสลัดหมวกฟาง ผู้ใฝ่ฝันจะเป็นราชาโจรสลัด",
|
| 74 |
"Zoro": "โรโรโนอา โซโล นักดาบสามเล่มแห่งกลุ่มหมวกฟาง ผู้มีเป้าหมายเป็นนักดาบอันดับหนึ่งของโลก",
|
|
@@ -90,11 +95,11 @@ CHARACTER_INFO = {
|
|
| 90 |
|
| 91 |
def generate_thai_response(character_name, confidence):
|
| 92 |
"""
|
| 93 |
-
|
| 94 |
"""
|
| 95 |
info = CHARACTER_INFO.get(character_name, "ตัวละครวันพีซ")
|
| 96 |
|
| 97 |
-
# 1.
|
| 98 |
prompt = (
|
| 99 |
f"จากผลการวิเคราะห์ภาพ (ความมั่นใจ {confidence*100:.2f}%), ตัวละครที่ทำนายคือ '{character_name}'. "
|
| 100 |
f"ตัวละครนี้คือ {info}. "
|
|
@@ -102,7 +107,7 @@ def generate_thai_response(character_name, confidence):
|
|
| 102 |
f"และรวมข้อมูลทั้งหมดนี้เข้าด้วยกันในประโยคเดียวโดยใช้ Markdown bold สำหรับชื่อตัวละครและความมั่นใจ (XX.XX%)."
|
| 103 |
)
|
| 104 |
|
| 105 |
-
# 2.
|
| 106 |
payload = {
|
| 107 |
"inputs": prompt,
|
| 108 |
"parameters": {
|
|
@@ -111,47 +116,50 @@ def generate_thai_response(character_name, confidence):
|
|
| 111 |
}
|
| 112 |
}
|
| 113 |
|
| 114 |
-
# 3.
|
| 115 |
llm_response = query_typhoon_api(payload)
|
| 116 |
|
| 117 |
-
# 4. หาก API call สำเร็จ ให้คืนคำตอบ หากล้มเหลวให้คืน Error
|
| 118 |
if llm_response.startswith("Error"):
|
| 119 |
-
|
|
|
|
|
|
|
|
|
|
| 120 |
|
| 121 |
-
# Typhoon 2.5 มักจะตอบกลับเป็นประโยคที่ถูกต้องตาม Prompt
|
| 122 |
return llm_response
|
| 123 |
|
| 124 |
-
#
|
|
|
|
| 125 |
@spaces.GPU
|
| 126 |
def predict_one_piece_character(pil_image):
|
| 127 |
if pil_image is None or sess is None:
|
| 128 |
return "⚠️ โมเดลไม่พร้อมใช้งาน กรุณาตรวจสอบไฟล์ ONNX และการตั้งค่า"
|
| 129 |
|
| 130 |
try:
|
| 131 |
-
#
|
| 132 |
inputs = processor(images=pil_image, return_tensors="np")
|
| 133 |
onnx_input = inputs['pixel_values'].astype(np.float32)
|
| 134 |
|
| 135 |
-
#
|
| 136 |
onnx_predictions = sess.run([onnx_output_name], {onnx_input_name: onnx_input})
|
| 137 |
logits = onnx_predictions[0].squeeze()
|
| 138 |
|
| 139 |
-
#
|
| 140 |
probabilities = softmax(logits)
|
| 141 |
predicted_index = np.argmax(probabilities)
|
| 142 |
predicted_character = CHARACTER_LABELS[predicted_index]
|
| 143 |
confidence = probabilities[predicted_index].item()
|
| 144 |
|
| 145 |
-
#
|
| 146 |
final_response = generate_thai_response(predicted_character, confidence)
|
| 147 |
|
| 148 |
return final_response
|
| 149 |
|
| 150 |
except Exception as e:
|
| 151 |
-
print(f"ERROR
|
| 152 |
return f"เกิดข้อผิดพลาดในการทำนาย: {e}"
|
| 153 |
|
| 154 |
-
#
|
|
|
|
| 155 |
interface = gr.Interface(
|
| 156 |
fn=predict_one_piece_character,
|
| 157 |
inputs=gr.Image(type="pil", label="อัปโหลดรูปภาพตัวละครวันพีซ"),
|
|
|
|
| 5 |
import os
|
| 6 |
import spaces
|
| 7 |
import torch
|
| 8 |
+
# Use AutoImageProcessor for robust image preprocessing (ConvNeXt/ViT)
|
| 9 |
from transformers import AutoImageProcessor
|
| 10 |
+
from scipy.special import softmax # Used for computing probabilities from model output
|
| 11 |
+
import requests # Used for making HTTP API calls (Typhoon 2.5)
|
| 12 |
|
| 13 |
+
# 1. ONNX MODEL AND LLM CONFIGURATION
|
| 14 |
# ----------------------------------------------------
|
| 15 |
ONNX_MODEL_PATH = "model.onnx"
|
| 16 |
CLASS_LABELS_FILE = "class_labels.txt"
|
| 17 |
+
MODEL_ID = 'facebook/convnext-tiny-224' # The base model ID used for training
|
| 18 |
|
| 19 |
+
# **LLM CONFIGURATION (Actual API Call Setup)**
|
| 20 |
+
MODEL_NAME = "scb10x/typhoon2.5-qwen3-4b" # Typhoon 2.5 Model ID
|
| 21 |
+
# Fetch Hugging Face Token from Space Secrets
|
| 22 |
HF_TOKEN = os.getenv("HF_TOKEN")
|
| 23 |
API_URL = f"https://api-inference.huggingface.co/models/{MODEL_NAME}"
|
| 24 |
|
| 25 |
|
| 26 |
+
# Load character classes from the file created during training
|
| 27 |
try:
|
| 28 |
with open(CLASS_LABELS_FILE, 'r', encoding='utf-8') as f:
|
| 29 |
CHARACTER_LABELS = [line.strip() for line in f.readlines()]
|
|
|
|
| 31 |
CHARACTER_LABELS = ['Luffy', 'Zoro', 'Nami', 'Sanji', 'Chopper', 'Franky', 'Brook', 'Usopp', 'Jinbei', 'Robin', 'Ace', 'Law', 'Shanks', 'Kurohige', 'Mihawk', 'Rayleigh']
|
| 32 |
print(f"⚠️ WARNING: {CLASS_LABELS_FILE} not found. Using default labels.")
|
| 33 |
|
| 34 |
+
# Load ONNX Runtime Session
|
| 35 |
try:
|
| 36 |
+
print(f"Attempting to load ONNX model from: {ONNX_MODEL_PATH}") # <-- NEW DEBUG LINE
|
| 37 |
sess = rt.InferenceSession(ONNX_MODEL_PATH)
|
| 38 |
onnx_input_name = sess.get_inputs()[0].name
|
| 39 |
onnx_output_name = sess.get_outputs()[0].name
|
| 40 |
+
# Load Image Processor (essential for correct image preparation)
|
| 41 |
processor = AutoImageProcessor.from_pretrained(MODEL_ID)
|
| 42 |
print("ONNX model and Image Processor loaded successfully.")
|
| 43 |
except Exception as e:
|
| 44 |
+
# <--- NEW DEBUG BLOCK: พิมพ์ Error จริงออกมา
|
| 45 |
+
print(f"FATAL ERROR LOADING ONNX MODEL: {e}")
|
| 46 |
+
print("Please ensure model.onnx is tracked by Git LFS and is uploaded correctly.")
|
| 47 |
sess = None
|
| 48 |
|
| 49 |
|
| 50 |
+
# 2. LLM API FUNCTION (Replaces Placeholder)
|
| 51 |
# ----------------------------------------------------
|
| 52 |
def query_typhoon_api(payload):
|
| 53 |
+
"""Sends prompt to the Hugging Face Inference API of Typhoon 2.5."""
|
| 54 |
if not HF_TOKEN:
|
| 55 |
return "Error: HF_TOKEN is not set in Hugging Face Space secrets."
|
| 56 |
|
| 57 |
headers = {"Authorization": f"Bearer {HF_TOKEN}"}
|
| 58 |
response = requests.post(API_URL, headers=headers, json=payload)
|
| 59 |
|
| 60 |
+
# Check for non-successful status codes (e.g., 401 Unauthorized, 503 Service Unavailable)
|
| 61 |
if response.status_code != 200:
|
| 62 |
return f"Error {response.status_code}: API call failed. {response.text}"
|
| 63 |
|
|
|
|
| 64 |
try:
|
| 65 |
+
# Extract the generated text from the response structure
|
| 66 |
result = response.json()[0]['generated_text']
|
| 67 |
+
# Remove the input prompt part from the output text
|
| 68 |
return result.split(payload['inputs'])[-1].strip()
|
| 69 |
except Exception as e:
|
| 70 |
return f"Error processing API response: {e}"
|
| 71 |
|
| 72 |
|
| 73 |
+
# 3. TYPHOON 2.5 LOGIC (Knowledge Base + Prompt Generation)
|
| 74 |
+
# ---------------------------------------------------------
|
| 75 |
+
# This dictionary serves as the LLM's knowledge base for the character's full name/role
|
| 76 |
CHARACTER_INFO = {
|
|
|
|
| 77 |
"Ace": "โพโทกัส ดี เอส พี่ชายบุญธรรม���องลูฟี่ ผู้ใช้พลังผลปีศาจเมระ เมระ",
|
| 78 |
"Luffy": "มังกี้ ดี ลูฟี่ กัปตันกลุ่มโจรสลัดหมวกฟาง ผู้ใฝ่ฝันจะเป็นราชาโจรสลัด",
|
| 79 |
"Zoro": "โรโรโนอา โซโล นักดาบสามเล่มแห่งกลุ่มหมวกฟาง ผู้มีเป้าหมายเป็นนักดาบอันดับหนึ่งของโลก",
|
|
|
|
| 95 |
|
| 96 |
def generate_thai_response(character_name, confidence):
|
| 97 |
"""
|
| 98 |
+
Constructs a sophisticated prompt and queries the Typhoon 2.5 API.
|
| 99 |
"""
|
| 100 |
info = CHARACTER_INFO.get(character_name, "ตัวละครวันพีซ")
|
| 101 |
|
| 102 |
+
# 1. Build a clear, instructional prompt for the LLM
|
| 103 |
prompt = (
|
| 104 |
f"จากผลการวิเคราะห์ภาพ (ความมั่นใจ {confidence*100:.2f}%), ตัวละครที่ทำนายคือ '{character_name}'. "
|
| 105 |
f"ตัวละครนี้คือ {info}. "
|
|
|
|
| 107 |
f"และรวมข้อมูลทั้งหมดนี้เข้าด้วยกันในประโยคเดียวโดยใช้ Markdown bold สำหรับชื่อตัวละครและความมั่นใจ (XX.XX%)."
|
| 108 |
)
|
| 109 |
|
| 110 |
+
# 2. Prepare Payload
|
| 111 |
payload = {
|
| 112 |
"inputs": prompt,
|
| 113 |
"parameters": {
|
|
|
|
| 116 |
}
|
| 117 |
}
|
| 118 |
|
| 119 |
+
# 3. Call the API and handle potential errors
|
| 120 |
llm_response = query_typhoon_api(payload)
|
| 121 |
|
|
|
|
| 122 |
if llm_response.startswith("Error"):
|
| 123 |
+
# Fallback to a static, simple response if API fails
|
| 124 |
+
thai_name = info.split(' ')[0]
|
| 125 |
+
return (f"⚠️ LLM API ไม่ตอบสนอง: ตัวละครคือ **{thai_name}** ({info}) "
|
| 126 |
+
f"[ความมั่นใจ: **{confidence*100:.2f}%**]")
|
| 127 |
|
|
|
|
| 128 |
return llm_response
|
| 129 |
|
| 130 |
+
# 4. ONNX INFERENCE FUNCTION
|
| 131 |
+
# ----------------------------------------------------
|
| 132 |
@spaces.GPU
|
| 133 |
def predict_one_piece_character(pil_image):
|
| 134 |
if pil_image is None or sess is None:
|
| 135 |
return "⚠️ โมเดลไม่พร้อมใช้งาน กรุณาตรวจสอบไฟล์ ONNX และการตั้งค่า"
|
| 136 |
|
| 137 |
try:
|
| 138 |
+
# 4.1 Preprocessing (ConvNeXt standard input)
|
| 139 |
inputs = processor(images=pil_image, return_tensors="np")
|
| 140 |
onnx_input = inputs['pixel_values'].astype(np.float32)
|
| 141 |
|
| 142 |
+
# 4.2 Run Inference
|
| 143 |
onnx_predictions = sess.run([onnx_output_name], {onnx_input_name: onnx_input})
|
| 144 |
logits = onnx_predictions[0].squeeze()
|
| 145 |
|
| 146 |
+
# 4.3 Post-processing (Softmax and Argmax)
|
| 147 |
probabilities = softmax(logits)
|
| 148 |
predicted_index = np.argmax(probabilities)
|
| 149 |
predicted_character = CHARACTER_LABELS[predicted_index]
|
| 150 |
confidence = probabilities[predicted_index].item()
|
| 151 |
|
| 152 |
+
# 4.4 LLM Integration
|
| 153 |
final_response = generate_thai_response(predicted_character, confidence)
|
| 154 |
|
| 155 |
return final_response
|
| 156 |
|
| 157 |
except Exception as e:
|
| 158 |
+
print(f"RUNTIME ERROR: {e}")
|
| 159 |
return f"เกิดข้อผิดพลาดในการทำนาย: {e}"
|
| 160 |
|
| 161 |
+
# 5. GRADIO INTERFACE
|
| 162 |
+
# ----------------------------------------------------
|
| 163 |
interface = gr.Interface(
|
| 164 |
fn=predict_one_piece_character,
|
| 165 |
inputs=gr.Image(type="pil", label="อัปโหลดรูปภาพตัวละครวันพีซ"),
|