AnasHXH commited on
Commit
bb87c82
·
verified ·
1 Parent(s): 4472dd7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +127 -81
app.py CHANGED
@@ -1,9 +1,5 @@
1
  import os
2
- import io
3
  import cv2
4
- import time
5
- import json
6
- import math
7
  import torch
8
  import numpy as np
9
  import pandas as pd
@@ -16,92 +12,105 @@ import gradio as gr
16
  DEFAULT_MODEL_PATH = os.getenv("MODEL_PATH", "weights/best.pt")
17
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  # ----------------------------
20
- # Model loading (edit this)
21
  # ----------------------------
22
  _model = None
23
-
24
  def load_model(model_path: str = DEFAULT_MODEL_PATH):
25
- """
26
- Load your trained model once when the Space boots.
27
- Replace the placeholder with your code.
28
- """
29
  global _model
30
  if _model is not None:
31
  return _model
32
 
33
- # >>> YOUR MODEL HERE <<<
34
- # Example (PyTorch scripted/ckpt):
35
- # ckpt = torch.load(model_path, map_location=DEVICE)
36
- # model = MyNet(...)
37
- # model.load_state_dict(ckpt["state_dict"] if "state_dict" in ckpt else ckpt)
38
- # model.to(DEVICE).eval()
39
- #
40
- # For YOLO-like:
41
- # from ultralytics import YOLO
42
- # model = YOLO(model_path)
43
-
44
- # Placeholder “no-model” to keep UI running:
45
  class DummyModel:
46
- def __init__(self):
47
- pass
48
  _model = DummyModel()
49
  return _model
50
 
 
51
  # ----------------------------
52
- # Inference wrapper (edit this)
53
  # ----------------------------
54
- def infer(image_bgr: np.ndarray, conf: float = 0.25):
 
 
 
 
 
 
 
 
 
 
 
55
  """
56
- Return defects as a list of boxes: [x1,y1,x2,y2,score,label]
57
- OR return a binary mask (H,W) where 1=defect.
58
- Edit this to call your model.
59
  """
60
- model = load_model()
61
-
62
- # >>> YOUR MODEL HERE <<<
63
- # Option A (detection):
64
- # results = model(image_bgr[..., ::-1]) # example if model expects RGB
65
- # boxes = [[x1,y1,x2,y2,score,"defect"], ...]
66
- # return {"type": "boxes", "boxes": boxes}
 
 
 
67
 
68
- # Option B (segmentation):
69
- # mask = your_segmentation(image_bgr) # 0/1 uint8
70
- # return {"type": "mask", "mask": mask}
71
 
72
- # --------- PLACEHOLDER (edge blobs as fake defects) ---------
73
- gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
74
- e = cv2.Canny(gray, 50, 150)
75
- cnts, _ = cv2.findContours(e, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
76
- boxes = []
77
- h, w = gray.shape[:2]
78
- for c in cnts:
79
- x, y, bw, bh = cv2.boundingRect(c)
80
- if bw * bh < max(0.0005 * w * h, 150): # skip tiny
81
- continue
82
- boxes.append([x, y, x + bw, y + bh, 0.5, "defect"])
83
- if len(boxes) >= 20:
84
- break
85
- return {"type": "boxes", "boxes": boxes}
86
 
87
- # ----------------------------
88
- # Utilities
89
- # ----------------------------
90
  def draw_boxes_with_x(image_bgr: np.ndarray, boxes, thickness: int = 3):
91
  img = image_bgr.copy()
92
- color = (0, 0, 255) # red in BGR
93
  for (x1, y1, x2, y2, score, label) in boxes:
94
  x1, y1, x2, y2 = map(int, [x1, y1, x2, y2])
95
  cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness)
96
- # draw X
97
  cv2.line(img, (x1, y1), (x2, y2), color, thickness)
98
  cv2.line(img, (x1, y2), (x2, y1), color, thickness)
99
- # label
100
  txt = f"{label}:{score:.2f}"
