AnasHXH's picture
Update app.py
5368d55 verified
Raw
History Blame Contribute Delete
6.97 kB
import os
import cv2
import torch
import numpy as np
import pandas as pd
from PIL import Image
import gradio as gr
# ----------------------------
# Config
# ----------------------------
DEFAULT_MODEL_PATH = os.getenv("MODEL_PATH", "weights/best.pt")
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
DEMO_INPUTS = {
"input_1.png": {
"output_path": "output_1.png",
"summary": (
"**Detected defects (input_1.png):**\n"
"- Row 1: Cell 1, Cell 3, Cell 5\n"
"- Row 2: None\n"
"- Row 3: Cell 1"
),
"rows": [
{"row": 1, "cell": 1, "defect": "defect"},
{"row": 1, "cell": 3, "defect": "defect"},
{"row": 1, "cell": 5, "defect": "defect"},
{"row": 3, "cell": 1, "defect": "defect"},
],
},
"input_2.png": {
"output_path": "output_2.png",
"summary": (
"**Detected defects (input_2.png):**\n"
"- Row 1: Cell 4 (crack), Cell 5 (large crack)\n"
"- Row 2: Cell 1 (crack), Cell 5 (multiple cracks)\n"
"- Row 3: Cell 1 (dark defect), Cell 2 (fine crack)"
),
"rows": [
{"row": 1, "cell": 4, "defect": "crack"},
{"row": 1, "cell": 5, "defect": "large crack"},
{"row": 2, "cell": 1, "defect": "crack"},
{"row": 2, "cell": 5, "defect": "multiple cracks"},
{"row": 3, "cell": 1, "defect": "dark defect"},
{"row": 3, "cell": 2, "defect": "fine crack"},
],
},
}
# ----------------------------
# Model stub (kept)
# ----------------------------
_model = None
def load_model(model_path: str = DEFAULT_MODEL_PATH):
global _model
if _model is not None:
return _model
class Dummy: ...
_model = Dummy()
return _model
# ----------------------------
# Helpers
# ----------------------------
def _norm_mae(a: np.ndarray, b: np.ndarray) -> float:
if a.shape != b.shape:
b = cv2.resize(b, (a.shape[1], a.shape[0]), interpolation=cv2.INTER_AREA)
if a.ndim == 3: a = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY)
if b.ndim == 3: b = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY)
a = a.astype(np.float32) / 255.0
b = b.astype(np.float32) / 255.0
return float(np.mean(np.abs(a - b)))
def match_demo_image(upload_bgr: np.ndarray) -> str | None:
best_name, best_score = None, 1.0
for fname in DEMO_INPUTS.keys():
if not os.path.exists(fname):
continue
ref = cv2.imread(fname, cv2.IMREAD_COLOR)
if ref is None:
continue
score = _norm_mae(upload_bgr, ref)
if score < best_score:
best_score, best_name = score, fname
return best_name if best_score < 0.01 else None # ~99% similar
def draw_boxes_with_x(image_bgr: np.ndarray, boxes, thickness: int = 3):
img = image_bgr.copy()
color = (0, 0, 255)
for (x1, y1, x2, y2, score, label) in boxes:
x1, y1, x2, y2 = map(int, [x1, y1, x2, y2])
cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness)
cv2.line(img, (x1, y1), (x2, y2), color, thickness)
cv2.line(img, (x1, y2), (x2, y1), color, thickness)
cv2.putText(img, f"{label}:{score:.2f}", (x1, max(y1-6,0)),
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
return img
def to_csv(df: pd.DataFrame, path="/tmp/defect_report.csv"):
df.to_csv(path, index=False)
return path, df
def placeholder_boxes(image_bgr: np.ndarray, conf: float = 0.25):
gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
e = cv2.Canny(gray, 50, 150)
cnts, _ = cv2.findContours(e, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
boxes = []
h, w = gray.shape[:2]
for c in cnts:
x, y, bw, bh = cv2.boundingRect(c)
if bw * bh < max(0.0005 * w * h, 150):
continue
boxes.append([x, y, x+bw, y+bh, 0.5, "defect"])
if len(boxes) >= 20:
break
return [b for b in boxes if b[4] >= conf]
# ----------------------------
# Gradio handler (filepath input)
# ----------------------------
def process(image_path: str, conf: float, draw_x: bool, min_area: int):
try:
if not image_path or not os.path.exists(image_path):
return None, pd.DataFrame(), None, "Please upload an image."
# read as BGR
pil_img = Image.open(image_path).convert("RGB")
img_bgr = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
# Demo match?
match_name = match_demo_image(img_bgr)
if match_name is not None:
meta = DEMO_INPUTS[match_name]
vis_pil = Image.open(meta["output_path"]).convert("RGB")
df = pd.DataFrame(meta["rows"], columns=["row", "cell", "defect"])
csv_path, df = to_csv(df)
return vis_pil, df, csv_path, meta["summary"]
# Fallback placeholder
boxes = placeholder_boxes(img_bgr, conf=conf)
vis = draw_boxes_with_x(img_bgr, boxes) if draw_x else img_bgr.copy()
vis_pil = Image.fromarray(cv2.cvtColor(vis, cv2.COLOR_BGR2RGB))
if boxes:
df = pd.DataFrame(
[{"x1": b[0], "y1": b[1], "x2": b[2], "y2": b[3], "score": b[4], "label": b[5]} for b in boxes]
)
summary = f"Detected {len(boxes)} region(s) by placeholder."
else:
df = pd.DataFrame(columns=["x1","y1","x2","y2","score","label"])
summary = "No defects detected by placeholder."
csv_path, df = to_csv(df)
return vis_pil, df, csv_path, summary
except Exception as e:
# surface errors to UI instead of crashing
return None, pd.DataFrame(), None, f"Error: {type(e).__name__}: {e}"
# ----------------------------
# UI
# ----------------------------
with gr.Blocks(title="AI-Driven EL Defect Recognition") as demo:
gr.Markdown(
"## Defect Images\n"
" .\n"
)
with gr.Row():
with gr.Column():
# Use filepath to avoid large base64 uploads
inp = gr.Image(type="filepath", label="Upload EL image")
conf = gr.Slider(0.0, 1.0, value=0.25, step=0.01, label="Confidence threshold")
draw_x = gr.Checkbox(True, label="Draw red box + X (non-demo only)")
min_area = gr.Slider(10, 5000, value=120, step=10, label="Min defect area (for masks)")
run_btn = gr.Button("Run inference", variant="primary")
with gr.Column():
out_img = gr.Image(type="pil", label="Annotated output")
out_table = gr.Dataframe(label="Defect report (preview)")
out_csv = gr.File(label="Download CSV")
summary_md = gr.Markdown()
run_btn.click(process, inputs=[inp, conf, draw_x, min_area],
outputs=[out_img, out_table, out_csv, summary_md])
if __name__ == "__main__":
load_model()
# Disable SSR to avoid upload edge-cases
demo.launch(ssr_mode=False)