| |
| """app.ipynb |
| |
| Automatically generated by Colab. |
| |
| Original file is located at |
| https://colab.research.google.com/drive/1pj05m47J2hNpEuUjfEoP7Jk3UaGpUjPE |
| """ |
|
|
| import gradio as gr |
| import joblib |
| import torch |
| import torch.nn as nn |
| import numpy as np |
| from PIL import Image |
|
|
| import torchvision.transforms as T |
| from torchvision.models import resnet18, ResNet18_Weights |
|
|
| device = "cuda" if torch.cuda.is_available() else "cpu" |
|
|
| cnn = resnet18(weights=ResNet18_Weights.DEFAULT) |
| cnn.fc = nn.Identity() |
| cnn.eval().to(device) |
|
|
| for p in cnn.parameters(): |
| p.requires_grad = False |
|
|
| transform = T.Compose([ |
| T.Resize((640, 640)), |
| T.ToTensor() |
| ]) |
|
|
| def extract_feature(image: Image.Image): |
| img = transform(image).unsqueeze(0).to(device) |
| with torch.no_grad(): |
| feat = cnn(img).cpu().numpy().flatten() |
| return feat |
|
|
| MODELS = { |
| "XGBoost": joblib.load("saved_models/xgb/hier_xgb_bundle.pkl"), |
| "Logistic": joblib.load("saved_models/logistic/hier_logistic_bundle.pkl"), |
| "MLP": joblib.load("saved_models/mlp/hier_mlp_bundle.pkl"), |
| } |
|
|
| def predict_brix(model_name, image, weight, width, height): |
|
|
| if image is None: |
| return "❌ Please upload an image", {"Low": 0, "Medium": 0, "High": 0} |
|
|
| bundle = MODELS[model_name] |
|
|
| model_A = bundle["model_A"] |
| model_B = bundle["model_B"] |
| scaler = bundle["scaler"] |
| th = bundle["thresholds"]["BEST_TH"] |
|
|
| |
| feat_img = extract_feature(image) |
|
|
| num = np.array([[weight, width, height]]) |
| num = scaler.transform(num).flatten() |
|
|
| x = np.concatenate([feat_img, num]).reshape(1, -1) |
|
|
| |
| p_sweet = model_A.predict_proba(x)[0][1] |
|
|
| probs = {"Low": 0.0, "Medium": 0.0, "High": 0.0} |
|
|
| if p_sweet < (1 - th): |
| label = "🍈 Low Brix" |
| probs["Low"] = float(1 - p_sweet) |
|
|
| elif p_sweet > th: |
| cls = model_B.predict(x)[0] + 1 |
| p2 = model_B.predict_proba(x)[0] |
|
|
| probs["Medium"] = float(p2[0]) |
| probs["High"] = float(p2[1]) |
|
|
| label = "🍈 Medium Brix" if cls == 1 else "🍈 High Brix" |
|
|
| else: |
| label = "⚠️ Abstain (Uncertain)" |
|
|
| return label, probs |
|
|
| with gr.Blocks() as demo: |
|
|
| gr.Markdown( |
| """ |
| # 🍈 Melon Brix Classifier |
| **Hierarchical Classification with Abstention** |
| CNN Feature Extraction + Tabular Data |
| |
| เลือกโมเดล → ใส่ข้อมูล → ระบบจะทำนายความหวาน |
| ถ้าไม่มั่นใจ ระบบจะ *Abstain* แทนการเดา |
| """ |
| ) |
|
|
| with gr.Tab("🍈 Prediction"): |
| with gr.Row(): |
| with gr.Column(scale=1): |
| model_dd = gr.Dropdown( |
| ["XGBoost", "Logistic", "MLP"], |
| value="MLP", |
| label="Select Model" |
| ) |
|
|
| image_in = gr.Image( |
| type="pil", |
| image_mode="RGB", |
| label="Melon Image (.jpg / .png)", |
| sources=["upload"]) |
|
|
| weight_in = gr.Number(label="Weight (kg)", value=1.5) |
| width_in = gr.Number(label="Width (cm)", value=15.0) |
| height_in = gr.Number(label="Height (cm)", value=15.0) |
|
|
| btn = gr.Button("🔍 Predict") |
|
|
| with gr.Column(scale=1): |
| pred_out = gr.Label(label="Prediction") |
|
|
| prob_out = gr.BarPlot( |
| x=["Low", "Medium", "High"], |
| y="value", |
| title="Class Probability", |
| height=300 |
| ) |
|
|
| btn.click( |
| fn=predict_brix, |
| inputs=[model_dd, image_in, weight_in, width_in, height_in], |
| outputs=[pred_out, prob_out] |
| ) |
|
|
| demo.launch() |