101
  cv2.putText(img, txt, (x1, max(y1 - 6, 0)),
102
  cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
103
  return img
104
 
 
105
  def boxes_from_mask(mask: np.ndarray, min_area: int = 50):
106
  mask = (mask > 0).astype(np.uint8)
107
  cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
@@ -112,37 +121,72 @@ def boxes_from_mask(mask: np.ndarray, min_area: int = 50):
112
  out.append([x, y, x + w, y + h, 1.0, "defect"])
113
  return out
114
 
115
- def to_csv_file(rows, path="/tmp/defect_report.csv"):
116
- df = pd.DataFrame(rows, columns=["x1", "y1", "x2", "y2", "score", "label"])
117
  df.to_csv(path, index=False)
118
  return path, df
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  # ----------------------------
121
- # Gradio handlers
122
  # ----------------------------
123
  def process(image: Image.Image, conf: float, draw_x: bool, min_area: int):
124
  if image is None:
125
- return None, pd.DataFrame(), None
126
 
127
- # PIL -> BGR np
128
  img_rgb = np.array(image.convert("RGB"))
129
  img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
130
 
131
- res = infer(img_bgr, conf=conf)
132
-
133
- if res["type"] == "mask":
134
- boxes = boxes_from_mask(res["mask"], min_area=min_area)
135
- else:
136
- boxes = [b for b in res["boxes"] if b[4] >= conf]
137
-
138
- # draw
 
 
 
 
 
139
  vis = draw_boxes_with_x(img_bgr, boxes) if draw_x else img_bgr.copy()
140
  vis_rgb = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)
 
 
 
 
 
 
 
 
 
 
 
141
 
142
- # csv + table
143
- csv_path, df = to_csv_file(boxes)
144
 
145
- return Image.fromarray(vis_rgb), df, csv_path
146
 
147
  # ----------------------------
148
  # UI
@@ -150,24 +194,26 @@ def process(image: Image.Image, conf: float, draw_x: bool, min_area: int):
150
  with gr.Blocks(title="AI-Driven EL Defect Recognition") as demo:
151
  gr.Markdown(
152
  "## AI-Driven Defect Recognition in EL Images\n"
153
- "Upload an electroluminescence (EL) image. The app detects defective cells, "
154
- "draws a red square with an X, and provides a CSV report."
 
155
  )
156
  with gr.Row():
157
  with gr.Column():
158
  inp = gr.Image(type="pil", label="Upload EL image")
159
  conf = gr.Slider(0.0, 1.0, value=0.25, step=0.01, label="Confidence threshold")
160
- draw_x = gr.Checkbox(True, label="Draw red box + X")
161
  min_area = gr.Slider(10, 5000, value=120, step=10, label="Min defect area (pixels, for masks)")
162
  run_btn = gr.Button("Run inference", variant="primary")
163
  with gr.Column():
164
  out_img = gr.Image(type="pil", label="Annotated output")
165
- out_table = gr.Dataframe(headers=["x1","y1","x2","y2","score","label"], label="Defect report (preview)")
166
  out_csv = gr.File(label="Download CSV")
 
167
 
168
  run_btn.click(process, inputs=[inp, conf, draw_x, min_area],
169
- outputs=[out_img, out_table, out_csv])
170
 
171
  if __name__ == "__main__":
172
- load_model() # warmup
173
  demo.launch()
 
1
  import os
 
2
  import cv2
 
 
 
3
  import torch
4
  import numpy as np
5
  import pandas as pd
 
12
  DEFAULT_MODEL_PATH = os.getenv("MODEL_PATH", "weights/best.pt")
13
  DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
14
 
15
+ DEMO_INPUTS = {
16
+ "input_1.png": {
17
+ "output_path": "output_1.png",
18
+ "summary": (
19
+ "**Detected defects (input_1.png):**\n"
20
+ "- Row 1: Cell 1, Cell 3, Cell 5\n"
21
+ "- Row 2: None\n"
22
+ "- Row 3: Cell 1"
23
+ ),
24
+ "rows": [
25
+ {"row": 1, "cell": 1, "defect": "defect"},
26
+ {"row": 1, "cell": 3, "defect": "defect"},
27
+ {"row": 1, "cell": 5, "defect": "defect"},
28
+ {"row": 3, "cell": 1, "defect": "defect"},
29
+ ],
30
+ },
31
+ "input_2.png": {
32
+ "output_path": "output_2.png",
33
+ "summary": (
34
+ "**Detected defects (input_2.png):**\n"
35
+ "- Row 1: Cell 4 (crack), Cell 5 (large crack)\n"
36
+ "- Row 2: Cell 1 (crack), Cell 5 (multiple cracks)\n"
37
+ "- Row 3: Cell 1 (dark defect), Cell 2 (fine crack)"
38
+ ),
39
+ "rows": [
40
+ {"row": 1, "cell": 4, "defect": "crack"},
41
+ {"row": 1, "cell": 5, "defect": "large crack"},
42
+ {"row": 2, "cell": 1, "defect": "crack"},
43
+ {"row": 2, "cell": 5, "defect": "multiple cracks"},
44
+ {"row": 3, "cell": 1, "defect": "dark defect"},
45
+ {"row": 3, "cell": 2, "defect": "fine crack"},
46
+ ],
47
+ },
48
+ }
49
+
50
  # ----------------------------
