| import os |
| import sys |
| import json |
| import torch |
| import numpy as np |
| import cv2 |
| from PIL import Image |
| from torchvision import transforms |
| from safetensors.torch import load_model |
| from huggingface_hub import hf_hub_download |
| import gradio as gr |
|
|
| |
| sys.path.append(os.path.join(os.path.dirname(__file__), 'src')) |
| from model import SEMViTAutoencoder |
|
|
| |
| REPO_ID = "morinousagi/sem-vit-anomaly" |
|
|
| def load_resources(): |
| device = torch.device("cpu") |
| |
| |
| print("Downloading model weights and config from Hub...") |
| model_path = hf_hub_download(repo_id=REPO_ID, filename="model.safetensors") |
| config_path = hf_hub_download(repo_id=REPO_ID, filename="config.json") |
| |
| |
| model = SEMViTAutoencoder() |
| load_model(model, model_path, strict=False) |
| model.to(device) |
| model.eval() |
| |
| |
| with open(config_path, "r") as f: |
| config = json.load(f) |
| threshold = config.get("threshold", 1.0) |
| |
| return model, threshold, device |
|
|
| |
| MODEL, THRESHOLD, DEVICE = load_resources() |
|
|
|
|
|
|
| def predict(img): |
| if img is None: |
| return None, None |
| |
| |
| transform = transforms.Compose([ |
| transforms.Resize((512, 512)), |
| transforms.Grayscale(num_output_channels=3), |
| transforms.ToTensor() |
| ]) |
| |
| pil_img = Image.fromarray(img.astype('uint8')) |
| input_tensor = transform(pil_img).unsqueeze(0).to(DEVICE) |
| |
| |
| with torch.no_grad(): |
| output_tensor = MODEL(input_tensor) |
| |
| |
| |
| orig = input_tensor.squeeze().cpu().numpy() |
| recon = output_tensor.squeeze().cpu().numpy() |
| |
| |
| diff = np.mean(np.square(orig - recon), axis=0) |
| |
| |
| |
| smoothed_diff = cv2.GaussianBlur(diff, (15, 15), 0) |
| |
| |
| score = np.max(smoothed_diff) |
| |
| is_defective = score > THRESHOLD |
| |
| status = "DEFECTIVE" if is_defective else "NORMAL" |
| bg_color = "#fee2e2" if is_defective else "#dcfce7" |
| text_color = "#b91c1c" if is_defective else "#15803d" |
| |
| html_result = f""" |
| <div style="background-color: {bg_color}; padding: 20px; border-radius: 10px; text-align: center;"> |
| <h2 style="color: {text_color}; margin: 0;">{status}</h2> |
| <p style="color: {text_color}; opacity: 0.8;">Peak Score: {score:.5f} | Threshold: {THRESHOLD:.5f}</p> |
| </div> |
| """ |
| |
| |
| |
| heatmap_gray = cv2.normalize(smoothed_diff, None, 0, 255, cv2.NORM_MINMAX).astype(np.uint8) |
| heatmap_color = cv2.applyColorMap(heatmap_gray, cv2.COLORMAP_JET) |
| heatmap_rgb = cv2.cvtColor(heatmap_color, cv2.COLOR_BGR2RGB) |
| |
| return html_result, heatmap_rgb |
|
|
|
|
|
|
|
|
| |
| with gr.Blocks() as demo: |
| gr.Markdown("# 🔬 Wafer SEM Anomaly Inspection") |
| gr.Markdown("For more information, refer to [README.md](https://huggingface.co/spaces/morinousagi/pytorch-vit-defect-detect/blob/main/README.md)") |
| |
| with gr.Row(): |
| with gr.Column(scale=1): |
| input_ui = gr.Image(label="Input SEM Image (512x512)", type="numpy") |
| with gr.Row(): |
| reset_btn = gr.Button("Reset") |
| run_btn = gr.Button("Run Analysis", variant="primary") |
| |
| with gr.Column(scale=1): |
| result_ui = gr.HTML() |
| heatmap_ui = gr.Image(label="Defect Location Heatmap") |
|
|
| |
| run_btn.click(predict, inputs=input_ui, outputs=[result_ui, heatmap_ui]) |
| |
| |
| reset_btn.click( |
| lambda: (None, "", None), |
| inputs=None, |
| outputs=[input_ui, result_ui, heatmap_ui] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch(theme=gr.themes.Default(), server_name="0.0.0.0", server_port=7860) |