budijuarto commited on
Commit
d1c2be8
·
verified ·
1 Parent(s): c91c838

Upload src/egg_damage/gradio_app.py

Browse files
Files changed (1) hide show
  1. src/egg_damage/gradio_app.py +134 -0
src/egg_damage/gradio_app.py ADDED
@@ -0,0 +1,134 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from pathlib import Path
5
+ from typing import Any
6
+
7
+ import matplotlib.pyplot as plt
8
+
9
+ from .compare_models import load_best_model_record
10
+ from .config import load_config
11
+ from .inference import EggDamagePredictor, list_available_model_records
12
+ from .utils import get_logger
13
+
14
+
15
+ LOGGER = get_logger(__name__)
16
+
17
+
18
+ def probability_figure(probabilities: dict[str, float]):
19
+ fig, ax = plt.subplots(figsize=(5.2, 3.2))
20
+ labels = list(probabilities.keys())
21
+ values = [probabilities[label] for label in labels]
22
+ colors = ["#2a9d8f", "#e76f51"]
23
+ ax.bar(labels, values, color=colors[: len(labels)])
24
+ ax.set_ylim(0, 1)
25
+ ax.set_ylabel("Probability")
26
+ ax.set_title("Class Confidence")
27
+ for idx, value in enumerate(values):
28
+ ax.text(idx, min(value + 0.03, 0.98), f"{value:.2f}", ha="center", va="bottom")
29
+ fig.tight_layout()
30
+ return fig
31
+
32
+
33
+ def build_app(config: dict[str, Any]):
34
+ import gradio as gr
35
+
36
+ records = list_available_model_records(config)
37
+ if not records:
38
+ raise FileNotFoundError(
39
+ f"No trained models found in {config['paths']['model_dir']}. "
40
+ "Run python scripts/train_all.py and python scripts/evaluate_all.py first."
41
+ )
42
+ try:
43
+ best = load_best_model_record(config)
44
+ records = sorted(records, key=lambda r: 0 if r["model_name"] == best["model_name"] else 1)
45
+ except Exception:
46
+ best = records[0]
47
+ choices = [record["model_name"] for record in records]
48
+ by_name = {record["model_name"]: record for record in records}
49
+ cache: dict[str, EggDamagePredictor] = {}
50
+
51
+ def get_predictor(model_name: str) -> EggDamagePredictor:
52
+ if model_name not in cache:
53
+ cache[model_name] = EggDamagePredictor(by_name[model_name], config)
54
+ return cache[model_name]
55
+
56
+ def predict(image, model_name: str):
57
+ if image is None:
58
+ return {}, "Upload an egg image to classify it.", None, None, ""
59
+ predictor = get_predictor(model_name)
60
+ result = predictor.predict(image)
61
+ summary = (
62
+ f"Prediction: {result['predicted_label']}\n"
63
+ f"Confidence: {result['confidence']:.3f}\n"
64
+ f"Model: {result['model_name']}"
65
+ )
66
+ warning = ""
67
+ if result["low_confidence"]:
68
+ warning = (
69
+ "Confidence is modest. Use this as a screening signal, and review the image manually "
70
+ "if the egg surface, lighting, or background looks unusual."
71
+ )
72
+ explanation = None
73
+ if (
74
+ config.get("explainability", {}).get("enabled", True)
75
+ and predictor.model_type == "deep_learning"
76
+ and predictor.metadata.get("family", "cnn") == "cnn"
77
+ ):
78
+ try:
79
+ from .explainability import gradcam_overlay
80
+
81
+ explanation = gradcam_overlay(
82
+ predictor.model,
83
+ image,
84
+ predictor.metadata.get("config", config),
85
+ target_class=result["predicted_index"],
86
+ device=predictor.device,
87
+ )
88
+ except Exception as exc:
89
+ LOGGER.warning("Grad-CAM not available for this prediction: %s", exc)
90
+ return result["probabilities"], summary, probability_figure(result["probabilities"]), explanation, warning
91
+
92
+ with gr.Blocks(title="Egg Damage Classifier") as demo:
93
+ gr.Markdown(
94
+ "# Egg Damage Classifier\n"
95
+ "Upload a clear egg image and choose a trained model. The default is the best-ranked model from evaluation."
96
+ )
97
+ with gr.Row():
98
+ with gr.Column(scale=1):
99
+ image = gr.Image(type="pil", label="Egg image")
100
+ model_choice = gr.Dropdown(
101
+ choices=choices,
102
+ value=best.get("model_name", choices[0]),
103
+ label="Model",
104
+ )
105
+ button = gr.Button("Classify", variant="primary")
106
+ with gr.Column(scale=1):
107
+ label = gr.Label(label="Prediction", num_top_classes=2)
108
+ summary = gr.Textbox(label="Result", lines=4)
109
+ plot = gr.Plot(label="Probability chart")
110
+ explanation = gr.Image(type="pil", label="Explanation")
111
+ warning = gr.Markdown()
112
+ button.click(predict, inputs=[image, model_choice], outputs=[label, summary, plot, explanation, warning])
113
+ return demo
114
+
115
+
116
+ def launch(config: dict[str, Any]) -> None:
117
+ demo = build_app(config)
118
+ demo.launch(
119
+ server_name=str(config["gradio"].get("host", "127.0.0.1")),
120
+ server_port=int(config["gradio"].get("port", 7860)),
121
+ share=bool(config["gradio"].get("share", False)),
122
+ )
123
+
124
+
125
+ def main() -> None:
126
+ parser = argparse.ArgumentParser(description="Launch the local Gradio app.")
127
+ parser.add_argument("--config", default="configs/default.yaml")
128
+ args = parser.parse_args()
129
+ launch(load_config(args.config))
130
+
131
+
132
+ if __name__ == "__main__":
133
+ main()
134
+