51
+ # Model loading (kept placeholder)
52
  # ----------------------------
53
  _model = None
 
54
  def load_model(model_path: str = DEFAULT_MODEL_PATH):
 
 
 
 
55
  global _model
56
  if _model is not None:
57
  return _model
58
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  class DummyModel:
60
+ pass
61
+
62
  _model = DummyModel()
63
  return _model
64
 
65
+
66
  # ----------------------------
67
+ # Helpers
68
  # ----------------------------
69
+ def _norm_mae(a: np.ndarray, b: np.ndarray) -> float:
70
+ """Normalized mean absolute error in [0,1] for matching demo images."""
71
+ if a.shape != b.shape:
72
+ b = cv2.resize(b, (a.shape[1], a.shape[0]), interpolation=cv2.INTER_AREA)
73
+ a = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY) if a.ndim == 3 else a
74
+ b = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY) if b.ndim == 3 else b
75
+ a = a.astype(np.float32) / 255.0
76
+ b = b.astype(np.float32) / 255.0
77
+ return float(np.mean(np.abs(a - b)))
78
+
79
+
80
+ def match_demo_image(upload_bgr: np.ndarray) -> str | None:
81
  """
82
+ Return 'input_1.png' or 'input_2.png' if the uploaded image matches,
83
+ else None. Uses simple normalized MAE with resizing.
 
84
  """
85
+ best_name, best_score = None, 1.0
86
+ for fname in DEMO_INPUTS.keys():
87
+ if not os.path.exists(fname):
88
+ continue
89
+ ref = cv2.imread(fname, cv2.IMREAD_COLOR)
90
+ if ref is None:
91
+ continue
92
+ score = _norm_mae(upload_bgr, ref)
93
+ if score < best_score:
94
+ best_score, best_name = score, fname
95
 
96
+ # Loose threshold to cope with minor metadata/encoding differences
97
+ return best_name if best_score < 0.01 else None # ~99% similar
 
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
 
 
 
100
  def draw_boxes_with_x(image_bgr: np.ndarray, boxes, thickness: int = 3):
101
  img = image_bgr.copy()
102
+ color = (0, 0, 255)
103
  for (x1, y1, x2, y2, score, label) in boxes:
104
  x1, y1, x2, y2 = map(int, [x1, y1, x2, y2])
105
  cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness)
 
106
  cv2.line(img, (x1, y1), (x2, y2), color, thickness)
107
  cv2.line(img, (x1, y2), (x2, y1), color, thickness)
 
108
  txt = f"{label}:{score:.2f}"
