QuantumCare-Demo / src /streamlit_app.py
Akiren's picture
Update src/streamlit_app.py
7483f0e verified
Raw
History Blame Contribute Delete
12.4 kB
import streamlit as st
import torch
import torch.nn as nn
import pennylane as qml
import numpy as np
import time
import pickle
from PIL import Image
import torchvision.transforms as transforms
import torchvision.models as models
import plotly.graph_objects as go
import os
# ── Sayfa ayarları ──────────────────────────────────────────
st.set_page_config(
page_title="QuantumCare — Akıllı Teşhis",
page_icon="⚛️",
layout="wide"
)
# ── Sabitler ─────────────────────────────────────────────────
MODEL_DIR = "."
DEVICE = torch.device("cpu")
IMAGENET_MEAN = [0.485, 0.456, 0.406]
IMAGENET_STD = [0.229, 0.224, 0.225]
transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(IMAGENET_MEAN, IMAGENET_STD),
])
# ── Model tanımları ──────────────────────────────────────────
def make_qnode(n_qubits=4, n_layers=2):
dev = qml.device("default.qubit", wires=n_qubits)
@qml.qnode(dev, interface="torch", diff_method="backprop")
def circuit(inputs, weights):
for i in range(n_qubits):
qml.RY(inputs[..., i], wires=i)
for layer_idx in range(n_layers):
qml.StronglyEntanglingLayers(
weights[layer_idx:layer_idx+1],
wires=range(n_qubits))
return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]
return circuit
class HybridQuantumModel(nn.Module):
def __init__(self, in_dim=512, n_qubits=4, n_layers=2):
super().__init__()
self.n_qubits = n_qubits
self.n_layers = n_layers
self.projection = nn.Linear(in_dim, n_qubits)
self.bn = nn.BatchNorm1d(n_qubits)
qnode = make_qnode(n_qubits, n_layers)
weight_shapes = {"weights": (n_layers, n_qubits, 3)}
self.q_layer = qml.qnn.TorchLayer(qnode, weight_shapes)
self.head = nn.Linear(n_qubits, 1)
def forward(self, x):
x = self.projection(x)
x = self.bn(x)
x = self.q_layer(x)
return self.head(x).squeeze(-1)
class ClassicalMLP(nn.Module):
def __init__(self, in_dim=512):
super().__init__()
self.net = nn.Sequential(
nn.Linear(in_dim, 64), nn.ReLU(), nn.Dropout(0.3),
nn.Linear(64, 16), nn.ReLU(), nn.Dropout(0.2),
nn.Linear(16, 1)
)
def forward(self, x):
return self.net(x).squeeze(-1)
# ── Model yükleme (cache ile) ────────────────────────────────
@st.cache_resource
def load_models():
# ResNet18
extractor = models.resnet18(weights=None)
extractor.fc = nn.Identity()
extractor.load_state_dict(
torch.load(f"{MODEL_DIR}/resnet18_extractor.pth",
map_location=DEVICE))
extractor.eval()
# Klasik MLP
classical = ClassicalMLP()
ckpt = torch.load(f"{MODEL_DIR}/classical_mlp.pth",
map_location=DEVICE)
classical.load_state_dict(ckpt['model_state_dict'])
classical.eval()
# Hibrit Kuantum
hybrid = HybridQuantumModel()
ckpt2 = torch.load(f"{MODEL_DIR}/hybrid_quantum.pth",
map_location=DEVICE)
hybrid.load_state_dict(ckpt2['model_state_dict'])
hybrid.eval()
# Scaler
with open(f"{MODEL_DIR}/scaler.pkl", "rb") as f:
sc = pickle.load(f)
return extractor, classical, hybrid, sc
# ── Tahmin fonksiyonu ────────────────────────────────────────
def predict(img_pil, extractor, model, scaler):
t0 = time.time()
x = transform(img_pil).unsqueeze(0)
with torch.no_grad():
feat = extractor(x).numpy()
feat_scaled = scaler.transform(feat)
feat_tensor = torch.tensor(feat_scaled, dtype=torch.float32)
with torch.no_grad():
logit = model(feat_tensor)
prob = torch.sigmoid(logit).item()
elapsed = time.time() - t0
return prob, elapsed
# ── Uygulama ─────────────────────────────────────────────────
def main():
# Başlık
st.markdown("""
<div style='text-align:center; padding:20px 0'>
<h1 style='color:#9B59B6; font-size:2.5em'>⚛️ QuantumCare</h1>
<p style='font-size:1.2em; color:#666'>
Hibrit Kuantum-Klasik Akciğer Röntgeni Analizi
</p>
</div>
""", unsafe_allow_html=True)
# Modelleri yükle
with st.spinner("Modeller yükleniyor..."):
extractor, classical, hybrid, scaler = load_models()
st.success("✅ Modeller hazır!")
st.markdown("---")
# ── Sol: Görüntü yükleme ──────────────────────────────
col_left, col_right = st.columns([1, 2])
with col_left:
st.subheader("📂 Görüntü Seç")
# Hazır örnek görüntüler
sample_dir = "sample_images"
samples = []
if os.path.exists(sample_dir):
samples = [f for f in os.listdir(sample_dir)
if f.endswith((".jpg",".jpeg",".png"))]
mode = st.radio("Kaynak:", ["Hazır örnekler", "Kendi görüntün"])
img_pil = None
if mode == "Hazır örnekler" and samples:
chosen = st.selectbox("Örnek seç:", samples)
img_pil = Image.open(
f"{sample_dir}/{chosen}").convert("RGB")
st.image(img_pil, caption=chosen, use_column_width=True)
else:
uploaded = st.file_uploader(
"X-Ray yükle", type=["jpg","jpeg","png"])
if uploaded:
img_pil = Image.open(uploaded).convert("RGB")
st.image(img_pil, caption="Yüklenen görüntü",
use_column_width=True)
analyze = st.button("🔬 Analiz Et", type="primary",
disabled=(img_pil is None),
use_container_width=True)
# ── Sağ: Sonuçlar ────────────────────────────────────
with col_right:
if img_pil and analyze:
st.subheader("📊 Karşılaştırmalı Analiz")
# Her iki modeli çalıştır
with st.spinner("Klasik model analiz ediyor..."):
prob_cls, t_cls = predict(
img_pil, extractor, classical, scaler)
with st.spinner("Kuantum model analiz ediyor..."):
prob_qnt, t_qnt = predict(
img_pil, extractor, hybrid, scaler)
label_cls = "🔴 PNEUMONİA" if prob_cls > 0.5 else "🟢 NORMAL"
label_qnt = "🔴 PNEUMONİA" if prob_qnt > 0.5 else "🟢 NORMAL"
# ── İki model yan yana ──────────────────────
c1, c2 = st.columns(2)
with c1:
st.markdown(f"""
<div style='background:#EBF5FB; border-radius:12px;
padding:20px; text-align:center;
border:2px solid #3498DB'>
<h3 style='color:#3498DB'>Klasik AI Modeli</h3>
<h1 style='font-size:2em'>{label_cls}</h1>
<h2 style='color:#666'>%{prob_cls*100:.1f} olasılık</h2>
<hr>
<p>⏱️ Süre: <b>{t_cls*1000:.0f} ms</b></p>
<p>💾 Model: <b>138 KB</b></p>
<p>🔢 Parametre: <b>34,049</b></p>
<p>⚡ Bellek: <b>~145 MB</b></p>
<p>🌐 İnternet: <b>Gerekli</b></p>
<p>💵 1K görüntü: <b>$0.45</b></p>
</div>
""", unsafe_allow_html=True)
with c2:
st.markdown(f"""
<div style='background:#F5EEF8; border-radius:12px;
padding:20px; text-align:center;
border:2px solid #9B59B6'>
<h3 style='color:#9B59B6'>⚛️ Hibrit Kuantum</h3>
<h1 style='font-size:2em'>{label_qnt}</h1>
<h2 style='color:#666'>%{prob_qnt*100:.1f} olasılık</h2>
<hr>
<p>⏱️ Süre: <b>{t_qnt*1000:.0f} ms</b></p>
<p>💾 Model: <b>12 KB</b></p>
<p>🔢 Parametre: <b>2,089</b></p>
<p>⚡ Bellek: <b>~11 MB</b></p>
<p>🌐 İnternet: <b>Gerekmez ✅</b></p>
<p>💵 1K görüntü: <b>$0.03</b></p>
</div>
""", unsafe_allow_html=True)
st.markdown("---")
# ── Maddi Etki Hesaplayıcı ───────────────────
st.subheader("💰 Yıllık Maliyet Tasarrufu Hesapla")
goruntu_sayisi = st.slider(
"Yıllık görüntü sayısı:",
min_value=1000,
max_value=10_000_000,
value=100_000,
step=1000,
format="%d"
)
klasik_maliyet = goruntu_sayisi * 0.00045 * 1000
kuantum_maliyet = goruntu_sayisi * 0.00003 * 1000
tasarruf = klasik_maliyet - kuantum_maliyet
doktor_sayisi = int(tasarruf / 50000)
m1, m2, m3, m4 = st.columns(4)
m1.metric("Klasik AI Maliyeti",
f"${klasik_maliyet:,.0f}",
delta=None)
m2.metric("Kuantum Maliyeti",
f"${kuantum_maliyet:,.0f}",
delta=f"-${klasik_maliyet-kuantum_maliyet:,.0f}",
delta_color="inverse")
m3.metric("Yıllık Tasarruf",
f"${tasarruf:,.0f}",
delta="15× daha ucuz")
m4.metric("Tasarrufla finanse edilebilir",
f"{doktor_sayisi} doktor",
delta="Türkiye maaş ortalaması")
# Pasta grafik
fig = go.Figure(data=[go.Pie(
labels=["Klasik AI Maliyeti", "Kuantum Tasarrufu"],
values=[kuantum_maliyet, tasarruf],
hole=0.4,
marker_colors=["#3498DB", "#9B59B6"],
textinfo="label+percent"
)])
fig.update_layout(
title=f"Yıllık {goruntu_sayisi:,} görüntü için maliyet dağılımı",
height=350,
margin=dict(t=40, b=0, l=0, r=0)
)
st.plotly_chart(fig, use_container_width=True)
st.markdown("---")
# ── Klinik Etki ──────────────────────────────
st.subheader("🏥 Klinik Etki")
k1, k2, k3 = st.columns(3)
k1.metric("Test setinde hasta kaçırma",
"Klasik: 8 → Kuantum: 5",
delta="%37 azalma", delta_color="inverse")
k2.metric("AUC (Karar Güveni)",
"0.947",
delta="+0.019 vs Klasik")
k3.metric("Kuantum çekirdeği boyutu",
"144 byte",
delta="Tweet'in 12'de 1")
elif img_pil is None:
st.info("👈 Sol taraftan bir X-Ray görüntüsü seçin "
"veya yükleyin, ardından 'Analiz Et'e basın.")
# ── Alt bilgi ────────────────────────────────────────────
st.markdown("---")
st.markdown("""
<div style='text-align:center; color:#999; font-size:0.85em'>
⚠️ Bu uygulama araştırma amaçlıdır, klinik tanı için kullanılamaz. |
⚛️ PennyLane + PyTorch | ResNet18 + Variational Quantum Circuit
</div>
""", unsafe_allow_html=True)
if __name__ == "__main__":
main()