File size: 6,973 Bytes
6399a41
 
 
 
 
 
 
 
 
 
 
 
 
 
bb87c82
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6399a41
5cddc47
6399a41
 
 
 
 
 
5cddc47
 
6399a41
 
 
bb87c82
6399a41
bb87c82
 
 
5cddc47
 
bb87c82
 
 
 
 
 
 
5cddc47
bb87c82
 
 
 
 
 
 
 
6399a41
 
 
bb87c82
6399a41
 
 
 
 
5cddc47
6399a41
 
 
5cddc47
6399a41
 
 
5cddc47
bb87c82
 
 
 
 
 
 
5cddc47
bb87c82
5cddc47
bb87c82
 
5cddc47
bb87c82
6399a41
5cddc47
6399a41
5cddc47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6399a41
 
 
 
 
 
5368d55
 
6399a41
 
 
5cddc47
 
6399a41
bb87c82
5cddc47
6399a41
 
 
bb87c82
6399a41
bb87c82
6399a41
 
bb87c82
6399a41
 
bb87c82
5cddc47
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
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)