109
  cv2.putText(img, txt, (x1, max(y1 - 6, 0)),
110
  cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
111
  return img
112
 
113
+
114
  def boxes_from_mask(mask: np.ndarray, min_area: int = 50):
115
  mask = (mask > 0).astype(np.uint8)
116
  cnts, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
 
121
  out.append([x, y, x + w, y + h, 1.0, "defect"])
122
  return out
123
 
124
+
125
+ def to_csv_file_from_df(df: pd.DataFrame, path="/tmp/defect_report.csv"):
126
  df.to_csv(path, index=False)
127
  return path, df
128
 
129
+
130
+ # ----------------------------
131
+ # Placeholder inference for non-demo images
132
+ # ----------------------------
133
+ def infer_placeholder(image_bgr: np.ndarray, conf: float = 0.25):
134
+ gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
135
+ e = cv2.Canny(gray, 50, 150)
136
+ cnts, _ = cv2.findContours(e, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
137
+ boxes = []
138
+ h, w = gray.shape[:2]
139
+ for c in cnts:
140
+ x, y, bw, bh = cv2.boundingRect(c)
141
+ if bw * bh < max(0.0005 * w * h, 150):
142
+ continue
143
+ boxes.append([x, y, x + bw, y + bh, 0.5, "defect"])
144
+ if len(boxes) >= 20:
145
+ break
146
+ return boxes
147
+
148
+
149
  # ----------------------------
150
+ # Gradio handler
151
  # ----------------------------
152
  def process(image: Image.Image, conf: float, draw_x: bool, min_area: int):
153
  if image is None:
154
+ return None, pd.DataFrame(), None, "Please upload an image."
155
 
156
+ # PIL -> BGR
157
  img_rgb = np.array(image.convert("RGB"))
158
  img_bgr = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR)
159
 
160
+ # 1) Check if this is one of our demo images
161
+ match_name = match_demo_image(img_bgr)
162
+ if match_name is not None:
163
+ meta = DEMO_INPUTS[match_name]
164
+ # Use the pre-rendered annotated image
165
+ vis_pil = Image.open(meta["output_path"]).convert("RGB")
166
+ df = pd.DataFrame(meta["rows"], columns=["row", "cell", "defect"])
167
+ csv_path, df = to_csv_file_from_df(df)
168
+ return vis_pil, df, csv_path, meta["summary"]
169
+
170
+ # 2) Otherwise fall back to placeholder detection
171
+ boxes = infer_placeholder(img_bgr, conf=conf)
172
+ boxes = [b for b in boxes if b[4] >= conf]
173
  vis = draw_boxes_with_x(img_bgr, boxes) if draw_x else img_bgr.copy()
174
  vis_rgb = cv2.cvtColor(vis, cv2.COLOR_BGR2RGB)
175
+ vis_pil = Image.fromarray(vis_rgb)
176
+
177
+ # Build a simple table for non-demo case
178
+ if boxes:
179
+ df = pd.DataFrame(
180
+ [{"x1": b[0], "y1": b[1], "x2": b[2], "y2": b[3], "score": b[4], "label": b[5]} for b in boxes]
181
+ )
182
+ summary = f"Detected {len(boxes)} defect region(s)."
183
+ else:
184
+ df = pd.DataFrame(columns=["x1", "y1", "x2", "y2", "score", "label"])
185
+ summary = "No defects detected by placeholder."
186
 
187
+ csv_path, df = to_csv_file_from_df(df)
188
+ return vis_pil, df, csv_path, summary
189
 
 
190
 
191
  # ----------------------------
192
  # UI
 
194
  with gr.Blocks(title="AI-Driven EL Defect Recognition") as demo:
195
  gr.Markdown(
196
  "## AI-Driven Defect Recognition in EL Images\n"
197
+ "Upload an electroluminescence (EL) image. For the demo, uploading **input_1.png** "
198
+ "or **input_2.png** will return your predefined annotated results and a CSV.\n"
199
+ "For other images, a lightweight placeholder detector runs."
200
  )
201
  with gr.Row():
202
  with gr.Column():
203
  inp = gr.Image(type="pil", label="Upload EL image")
204
  conf = gr.Slider(0.0, 1.0, value=0.25, step=0.01, label="Confidence threshold")
205
+ draw_x = gr.Checkbox(True, label="Draw red box + X (non-demo only)")
206
  min_area = gr.Slider(10, 5000, value=120, step=10, label="Min defect area (pixels, for masks)")
207
  run_btn = gr.Button("Run inference", variant="primary")
208
  with gr.Column():
209
  out_img = gr.Image(type="pil", label="Annotated output")
210
+ out_table = gr.Dataframe(label="Defect report (preview)")
211
  out_csv = gr.File(label="Download CSV")
212
+ summary_md = gr.Markdown()
213
 
214
  run_btn.click(process, inputs=[inp, conf, draw_x, min_area],
215
+ outputs=[out_img, out_table, out_csv, summary_md])
216
 
217
  if __name__ == "__main__":
218
+ load_model()
219
  demo.launch()