| | import streamlit as st |
| | import cv2 |
| | import numpy as np |
| | import matplotlib.pyplot as plt |
| | import seaborn as sns |
| |
|
| | def render_css(): |
| | """Injects custom CSS for a professional medical dashboard optimized for horizontal screens.""" |
| | st.markdown(""" |
| | <style> |
| | .main { |
| | max-width: 100% !important; |
| | padding: 1rem 2rem; |
| | } |
| | .block-container { |
| | max-width: 100% !important; |
| | padding-left: 2rem !important; |
| | padding-right: 2rem !important; |
| | } |
| | h1, h2, h3 { |
| | font-family: 'Segoe UI', sans-serif; |
| | font-weight: 600; |
| | } |
| | |
| | div[data-testid="stMetric"] { |
| | padding: 18px; |
| | border-radius: 12px; |
| | box-shadow: 0 2px 8px rgba(0,0,0,0.08); |
| | } |
| | |
| | img { |
| | border-radius: 10px; |
| | box-shadow: 0 4px 12px rgba(0,0,0,0.1); |
| | image-rendering: -webkit-optimize-contrast; |
| | image-rendering: crisp-edges; |
| | } |
| | |
| | .report-box { |
| | padding: 24px; |
| | border-radius: 12px; |
| | border-left: 6px solid #dc3545; |
| | margin-bottom: 20px; |
| | box-shadow: 0 2px 8px rgba(0,0,0,0.08); |
| | } |
| | |
| | .stTabs [data-baseweb="tab-list"] { |
| | gap: 8px; |
| | } |
| | .stTabs [data-baseweb="tab"] { |
| | padding: 12px 24px; |
| | border-radius: 8px; |
| | } |
| | |
| | .dataframe { |
| | font-size: 0.95rem !important; |
| | } |
| | </style> |
| | """, unsafe_allow_html=True) |
| |
|
| | def render_header(app_name, version): |
| | st.title(app_name) |
| | st.caption(f"Klinik Karar Destek Sistemi | Sürüm: {version}") |
| | st.markdown("---") |
| |
|
| | def apply_heatmap_overlay(img_rgb, heatmap_float, colormap=cv2.COLORMAP_JET, alpha=0.6): |
| | if np.max(heatmap_float) > 0: |
| | heatmap_float = heatmap_float / np.max(heatmap_float) |
| | heatmap_uint8 = np.uint8(255 * heatmap_float) |
| | heatmap_colored = cv2.applyColorMap(heatmap_uint8, colormap) |
| | heatmap_colored = cv2.cvtColor(heatmap_colored, cv2.COLOR_BGR2RGB) |
| | |
| | if heatmap_colored.shape[:2] != img_rgb.shape[:2]: |
| | heatmap_colored = cv2.resize(heatmap_colored, (img_rgb.shape[1], img_rgb.shape[0])) |
| | |
| | overlay = cv2.addWeighted(img_rgb, 1-alpha, heatmap_colored, alpha, 0) |
| | return overlay |
| |
|
| | def render_classification_panel(img_rgb, diagnosis, cls_conf, seg_conf, gradcam_map): |
| | st.subheader("1. Tanı ve Model Güven Analizi") |
| | |
| | col_diag, col_orig, col_xai = st.columns([1.2, 1.4, 1.4]) |
| | |
| | with col_diag: |
| | |
| | color = "#dc3545" if "Benign" not in diagnosis else "#28a745" |
| | st.markdown(f""" |
| | <div style="padding: 24px; border-radius: 12px; border-left: 6px solid {color}; box-shadow: 0 4px 12px rgba(0,0,0,0.1);"> |
| | <h3 style="margin:0; color: {color} !important; font-size: 1.8rem;">{diagnosis}</h3> |
| | <p style="margin-top: 12px;">Yapay Zeka Nihai Kararı</p> |
| | </div> |
| | """, unsafe_allow_html=True) |
| | |
| | st.markdown("#### Güvenilirlik Metrikleri") |
| | c1, c2 = st.columns(2) |
| | c1.metric("Teşhis Güveni", f"%{cls_conf*100:.1f}", help="ResNet50 modelinin sınıflandırma kesinliği.") |
| | c2.metric("Segmentasyon Güveni", f"%{seg_conf*100:.1f}", help="CIA-Net modelinin hücre tespit kesinliği (Ortalama Piksel Olasılığı).") |
| |
|
| | if cls_conf < 0.70: |
| | st.warning("Düşük güven skoru. Lütfen manuel inceleme yapınız.") |
| |
|
| | with col_orig: |
| | st.image(img_rgb, caption="Orijinal Görüntü", width="stretch") |
| |
|
| | with col_xai: |
| | overlay = apply_heatmap_overlay(img_rgb, gradcam_map, alpha=0.5) |
| | st.image(overlay, caption="Yapay Zeka Odak Alanları (Grad-CAM)", width="stretch") |
| |
|
| | def render_segmentation_panel(img_rgb, nuc_map, uncertainty_map, instance_mask, stats): |
| | st.markdown("---") |
| | st.subheader("2. Hücresel Morfoloji ve Biyolojik Analiz") |
| | |
| | tab_seg, tab_unc, tab_data, tab_plots = st.tabs([ |
| | "Segmentasyon", |
| | "Belirsizlik (Uncertainty)", |
| | "Kantitatif Veriler", |
| | "Dağılım Grafikleri" |
| | ]) |
| | |
| | with tab_seg: |
| | c1, c2 = st.columns(2) |
| | with c1: |
| | nuc_colored = apply_heatmap_overlay(img_rgb, nuc_map, colormap=cv2.COLORMAP_OCEAN, alpha=0.6) |
| | st.image(nuc_colored, caption="Nükleus Olasılık Haritası (AI Çıktısı)", width="stretch") |
| | with c2: |
| | mask_rgb = np.zeros_like(img_rgb) |
| | mask_rgb[instance_mask > 0] = [0, 255, 0] |
| | overlay = cv2.addWeighted(img_rgb, 0.7, mask_rgb, 0.3, 0) |
| | st.image(overlay, caption="Ayrıştırılmış Hücreler (Watershed)", width="stretch") |
| |
|
| | with tab_unc: |
| | c1, c2 = st.columns([1, 2]) |
| | with c1: |
| | st.info(""" |
| | **Nasıl Okunmalı?** |
| | * **Siyah/Koyu Alanlar:** Modelin kararından %100 emin olduğu bölgeler. |
| | * **Parlak/Sarı Alanlar:** Modelin kararsız kaldığı ("Burası hücre mi değil mi?") bölgeler. |
| | |
| | Sarı alanların çokluğu, görüntünün kalitesiz veya dokunun karmaşık olduğunu gösterir. |
| | """) |
| | with c2: |
| | unc_colored = apply_heatmap_overlay(img_rgb, uncertainty_map, colormap=cv2.COLORMAP_INFERNO, alpha=0.7) |
| | st.image(unc_colored, caption="Model Entropi (Belirsizlik) Haritası", width="stretch") |
| |
|
| | with tab_data: |
| | if not stats.empty: |
| | m1, m2, m3, m4 = st.columns(4) |
| | m1.metric("Toplam Hücre", f"{len(stats)}") |
| | m2.metric("Ort. Alan", f"{stats['Area'].mean():.1f} px") |
| | m3.metric("Düzensizlik", f"{1 - stats['Circularity'].mean():.2f}", help="0'a yaklaştıkça hücreler daha yuvarlak (sağlıklı) demektir.") |
| | m4.metric("Varyasyon", f"{stats['Area'].std():.1f}", help="Yüksek varyasyon (Anizonükleoz) kanser belirtisi olabilir.") |
| | |
| | st.dataframe( |
| | stats.style.background_gradient(cmap='Reds', subset=['Area']) |
| | .format("{:.2f}"), |
| | width="stretch" |
| | ) |
| | else: |
| | st.warning("Hücre tespit edilemedi.") |
| |
|
| | with tab_plots: |
| | if not stats.empty: |
| | |
| | plt.style.use('seaborn-v0_8-whitegrid') |
| | sns.set_context("notebook", font_scale=1.3) |
| | sns.set_palette("husl") |
| | |
| | c1, c2 = st.columns(2) |
| | with c1: |
| | fig, ax = plt.subplots(figsize=(10, 6), dpi=150) |
| | sns.histplot(stats['Area'], kde=True, ax=ax, color='#3498db', fill=True, alpha=0.7, linewidth=2) |
| | ax.set_title("Hücre Boyut Dağılımı (Histogram)", fontsize=16, fontweight='bold', pad=20) |
| | ax.set_xlabel("Alan (Piksel)", fontsize=13, fontweight='600') |
| | ax.set_ylabel("Frekans", fontsize=13, fontweight='600') |
| | ax.spines['top'].set_visible(False) |
| | ax.spines['right'].set_visible(False) |
| | ax.grid(True, alpha=0.3, linestyle='--') |
| | plt.tight_layout() |
| | st.pyplot(fig) |
| | plt.close() |
| | |
| | with c2: |
| | fig, ax = plt.subplots(figsize=(10, 6), dpi=150) |
| | scatter = sns.scatterplot(data=stats, x='Area', y='Circularity', hue='Solidity', |
| | ax=ax, palette='viridis', s=100, alpha=0.8, edgecolor='white', linewidth=1.5) |
| | ax.set_title("Boyut vs. Şekil Düzensizliği", fontsize=16, fontweight='bold', pad=20) |
| | ax.set_xlabel("Alan (Piksel)", fontsize=13, fontweight='600') |
| | ax.set_ylabel("Dairesellik", fontsize=13, fontweight='600') |
| | ax.spines['top'].set_visible(False) |
| | ax.spines['right'].set_visible(False) |
| | ax.grid(True, alpha=0.3, linestyle='--') |
| | ax.legend(title='Solidity', title_fontsize=11, fontsize=10, loc='best', frameon=True, |
| | fancybox=True, shadow=True, framealpha=0.95) |
| | plt.tight_layout() |
| | st.pyplot(fig) |
| | plt.close() |