File size: 4,277 Bytes
2530391 5116187 2530391 6b156e7 2530391 76a66ec 2530391 76a66ec 9117135 2530391 76a66ec 2530391 76a66ec 2530391 76a66ec 2530391 b5200fd 6b156e7 2530391 76a66ec 2530391 6b156e7 76a66ec 6b156e7 2530391 b5200fd 2530391 17988af 2530391 17988af 2530391 17988af | 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 | 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
# Add src to path
sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
from model import SEMViTAutoencoder
# --- Hub Configuration ---
REPO_ID = "morinousagi/sem-vit-anomaly"
def load_resources():
device = torch.device("cpu")
# 1. Download files from the Model Repo
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")
# 2. Initialize Model
model = SEMViTAutoencoder()
load_model(model, model_path, strict=False)
model.to(device)
model.eval()
# 3. Load Threshold
with open(config_path, "r") as f:
config = json.load(f)
threshold = config.get("threshold", 1.0) # changed to 1.0
return model, threshold, device
# Global initialization
MODEL, THRESHOLD, DEVICE = load_resources()
def predict(img):
if img is None:
return None, None
# 1. Preprocess (matching dataset.py)
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)
# 2. Inference
with torch.no_grad():
output_tensor = MODEL(input_tensor)
# 3. Calculate Anomaly Map (matching Peak Score Logic)
# Move to CPU and numpy
orig = input_tensor.squeeze().cpu().numpy() # [3, 512, 512]
recon = output_tensor.squeeze().cpu().numpy() # [3, 512, 512]
# Pixel-wise MSE across channels
diff = np.mean(np.square(orig - recon), axis=0) # [512, 512]
# 4. Scoring Logic (matching train_eval.py)
# Apply Gaussian Blur to aggregate the defect signal (mimics avg_pool2d)
smoothed_diff = cv2.GaussianBlur(diff, (15, 15), 0)
# Peak Score (Max value in the smoothed map)
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>
"""
# 5. Generate Heatmap
# Use the smoothed_diff for the heatmap so the "glow" matches the score location
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
# --- Minimal UI Layout ---
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")
# Interactivity
run_btn.click(predict, inputs=input_ui, outputs=[result_ui, heatmap_ui])
# Reset functionality
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) |