Spaces:
Sleeping
Sleeping
| # -*- coding: utf-8 -*- | |
| """ | |
| تحليل قرحة القدم باستخدام Unet + EfficientNet-b0 | |
| النموذج من Google Drive (best_model_5.pth) | |
| """ | |
| import os | |
| import cv2 | |
| import gdown | |
| import numpy as np | |
| from PIL import Image | |
| import torch | |
| import gradio as gr | |
| import segmentation_models_pytorch as smp | |
| # ========================================================= | |
| # الإعدادات العامة | |
| # ========================================================= | |
| DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| IMG_SIZE = 512 | |
| THRESHOLD = 0.35 | |
| MODEL_PATH = "best_model_5.pth" | |
| MODEL_URL = "https://drive.google.com/uc?id=1Ovaczsjdp3E-_gYF2pbUibDjPWAC1a6c" | |
| CLASS_NAMES = ["قرحة (Granulation)", "Slough", "نخر (Necrosis)"] | |
| CLASS_COLORS = { | |
| "قرحة (Granulation)": (255, 0, 0), # أحمر | |
| "Slough": (255, 255, 0), # أصفر | |
| "نخر (Necrosis)": (0, 0, 0) # أسود | |
| } | |
| segmenter = None | |
| # ========================================================= | |
| # تحميل النموذج | |
| # ========================================================= | |
| def initialize_model(): | |
| """تحميل نموذج Unet EfficientNet من Google Drive""" | |
| global segmenter | |
| if not os.path.exists(MODEL_PATH): | |
| print("📥 تحميل النموذج من Google Drive...") | |
| gdown.download(MODEL_URL, MODEL_PATH, quiet=False) | |
| try: | |
| print("🔄 تحميل Unet EfficientNet...") | |
| model = smp.Unet( | |
| encoder_name="efficientnet-b0", | |
| encoder_weights=None, | |
| classes=len(CLASS_NAMES), | |
| activation="sigmoid" | |
| ) | |
| checkpoint = torch.load(MODEL_PATH, map_location=DEVICE) | |
| if "state_dict" in checkpoint: | |
| state_dict = checkpoint["state_dict"] | |
| else: | |
| state_dict = checkpoint | |
| clean_state = {k.replace("module.", "").replace("model.", ""): v for k, v in state_dict.items()} | |
| model.load_state_dict(clean_state, strict=False) | |
| model.to(DEVICE) | |
| model.eval() | |
| segmenter = model | |
| print("✅ تم تحميل النموذج بنجاح.") | |
| except Exception as e: | |
| print(f"❌ فشل تحميل النموذج: {e}") | |
| import traceback; traceback.print_exc() | |
| segmenter = None | |
| # ========================================================= | |
| # أدوات مساعدة | |
| # ========================================================= | |
| def ensure_rgb(np_img): | |
| """تحويل الصورة إلى RGB إذا لزم""" | |
| if np_img.ndim == 2: | |
| return cv2.cvtColor(np_img, cv2.COLOR_GRAY2RGB) | |
| if np_img.shape[-1] == 4: | |
| return cv2.cvtColor(np_img, cv2.COLOR_RGBA2RGB) | |
| return np_img | |
| def preprocess_image(img: Image.Image): | |
| img_np = ensure_rgb(np.array(img)) | |
| img_resized = cv2.resize(img_np, (IMG_SIZE, IMG_SIZE)) | |
| img_norm = img_resized.astype(np.float32) / 255.0 | |
| # ✅ تطبيع ImageNet الصحيح | |
| mean = np.array([0.485, 0.456, 0.406]) | |
| std = np.array([0.229, 0.224, 0.225]) | |
| img_norm = (img_norm - mean) / std | |
| # ✅ تحويل إلى double لأن النموذج يستخدم float64 | |
| tensor = torch.from_numpy(img_norm).permute(2, 0, 1).unsqueeze(0).double() | |
| return tensor.to(DEVICE), img_np | |
| # ========================================================= | |
| # التجزئة والتحليل | |
| # ========================================================= | |
| def analyze_image(img: Image.Image): | |
| """تحليل صورة القدم وعرض النسب""" | |
| if segmenter is None: | |
| return img, img, {"خطأ": "النموذج غير مهيأ بعد."} | |
| try: | |
| print("🔍 بدء التحليل...") | |
| tensor, img_np = preprocess_image(img) | |
| with torch.no_grad(): | |
| output = segmenter(tensor).cpu().squeeze(0).numpy() # (3,H,W) | |
| masks = (output >= THRESHOLD).astype(np.uint8) | |
| # تنظيف الأقنعة | |
| kernel = np.ones((5,5), np.uint8) | |
| for i in range(masks.shape[0]): | |
| masks[i] = cv2.morphologyEx(masks[i], cv2.MORPH_OPEN, kernel) | |
| masks[i] = cv2.morphologyEx(masks[i], cv2.MORPH_CLOSE, kernel) | |
| # حساب النسب | |
| total_pixels = masks.shape[1] * masks.shape[2] | |
| ratios = { | |
| CLASS_NAMES[0]: np.sum(masks[0]) / total_pixels * 100, | |
| CLASS_NAMES[1]: np.sum(masks[1]) / total_pixels * 100, | |
| CLASS_NAMES[2]: np.sum(masks[2]) / total_pixels * 100 | |
| } | |
| total_ratio = sum(ratios.values()) | |
| # إنشاء قناع لوني | |
| color_mask = np.zeros((masks.shape[1], masks.shape[2], 3), dtype=np.uint8) | |
| color_mask[masks[0] == 1] = CLASS_COLORS[CLASS_NAMES[0]] | |
| color_mask[masks[1] == 1] = CLASS_COLORS[CLASS_NAMES[1]] | |
| color_mask[masks[2] == 1] = CLASS_COLORS[CLASS_NAMES[2]] | |
| color_mask = cv2.resize(color_mask, (img_np.shape[1], img_np.shape[0])) | |
| # دمج القناع مع الصورة | |
| alpha = 0.5 | |
| blended = cv2.addWeighted(img_np, 1 - alpha, color_mask, alpha, 0) | |
| # تقييم الخطورة | |
| if total_ratio == 0: | |
| risk = "No Risk 🟢" | |
| elif total_ratio < 1: | |
| risk = "Low Risk 🟡" | |
| elif total_ratio < 5: | |
| risk = "Medium Risk 🟠" | |
| else: | |
| risk = "High Risk 🔴" | |
| report = { | |
| "نسب الأنسجة (%)": {k: f"{v:.2f}" for k, v in ratios.items()}, | |
| "إجمالي (%)": f"{total_ratio:.2f}", | |
| "مستوى الخطورة": risk | |
| } | |
| print(f"📊 النتائج: {report}") | |
| return Image.fromarray(blended), Image.fromarray(color_mask), report | |
| except Exception as e: | |
| print(f"❌ خطأ أثناء التحليل: {e}") | |
| import traceback; traceback.print_exc() | |
| return img, img, {"خطأ": str(e)} | |
| # ========================================================= | |
| # واجهة Gradio | |
| # ========================================================= | |
| def build_ui(): | |
| with gr.Blocks(title="تحليل قرحة القدم - EfficientNet Unet", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown("# 🦶 تحليل صورة القدم السكري (Unet + EfficientNet)") | |
| gr.Markdown("الكشف عن أنواع الأنسجة المصابة (قرحة / Slough / نخر) وتقدير مستوى الخطورة.") | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| input_img = gr.Image(type="pil", label="📤 ارفع صورة القدم", height=320) | |
| analyze_btn = gr.Button("🔍 بدء التحليل", variant="primary") | |
| with gr.Column(scale=1): | |
| out_blended = gr.Image(type="pil", label="🩸 الصورة مع القناع", height=320) | |
| out_mask = gr.Image(type="pil", label="🧩 القناع اللوني", height=320) | |
| out_json = gr.JSON(label="📊 التقرير التفصيلي") | |
| analyze_btn.click( | |
| fn=analyze_image, | |
| inputs=[input_img], | |
| outputs=[out_blended, out_mask, out_json] | |
| ) | |
| return demo | |
| # ========================================================= | |
| # تشغيل التطبيق | |
| # ========================================================= | |
| if __name__ == "__main__": | |
| print("🚀 تهيئة النموذج...") | |
| initialize_model() | |
| app = build_ui() | |
| app.launch(server_name="0.0.0.0", server_port=7860) | |