Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -48,43 +48,33 @@ DEMO_INPUTS = {
|
|
| 48 |
}
|
| 49 |
|
| 50 |
# ----------------------------
|
| 51 |
-
# Model
|
| 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 |
-
|
| 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)
|
| 74 |
-
b = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY)
|
| 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:
|
|
@@ -92,11 +82,8 @@ def match_demo_image(upload_bgr: np.ndarray) -> str | None:
|
|
| 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)
|
|
@@ -105,32 +92,15 @@ def draw_boxes_with_x(image_bgr: np.ndarray, boxes, thickness: int = 3):
|
|
| 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 |
-
|
| 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)
|
| 117 |
-
out = []
|
| 118 |
-
for c in cnts:
|
| 119 |
-
x, y, w, h = cv2.boundingRect(c)
|
| 120 |
-
if w * h >= min_area:
|
| 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)
|
|
@@ -138,55 +108,51 @@ def infer_placeholder(image_bgr: np.ndarray, conf: float = 0.25):
|
|
| 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
|
| 144 |
if len(boxes) >= 20:
|
| 145 |
break
|
| 146 |
-
return boxes
|
| 147 |
-
|
| 148 |
|
| 149 |
# ----------------------------
|
| 150 |
-
# Gradio handler
|
| 151 |
# ----------------------------
|
| 152 |
-
def process(
|
| 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 |
-
|
| 187 |
-
csv_path, df = to_csv_file_from_df(df)
|
| 188 |
-
return vis_pil, df, csv_path, summary
|
| 189 |
-
|
| 190 |
|
| 191 |
# ----------------------------
|
| 192 |
# UI
|
|
@@ -194,16 +160,16 @@ def process(image: Image.Image, conf: float, draw_x: bool, min_area: int):
|
|
| 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 |
-
"
|
| 198 |
-
"
|
| 199 |
-
"For other images, a lightweight placeholder detector runs."
|
| 200 |
)
|
| 201 |
with gr.Row():
|
| 202 |
with gr.Column():
|
| 203 |
-
|
|
|
|
| 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 (
|
| 207 |
run_btn = gr.Button("Run inference", variant="primary")
|
| 208 |
with gr.Column():
|
| 209 |
out_img = gr.Image(type="pil", label="Annotated output")
|
|
@@ -216,4 +182,5 @@ with gr.Blocks(title="AI-Driven EL Defect Recognition") as demo:
|
|
| 216 |
|
| 217 |
if __name__ == "__main__":
|
| 218 |
load_model()
|
| 219 |
-
|
|
|
|
|
|
| 48 |
}
|
| 49 |
|
| 50 |
# ----------------------------
|
| 51 |
+
# Model stub (kept)
|
| 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 |
+
class Dummy: ...
|
| 59 |
+
_model = Dummy()
|
|
|
|
|
|
|
|
|
|
| 60 |
return _model
|
| 61 |
|
|
|
|
| 62 |
# ----------------------------
|
| 63 |
# Helpers
|
| 64 |
# ----------------------------
|
| 65 |
def _norm_mae(a: np.ndarray, b: np.ndarray) -> float:
|
|
|
|
| 66 |
if a.shape != b.shape:
|
| 67 |
b = cv2.resize(b, (a.shape[1], a.shape[0]), interpolation=cv2.INTER_AREA)
|
| 68 |
+
if a.ndim == 3: a = cv2.cvtColor(a, cv2.COLOR_BGR2GRAY)
|
| 69 |
+
if b.ndim == 3: b = cv2.cvtColor(b, cv2.COLOR_BGR2GRAY)
|
| 70 |
a = a.astype(np.float32) / 255.0
|
| 71 |
b = b.astype(np.float32) / 255.0
|
| 72 |
return float(np.mean(np.abs(a - b)))
|
| 73 |
|
|
|
|
| 74 |
def match_demo_image(upload_bgr: np.ndarray) -> str | None:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
best_name, best_score = None, 1.0
|
| 76 |
for fname in DEMO_INPUTS.keys():
|
| 77 |
+
if not os.path.exists(fname):
|
| 78 |
continue
|
| 79 |
ref = cv2.imread(fname, cv2.IMREAD_COLOR)
|
| 80 |
if ref is None:
|
|
|
|
| 82 |
score = _norm_mae(upload_bgr, ref)
|
| 83 |
if score < best_score:
|
| 84 |
best_score, best_name = score, fname
|
|
|
|
|
|
|
| 85 |
return best_name if best_score < 0.01 else None # ~99% similar
|
| 86 |
|
|
|
|
| 87 |
def draw_boxes_with_x(image_bgr: np.ndarray, boxes, thickness: int = 3):
|
| 88 |
img = image_bgr.copy()
|
| 89 |
color = (0, 0, 255)
|
|
|
|
| 92 |
cv2.rectangle(img, (x1, y1), (x2, y2), color, thickness)
|
| 93 |
cv2.line(img, (x1, y1), (x2, y2), color, thickness)
|
| 94 |
cv2.line(img, (x1, y2), (x2, y1), color, thickness)
|
| 95 |
+
cv2.putText(img, f"{label}:{score:.2f}", (x1, max(y1-6,0)),
|
|
|
|
| 96 |
cv2.FONT_HERSHEY_SIMPLEX, 0.6, color, 2, cv2.LINE_AA)
|
| 97 |
return img
|
| 98 |
|
| 99 |
+
def to_csv(df: pd.DataFrame, path="/tmp/defect_report.csv"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
df.to_csv(path, index=False)
|
| 101 |
return path, df
|
| 102 |
|
| 103 |
+
def placeholder_boxes(image_bgr: np.ndarray, conf: float = 0.25):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 104 |
gray = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2GRAY)
|
| 105 |
e = cv2.Canny(gray, 50, 150)
|
| 106 |
cnts, _ = cv2.findContours(e, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
|
|
|
| 108 |
h, w = gray.shape[:2]
|
| 109 |
for c in cnts:
|
| 110 |
x, y, bw, bh = cv2.boundingRect(c)
|
| 111 |
+
if bw * bh < max(0.0005 * w * h, 150):
|
| 112 |
continue
|
| 113 |
+
boxes.append([x, y, x+bw, y+bh, 0.5, "defect"])
|
| 114 |
if len(boxes) >= 20:
|
| 115 |
break
|
| 116 |
+
return [b for b in boxes if b[4] >= conf]
|
|
|
|
| 117 |
|
| 118 |
# ----------------------------
|
| 119 |
+
# Gradio handler (filepath input)
|
| 120 |
# ----------------------------
|
| 121 |
+
def process(image_path: str, conf: float, draw_x: bool, min_area: int):
|
| 122 |
+
try:
|
| 123 |
+
if not image_path or not os.path.exists(image_path):
|
| 124 |
+
return None, pd.DataFrame(), None, "Please upload an image."
|
| 125 |
+
|
| 126 |
+
# read as BGR
|
| 127 |
+
pil_img = Image.open(image_path).convert("RGB")
|
| 128 |
+
img_bgr = cv2.cvtColor(np.array(pil_img), cv2.COLOR_RGB2BGR)
|
| 129 |
+
|
| 130 |
+
# Demo match?
|
| 131 |
+
match_name = match_demo_image(img_bgr)
|
| 132 |
+
if match_name is not None:
|
| 133 |
+
meta = DEMO_INPUTS[match_name]
|
| 134 |
+
vis_pil = Image.open(meta["output_path"]).convert("RGB")
|
| 135 |
+
df = pd.DataFrame(meta["rows"], columns=["row", "cell", "defect"])
|
| 136 |
+
csv_path, df = to_csv(df)
|
| 137 |
+
return vis_pil, df, csv_path, meta["summary"]
|
| 138 |
+
|
| 139 |
+
# Fallback placeholder
|
| 140 |
+
boxes = placeholder_boxes(img_bgr, conf=conf)
|
| 141 |
+
vis = draw_boxes_with_x(img_bgr, boxes) if draw_x else img_bgr.copy()
|
| 142 |
+
vis_pil = Image.fromarray(cv2.cvtColor(vis, cv2.COLOR_BGR2RGB))
|
| 143 |
+
if boxes:
|
| 144 |
+
df = pd.DataFrame(
|
| 145 |
+
[{"x1": b[0], "y1": b[1], "x2": b[2], "y2": b[3], "score": b[4], "label": b[5]} for b in boxes]
|
| 146 |
+
)
|
| 147 |
+
summary = f"Detected {len(boxes)} region(s) by placeholder."
|
| 148 |
+
else:
|
| 149 |
+
df = pd.DataFrame(columns=["x1","y1","x2","y2","score","label"])
|
| 150 |
+
summary = "No defects detected by placeholder."
|
| 151 |
+
csv_path, df = to_csv(df)
|
| 152 |
+
return vis_pil, df, csv_path, summary
|
| 153 |
+
except Exception as e:
|
| 154 |
+
# surface errors to UI instead of crashing
|
| 155 |
+
return None, pd.DataFrame(), None, f"Error: {type(e).__name__}: {e}"
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
# ----------------------------
|
| 158 |
# UI
|
|
|
|
| 160 |
with gr.Blocks(title="AI-Driven EL Defect Recognition") as demo:
|
| 161 |
gr.Markdown(
|
| 162 |
"## AI-Driven Defect Recognition in EL Images\n"
|
| 163 |
+
"Demo: uploading **input_1.png** or **input_2.png** returns predefined annotated results and CSV.\n"
|
| 164 |
+
"Other images run a lightweight placeholder."
|
|
|
|
| 165 |
)
|
| 166 |
with gr.Row():
|
| 167 |
with gr.Column():
|
| 168 |
+
# Use filepath to avoid large base64 uploads
|
| 169 |
+
inp = gr.Image(type="filepath", label="Upload EL image")
|
| 170 |
conf = gr.Slider(0.0, 1.0, value=0.25, step=0.01, label="Confidence threshold")
|
| 171 |
draw_x = gr.Checkbox(True, label="Draw red box + X (non-demo only)")
|
| 172 |
+
min_area = gr.Slider(10, 5000, value=120, step=10, label="Min defect area (for masks)")
|
| 173 |
run_btn = gr.Button("Run inference", variant="primary")
|
| 174 |
with gr.Column():
|
| 175 |
out_img = gr.Image(type="pil", label="Annotated output")
|
|
|
|
| 182 |
|
| 183 |
if __name__ == "__main__":
|
| 184 |
load_model()
|
| 185 |
+
# Disable SSR to avoid upload edge-cases
|
| 186 |
+
demo.launch(ssr_mode=False)
|