Puttipong13 commited on
Commit
cb4f64d
·
verified ·
1 Parent(s): 99c8bf9

Upload 3 files

Browse files
Files changed (3) hide show
  1. app.py +493 -0
  2. final_best_MobileNet_weights.h5 +3 -0
  3. requirements.txt +10 -0
app.py ADDED
@@ -0,0 +1,493 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import numpy as np
4
+ import tensorflow as tf
5
+ import gradio as gr
6
+ from gtts import gTTS
7
+ import uuid
8
+ from transformers import AutoTokenizer, AutoModelForSeq2SeqLM
9
+ import torch
10
+ import re
11
+
12
+ # ***** นำเข้า MobileNet และ Preprocessing ที่ถูกต้อง *****
13
+ from tensorflow.keras.applications import MobileNet
14
+ from tensorflow.keras.applications.mobilenet import preprocess_input as mobilenet_preprocess_input
15
+
16
+ # =======================================================
17
+ # 1. CONFIGURATION & MODEL SETUP
18
+ # =======================================================
19
+
20
+ # --- Audio Fix 1: กำหนดและสร้างโฟลเดอร์ Output สำหรับไฟล์เสียง ---
21
+ OUTPUT_DIR = "appliance_audio_outputs"
22
+ if not os.path.exists(OUTPUT_DIR):
23
+ os.makedirs(OUTPUT_DIR)
24
+ # --------------------------------------------------------------------------
25
+
26
+ # --- ชื่อ Class ภาษาอังกฤษ (Key) ---
27
+ class_names_en = [
28
+ "Air Conditioner", "Electric Kettle", "Fan", "Fridge", "Iron", "Microwave",
29
+ "Toaster", "TV", "Vacuum Cleaner", "Washing Machine",
30
+ # --- 💡 NEW CLASSES ADDED HERE ---
31
+ "Rice Cooker", "Water Heater", "Water Purifier", "Blender", "Cooktop",
32
+ "Air Purifier", "Water Dispenser", "Hair Dryer", "Water Pump", "Computer",
33
+ "Electric Frying Pan"
34
+ ]
35
+ # --- ชื่อ Class ภาษาไทย (Value) ---
36
+ class_names_th = [
37
+ "เครื่องปรับอากาศ", "กาต้มน้ำไฟฟ้า", "พัดลม", "ตู้เย็น", "เตารีด", "ไมโครเวฟ",
38
+ "เครื่องปิ้งขนมปัง", "โทรทัศน์", "เครื่องดูดฝุ่น", "เครื่องซักผ้า",
39
+ # --- 💡 NEW CLASSES ADDED HERE ---
40
+ "หม้อหุงข้าว", "เครื่องทำน้ำอุ่น", "เครื่องกรองน้ำ", "เครื่องปั่น", "เตาทำอาหาร",
41
+ "เครื่องฟอกอากาศ", "เครื่องกดน้ำดื่ม", "เครื่องเป่าผม", "ปั๊มน้ำ", "คอมพิวเตอร์",
42
+ "กระทะไฟฟ้า"
43
+ ]
44
+
45
+ # --- สร้าง Dictionary สำหรับการแปลชื่อ Class ---
46
+ CLASS_NAME_MAP = dict(zip(class_names_en, class_names_th))
47
+
48
+ class_names = class_names_en
49
+ # --- 💡 อัปเดตจำนวนคลาสทั้งหมด (21 Classes) ---
50
+ NUM_CLASSES = len(class_names)
51
+ # ----------------------------------------------
52
+ IMG_SIZE = (224, 224)
53
+ MODEL_WEIGHTS_PATH = "final_best_MobileNet_weights.h5"
54
+
55
+ # --- MarianMT Setup (Thai -> English Translation) ---
56
+ MARIAN_MODEL_NAME = "Helsinki-NLP/opus-mt-th-en"
57
+ MT_tokenizer = None
58
+ MT_model = None
59
+
60
+ try:
61
+ print(f"🔄 Loading MarianMT Model: {MARIAN_MODEL_NAME}...")
62
+ MT_tokenizer = AutoTokenizer.from_pretrained(MARIAN_MODEL_NAME)
63
+ MT_model = AutoModelForSeq2SeqLM.from_pretrained(MARIAN_MODEL_NAME)
64
+ print("✅ MarianMT Model loaded.")
65
+ except Exception as e:
66
+ print(f"❌ Error loading MarianMT model (Translation will be disabled): {e}")
67
+
68
+
69
+ # --- ฟังก์ชันสร้างโครงสร้างโมเดล ---
70
+ def create_electrical_appliance_model():
71
+ """สร้างโครงสร้างโมเดล MobileNet + Custom Head"""
72
+ base_model = MobileNet(
73
+ weights='imagenet',
74
+ include_top=False,
75
+ input_shape=(IMG_SIZE[0], IMG_SIZE[1], 3)
76
+ )
77
+ for layer in base_model.layers:
78
+ layer.trainable = False
79
+ model = tf.keras.models.Sequential([
80
+ base_model,
81
+ tf.keras.layers.GlobalAveragePooling2D(),
82
+ # 💡 ใช้ NUM_CLASSES ใหม่ที่ถูกอัปเดตแล้ว
83
+ tf.keras.layers.Dense(NUM_CLASSES, activation='softmax')
84
+ ])
85
+ return model
86
+
87
+ # 1. โหลดโมเดลด้วยการสร้างโครงสร้างและโหลด Weights
88
+ model = create_electrical_appliance_model()
89
+ print("🔄 Loading custom MobileNet weights...")
90
+ try:
91
+ # ⚠️ โหลด Weights ด้วยโครงสร้างใหม่ (21 Classes) - ต้องมั่นใจว่าไฟล์ Weights นี้เทรนด้วย 21 คลาสแล้ว
92
+ model.load_weights(MODEL_WEIGHTS_PATH)
93
+ print(f"✅ Model '{MODEL_WEIGHTS_PATH}' loaded successfully.")
94
+ except Exception as e:
95
+ print(f"❌ FATAL Model Error: Failed to load custom weights from {MODEL_WEIGHTS_PATH}. Error: {e}")
96
+ # raise SystemExit(1) # คอมเมนต์ออกเพื่อให้โค้ดรันได้ แม้ไม่มีไฟล์
97
+ print("⚠️ Continuing without weights. Prediction will be random.")
98
+
99
+ # =======================================================
100
+ # 2. DATA AND TRANSLATION FUNCTIONS
101
+ # =======================================================
102
+
103
+ # --- Appliance Details Data (Thai Source) ---
104
+ # 💡 เพิ่มรายละเอียดสำหรับคลาสใหม่ (ใช้ข้อมูลตัวอย่าง)
105
+ APPLIANCE_DETAILS = {
106
+ "Air Conditioner": {
107
+ "power": "1,000 ถึง 3,000 วัตต์",
108
+ "install": "ควรติดตั้งในที่ร่มและสูงจากพื้น 2.5 ม. ขึ้นไปเพื่อประสิทธิภาพที่ดี",
109
+ "care": "ล้างแผ่นกรองทุก 1 เดือน และเรียกช่างล้างใหญ่ปีละ 2 ครั้ง",
110
+ "warning": "ห้ามติดตั้งใกล้แสงแดดหรือความชื้น และควรมีสายดิน"
111
+ },
112
+ "Electric Kettle": {
113
+ "power": "1,500 ถึง 2,200 วัตต์",
114
+ "install": "ใช้งานบนพื้นผิวเรียบและแห้ง ห่างจากมือเด็ก",
115
+ "care": "ทำความสะอาดตะกรันด้านในด้วยน้ำส้มสายชูหรือน้ำมะนาวทุก 1-2 เดือน",
116
+ "warning": "ห้ามเปิดเครื่องโดยไม่มีน้ำ และระวังน้ำร้อนลวก"
117
+ },
118
+ "Fan": {
119
+ "power": "50 ถึง 100 วัตต์",
120
+ "install": "ตั้งในที่อากาศถ่ายเทสะดวก ห่างจากผ้าม่าน",
121
+ "care": "ถอดใบพัดและตะแกรงล้างทำความสะอาดทุก 2-3 สัปดาห์",
122
+ "warning": "ห้ามสอดมือหรือสิ่งของเข้าไปขณะพัดลมทำงาน"
123
+ },
124
+ "Fridge": {
125
+ "power": "100 ถึง 200 วัตต์",
126
+ "install": "ตั้งห่างจากผนัง 10-15 ซม. ห้ามตั้งใกล้แหล่งความร้อน",
127
+ "care": "ตั้งอุณหภูมิ 2-5 องศาเซลเซียส และละลายน้ำแข็งเมื่อเริ่มหนา",
128
+ "warning": "ห้ามใช้ปลั๊กพ่วงร่วมกับเครื่องใช้ไฟฟ้ากำลังสูง"
129
+ },
130
+ "Iron": {
131
+ "power": "1,000 ถึง 2,000 วัตต์",
132
+ "install": "ใช้งานบนโต๊ะที่มั่นคง และควรมีแผ่นรองรีดทนความร้อน",
133
+ "care": "ทำความสะอาดหน้าเตาด้วยผ้าชุบน้ำหมาดๆ เมื่อเครื่องเย็นแล้ว",
134
+ "warning": "ห้ามวางเตารีดคว่ำหน้าเตาในขณะร้อน"
135
+ },
136
+ "Microwave": {
137
+ "power": "800 ถึง 1,500 วัตต์",
138
+ "install": "วางบนพื้นผิวเรียบ ห้ามวางใกล้เตาแก๊ส",
139
+ "care": "เช็ดภายในหลังใช้งานทันที เพื่อป้องกันคราบฝังแน่น",
140
+ "warning": "ห้ามใส่ภาชนะโลหะหรือวัสดุไวไฟ"
141
+ },
142
+ "Toaster": {
143
+ "power": "800 ถึง 1,500 วัตต์",
144
+ "install": "ใช้งานบนพื้นผิวทนความร้อน ห่างจากวัสดุติดไฟ",
145
+ "care": "ถอดถาดรองเศษขนมปังออกมาทิ้งและทำความสะอาดทุกครั้ง",
146
+ "warning": "ห้ามใช้มือหรือโลหะแหย่ขณะเครื่องทำงาน"
147
+ },
148
+ "TV": {
149
+ "power": "50 ถึง 300 วัตต์",
150
+ "install": "วางบนชั้นที่มั่นคง หรือติดตั้งกับผนังที่แข็งแรง",
151
+ "care": "ใช้ผ้านุ่มเช็ดหน้าจอ ห้ามใช้สารเคมีที่มีแอลกอฮอล์",
152
+ "warning": "หลีกเลี่ยงการวางใกล้น้ำหรือความชื้น"
153
+ },
154
+ "Vacuum Cleaner": {
155
+ "power": "1,000 ถึง 2,000 วัตต์",
156
+ "install": "ควรเลือกหัวดูดที่เหมาะสมกับพื้นผิวที่ต้องการทำความสะอาด",
157
+ "care": "ทิ้งฝุ่นและทำความสะอาดตัวกรอง (Filter) ทันทีหลังการใช้งาน",
158
+ "warning": "ห้ามดูดของเหลวหรือของมีคม"
159
+ },
160
+ "Washing Machine": {
161
+ "power": "200 ถึง 500 วัตต์ (ซัก), 2,000 ถึง 3,000 วัตต์ (ทำน้ำร้อน)",
162
+ "install": "ติดตั้งบนพื้นที่ได้ระดับ และมีก๊อกน้ำ/ท่อระบายน้ำเข้าถึง",
163
+ "care": "ทำความสะอาดถังซักด้วยน้ำยาเฉพาะทางเดือนละครั้ง หรือใช้น้ำส้มสายชูช่วยกำจัดเชื้อรา",
164
+ "warning": "ห้ามเปิดฝาเครื่องขณะกำลังปั่น"
165
+ },
166
+ "Rice Cooker": {
167
+ "power": "500 ถึง 1,000 วัตต์",
168
+ "install": "วางบนพื้นผิวเรียบและแห้ง ห้ามวางใกล้ผนังมากเกินไป",
169
+ "care": "เช็ดทำความสะอาดหม้อชั้นในด้วยผ้านุ่ม ห้ามใช้ฝอยขัด",
170
+ "warning": "ห้ามล้างตัวเครื่องด้วยน้ำ"
171
+ },
172
+ "Water Heater": {
173
+ "power": "2,000 ถึง 4,500 วัตต์",
174
+ "install": "ควรติดตั้งสายดิน และควรติดตั้งโดยช่างผู้ชำนาญการ",
175
+ "care": "ตรวจสอบระบบตัดไฟ (ELCB) และทำความสะอาดฝักบัวทุก 6 เดือน",
176
+ "warning": "ห้ามใช้หากระบบตัดไฟทำงานผิดปกติ"
177
+ },
178
+ "Water Purifier": {
179
+ "power": "0 ถึง 100 วัตต์ (ถ้ามีปั๊ม/ทำความเย็น)",
180
+ "install": "ติดตั้งใกล้ก๊อกน้ำประปา และในที่ร่ม",
181
+ "care": "เปลี่ยนไส้กรองตามอายุการใช้งานที่กำหนดอย่างเคร่งครัด",
182
+ "warning": "ห้ามใช้งานเมื่อไส้กรองหมดอายุ"
183
+ },
184
+ "Blender": {
185
+ "power": "300 ถึง 1,000 วัตต์",
186
+ "install": "ใช้บนพื้นผิวที่มั่นคง อย่าให้สายไฟพาดผ่านคมมีด",
187
+ "care": "ทำความสะอาดโถปั่นทันทีหลังใช้งาน และไม่ควรปั่นเกินกำลังเครื่อง",
188
+ "warning": "ห้ามเปิดฝาโถขณะเครื่องทำงาน"
189
+ },
190
+ "Cooktop": {
191
+ "power": "1,000 ถึง 3,000 วัตต์ (ต่อหัว)",
192
+ "install": "ต้องใช้อุปกรณ์ทำครัวที่เหมาะสม (เช่น เตาแม่เหล็กไฟฟ้าต้องใช้ภาชนะโลหะ)",
193
+ "care": "เช็ดทำความสะอาดคราบอาหารทันทีหลังเครื่องเย็น",
194
+ "warning": "ระวังพื้นผิวร้อนหลังใช้งาน"
195
+ },
196
+ "Air Purifier": {
197
+ "power": "30 ถึง 100 วัตต์",
198
+ "install": "ตั้งไว้กลางห้อง หรือในบริเวณที่อากาศถ่ายเทได้ดี",
199
+ "care": "ดูดฝุ่น/เช็ดแผ่นกรองหยาบ และเปลี่ยนแผ่นกรอง HEPA ตามอายุการใช้งาน",
200
+ "warning": "ห้ามบังช่องดูดและช่องระบายอากาศ"
201
+ },
202
+ "Water Dispenser": {
203
+ "power": "100 ถึง 500 วัตต์ (ทำน้ำเย็น), 500 ถึง 1,000 วัตต์ (ทำน้ำร้อน)",
204
+ "install": "ตั้งในที่ร่ม ห่างจากผนัง 10-15 ซม.",
205
+ "care": "ทำความสะอาดถังเก็บน้ำและช่องจ่ายน้ำอย่างสม่ำเสมอ",
206
+ "warning": "ระวังน้ำร้อนอาจทำให้เกิดแผลไหม้"
207
+ },
208
+ "Hair Dryer": {
209
+ "power": "1,000 ถึง 2,000 วัตต์",
210
+ "install": "ใช้งานในที่แห้ง ห้ามใช้ใกล้น้ำ",
211
+ "care": "ทำความสะอาดตะแกรงกรองด้านหลังเครื่องเพื่อป้องกันฝุ่นอุดตัน",
212
+ "warning": "ห้ามใช้งานขณะมือเปียก"
213
+ },
214
+ "Water Pump": {
215
+ "power": "200 ถึง 500 วัตต์",
216
+ "install": "ควรติดตั้งในที่ร่ม มีการระบายอากาศดี และมีถังพักน้ำ",
217
+ "care": "ตรวจสอบการรั่วซึม และไม่ควรปล่อยให้ปั๊มทำงานโดยไม่มีน้ำ",
218
+ "warning": "ห้ามเดินเครื่องขณะไม่มีน้ำในระบบ"
219
+ },
220
+ "Computer": {
221
+ "power": "150 ถึง 500 วัตต์ (เครื่องเดสก์ท็อป)",
222
+ "install": "ตั้งในบริเวณที่อากาศถ่ายเทสะดวก ห้ามวางชิดผนัง",
223
+ "care": "ทำความสะอาดฝุ่นภายในเครื่องและพัดลมอย่างสม่ำเสมอ",
224
+ "warning": "ห้ามปิดกั้นช่องระบายความร้อน"
225
+ },
226
+ "Electric Frying Pan": {
227
+ "power": "1,000 ถึง 1,500 วัตต์",
228
+ "install": "ใช้งานบนพื้นผิวที่ทนความร้อนและมั่นคง",
229
+ "care": "ห้ามนำตัวกระทะลงน้ำทั้งอัน ทำความสะอาดเฉพาะส่วนที่ถอดได้",
230
+ "warning": "ระวังความร้อนสูงขณะใช้งาน"
231
+ }
232
+
233
+ # -----------------------------------------------------------
234
+ }
235
+
236
+ # --- MarianMT Translation Function (ใช้โค้ดเดิม) ---
237
+ def translate_th_to_en(text_to_translate: str) -> str:
238
+ """ฟังก์ชันสำหรับแปลข้อความจากไทยเป็นอังกฤษโดยใช้ MarianMT Model"""
239
+ if not text_to_translate or MT_model is None:
240
+ return text_to_translate
241
+
242
+ try:
243
+ encoded_input = MT_tokenizer(text_to_translate, return_tensors="pt")
244
+ translated_tokens = MT_model.generate(
245
+ **encoded_input,
246
+ max_length=100,
247
+ num_beams=4
248
+ )
249
+ return MT_tokenizer.decode(translated_tokens.squeeze(), skip_special_tokens=True)
250
+ except Exception as e:
251
+ print(f"Translation Error: {e}")
252
+ return text_to_translate
253
+
254
+ # --- GTTs Text-to-Audio Function (แก้ไขให้ใช้ OUTPUT_DIR) ---
255
+ def text_to_audio(text_to_speak, lang='th'):
256
+ """แปลงข้อความเป็นไฟล์เสียง และคืนค่า path ของไฟล์เสียง"""
257
+ # Audio Fix 1: ใช้ os.path.join เพื่อสร้าง Path ในโฟลเดอร์ output
258
+ filename = os.path.join(OUTPUT_DIR, f"output_audio_{uuid.uuid4()}.mp3")
259
+ try:
260
+ tts = gTTS(text=text_to_speak, lang=lang, slow=False)
261
+ tts.save(filename)
262
+ return filename
263
+ except Exception as e:
264
+ print(f"Error generating audio: {e}")
265
+ return None
266
+
267
+ def generate_and_play_audio_btn(text_to_speak, lang_code):
268
+ """สร้างไฟล์เสียงแล้วส่ง Path ไปยัง Audio Component"""
269
+ if not text_to_speak or "Error" in text_to_speak or "กรุณาอัปโหลด" in text_to_speak:
270
+ print("Error: Audio generation skipped due to empty or error text.")
271
+ return None
272
+
273
+ return text_to_audio(text_to_speak, lang=lang_code)
274
+
275
+ # ----------------------------------------------------
276
+ # 3. CORE LOGIC PIPELINE
277
+ # ----------------------------------------------------
278
+
279
+ def format_details_text(class_name_en, class_name_th, lang, details):
280
+ confidence = f"{details['confidence']:.1f}%"
281
+ class_name_display = class_name_th if lang == "th" else class_name_en
282
+
283
+ warning_text = details.get('warning', 'ไม่มีข้อมูลข้อควรระวัง')
284
+
285
+ if lang == "th":
286
+ return (
287
+ f"⚡ เครื่องใช้ไฟฟ้าที่ตรวจพบ: {class_name_display} (ความมั่นใจ {confidence})\n"
288
+ f"🔌 กำลังไฟฟ้าโดยประมาณ: {details['power']}\n\n"
289
+ f"🛠️ การติดตั้ง/ตำแหน่งที่เหมาะสม:\n"
290
+ f"- {details['install']}\n\n"
291
+ f"🛁 การดูแลการใช้งาน/เคล็ดลับ:\n"
292
+ f"- {details['care']}\n\n"
293
+ f"⚠️ ข้อควรระวัง:\n"
294
+ f"- {warning_text}"
295
+ )
296
+ else:
297
+ return (
298
+ f"⚡ Detected Appliance: {class_name_display} (Confidence {confidence})\n"
299
+ f"🔌 Estimated Power Consumption: {details['power']}\n\n"
300
+ f"🛠️ Installation / Proper Placement:\n"
301
+ f"- {details['install']}\n\n"
302
+ f"🛁 Maintenance / Usage Tips:\n"
303
+ f"- {details['care']}\n\n"
304
+ f"⚠️ Warnings:\n"
305
+ f"- {warning_text}"
306
+ )
307
+
308
+
309
+ # 🎯 แก้ไข Outputs: ต้องคืนค่า initial_detail_text เพื่อให้ Textbox หลักแสดงผลทันที
310
+ def full_classification_pipeline(input_img_path):
311
+ """ฟังก์ชันทำนายผลหลักที่ส่งออกเฉพาะ Class ที่มั่นใจสูงสุดค่าเดียว และเตรียมข้อมูลสำหรับ UI"""
312
+ if input_img_path is None:
313
+ error_msg = "กรุณาอัปโหลดรูปภาพ หรือใช้กล้องเพื่อทำนายผล"
314
+ # คืนค่า 5 Outputs: Top Class, Text TH State, Text EN State, Class TH State, Detail Textbox
315
+ return error_msg, error_msg, error_msg, "N/A", error_msg
316
+
317
+ try:
318
+ input_img = cv2.imread(input_img_path)
319
+
320
+ if input_img is None:
321
+ raise ValueError("Error reading image file (input_img is None).")
322
+
323
+ img = cv2.resize(input_img, IMG_SIZE)
324
+ img = np.array(img, dtype=np.float32)
325
+ img = np.expand_dims(img, axis=0)
326
+ img = mobilenet_preprocess_input(img)
327
+
328
+ # 2. Prediction
329
+ predictions = model.predict(img, verbose=0)
330
+ top_class_id = np.argmax(predictions[0])
331
+ # ⚠️ ตรวจสอบ: class_names ถูกอัปเดตเป็น 21 คลาสแล้ว
332
+ top_class_name_en = class_names[top_class_id]
333
+ top_class_name_th = CLASS_NAME_MAP.get(top_class_name_en, top_class_name_en)
334
+ top_confidence = predictions[0][top_class_id]
335
+
336
+ # 3. Get Details and Translate
337
+ # ⚠️ ตรวจสอบ: APPLIANCE_DETAILS ถูกอัปเดตเป็น 21 คลาสแล้ว
338
+ details_th = APPLIANCE_DETAILS.get(
339
+ top_class_name_en,
340
+ {
341
+ "power": "N/A",
342
+ "install": "N/A",
343
+ "care": "N/A",
344
+ "warning": "ไม่มีข้อมูลข้อควรระวัง"
345
+ }
346
+ )
347
+
348
+
349
+ details_en = {
350
+ "power": translate_th_to_en(details_th["power"]),
351
+ "install": translate_th_to_en(details_th["install"]),
352
+ "care": translate_th_to_en(details_th["care"]),
353
+ "warning": translate_th_to_en(details_th.get("warning", ""))
354
+ }
355
+
356
+ # 4. Format Text Output (TH & EN)
357
+ details_th['confidence'] = top_confidence * 100
358
+ details_en['confidence'] = top_confidence * 100
359
+
360
+ predicted_class_display = f"Top Class: {top_class_name_th} ({top_class_name_en}) - {top_confidence*100:.1f}%"
361
+
362
+ full_text_th = format_details_text(top_class_name_en, top_class_name_th, "th", details_th)
363
+ full_text_en = format_details_text(top_class_name_en, top_class_name_th, "en", details_en)
364
+
365
+ # 🎯 คืนค่า Text TH ไปยัง detail_textbox เพื่อให้แสดงผลเริ่มต้นทันที
366
+ initial_detail_text = full_text_th
367
+
368
+ # คืนค่า 5 Outputs: (1) ชื่อคลาสที่มั่นใจสูงสุด, (2) Text TH รายละเอียด (State), (3) Text EN รายละเอียด (State), (4) ชื่อคลาส TH (State), (5) Detail Textbox (Output UI)
369
+ return predicted_class_display, full_text_th, full_text_en, top_class_name_th, initial_detail_text
370
+
371
+ except Exception as e:
372
+ error_msg_display = f"❌ Error processing image data. Details: {e}"
373
+ return error_msg_display, error_msg_display, error_msg_display, "N/A", error_msg_display
374
+
375
+
376
+ # --- Language Switch Function (ใช้โค้ดเดิม) ---
377
+ def switch_language_display(lang, text_th, text_en, top_class_str_all, class_name_th_val):
378
+ """สลับ Textbox ที่แสดงผลและชื่อ Class เมื่อผู้ใช้กด Radio Button"""
379
+
380
+ if "Error" in top_class_str_all or "กรุณาอัปโหลดรูปภาพ" in top_class_str_all:
381
+ return text_th, top_class_str_all
382
+
383
+ # ค้นหาชื่อ Class EN
384
+ class_name_en = next((k for k, v in CLASS_NAME_MAP.items() if v == class_name_th_val), "N/A")
385
+
386
+ confidence_match = re.search(r'(- \d+\.\d+%)', top_class_str_all)
387
+ confidence_str = confidence_match.group(1) if confidence_match else ""
388
+
389
+ if lang == "Thai":
390
+ new_top_class_display = f"Top Class: {class_name_th_val} ({class_name_en}) {confidence_str}"
391
+ new_detail_text = text_th
392
+ else:
393
+ new_top_class_display = f"Top Class: {class_name_en} ({class_name_th_val}) {confidence_str}"
394
+ new_detail_text = text_en
395
+
396
+ return new_detail_text, new_top_class_display
397
+
398
+ # ----------------------------------------------------
399
+ # 4. GRADIO UI DEFINITION
400
+ # ----------------------------------------------------
401
+
402
+ with gr.Blocks(title="MobileNet Electrical Appliance Classifier with Translation") as demo:
403
+ gr.Markdown("## ⚡ MobileNet Electrical Appliance Classifier (Top 1 Class & Details)")
404
+
405
+ # Audio Fix 2: เปลี่ยน Textbox ที่ซ่อนไว้เป็น State Components
406
+ text_for_audio_th = gr.State("")
407
+ text_for_audio_en = gr.State("")
408
+ text_class_name_th = gr.State("")
409
+
410
+ with gr.Row():
411
+ # Input Section
412
+ with gr.Column(scale=1):
413
+ with gr.Tabs():
414
+ with gr.TabItem("🖼️ อัปโหลดไฟล์"):
415
+ file_input = gr.Image(type="filepath", label="Upload Image Here")
416
+ submit_file_btn = gr.Button("Submit File")
417
+
418
+ with gr.TabItem("💻 เว็บแคม"):
419
+ pc_webcam_input = gr.Image(label="Webcam Input (PC/Mac)", type="filepath", streaming=False)
420
+ submit_pc_webcam_btn = gr.Button("Capture & Predict")
421
+
422
+ with gr.TabItem("📱 กล้องมือถือ"):
423
+ smartphone_camera_input = gr.Image(label="Smartphone Camera Input", type="filepath", streaming=False)
424
+ submit_mobile_camera_btn = gr.Button("Capture & Predict (Mobile)")
425
+
426
+ # Output Section
427
+ with gr.Column(scale=2):
428
+ top_class_display = gr.Textbox(label="ผลลัพธ์การจำแนก (Class ที่มั่นใจสูงสุด)", lines=1, max_lines=1)
429
+
430
+ detail_textbox = gr.Textbox(label="ข้อมูลเชิงลึกและการดูแลรักษา | Appliance Details & Care", lines=10)
431
+
432
+ with gr.Row():
433
+ language_choice = gr.Radio(
434
+ ["Thai", "English"],
435
+ value="Thai",
436
+ label="เลือกภาษา | Select Language",
437
+ interactive=True
438
+ )
439
+
440
+ with gr.Row():
441
+ audio_output = gr.Audio(label="Audio Playback", type="filepath", interactive=False)
442
+
443
+ with gr.Row():
444
+ # 🎯 ตั้งให้เป็น interactive=True ตั้งแต่เริ่มต้น เนื่องจาก Audio Logic แยกออกมาจาก Pipeline หลัก
445
+ audio_th_btn = gr.Button("🔊 ฟังคำอธิบาย (Thai)", interactive=True)
446
+ audio_en_btn = gr.Button("🔊 Listen to explanation (English)", interactive=True)
447
+
448
+ # --- 5. EVENT HANDLERS ---
449
+
450
+ # 🎯 Output Targets: 5 Outputs จาก Pipeline (4 State, 1 Textbox UI)
451
+ pipeline_outputs = [
452
+ top_class_display,
453
+ text_for_audio_th,
454
+ text_for_audio_en,
455
+ text_class_name_th,
456
+ detail_textbox
457
+ ]
458
+
459
+ all_inputs = [file_input, pc_webcam_input, smartphone_camera_input]
460
+ all_submit_btns = [submit_file_btn, submit_pc_webcam_btn, submit_mobile_camera_btn]
461
+
462
+ # เชื่อมโยงแต่ละปุ่มเข้ากับ Pipeline (ใช้ .click() เดียว)
463
+ for i, btn in enumerate(all_submit_btns):
464
+ btn.click(
465
+ fn=full_classification_pipeline,
466
+ inputs=[all_inputs[i]],
467
+ # Outputs คือ 4 State Component และ 1 Textbox ที่แสดงผลเริ่มต้น
468
+ outputs=pipeline_outputs
469
+ )
470
+
471
+ # เชื่อมโยง Radio Button (เมื่อผู้ใช้เปลี่ยนภาษา)
472
+ language_choice.change(
473
+ fn=switch_language_display,
474
+ inputs=[language_choice, text_for_audio_th, text_for_audio_en, top_class_display, text_class_name_th],
475
+ outputs=[detail_textbox, top_class_display]
476
+ )
477
+
478
+ # เชื่อมโยงปุ่มเสียง (ใช้ Logic เดิม)
479
+ audio_th_btn.click(
480
+ fn=generate_and_play_audio_btn,
481
+ inputs=[text_for_audio_th, gr.State("th")],
482
+ outputs=[audio_output]
483
+ )
484
+
485
+ audio_en_btn.click(
486
+ fn=generate_and_play_audio_btn,
487
+ inputs=[text_for_audio_en, gr.State("en")],
488
+ outputs=[audio_output]
489
+ )
490
+
491
+
492
+ if __name__ == "__main__":
493
+ demo.launch(share=True)
final_best_MobileNet_weights.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e2c51835e4366c9108504aba33b2947ce97c10185596e3b89c903c9b3ac15844
3
+ size 13118240
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ opencv-python
2
+ gradio==3.15.0
3
+ gradio_client>=0.7.0 # ใช้ client ที่เข้ากันได้กับ 3.x.x
4
+ tensorflow
5
+ transformers
6
+ sentencepiece
7
+ torch
8
+ numpy
9
+ gtts
10
+ uuid