Spaces:
Running on Zero
Running on Zero
Download app.py from Puttipong13/SmartSafe: direct link, hf CLI and curl.
- Browser
- Download file 31.6 kB
-
https://huggingface.co/spaces/Puttipong13/SmartSafe/resolve/main/app.py
- Command line
-
hf download hf://spaces/Puttipong13/SmartSafe/app.py
-
curl -L -o app.py https://huggingface.co/spaces/Puttipong13/SmartSafe/resolve/main/app.py
31.6 kB
| # --- Crash-safety header: ต้องอยู่บนสุดก่อน import ไลบรารีอื่นทั้งหมด --- | |
| import os | |
| os.environ["USE_TF"] = "0" # ไม่ให้ transformers โหลด TensorFlow ซ้ำ (เราใช้แค่ PyTorch ฝั่งแปลภาษา) | |
| os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2" # ลด log ของ TensorFlow | |
| os.environ["TF_ENABLE_ONEDNN_OPTS"] = "0" # ปิด oneDNN กัน crash บน CPU บางรุ่น | |
| os.environ["TOKENIZERS_PARALLELISM"] = "false" | |
| import sys | |
| sys.modules["triton"] = None # triton segfault ตอน import บน CPU-only Space: ทำให้ import ล้มเหลวแบบปกติ torch จะข้ามไปเอง | |
| import faulthandler | |
| faulthandler.enable() # ถ้า crash อีก จะพิมพ์ตำแหน่งที่พังลง log | |
| # --- ZeroGPU: ต้อง import spaces ก่อน torch และต้องมีฟังก์ชัน @spaces.GPU อย่างน้อย 1 ตัว --- | |
| try: | |
| import spaces | |
| def _zerogpu_placeholder(): | |
| """ฟังก์ชันเปล่า ใช้ผ่านเงื่อนไข ZeroGPU เท่านั้น (โมเดล TensorFlow ของเรารันบน CPU ตามปกติ)""" | |
| return None | |
| except ImportError: | |
| pass # รันนอก Hugging Face Spaces ไม่ต้องใช้ | |
| # --------------------------------------------------------------------------------------- | |
| import torch # import torch ก่อน tensorflow เพื่อเลี่ยง segfault | |
| # --------------------------------------------------------------------- | |
| import os | |
| import cv2 | |
| import numpy as np | |
| import tensorflow as tf | |
| import gradio as gr | |
| from gtts import gTTS | |
| import uuid | |
| from transformers import AutoTokenizer, AutoModelForSeq2SeqLM | |
| import torch | |
| import re | |
| # ***** นำเข้า MobileNet และ Preprocessing ที่ถูกต้อง ***** | |
| from tensorflow.keras.applications import MobileNet | |
| from tensorflow.keras.applications.mobilenet import preprocess_input as mobilenet_preprocess_input | |
| # ======================================================= | |
| # 1. CONFIGURATION & MODEL SETUP | |
| # ======================================================= | |
| # --- Audio Fix 1: กำหนดและสร้างโฟลเดอร์ Output สำหรับไฟล์เสียง --- | |
| OUTPUT_DIR = "appliance_audio_outputs" | |
| if not os.path.exists(OUTPUT_DIR): | |
| os.makedirs(OUTPUT_DIR) | |
| # -------------------------------------------------------------------------- | |
| # --- ชื่อ Class ภาษาอังกฤษ (Key) --- | |
| class_names_en = [ | |
| "Air Conditioner", "Electric Kettle", "Fan", "Fridge", "Iron", "Microwave", | |
| "Toaster", "TV", "Vacuum Cleaner", "Washing Machine", | |
| # --- 💡 NEW CLASSES ADDED HERE --- | |
| "Rice Cooker", "Water Heater", "Water Purifier", "Blender", "Cooktop", | |
| "Air Purifier", "Water Dispenser", "Hair Dryer", "Water Pump", "Computer", | |
| "Electric Frying Pan" | |
| ] | |
| # --- ชื่อ Class ภาษาไทย (Value) --- | |
| class_names_th = [ | |
| "เครื่องปรับอากาศ", "กาต้มน้ำไฟฟ้า", "พัดลม", "ตู้เย็น", "เตารีด", "ไมโครเวฟ", | |
| "เครื่องปิ้งขนมปัง", "โทรทัศน์", "เครื่องดูดฝุ่น", "เครื่องซักผ้า", | |
| # --- 💡 NEW CLASSES ADDED HERE --- | |
| "หม้อหุงข้าว", "เครื่องทำน้ำอุ่น", "เครื่องกรองน้ำ", "เครื่องปั่น", "เตาทำอาหาร", | |
| "เครื่องฟอกอากาศ", "เครื่องกดน้ำดื่ม", "เครื่องเป่าผม", "ปั๊มน้ำ", "คอมพิวเตอร์", | |
| "กระทะไฟฟ้า" | |
| ] | |
| # --- สร้าง Dictionary สำหรับการแปลชื่อ Class --- | |
| CLASS_NAME_MAP = dict(zip(class_names_en, class_names_th)) | |
| class_names = class_names_en | |
| # --- 💡 อัปเดตจำนวนคลาสทั้งหมด (21 Classes) --- | |
| NUM_CLASSES = len(class_names) | |
| # ---------------------------------------------- | |
| IMG_SIZE = (224, 224) | |
| MODEL_WEIGHTS_PATH = "final_best_MobileNet_weights.h5" | |
| # --- MarianMT Setup (Thai -> English Translation) --- | |
| MARIAN_MODEL_NAME = "Helsinki-NLP/opus-mt-th-en" | |
| MT_tokenizer = None | |
| MT_model = None | |
| try: | |
| print(f"🔄 Loading MarianMT Model: {MARIAN_MODEL_NAME}...") | |
| MT_tokenizer = AutoTokenizer.from_pretrained(MARIAN_MODEL_NAME) | |
| MT_model = AutoModelForSeq2SeqLM.from_pretrained(MARIAN_MODEL_NAME) | |
| print("✅ MarianMT Model loaded.") | |
| except Exception as e: | |
| print(f"❌ Error loading MarianMT model (Translation will be disabled): {e}") | |
| # --- ฟังก์ชันสร้างโครงสร้างโมเดล --- | |
| def create_electrical_appliance_model(): | |
| """สร้างโครงสร้างโมเดล MobileNet + Custom Head""" | |
| base_model = MobileNet( | |
| weights='imagenet', | |
| include_top=False, | |
| input_shape=(IMG_SIZE[0], IMG_SIZE[1], 3) | |
| ) | |
| for layer in base_model.layers: | |
| layer.trainable = False | |
| model = tf.keras.models.Sequential([ | |
| base_model, | |
| tf.keras.layers.GlobalAveragePooling2D(), | |
| # 💡 ใช้ NUM_CLASSES ใหม่ที่ถูกอัปเดตแล้ว | |
| tf.keras.layers.Dense(NUM_CLASSES, activation='softmax') | |
| ]) | |
| return model | |
| # 1. โหลดโมเดลด้วยการสร้างโครงสร้างและโหลด Weights | |
| model = create_electrical_appliance_model() | |
| print("🔄 Loading custom MobileNet weights...") | |
| try: | |
| # ⚠️ โหลด Weights ด้วยโครงสร้างใหม่ (21 Classes) - ต้องมั่นใจว่าไฟล์ Weights นี้เทรนด้วย 21 คลาสแล้ว | |
| model.load_weights(MODEL_WEIGHTS_PATH) | |
| print(f"✅ Model '{MODEL_WEIGHTS_PATH}' loaded successfully.") | |
| except Exception as e: | |
| print(f"❌ FATAL Model Error: Failed to load custom weights from {MODEL_WEIGHTS_PATH}. Error: {e}") | |
| # raise SystemExit(1) # คอมเมนต์ออกเพื่อให้โค้ดรันได้ แม้ไม่มีไฟล์ | |
| print("⚠️ Continuing without weights. Prediction will be random.") | |
| # ======================================================= | |
| # 2. DATA AND TRANSLATION FUNCTIONS | |
| # ======================================================= | |
| # --- Appliance Details Data (Thai Source) --- | |
| # 💡 เพิ่มรายละเอียดสำหรับคลาสใหม่ (ใช้ข้อมูลตัวอย่าง) | |
| APPLIANCE_DETAILS = { | |
| "Air Conditioner": { | |
| "power": "1,000 ถึง 3,000 วัตต์", | |
| "install": "ควรติดตั้งในที่ร่มและสูงจากพื้น 2.5 ม. ขึ้นไปเพื่อประสิทธิภาพที่ดี", | |
| "care": "ล้างแผ่นกรองทุก 1 เดือน และเรียกช่างล้างใหญ่ปีละ 2 ครั้ง", | |
| "warning": "ห้ามติดตั้งใกล้แสงแดดหรือความชื้น และควรมีสายดิน" | |
| }, | |
| "Electric Kettle": { | |
| "power": "1,500 ถึง 2,200 วัตต์", | |
| "install": "ใช้งานบนพื้นผิวเรียบและแห้ง ห่างจากมือเด็ก", | |
| "care": "ทำความสะอาดตะกรันด้านในด้วยน้ำส้มสายชูหรือน้ำมะนาวทุก 1-2 เดือน", | |
| "warning": "ห้ามเปิดเครื่องโดยไม่มีน้ำ และระวังน้ำร้อนลวก" | |
| }, | |
| "Fan": { | |
| "power": "50 ถึง 100 วัตต์", | |
| "install": "ตั้งในที่อากาศถ่ายเทสะดวก ห่างจากผ้าม่าน", | |
| "care": "ถอดใบพัดและตะแกรงล้างทำความสะอาดทุก 2-3 สัปดาห์", | |
| "warning": "ห้ามสอดมือหรือสิ่งของเข้าไปขณะพัดลมทำงาน" | |
| }, | |
| "Fridge": { | |
| "power": "100 ถึง 200 วัตต์", | |
| "install": "ตั้งห่างจากผนัง 10-15 ซม. ห้ามตั้งใกล้แหล่งความร้อน", | |
| "care": "ตั้งอุณหภูมิ 2-5 องศาเซลเซียส และละลายน้ำแข็งเมื่อเริ่มหนา", | |
| "warning": "ห้ามใช้ปลั๊กพ่วงร่วมกับเครื่องใช้ไฟฟ้ากำลังสูง" | |
| }, | |
| "Iron": { | |
| "power": "1,000 ถึง 2,000 วัตต์", | |
| "install": "ใช้งานบนโต๊ะที่มั่นคง และควรมีแผ่นรองรีดทนความร้อน", | |
| "care": "ทำความสะอาดหน้าเตาด้วยผ้าชุบน้ำหมาดๆ เมื่อเครื่องเย็นแล้ว", | |
| "warning": "ห้ามวางเตารีดคว่ำหน้าเตาในขณะร้อน" | |
| }, | |
| "Microwave": { | |
| "power": "800 ถึง 1,500 วัตต์", | |
| "install": "วางบนพื้นผิวเรียบ ห้ามวางใกล้เตาแก๊ส", | |
| "care": "เช็ดภายในหลังใช้งานทันที เพื่อป้องกันคราบฝังแน่น", | |
| "warning": "ห้ามใส่ภาชนะโลหะหรือวัสดุไวไฟ" | |
| }, | |
| "Toaster": { | |
| "power": "800 ถึง 1,500 วัตต์", | |
| "install": "ใช้งานบนพื้นผิวทนความร้อน ห่างจากวัสดุติดไฟ", | |
| "care": "ถอดถาดรองเศษขนมปังออกมาทิ้งและทำความสะอาดทุกครั้ง", | |
| "warning": "ห้ามใช้มือหรือโลหะแหย่ขณะเครื่องทำงาน" | |
| }, | |
| "TV": { | |
| "power": "50 ถึง 300 วัตต์", | |
| "install": "วางบนชั้นที่มั่นคง หรือติดตั้งกับผนังที่แข็งแรง", | |
| "care": "ใช้ผ้านุ่มเช็ดหน้าจอ ห้ามใช้สารเคมีที่มีแอลกอฮอล์", | |
| "warning": "หลีกเลี่ยงการวางใกล้น้ำหรือความชื้น" | |
| }, | |
| "Vacuum Cleaner": { | |
| "power": "1,000 ถึง 2,000 วัตต์", | |
| "install": "ควรเลือกหัวดูดที่เหมาะสมกับพื้นผิวที่ต้องการทำความสะอาด", | |
| "care": "ทิ้งฝุ่นและทำความสะอาดตัวกรอง (Filter) ทันทีหลังการใช้งาน", | |
| "warning": "ห้ามดูดของเหลวหรือของมีคม" | |
| }, | |
| "Washing Machine": { | |
| "power": "200 ถึง 500 วัตต์ (ซัก), 2,000 ถึง 3,000 วัตต์ (ทำน้ำร้อน)", | |
| "install": "ติดตั้งบนพื้นที่ได้ระดับ และมีก๊อกน้ำ/ท่อระบายน้ำเข้าถึง", | |
| "care": "ทำความสะอาดถังซักด้วยน้ำยาเฉพาะทางเดือนละครั้ง หรือใช้น้ำส้มสายชูช่วยกำจัดเชื้อรา", | |
| "warning": "ห้ามเปิดฝาเครื่องขณะกำลังปั่น" | |
| }, | |
| "Rice Cooker": { | |
| "power": "500 ถึง 1,000 วัตต์", | |
| "install": "วางบนพื้นผิวเรียบและแห้ง ห้ามวางใกล้ผนังมากเกินไป", | |
| "care": "เช็ดทำความสะอาดหม้อชั้นในด้วยผ้านุ่ม ห้ามใช้ฝอยขัด", | |
| "warning": "ห้ามล้างตัวเครื่องด้วยน้ำ" | |
| }, | |
| "Water Heater": { | |
| "power": "2,000 ถึง 4,500 วัตต์", | |
| "install": "ควรติดตั้งสายดิน และควรติดตั้งโดยช่างผู้ชำนาญการ", | |
| "care": "ตรวจสอบระบบตัดไฟ (ELCB) และทำความสะอาดฝักบัวทุก 6 เดือน", | |
| "warning": "ห้ามใช้หากระบบตัดไฟทำงานผิดปกติ" | |
| }, | |
| "Water Purifier": { | |
| "power": "0 ถึง 100 วัตต์ (ถ้ามีปั๊ม/ทำความเย็น)", | |
| "install": "ติดตั้งใกล้ก๊อกน้ำประปา และในที่ร่ม", | |
| "care": "เปลี่ยนไส้กรองตามอายุการใช้งานที่กำหนดอย่างเคร่งครัด", | |
| "warning": "ห้ามใช้งานเมื่อไส้กรองหมดอายุ" | |
| }, | |
| "Blender": { | |
| "power": "300 ถึง 1,000 วัตต์", | |
| "install": "ใช้บนพื้นผิวที่มั่นคง อย่าให้สายไฟพาดผ่านคมมีด", | |
| "care": "ทำความสะอาดโถปั่นทันทีหลังใช้งาน และไม่ควรปั่นเกินกำลังเครื่อง", | |
| "warning": "ห้ามเปิดฝาโถขณะเครื่องทำงาน" | |
| }, | |
| "Cooktop": { | |
| "power": "1,000 ถึง 3,000 วัตต์ (ต่อหัว)", | |
| "install": "ต้องใช้อุปกรณ์ทำครัวที่เหมาะสม (เช่น เตาแม่เหล็กไฟฟ้าต้องใช้ภาชนะโลหะ)", | |
| "care": "เช็ดทำความสะอาดคราบอาหารทันทีหลังเครื่องเย็น", | |
| "warning": "ระวังพื้นผิวร้อนหลังใช้งาน" | |
| }, | |
| "Air Purifier": { | |
| "power": "30 ถึง 100 วัตต์", | |
| "install": "ตั้งไว้กลางห้อง หรือในบริเวณที่อากาศถ่ายเทได้ดี", | |
| "care": "ดูดฝุ่น/เช็ดแผ่นกรองหยาบ และเปลี่ยนแผ่นกรอง HEPA ตามอายุการใช้งาน", | |
| "warning": "ห้ามบังช่องดูดและช่องระบายอากาศ" | |
| }, | |
| "Water Dispenser": { | |
| "power": "100 ถึง 500 วัตต์ (ทำน้ำเย็น), 500 ถึง 1,000 วัตต์ (ทำน้ำร้อน)", | |
| "install": "ตั้งในที่ร่ม ห่างจากผนัง 10-15 ซม.", | |
| "care": "ทำความสะอาดถังเก็บน้ำและช่องจ่ายน้ำอย่างสม่ำเสมอ", | |
| "warning": "ระวังน้ำร้อนอาจทำให้เกิดแผลไหม้" | |
| }, | |
| "Hair Dryer": { | |
| "power": "1,000 ถึง 2,000 วัตต์", | |
| "install": "ใช้งานในที่แห้ง ห้ามใช้ใกล้น้ำ", | |
| "care": "ทำความสะอาดตะแกรงกรองด้านหลังเครื่องเพื่อป้องกันฝุ่นอุดตัน", | |
| "warning": "ห้ามใช้งานขณะมือเปียก" | |
| }, | |
| "Water Pump": { | |
| "power": "200 ถึง 500 วัตต์", | |
| "install": "ควรติดตั้งในที่ร่ม มีการระบายอากาศดี และมีถังพักน้ำ", | |
| "care": "ตรวจสอบการรั่วซึม และไม่ควรปล่อยให้ปั๊มทำงานโดยไม่มีน้ำ", | |
| "warning": "ห้ามเดินเครื่องขณะไม่มีน้ำในระบบ" | |
| }, | |
| "Computer": { | |
| "power": "150 ถึง 500 วัตต์ (เครื่องเดสก์ท็อป)", | |
| "install": "ตั้งในบริเวณที่อากาศถ่ายเทสะดวก ห้ามวางชิดผนัง", | |
| "care": "ทำความสะอาดฝุ่นภายในเครื่องและพัดลมอย่างสม่ำเสมอ", | |
| "warning": "ห้ามปิดกั้นช่องระบายความร้อน" | |
| }, | |
| "Electric Frying Pan": { | |
| "power": "1,000 ถึง 1,500 วัตต์", | |
| "install": "ใช้งานบนพื้นผิวที่ทนความร้อนและมั่นคง", | |
| "care": "ห้ามนำตัวกระทะลงน้ำทั้งอัน ทำความสะอาดเฉพาะส่วนที่ถอดได้", | |
| "warning": "ระวังความร้อนสูงขณะใช้งาน" | |
| } | |
| # ----------------------------------------------------------- | |
| } | |
| # --- MarianMT Translation Function (ใช้โค้ดเดิม) --- | |
| def translate_th_to_en(text_to_translate: str) -> str: | |
| """ฟังก์ชันสำหรับแปลข้อความจากไทยเป็นอังกฤษโดยใช้ MarianMT Model""" | |
| if not text_to_translate or MT_model is None: | |
| return text_to_translate | |
| try: | |
| encoded_input = MT_tokenizer(text_to_translate, return_tensors="pt") | |
| translated_tokens = MT_model.generate( | |
| **encoded_input, | |
| max_length=100, | |
| num_beams=4 | |
| ) | |
| return MT_tokenizer.decode(translated_tokens.squeeze(), skip_special_tokens=True) | |
| except Exception as e: | |
| print(f"Translation Error: {e}") | |
| return text_to_translate | |
| # --- GTTs Text-to-Audio Function (แก้ไขให้ใช้ OUTPUT_DIR) --- | |
| def text_to_audio(text_to_speak, lang='th'): | |
| """แปลงข้อความเป็นไฟล์เสียง และคืนค่า path ของไฟล์เสียง""" | |
| # Audio Fix 1: ใช้ os.path.join เพื่อสร้าง Path ในโฟลเดอร์ output | |
| filename = os.path.join(OUTPUT_DIR, f"output_audio_{uuid.uuid4()}.mp3") | |
| try: | |
| tts = gTTS(text=text_to_speak, lang=lang, slow=False) | |
| tts.save(filename) | |
| return filename | |
| except Exception as e: | |
| print(f"Error generating audio: {e}") | |
| return None | |
| def generate_and_play_audio_btn(text_to_speak, lang_code): | |
| """สร้างไฟล์เสียงแล้วส่ง Path ไปยัง Audio Component""" | |
| if not text_to_speak or "Error" in text_to_speak or "กรุณาอัปโหลด" in text_to_speak: | |
| print("Error: Audio generation skipped due to empty or error text.") | |
| return None | |
| return text_to_audio(text_to_speak, lang=lang_code) | |
| # ---------------------------------------------------- | |
| # 3. CORE LOGIC PIPELINE | |
| # ---------------------------------------------------- | |
| def format_details_text(class_name_en, class_name_th, lang, details): | |
| confidence = f"{details['confidence']:.1f}%" | |
| class_name_display = class_name_th if lang == "th" else class_name_en | |
| warning_text = details.get('warning', 'ไม่มีข้อมูลข้อควรระวัง') | |
| if lang == "th": | |
| return ( | |
| f"⚡ เครื่องใช้ไฟฟ้าที่ตรวจพบ: {class_name_display} (ความมั่นใจ {confidence})\n" | |
| f"🔌 กำลังไฟฟ้าโดยประมาณ: {details['power']}\n\n" | |
| f"🛠️ การติดตั้ง/ตำแหน่งที่เหมาะสม:\n" | |
| f"- {details['install']}\n\n" | |
| f"🛁 การดูแลการใช้งาน/เคล็ดลับ:\n" | |
| f"- {details['care']}\n\n" | |
| f"⚠️ ข้อควรระวัง:\n" | |
| f"- {warning_text}" | |
| ) | |
| else: | |
| return ( | |
| f"⚡ Detected Appliance: {class_name_display} (Confidence {confidence})\n" | |
| f"🔌 Estimated Power Consumption: {details['power']}\n\n" | |
| f"🛠️ Installation / Proper Placement:\n" | |
| f"- {details['install']}\n\n" | |
| f"🛁 Maintenance / Usage Tips:\n" | |
| f"- {details['care']}\n\n" | |
| f"⚠️ Warnings:\n" | |
| f"- {warning_text}" | |
| ) | |
| # 🎯 แก้ไข Outputs: ต้องคืนค่า initial_detail_text เพื่อให้ Textbox หลักแสดงผลทันที | |
| def full_classification_pipeline(input_img_path): | |
| """ฟังก์ชันทำนายผลหลักที่ส่งออกเฉพาะ Class ที่มั่นใจสูงสุดค่าเดียว และเตรียมข้อมูลสำหรับ UI""" | |
| if input_img_path is None: | |
| error_msg = "กรุณาอัปโหลดรูปภาพ หรือใช้กล้องเพื่อทำนายผล" | |
| # คืนค่า 5 Outputs: Top Class, Text TH State, Text EN State, Class TH State, Detail Textbox | |
| return error_msg, error_msg, error_msg, "N/A", error_msg | |
| try: | |
| input_img = cv2.imread(input_img_path) | |
| if input_img is None: | |
| raise ValueError("Error reading image file (input_img is None).") | |
| img = cv2.resize(input_img, IMG_SIZE) | |
| img = np.array(img, dtype=np.float32) | |
| img = np.expand_dims(img, axis=0) | |
| img = mobilenet_preprocess_input(img) | |
| # 2. Prediction | |
| predictions = model.predict(img, verbose=0) | |
| top_class_id = int(np.argmax(predictions[0])) | |
| # ⚠️ ตรวจสอบ: class_names ถูกอัปเดตเป็น 21 คลาสแล้ว | |
| top_class_name_en = class_names[top_class_id] | |
| top_class_name_th = CLASS_NAME_MAP.get(top_class_name_en, top_class_name_en) | |
| top_confidence = float(predictions[0][top_class_id]) | |
| # 3. Get Details and Translate | |
| # ⚠️ ตรวจสอบ: APPLIANCE_DETAILS ถูกอัปเดตเป็น 21 คลาสแล้ว | |
| details_th = dict(APPLIANCE_DETAILS.get( | |
| top_class_name_en, | |
| { | |
| "power": "N/A", | |
| "install": "N/A", | |
| "care": "N/A", | |
| "warning": "ไม่มีข้อมูลข้อควรระวัง" | |
| } | |
| )) | |
| details_en = { | |
| "power": translate_th_to_en(details_th["power"]), | |
| "install": translate_th_to_en(details_th["install"]), | |
| "care": translate_th_to_en(details_th["care"]), | |
| "warning": translate_th_to_en(details_th.get("warning", "")) | |
| } | |
| # 4. Format Text Output (TH & EN) | |
| details_th['confidence'] = top_confidence * 100 | |
| details_en['confidence'] = top_confidence * 100 | |
| predicted_class_display = f"Top Class: {top_class_name_th} ({top_class_name_en}) - {top_confidence*100:.1f}%" | |
| full_text_th = format_details_text(top_class_name_en, top_class_name_th, "th", details_th) | |
| full_text_en = format_details_text(top_class_name_en, top_class_name_th, "en", details_en) | |
| # 🎯 คืนค่า Text TH ไปยัง detail_textbox เพื่อให้แสดงผลเริ่มต้นทันที | |
| initial_detail_text = full_text_th | |
| # คืนค่า 5 Outputs: (1) ชื่อคลาสที่มั่นใจสูงสุด, (2) Text TH รายละเอียด (State), (3) Text EN รายละเอียด (State), (4) ชื่อคลาส TH (State), (5) Detail Textbox (Output UI) | |
| return predicted_class_display, full_text_th, full_text_en, top_class_name_th, initial_detail_text | |
| except Exception as e: | |
| error_msg_display = f"❌ Error processing image data. Details: {e}" | |
| return error_msg_display, error_msg_display, error_msg_display, "N/A", error_msg_display | |
| # --- Language Switch Function (ใช้โค้ดเดิม) --- | |
| def switch_language_display(lang, text_th, text_en, top_class_str_all, class_name_th_val): | |
| """สลับ Textbox ที่แสดงผลและชื่อ Class เมื่อผู้ใช้กด Radio Button""" | |
| if not top_class_str_all or "Error" in top_class_str_all or "กรุณาอัปโหลดรูปภาพ" in top_class_str_all: | |
| return gr.update(), gr.update() | |
| # ค้นหาชื่อ Class EN | |
| class_name_en = next((k for k, v in CLASS_NAME_MAP.items() if v == class_name_th_val), "N/A") | |
| confidence_match = re.search(r'(- \d+\.\d+%)', top_class_str_all) | |
| confidence_str = confidence_match.group(1) if confidence_match else "" | |
| if lang == "Thai": | |
| new_top_class_display = f"Top Class: {class_name_th_val} ({class_name_en}) {confidence_str}" | |
| new_detail_text = text_th | |
| else: | |
| new_top_class_display = f"Top Class: {class_name_en} ({class_name_th_val}) {confidence_str}" | |
| new_detail_text = text_en | |
| return new_detail_text, new_top_class_display | |
| # ---------------------------------------------------- | |
| # 4. GRADIO UI DEFINITION | |
| # ---------------------------------------------------- | |
| with gr.Blocks(title="MobileNet Electrical Appliance Classifier with Translation") as demo: | |
| gr.Markdown("## ⚡ MobileNet Electrical Appliance Classifier (Top 1 Class & Details)") | |
| # Audio Fix 2: เปลี่ยน Textbox ที่ซ่อนไว้เป็น State Components | |
| text_for_audio_th = gr.State("") | |
| text_for_audio_en = gr.State("") | |
| text_class_name_th = gr.State("") | |
| with gr.Row(): | |
| # Input Section | |
| with gr.Column(scale=1): | |
| with gr.Tabs(): | |
| with gr.Tab("🖼️ อัปโหลดไฟล์"): | |
| file_input = gr.Image(type="filepath", label="Upload Image Here", sources=["upload", "clipboard"]) | |
| submit_file_btn = gr.Button("Submit File") | |
| with gr.Tab("💻 เว็บแคม"): | |
| pc_webcam_input = gr.Image(label="Webcam Input (PC/Mac)", type="filepath", sources=["webcam"]) | |
| submit_pc_webcam_btn = gr.Button("Capture & Predict") | |
| with gr.Tab("📱 กล้องมือถือ"): | |
| smartphone_camera_input = gr.Image(label="Smartphone Camera Input", type="filepath", sources=["webcam"]) | |
| submit_mobile_camera_btn = gr.Button("Capture & Predict (Mobile)") | |
| # Output Section | |
| with gr.Column(scale=2): | |
| top_class_display = gr.Textbox(label="ผลลัพธ์การจำแนก (Class ที่มั่นใจสูงสุด)", lines=1, max_lines=1) | |
| detail_textbox = gr.Textbox(label="ข้อมูลเชิงลึกและการดูแลรักษา | Appliance Details & Care", lines=10) | |
| with gr.Row(): | |
| language_choice = gr.Radio( | |
| ["Thai", "English"], | |
| value="Thai", | |
| label="เลือกภาษา | Select Language", | |
| interactive=True | |
| ) | |
| with gr.Row(): | |
| audio_output = gr.Audio(label="Audio Playback", type="filepath", interactive=False, autoplay=True) | |
| with gr.Row(): | |
| # 🎯 ตั้งให้เป็น interactive=True ตั้งแต่เริ่มต้น เนื่องจาก Audio Logic แยกออกมาจาก Pipeline หลัก | |
| audio_th_btn = gr.Button("🔊 ฟังคำอธิบาย (Thai)", interactive=True) | |
| audio_en_btn = gr.Button("🔊 Listen to explanation (English)", interactive=True) | |
| # --- 5. EVENT HANDLERS --- | |
| # 🎯 Output Targets: 5 Outputs จาก Pipeline (4 State, 1 Textbox UI) | |
| pipeline_outputs = [ | |
| top_class_display, | |
| text_for_audio_th, | |
| text_for_audio_en, | |
| text_class_name_th, | |
| detail_textbox | |
| ] | |
| all_inputs = [file_input, pc_webcam_input, smartphone_camera_input] | |
| all_submit_btns = [submit_file_btn, submit_pc_webcam_btn, submit_mobile_camera_btn] | |
| # เชื่อมโยงแต่ละปุ่มเข้ากับ Pipeline (ใช้ .click() เดียว) | |
| for i, btn in enumerate(all_submit_btns): | |
| btn.click( | |
| fn=full_classification_pipeline, | |
| inputs=[all_inputs[i]], | |
| # Outputs คือ 4 State Component และ 1 Textbox ที่แสดงผลเริ่มต้น | |
| outputs=pipeline_outputs | |
| ) | |
| # เชื่อมโยง Radio Button (เมื่อผู้ใช้เปลี่ยนภาษา) | |
| language_choice.change( | |
| fn=switch_language_display, | |
| inputs=[language_choice, text_for_audio_th, text_for_audio_en, top_class_display, text_class_name_th], | |
| outputs=[detail_textbox, top_class_display] | |
| ) | |
| # เชื่อมโยงปุ่มเสียง (ใช้ Logic เดิม) | |
| audio_th_btn.click( | |
| fn=generate_and_play_audio_btn, | |
| inputs=[text_for_audio_th, gr.State("th")], | |
| outputs=[audio_output] | |
| ) | |
| audio_en_btn.click( | |
| fn=generate_and_play_audio_btn, | |
| inputs=[text_for_audio_en, gr.State("en")], | |
| outputs=[audio_output] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |