import gradio as gr from huggingface_hub import snapshot_download import sys # ========================= # Load model + inspector # ========================= MODEL_REPO = "Janani-V/pcb-defect-yolov8s-deeppcb" local_dir = snapshot_download(repo_id=MODEL_REPO) sys.path.append(local_dir) from inspector import PCBDefectInspector inspector = PCBDefectInspector(weights_path=f"{local_dir}/best.pt") # ========================= # Severity config # ========================= SEVERITY_COLORS = { "Critical": "#ef4444", "High": "#f97316", "Medium-High": "#f59e0b", "Medium": "#eab308", "Low-Medium": "#84cc16", "Low": "#22c55e", "Unknown": "#64748b", } SEVERITY_RANK = { "Critical": 6, "High": 5, "Medium-High": 4, "Medium": 3, "Low-Medium": 2, "Low": 1, "Unknown": 0, } # ========================= # Example images # Make sure these exist in your Space repo # ========================= EXAMPLES = [ "sample1_input.jpg", "sample2_input.jpg", "sample3_input.jpg", "sample4_input.jpg", "sample5_input.jpg", "sample6_input.jpg", "sample7_input.jpg", "sample8_input.jpg", ] # ========================= # Helper functions # ========================= def empty_summary_cards(): return """
Defects Found
-
Highest Severity
-
Unique Defect Types
-
Avg Confidence
-
""" def empty_report(): return """

Inspection report will appear here

Upload a PCB image and run inspection to view defect analysis, severity, likely root cause, impact, and recommended action.

""" def make_summary_cards(findings): if not findings: return """
Defects Found
0
Highest Severity
None
Unique Defect Types
0
Avg Confidence
0.00
""" total_defects = len(findings) unique_classes = len(set(f["class"] for f in findings)) avg_conf = sum(float(f["confidence"]) for f in findings) / max(total_defects, 1) highest_severity = max( (f.get("severity", "Unknown") for f in findings), key=lambda s: SEVERITY_RANK.get(s, 0), default="Unknown" ) sev_color = SEVERITY_COLORS.get(highest_severity, "#64748b") return f"""
Defects Found
{total_defects}
Highest Severity
{highest_severity}
Unique Defect Types
{unique_classes}
Avg Confidence
{avg_conf:.2f}
""" def make_report_html(result): findings = result.get("findings", []) summary = result.get("summary", "Inspection completed.") if not findings: return f"""
Inspection Summary
{summary}

No defects detected above the selected confidence threshold.

The model did not identify any PCB manufacturing defect in the uploaded image.

