import streamlit as st
import torch
import torch.nn as nn
from torchvision import models, transforms
from PIL import Image
import numpy as np
# ── Page configuration ──────────────────────────────────
st.set_page_config(
page_title="DermaScan AI",
page_icon="🔬",
layout="wide",
initial_sidebar_state="expanded"
)
# ── Custom CSS styling ──────────────────────────────────
st.markdown("""
""", unsafe_allow_html=True)
# ── Load model ──────────────────────────────────────────
@st.cache_resource
def load_model():
classes = np.load('classes.npy', allow_pickle=True)
num_classes = len(classes)
model = models.resnet50(weights=None)
num_features = model.fc.in_features
model.fc = nn.Sequential(
nn.Linear(num_features, 1024), nn.ReLU(), nn.Dropout(0.2),
nn.Linear(1024, 512), nn.ReLU(), nn.Dropout(0.1),
nn.Linear(512, 128), nn.ReLU(),
nn.Linear(128, num_classes)
)
model.load_state_dict(torch.load('skin_lesion_resnet50_best.pth', map_location='cpu'))
model.eval()
return model, classes
def preprocess(image):
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
])
return transform(image).unsqueeze(0)
def confidence_pill(conf):
if conf >= 0.80:
return f'{conf*100:.1f}% High Confidence'
elif conf >= 0.60:
return f'{conf*100:.1f}% Moderate'
else:
return f'{conf*100:.1f}% Low Confidence'
# ── Hero ─────────────────────────────────────────────────
st.markdown("""
🔬
DermaScan AI
Deep learning–powered dermoscopy analysis · ResNet50 classification engine
⚡ Live Inference
""", unsafe_allow_html=True)
# ── Sidebar ──────────────────────────────────────────────
with st.sidebar:
st.markdown('', unsafe_allow_html=True)
st.markdown("""
""", unsafe_allow_html=True)
st.markdown("
", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.markdown("""
ArchitectureResNet50
FrameworkPyTorch
Input Size224 × 224 px
TaskMulti-class
NormalizationImageNet
""", unsafe_allow_html=True)
st.markdown("
", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.markdown("""
1. Upload a dermoscopy image (JPG / PNG)
2. Wait for model inference to complete
3. Review ranked predictions and confidence scores
4. Consult a dermatologist for clinical decisions
""", unsafe_allow_html=True)
# ── Upload ────────────────────────────────────────────────
st.markdown("""
""", unsafe_allow_html=True)
uploaded_file = st.file_uploader(
"Drop a dermoscopy image here, or click to browse",
type=["jpg", "jpeg", "png"],
help="Supported: JPG, JPEG, PNG"
)
# ── Results ────────────────────────────────────────────────
if uploaded_file:
image = Image.open(uploaded_file).convert("RGB")
w, h = image.size
col1, col2 = st.columns([1, 1], gap="large")
with col1:
st.markdown("""
""", unsafe_allow_html=True)
st.markdown('', unsafe_allow_html=True)
st.image(image, use_column_width=True)
st.markdown(f"""
""", unsafe_allow_html=True)
with col2:
st.markdown("""
""", unsafe_allow_html=True)
with st.spinner("Running model inference…"):
model, classes = load_model()
tensor = preprocess(image)
with torch.no_grad():
outputs = model(tensor)
probs = torch.softmax(outputs, dim=1)[0]
top_prob, top_idx = torch.topk(probs, 3)
top_prediction = classes[top_idx[0]]
top_confidence = float(top_prob[0])
# Primary diagnosis
st.markdown(f"""
Primary Diagnosis
{top_prediction}
{confidence_pill(top_confidence)}
""", unsafe_allow_html=True)
# Top-3 predictions
st.markdown("""
""", unsafe_allow_html=True)
rank_labels = ["1st", "2nd", "3rd"]
for i, (prob, idx) in enumerate(zip(top_prob, top_idx)):
conf = float(prob)
bar_width = int(conf * 100)
st.markdown(f"""
{rank_labels[i]}
{classes[idx]}
{conf*100:.1f}%
""", unsafe_allow_html=True)
# ── Full results table ──
st.markdown("
", unsafe_allow_html=True)
st.markdown("""
""", unsafe_allow_html=True)
results_data = {
"Rank": [f"#{i+1}" for i in range(len(top_prob))],
"Diagnosis": [classes[idx] for idx in top_idx],
"Confidence": [f"{prob*100:.2f}%" for prob in top_prob],
"Status": [
"✅ Primary" if i == 0 else ("⚠️ Alternate" if i == 1 else "ℹ️ Low prob")
for i in range(len(top_prob))
]
}
st.dataframe(results_data, use_container_width=True, hide_index=True)
# ── Clinical note ──
st.markdown("""
⚕️ Clinical Disclaimer
This AI classification is intended for informational and research purposes only.
It does not constitute a medical diagnosis. Always consult a board-certified dermatologist
for clinical evaluation, diagnosis, and treatment recommendations.
""", unsafe_allow_html=True)
else:
st.markdown("""
🔬
No Image Uploaded
Upload a dermoscopy image above to begin skin lesion classification
""", unsafe_allow_html=True)