""" # Group by class so repeated instances are summarized together grouped = {} for f in findings: cls = f["class"] if cls not in grouped: grouped[cls] = { "count": 0, "confidences": [], "info": f } grouped[cls]["count"] += 1 grouped[cls]["confidences"].append(f["confidence"]) sorted_items = sorted( grouped.items(), key=lambda x: SEVERITY_RANK.get(x[1]["info"].get("severity", "Unknown"), 0), reverse=True ) highest_severity = sorted_items[0][1]["info"].get("severity", "Unknown") banner_color = SEVERITY_COLORS.get(highest_severity, "#64748b") html = f"""
Inspection Summary
{summary}
Highest severity detected: {highest_severity}
""" for cls, data in sorted_items: info = data["info"] severity = info.get("severity", "Unknown") sev_color = SEVERITY_COLORS.get(severity, "#64748b") conf_list = ", ".join(f"{float(c):.2f}" for c in data["confidences"]) explanation = info.get("explanation", "No explanation available.") root_cause = info.get("root_cause", "Not available.") impact = info.get("impact", "Not available.") action = info.get("action", "Not available.") html += f"""
{cls.upper()}
Detected instances: {data['count']}
{severity}
Confidence: {conf_list}
Explanation
{explanation}
Likely Root Cause
{root_cause}
Potential Impact
{impact}
Recommended Action
{action}
""" html += "
" return html # ========================= # Main functions # ========================= def detect(image, conf): if image is None: return None, empty_summary_cards(), empty_report() inspector.conf = conf result = inspector.inspect(image) annotated = result.get("annotated_image", None) findings = result.get("findings", []) summary_cards = make_summary_cards(findings) report_html = make_report_html(result) return annotated, summary_cards, report_html def clear_all(): return None, 0.25, None, empty_summary_cards(), empty_report() # ========================= # CSS # ========================= CUSTOM_CSS = """ body, .gradio-container { background: #0b1220 !important; color: #f3f4f6 !important; font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; } .gradio-container { max-width: 1400px !important; margin: auto !important; padding-top: 18px !important; padding-bottom: 24px !important; } .main-title { font-size: 34px; font-weight: 800; margin-bottom: 6px; color: #f8fafc; } .sub-title { font-size: 16px; line-height: 1.8; color: #cbd5e1; margin-bottom: 24px; } .section-title { font-size: 22px; font-weight: 700; color: #f8fafc; margin: 8px 0 14px 0; } .card-subtext { color: #94a3b8; font-size: 14px; margin-top: 8px; line-height: 1.7; } .summary-grid { display: grid; grid-template-columns: repeat(4, minmax(120px, 1fr)); gap: 12px; margin-top: 12px; margin-bottom: 6px; } .summary-card { background: linear-gradient(180deg, #141d2d 0%, #0f172a 100%); border: 1px solid #243042; border-radius: 16px; padding: 16px; min-height: 92px; display: flex; flex-direction: column; justify-content: center; } .summary-title { color: #94a3b8; font-size: 13px; margin-bottom: 8px; } .summary-value { font-size: 24px; font-weight: 800; color: #f8fafc; } .report-wrapper { margin-top: 6px; } .report-banner { background: #111827; border: 1px solid #243042; border-radius: 16px; padding: 18px 18px 16px 18px; margin-bottom: 16px; } .report-banner-title { font-size: 18px; font-weight: 800; color: #f8fafc; margin-bottom: 6px; } .report-banner-text { color: #dbeafe; font-size: 15px; line-height: 1.7; margin-bottom: 6px; } .report-banner-sub { color: #cbd5e1; font-size: 14px; } .defect-card { background: #111827; border: 1px solid #243042; border-radius: 16px; padding: 18px; margin-bottom: 14px; } .defect-header { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 12px; } .defect-name { font-size: 20px; font-weight: 800; color: #f8fafc; } .defect-count { color: #94a3b8; font-size: 14px; margin-top: 4px; } .severity-pill { padding: 8px 14px; border-radius: 999px; font-weight: 800; font-size: 13px; white-space: nowrap; } .defect-meta { font-size: 14px; color: #cbd5e1; margin-bottom: 14px; } .defect-section { margin-top: 12px; padding-top: 10px; border-top: 1px solid #223045; } .defect-section-title { font-size: 14px; font-weight: 800; color: #93c5fd; margin-bottom: 6px; letter-spacing: 0.2px; } .defect-section-text { color: #e5e7eb; line-height: 1.7; font-size: 14px; } .empty-report { background: #111827; border: 1px dashed #334155; border-radius: 16px; padding: 28px; text-align: center; color: #cbd5e1; margin-top: 8px; } .empty-report h3 { margin: 0 0 10px 0; color: #f8fafc; font-size: 20px; } .note-box { background: #0f172a; border: 1px solid #223045; border-radius: 16px; padding: 16px; color: #cbd5e1; font-size: 14px; line-height: 1.7; margin-top: 18px; } .note-box b { color: #f8fafc; } .footer-link { color: #8b5cf6 !important; text-decoration: none !important; font-weight: 700; } .footer-link:hover { text-decoration: underline !important; } /* Make buttons cleaner */ button.primary { background: linear-gradient(90deg, #6D5DF6, #7C3AED) !important; border: none !important; color: white !important; } button.secondary { background: #1f2937 !important; border: 1px solid #374151 !important; color: #f8fafc !important; } @media (max-width: 900px) { .summary-grid { grid-template-columns: repeat(2, minmax(120px, 1fr)); } } @media (max-width: 560px) { .summary-grid { grid-template-columns: 1fr; } } /* Even, uniform example thumbnails (Gradio 5 renders Examples as a [data-testid="dataset"] grid, not a ) */ .gradio-container [data-testid="dataset"] { display: grid !important; grid-template-columns: repeat(4, minmax(90px, 1fr)) !important; gap: 14px !important; } .gradio-container [data-testid="dataset"] > div, .gradio-container [data-testid="dataset"] button { width: 100% !important; aspect-ratio: 1 / 1 !important; padding: 0 !important; margin: 0 !important; display: flex !important; align-items: center !important; justify-content: center !important; overflow: hidden !important; border-radius: 10px !important; } .gradio-container [data-testid="dataset"] img { width: 100% !important; height: 100% !important; object-fit: cover !important; border-radius: 10px !important; } @media (max-width: 900px) { .gradio-container [data-testid="dataset"] { grid-template-columns: repeat(2, minmax(90px, 1fr)) !important; } } """ # ========================= # Build UI # ========================= with gr.Blocks(css=CUSTOM_CSS) as demo: gr.HTML("""
PCB Defect Detection Dashboard
Upload a PCB image to detect manufacturing defects and receive an inspection report with severity assessment, likely root cause, impact, and recommended action for each detected defect type. This interface combines a fine-tuned YOLOv8s defect detector with a structured PCB defect interpretation layer.
""") with gr.Row(equal_height=False): with gr.Column(scale=4): gr.HTML('
📤 Input Panel
') input_image = gr.Image( type="numpy", label="Upload PCB Image", height=360 ) conf_slider = gr.Slider( minimum=0.10, maximum=0.90, value=0.25, step=0.05, label="Confidence Threshold" ) with gr.Row(): detect_btn = gr.Button("Run Inspection", variant="primary") clear_btn = gr.Button("Clear", variant="secondary") gr.HTML("""
Supported formats: JPG, JPEG, PNG
Recommended: clear grayscale PCB images similar to DeepPCB-style inspection images.
""") # RIGHT PANEL with gr.Column(scale=6): gr.HTML('
🔍 Detection Output
') output_image = gr.Image( type="numpy", label="Annotated PCB Output", height=360 ) summary_html = gr.HTML(empty_summary_cards()) gr.HTML('
📋 Intelligent Inspection Report
') report_html = gr.HTML(empty_report()) gr.HTML('
🖼 Example PCB Images
') gr.Examples( examples=EXAMPLES, inputs=input_image, label="Click any example to load it into the input panel" ) gr.HTML(f"""
Note: Severity, root cause, impact, and recommended action are generated from a structured PCB defect knowledge layer built on top of the model's class predictions. The YOLO model itself performs visual defect detection; higher-level interpretation should still be reviewed in the context of PCB manufacturing and inspection workflows.

Model card: {MODEL_REPO}
""") detect_btn.click( fn=detect, inputs=[input_image, conf_slider], outputs=[output_image, summary_html, report_html] ) clear_btn.click( fn=clear_all, inputs=[], outputs=[input_image, conf_slider, output_image, summary_html, report_html] ) if __name__ == "__main__": demo.launch()