EngReem85 commited on
Commit
24a7bb6
·
verified ·
1 Parent(s): f66114b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +427 -203
app.py CHANGED
@@ -1,32 +1,32 @@
1
- # -*- coding: utf-8 -*-
2
- # -*- coding: utf-8 -*-
3
- """
4
- تحليل قرحة القدم السكري بالاعتماد فقط على نموذج التجزئة FUSegNet
5
- """
6
-
7
- import os
8
  import numpy as np
 
9
  from PIL import Image
10
  import cv2
11
- import torch
12
- import torch.nn as nn
13
- import gdown
 
14
  import segmentation_models_pytorch as smp
15
- import gradio as gr
16
-
17
 
18
- # ================================
19
- # إعدادات عامة
20
- # ================================
21
  segmenter = None
 
22
  IMG_SIZE = 224
23
  DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
24
 
 
 
 
25
 
26
- # ================================
27
- # نموذج FUSegNet (Unet + scse)
28
- # ================================
29
  class FUSegNet(nn.Module):
 
30
  def __init__(self, encoder_name='efficientnet-b7', classes=1, activation='sigmoid'):
31
  super(FUSegNet, self).__init__()
32
  self.unet = smp.Unet(
@@ -34,228 +34,452 @@ class FUSegNet(nn.Module):
34
  encoder_weights=None,
35
  classes=classes,
36
  activation=activation,
37
- decoder_attention_type='scse',
38
  )
39
-
40
  def forward(self, x):
41
  return self.unet(x)
42
 
43
-
44
- # ================================
45
- # تحميل النموذج من Google Drive
46
- # ================================
47
  def check_and_download(url, path, min_size_mb=50):
 
48
  if not os.path.exists(path) or os.path.getsize(path) < min_size_mb * 1024 * 1024:
49
  print(f"📥 تحميل النموذج من Google Drive: {url}")
50
  try:
51
- gdown.download(url, path, quiet=False)
52
  except Exception as e:
53
  print(f"❌ خطأ في التحميل: {e}")
54
 
55
-
56
- def initialize_model():
57
- global segmenter
 
 
 
58
  SEG_MODEL_URL = "https://drive.google.com/uc?id=13jMlcH9yTSejL_IfMDXqdAiVy6Z_SpE1"
 
 
59
  SEG_MODEL_PATH = "best_model.pth"
 
 
 
60
  check_and_download(SEG_MODEL_URL, SEG_MODEL_PATH, min_size_mb=100)
61
-
 
 
 
 
 
 
 
 
 
 
62
  try:
63
- print("🔄 تحميل نموذج FUSegNet...")
64
  segmenter = FUSegNet()
 
65
  checkpoint = torch.load(SEG_MODEL_PATH, map_location=DEVICE)
66
- state_dict = checkpoint.get("state_dict", checkpoint)
67
-
68
- new_state = {}
 
 
 
 
69
  for k, v in state_dict.items():
70
- nk = k.replace("module.", "").replace("model.", "").replace("unet.", "")
71
- new_state[nk] = v
72
-
73
- segmenter.load_state_dict(new_state, strict=False)
74
  segmenter.to(DEVICE)
75
  segmenter.eval()
76
- print("✅ تم تحميل نموذج التجزئة بنجاح.")
 
77
  except Exception as e:
78
- print(f"❌ فشل تحميل نموذج التجزئة: {e}")
79
  segmenter = None
80
 
 
 
 
 
 
 
 
 
 
 
81
 
82
- # ================================
83
- # وظائف المساعدة
84
- # ================================
85
- def ensure_rgb(np_img):
86
- if np_img.ndim == 2:
87
- np_img = cv2.cvtColor(np_img, cv2.COLOR_GRAY2RGB)
88
- elif np_img.shape[-1] == 4:
89
- np_img = cv2.cvtColor(np_img, cv2.COLOR_RGBA2RGB)
90
- return np_img
91
-
92
-
93
- # ================================
94
- # التجزئة (تحليل القرحة)
95
- # ================================
96
- def segment_ulcer(pil_img: Image.Image):
97
- """تجزئة القرحة مع ضبط العتبة والتطبيع والتحقق من الانعكاس"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
  if segmenter is None:
99
- print("⚠️ نموذج التجزئة غير محمّل.")
100
- np_img = np.array(pil_img)
101
- return np.zeros((np_img.shape[0], np_img.shape[1]), dtype=np.uint8)
102
-
103
  try:
104
- img_np = np.array(pil_img)
105
- if img_np.ndim == 2:
106
- img_np = cv2.cvtColor(img_np, cv2.COLOR_GRAY2RGB)
107
- elif img_np.shape[-1] == 4:
108
- img_np = cv2.cvtColor(img_np, cv2.COLOR_RGBA2RGB)
109
-
110
- # تصغير وتطبيع
111
  img_resized = cv2.resize(img_np, (IMG_SIZE, IMG_SIZE))
112
- img_norm = img_resized.astype(np.float32) / 255.0
113
- tensor = torch.from_numpy(img_norm).permute(2, 0, 1).unsqueeze(0).to(DEVICE)
114
-
 
115
  with torch.no_grad():
116
- output = segmenter(tensor)
117
  pred = output.squeeze().cpu().numpy()
118
-
119
- # في بعض النماذج يكون الناتج بثلاث أبعاد
120
- if pred.ndim == 3:
121
- pred = pred[0, :, :]
122
-
123
- # ⚙️ تطبيع لضمان القيم بين 0–1
124
- pred = (pred - pred.min()) / (pred.max() - pred.min() + 1e-8)
125
-
126
- # ✅ عتبة أعلى لتجنب تلوين كل الصورة
127
- threshold = 0.55
128
- mask_bin = (pred >= threshold).astype(np.uint8)
129
-
130
- # تنظيف القناع
131
- kernel = np.ones((5, 5), np.uint8)
132
- mask_bin = cv2.morphologyEx(mask_bin, cv2.MORPH_OPEN, kernel)
133
- mask_bin = cv2.morphologyEx(mask_bin, cv2.MORPH_CLOSE, kernel)
134
-
135
- # إذا أغلب الصورة بيضاء (>70%) فالقناع مقلوب، نقلبه
136
- white_ratio = np.mean(mask_bin)
137
- if white_ratio > 0.7:
138
- print("🔄 القناع مقلوب، عكس الاتجاه.")
139
- mask_bin = 1 - mask_bin
140
-
141
- # إزالة المناطق الصغيرة جدًا
142
- contours, _ = cv2.findContours(mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
143
- for c in contours:
144
- if cv2.contourArea(c) < 150:
145
- cv2.fillPoly(mask_bin, [c], 0)
146
-
147
- mask_resized = cv2.resize(mask_bin, (img_np.shape[1], img_np.shape[0]), interpolation=cv2.INTER_NEAREST)
148
- ulcer_pixels = int(np.sum(mask_resized))
149
- ulcer_percentage = (ulcer_pixels / mask_resized.size) * 100
150
- print(f"🩸 عدد بكسلات القناع: {ulcer_pixels} ({ulcer_percentage:.2f}%)")
151
-
152
  return mask_resized
153
-
154
  except Exception as e:
155
  print(f"❌ خطأ في التجزئة: {e}")
156
- import traceback
157
- traceback.print_exc()
158
- np_img = np.array(pil_img)
159
- return np.zeros((np_img.shape[0], np_img.shape[1]), dtype=np.uint8)
160
-
161
-
162
- # ================================
163
- # تلوين القناع على الصورة
164
- # ================================
165
- def apply_ulcer_mask(pil_img: Image.Image, mask):
166
- base = ensure_rgb(np.array(pil_img))
167
- if mask.shape[:2] != base.shape[:2]:
168
- mask = cv2.resize(mask, (base.shape[1], base.shape[0]), interpolation=cv2.INTER_NEAREST)
169
-
170
- mask_bin = (mask > 0).astype(np.uint8) * 255
171
- mask_bin = cv2.GaussianBlur(mask_bin, (7, 7), 0)
172
-
173
- alpha = (mask_bin.astype(np.float32) / 255.0)[..., None]
174
- overlay = np.zeros_like(base, dtype=np.uint8)
175
- overlay[..., 0] = 255
176
-
177
- blended = (alpha * overlay + (1 - alpha) * base).astype(np.uint8)
178
- contours, _ = cv2.findContours((mask_bin > 0).astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
179
- for c in contours:
180
- if cv2.contourArea(c) > 150:
181
- cv2.drawContours(blended, [c], -1, (255, 0, 0), 3)
182
- return Image.fromarray(blended)
183
-
184
-
185
- # ================================
186
- # التحليل الكامل
187
- # ================================
188
- def analyze_single_image(pil_img: Image.Image):
189
- if pil_img is None:
190
- return None, None, {"خطأ": "لم يتم رفع صورة"}
191
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  try:
193
- ulcer_mask = segment_ulcer(pil_img)
194
- ulcer_pixels = int(np.sum(ulcer_mask == 1))
195
- ulcer_percentage = (ulcer_pixels / ulcer_mask.size) * 100.0
196
-
197
- # تلوين القناع
198
- blended_img = apply_ulcer_mask(pil_img, ulcer_mask)
199
- mask_display = Image.fromarray((ulcer_mask * 255).astype(np.uint8))
200
-
201
- # تقييم مستوى الخطورة
202
- if ulcer_percentage == 0:
203
- risk_level, emoji = "No Risk", "🟢"
204
- elif ulcer_percentage <= 1:
205
- risk_level, emoji = "Low Risk", "🟡"
206
- elif ulcer_percentage <= 5:
207
- risk_level, emoji = "Medium Risk", "🟠"
 
208
  else:
209
- risk_level, emoji = "High Risk", "🔴"
210
-
211
- report = {
212
- "نسبة_القرحة": f"{ulcer_percentage:.2f}%",
213
- "مستوى_الخطورة": f"{risk_level} {emoji}",
214
- "تفاصيل": "تم التحليل اعتمادًا على التجزئة فقط (بدون تصنيف)."
215
- }
216
-
217
- print(f"✅ النتيجة: {ulcer_percentage:.2f}% | {risk_level}")
218
- return blended_img, mask_display, report
219
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
  except Exception as e:
221
- print(f"❌ خطأ أثناء التحليل: {e}")
222
- import traceback
223
- traceback.print_exc()
224
- return pil_img, None, {"خطأ": str(e)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
 
 
 
 
226
 
227
- # ================================
228
  # واجهة Gradio
229
- # ================================
230
- def build_ui():
231
- with gr.Blocks(title="تحليل قرحة القدم السكري", theme=gr.themes.Soft()) as demo:
232
- gr.Markdown("# 🦶 نظام تحليل قرحة القدم السكري")
233
- gr.Markdown("### يعتمد التحليل على التجزئة لتحديد مناطق القرحة ونسبة الخطورة")
234
-
235
- with gr.Row():
236
- with gr.Column(scale=1):
237
- input_img = gr.Image(type="pil", label="📤 صورة القدم")
238
- analyze_btn = gr.Button("🔍 بدء التحليل", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
239
 
240
- with gr.Column(scale=1):
241
- output_img = gr.Image(type="pil", label="🩸 الصورة مع القناع", height=340)
242
- mask_img = gr.Image(type="pil", label="🧩 القناع الأبيض/الأسود", height=340)
243
- json_out = gr.JSON(label="📊 التقرير النهائي")
244
-
245
- analyze_btn.click(
246
- fn=analyze_single_image,
247
- inputs=[input_img],
248
- outputs=[output_img, mask_img, json_out]
249
- )
250
- return demo
251
-
252
-
253
- # ================================
254
- # التشغيل
255
- # ================================
256
  if __name__ == "__main__":
257
- print("🚀 تهيئة نموذج التجزئة فقط...")
258
- initialize_model()
259
- demo = build_ui()
260
- demo.launch(server_name="0.0.0.0", server_port=7860, share=True)
261
-
 
1
+ import tensorflow as tf
2
+ from tensorflow.keras.models import load_model
3
+ import torch
4
+ import torch.nn as nn
 
 
 
5
  import numpy as np
6
+ import gradio as gr
7
  from PIL import Image
8
  import cv2
9
+ from torchvision import transforms
10
+ import gdown
11
+ import os
12
+ import albumentations as A
13
  import segmentation_models_pytorch as smp
14
+ from rembg import remove
15
+ import mediapipe as mp
16
 
17
+ # تعريفات عالمية
18
+ classifier = None
 
19
  segmenter = None
20
+ class_names = ["Abnormal(Ulcer)", "Normal(Healthy skin)"]
21
  IMG_SIZE = 224
22
  DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
23
 
24
+ # تهيئة MediaPipe للكشف عن القدم
25
+ mp_pose = mp.solutions.pose
26
+ mp_drawing = mp.solutions.drawing_utils
27
 
 
 
 
28
  class FUSegNet(nn.Module):
29
+ """FUSegNet نموذج مخصص لمطابقة هيكل"""
30
  def __init__(self, encoder_name='efficientnet-b7', classes=1, activation='sigmoid'):
31
  super(FUSegNet, self).__init__()
32
  self.unet = smp.Unet(
 
34
  encoder_weights=None,
35
  classes=classes,
36
  activation=activation,
37
+ decoder_attention_type='pscse',
38
  )
39
+
40
  def forward(self, x):
41
  return self.unet(x)
42
 
 
 
 
 
43
  def check_and_download(url, path, min_size_mb=50):
44
+ """التحقق من وجود الملفات وتحميلها إذا كانت مفقودة"""
45
  if not os.path.exists(path) or os.path.getsize(path) < min_size_mb * 1024 * 1024:
46
  print(f"📥 تحميل النموذج من Google Drive: {url}")
47
  try:
48
+ gdown.download(url, path, quiet=False, fuzzy=True)
49
  except Exception as e:
50
  print(f"❌ خطأ في التحميل: {e}")
51
 
52
+ def initialize_models():
53
+ """تهيئة النماذج"""
54
+ global classifier, segmenter
55
+
56
+ # روابط ومسارات النماذج
57
+ EFF_MODEL_URL = "https://drive.google.com/uc?id=1vVmA_-D3pZPbKHrPFEbDJd2nexF1H9Ni"
58
  SEG_MODEL_URL = "https://drive.google.com/uc?id=13jMlcH9yTSejL_IfMDXqdAiVy6Z_SpE1"
59
+
60
+ EFF_MODEL_PATH = "efficientnetb3_dfu_model.keras"
61
  SEG_MODEL_PATH = "best_model.pth"
62
+
63
+ # تحميل النماذج
64
+ check_and_download(EFF_MODEL_URL, EFF_MODEL_PATH, min_size_mb=50)
65
  check_and_download(SEG_MODEL_URL, SEG_MODEL_PATH, min_size_mb=100)
66
+
67
+ # نموذج التصنيف
68
+ try:
69
+ print("🔄 جاري تحميل نموذج التصنيف...")
70
+ classifier = tf.keras.models.load_model(EFF_MODEL_PATH, compile=False)
71
+ print("✅ تم تحميل نموذج التصنيف بنجاح!")
72
+ except Exception as e:
73
+ print(f"❌ خطأ في تحميل نموذج التصنيف: {e}")
74
+ classifier = None
75
+
76
+ # نموذج التجزئة
77
  try:
78
+ print("🔄 جاري تحميل نموذج FUSegNet...")
79
  segmenter = FUSegNet()
80
+
81
  checkpoint = torch.load(SEG_MODEL_PATH, map_location=DEVICE)
82
+ if 'state_dict' in checkpoint:
83
+ state_dict = checkpoint['state_dict']
84
+ else:
85
+ state_dict = checkpoint
86
+
87
+ # تنظيف المفاتيح
88
+ new_state_dict = {}
89
  for k, v in state_dict.items():
90
+ new_key = k.replace('module.', '').replace('model.', '').replace('unet.', '')
91
+ new_state_dict[new_key] = v
92
+
93
+ segmenter.load_state_dict(new_state_dict, strict=False)
94
  segmenter.to(DEVICE)
95
  segmenter.eval()
96
+ print("✅ تم تحميل نموذج التجزئة بنجاح!")
97
+
98
  except Exception as e:
99
+ print(f"❌ خطأ في تحميل نموذج التجزئة: {e}")
100
  segmenter = None
101
 
102
+ def remove_background(img: Image.Image):
103
+ """إزالة خلفية الصورة للتركيز على القدم"""
104
+ try:
105
+ # استخدام rembg لإزالة الخلفية
106
+ img_np = np.array(img)
107
+ img_no_bg = remove(img_np)
108
+ return Image.fromarray(img_no_bg)
109
+ except Exception as e:
110
+ print(f"⚠️ فشل إزالة الخلفية: {e}")
111
+ return img
112
 
113
+ def detect_foot_region(img: Image.Image):
114
+ """الكشف عن منطقة القدم باستخدام MediaPipe"""
115
+ try:
116
+ img_np = np.array(img)
117
+
118
+ with mp_pose.Pose(static_image_mode=True, min_detection_confidence=0.5) as pose:
119
+ results = pose.process(cv2.cvtColor(img_np, cv2.COLOR_RGB2BGR))
120
+
121
+ if results.pose_landmarks:
122
+ # الحصول على معالم القدمين (المراجع 29-32 للقدم اليسرى و 30-31 للقدم اليمنى)
123
+ landmarks = results.pose_landmarks.landmark
124
+
125
+ h, w, _ = img_np.shape
126
+ foot_points = []
127
+
128
+ # القدم اليسرى
129
+ left_foot_indices = [27, 29, 31]
130
+ for idx in left_foot_indices:
131
+ if landmarks[idx].visibility > 0.5:
132
+ foot_points.append([int(landmarks[idx].x * w), int(landmarks[idx].y * h)])
133
+
134
+ # القدم اليمنى
135
+ right_foot_indices = [28, 30, 32]
136
+ for idx in right_foot_indices:
137
+ if landmarks[idx].visibility > 0.5:
138
+ foot_points.append([int(landmarks[idx].x * w), int(landmarks[idx].y * h)])
139
+
140
+ if foot_points:
141
+ foot_points = np.array(foot_points)
142
+ x_min, y_min = foot_points.min(axis=0)
143
+ x_max, y_max = foot_points.max(axis=0)
144
+
145
+ # توسيع منطقة الاهتمام قليلاً
146
+ padding = 50
147
+ x_min = max(0, x_min - padding)
148
+ y_min = max(0, y_min - padding)
149
+ x_max = min(w, x_max + padding)
150
+ y_max = min(h, y_max + padding)
151
+
152
+ return img_np[y_min:y_max, x_min:x_max]
153
+
154
+ return img_np
155
+ except Exception as e:
156
+ print(f"⚠️ فشل الكشف عن القدم: {e}")
157
+ return np.array(img)
158
+
159
+ def preprocess_for_classification(img: Image.Image):
160
+ """معالجة الصورة للتصنيف"""
161
+ img_processed = img.resize((IMG_SIZE, IMG_SIZE))
162
+ img_array = tf.keras.preprocessing.image.img_to_array(img_processed)
163
+ img_array = np.expand_dims(img_array, axis=0)
164
+ img_array = tf.keras.applications.efficientnet.preprocess_input(img_array)
165
+ return img_array
166
+
167
+ def classify_image(img: Image.Image):
168
+ """تصنيف الصورة مع إزالة الخلفية أولاً"""
169
+ print("🔄 معالجة الصورة وإزالة الخلفية...")
170
+
171
+ # 1. إزالة الخلفية
172
+ img_no_bg = remove_background(img)
173
+
174
+ # 2. الكشف عن منطقة القدم
175
+ foot_region = detect_foot_region(img_no_bg)
176
+ foot_img = Image.fromarray(foot_region)
177
+
178
+ if classifier is None:
179
+ # التصنيف الافتراضي باستخدام تحليل الألوان
180
+ return default_classification(foot_img), foot_img
181
+
182
+ try:
183
+ # 3. التصنيف باستخدام EfficientNetB3
184
+ img_array = preprocess_for_classification(foot_img)
185
+ preds = classifier.predict(img_array, verbose=0)
186
+ pred_class = np.argmax(preds, axis=1)[0]
187
+ confidence = np.max(preds)
188
+
189
+ result = class_names[pred_class]
190
+ print(f"🎯 نتيجة التصنيف: {result} (ثقة: {confidence:.3f})")
191
+
192
+ return result, foot_img
193
+
194
+ except Exception as e:
195
+ print(f"❌ خطأ في التصنيف: {e}")
196
+ return "Abnormal(Ulcer)", foot_img
197
+
198
+ def default_classification(img: Image.Image):
199
+ """التصنيف الافتراضي باستخدام تحليل الألوان"""
200
+ img_np = np.array(img)
201
+ hsv = cv2.cvtColor(img_np, cv2.COLOR_RGB2HSV)
202
+
203
+ # كشف الأحمر (الالتهاب والقرحة)
204
+ red_mask = cv2.inRange(hsv, np.array([0,50,50]), np.array([10,255,255])) + \
205
+ cv2.inRange(hsv, np.array([160,50,50]), np.array([180,255,255]))
206
+
207
+ # كشف البني/الأسود (الأنسجة الميتة)
208
+ brown_mask = cv2.inRange(hsv, np.array([0,40,20]), np.array([20,200,150]))
209
+
210
+ combined_mask = cv2.bitwise_or(red_mask, brown_mask)
211
+ ulcer_ratio = np.sum(combined_mask > 0) / combined_mask.size
212
+
213
+ result = "Abnormal(Ulcer)" if ulcer_ratio > 0.003 else "Normal(Healthy skin)"
214
+ print(f"🎯 التصنيف الافتراضي: {result} (نسبة الشذوذ: {ulcer_ratio:.4f})")
215
+
216
+ return result
217
+
218
+ def segment_ulcer(img: Image.Image):
219
+ """تجزئة القرحة باستخدام FUSegNet"""
220
  if segmenter is None:
221
+ return smart_ulcer_detection(img)
222
+
 
 
223
  try:
224
+ print("🔄 تجزئة القرحة باستخدام FUSegNet...")
225
+
226
+ img_np = np.array(img)
 
 
 
 
227
  img_resized = cv2.resize(img_np, (IMG_SIZE, IMG_SIZE))
228
+ img_normalized = img_resized.astype(np.float32) / 255.0
229
+
230
+ img_tensor = torch.from_numpy(img_normalized).permute(2, 0, 1).unsqueeze(0).to(DEVICE)
231
+
232
  with torch.no_grad():
233
+ output = segmenter(img_tensor)
234
  pred = output.squeeze().cpu().numpy()
235
+
236
+ # إيجاد أفضل عتبة
237
+ best_threshold = 0.3
238
+ mask_bin = (pred >= best_threshold).astype(np.uint8)
239
+ mask_resized = cv2.resize(mask_bin, (img.width, img.height))
240
+
241
+ ulcer_pixels = np.sum(mask_resized)
242
+ print(f"📊 FUSegNet اكتشف {ulcer_pixels} بيكسل قرحة")
243
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
244
  return mask_resized
245
+
246
  except Exception as e:
247
  print(f"❌ خطأ في التجزئة: {e}")
248
+ return smart_ulcer_detection(img)
249
+
250
+ def smart_ulcer_detection(img: Image.Image):
251
+ """كشف ذكي للقرحة باستخدام معالجة الصور"""
252
+ print("🔍 استخدام الخوارزمية الذكية للكشف...")
253
+
254
+ img_np = np.array(img)
255
+ hsv = cv2.cvtColor(img_np, cv2.COLOR_RGB2HSV)
256
+
257
+ # أقنعة الألوان المرتبطة بالقرحة
258
+ lower_red1 = np.array([0, 60, 60])
259
+ upper_red1 = np.array([10, 255, 255])
260
+ lower_red2 = np.array([160, 60, 60])
261
+ upper_red2 = np.array([180, 255, 255])
262
+ red_mask = cv2.inRange(hsv, lower_red1, upper_red1) + cv2.inRange(hsv, lower_red2, upper_red2)
263
+
264
+ lower_brown = np.array([0, 40, 20])
265
+ upper_brown = np.array([20, 200, 150])
266
+ brown_mask = cv2.inRange(hsv, lower_brown, upper_brown)
267
+
268
+ combined_mask = cv2.bitwise_or(red_mask, brown_mask)
269
+
270
+ # تنظيف القناع
271
+ kernel = np.ones((5,5), np.uint8)
272
+ combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_OPEN, kernel)
273
+ combined_mask = cv2.morphologyEx(combined_mask, cv2.MORPH_CLOSE, kernel)
274
+
275
+ return (combined_mask > 0).astype(np.uint8)
276
+
277
+ def calculate_risk_level(ulcer_percentage):
278
+ """حساب مستوى الخطر بناءً على نسبة القرحة"""
279
+ if ulcer_percentage == 0:
280
+ return "No Risk", 0, "🟢"
281
+ elif ulcer_percentage <= 1:
282
+ return "Low Risk", 1, "🟡"
283
+ elif ulcer_percentage <= 5:
284
+ return "Medium Risk", 3, "🟠"
285
+ else:
286
+ return "High Risk", 5, "🔴"
287
+
288
+ def check_early_symptoms(img: Image.Image):
289
+ """الكشف عن أعراض مبكرة لتقرح القدم"""
290
+ img_np = np.array(img)
291
+ hsv = cv2.cvtColor(img_np, cv2.COLOR_RGB2HSV)
292
+
293
+ symptoms = []
294
+
295
+ # 1. احمرار خفيف
296
+ light_red_mask = cv2.inRange(hsv, np.array([0, 30, 150]), np.array([10, 100, 255]))
297
+ light_red_ratio = np.sum(light_red_mask > 0) / light_red_mask.size
298
+ if light_red_ratio > 0.01:
299
+ symptoms.append("احمرار خفيف في الجلد")
300
+
301
+ # 2. جفاف الجلد
302
+ lab = cv2.cvtColor(img_np, cv2.COLOR_RGB2LAB)
303
+ l_channel = lab[:,:,0]
304
+ dry_skin_ratio = np.sum(l_channel > 200) / l_channel.size
305
+ if dry_skin_ratio > 0.1:
306
+ symptoms.append("جفاف في الجلد")
307
+
308
+ # 3. تورم محتمل
309
+ edges = cv2.Canny(cv2.cvtColor(img_np, cv2.COLOR_RGB2GRAY), 50, 150)
310
+ edge_density = np.sum(edges > 0) / edges.size
311
+ if edge_density > 0.05:
312
+ symptoms.append("تورم محتمل")
313
+
314
+ return symptoms
315
+
316
+ def apply_ulcer_mask(img: Image.Image, mask):
317
+ """تطبيق قناع القرحة على الصورة"""
318
+ img_np = np.array(img)
319
+
320
+ # إنشاء قناع ملون
321
+ colored_mask = np.zeros_like(img_np)
322
+ colored_mask[mask == 1] = [255, 0, 0] # أحمر
323
+
324
+ # دمج مع الصورة الأصلية
325
+ result = cv2.addWeighted(img_np, 0.7, colored_mask, 0.3, 0)
326
+
327
+ # إضافة حدود حمراء
328
+ contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
329
+ for contour in contours:
330
+ area = cv2.contourArea(contour)
331
+ if area > 50: # تجاهل المناطق الصغيرة جداً
332
+ cv2.drawContours(result, [contour], -1, (255, 0, 0), 2)
333
+
334
+ return Image.fromarray(result)
335
+
336
+ def generate_report(classification, ulcer_percentage, risk_level, risk_score, symptoms, has_ulcer):
337
+ """توليد تقرير مفصل"""
338
+ report = {
339
+ "التصنيف": classification,
340
+ "نسبة_القرحة": f"{ulcer_percentage:.2f}%",
341
+ "مستوى_الخطورة": risk_level,
342
+ "درجة_الخطورة": risk_score,
343
+ "التوصيات": []
344
+ }
345
+
346
+ if not has_ulcer:
347
+ if symptoms:
348
+ report["الأعراض_المبكرة"] = symptoms
349
+ report["التوصيات"].append("مراجعة طبية للكشف عن الأعراض المبكرة")
350
+ report["التوصيات"].append("العناية اليومية بالقدمين")
351
+ else:
352
+ report["الحالة"] = "سليمة"
353
+ report["التوصيات"].append("استمرار في العناية بالقدمين")
354
+ report["التوصيات"].append("فحص دوري للقدمين")
355
+ else:
356
+ report["التوصيات"].append("مراجعة عاجلة مع طبيب مختص")
357
+ report["التوصيات"].append("العناية المركزة بالمنطقة المصابة")
358
+ report["التوصيات"].append("تجنب الضغط على المنطقة المصابة")
359
+
360
+ return report
361
+
362
+ def analyze_single_image(img: Image.Image):
363
+ """تحليل صورة واحدة حسب السيناريو المطلوب"""
364
+ if img is None:
365
+ return None, {"خطأ": "لم يتم رفع صورة"}
366
+
367
  try:
368
+ print("\n" + "="*50)
369
+ print("🚀 بدء تحليل الصورة...")
370
+
371
+ # 1. التصنيف مع إزالة الخلفية
372
+ classification, processed_img = classify_image(img)
373
+ has_ulcer = classification == "Abnormal(Ulcer)"
374
+
375
+ # 2. إذا لا توجد قرحة، كشف الأعراض المبكرة
376
+ early_symptoms = []
377
+ ulcer_mask = None
378
+ ulcer_percentage = 0.0
379
+
380
+ if not has_ulcer:
381
+ print("✅ لا توجد قرحة - الكشف عن الأعراض المبكرة...")
382
+ early_symptoms = check_early_symptoms(processed_img)
383
+ final_img = processed_img
384
  else:
385
+ print("⚠️ اكتشاف قرحة - المتابعة للتجزئة...")
386
+ # 3. تجزئة القرحة
387
+ ulcer_mask = segment_ulcer(processed_img)
388
+ ulcer_pixels = np.sum(ulcer_mask == 1)
389
+ total_pixels = ulcer_mask.size
390
+ ulcer_percentage = (ulcer_pixels / total_pixels) * 100
391
+
392
+ # 4. تطبيق القناع
393
+ final_img = apply_ulcer_mask(processed_img, ulcer_mask)
394
+
395
+ # 5. حساب مستوى الخطر
396
+ risk_level, risk_score, risk_emoji = calculate_risk_level(ulcer_percentage)
397
+
398
+ # 6. توليد التقرير
399
+ report = generate_report(
400
+ classification, ulcer_percentage, risk_level, risk_score,
401
+ early_symptoms, has_ulcer
402
+ )
403
+
404
+ print(f"📊 النتائج النهائية:")
405
+ print(f" - التصنيف: {classification}")
406
+ print(f" - نسبة القرحة: {ulcer_percentage:.2f}%")
407
+ print(f" - مستوى الخطورة: {risk_level} {risk_emoji}")
408
+ if early_symptoms:
409
+ print(f" - الأعراض المبكرة: {', '.join(early_symptoms)}")
410
+
411
+ return final_img, report
412
+
413
  except Exception as e:
414
+ print(f"❌ خطأ في تحليل الصورة: {e}")
415
+ return img, {"خطأ": f"فشل في التحليل: {str(e)}"}
416
+
417
+ def analyze_images(img1, img2):
418
+ """تحليل صورتين"""
419
+ results = []
420
+ reports = {}
421
+
422
+ for idx, img in enumerate([img1, img2]):
423
+ image_key = f"صورة_{idx+1}"
424
+
425
+ if img is None:
426
+ results.append(None)
427
+ reports[image_key] = {"حالة": "لم يتم رفع صورة"}
428
+ else:
429
+ analyzed_img, report = analyze_single_image(img)
430
+ results.append(analyzed_img)
431
+ reports[image_key] = report
432
+
433
+ return results, reports
434
 
435
+ # تهيئة النماذج
436
+ print("🚀 جاري تهيئة النماذج...")
437
+ initialize_models()
438
 
 
439
  # واجهة Gradio
440
+ with gr.Blocks(title="نظام تحليل قرحة القدم السكري", theme=gr.themes.Soft()) as demo:
441
+ gr.Markdown("""
442
+ # 🦶 نظام الذكاء الاصطناعي لتحليل قرحة القدم السكري
443
+
444
+ ### 📋 السيناريو الطبي المتبع:
445
+ 1. **إزالة الخلفية** والتركيز على القدم
446
+ 2. **التصنيف**: الكشف عن وجود قرحة
447
+ 3. **إذا لا توجد قرحة**: الكشف عن أعراض مبكرة
448
+ 4. **إذا توجد قرحة**: تحديد منطقة القرحة بدقة
449
+ 5. **تقييم مستوى الخطورة** وتوليد التقرير
450
+ """)
451
+
452
+ with gr.Row():
453
+ with gr.Column():
454
+ gr.Markdown("### 📤 رفع الصور")
455
+ with gr.Row():
456
+ img1 = gr.Image(type="pil", label="الصورة الأولى", height=250)
457
+ img2 = gr.Image(type="pil", label="الصورة الثانية", height=250)
458
+
459
+ analyze_btn = gr.Button("🔍 بدء التحليل", variant="primary", size="lg")
460
+
461
+ with gr.Column():
462
+ gr.Markdown("### 📊 النتائج")
463
+ gallery = gr.Gallery(label="الصور المحللة", columns=2, height="auto")
464
+ json_output = gr.JSON(label="التقارير الطبية")
465
+
466
+ gr.Markdown("""
467
+ ---
468
+ **🎯 تفسير النتائج:**
469
+ - **🟢 No Risk**: لا توجد قرحة ولا أعراض مبكرة
470
+ - **🟡 Low Risk**: أعراض مبكرة أو قرحة صغيرة (<1%)
471
+ - **🟠 Medium Risk**: قرحة متوسطة (1-5%)
472
+ - **🔴 High Risk**: قرحة كبيرة (>5%)
473
+
474
+ **🔍 المناطق الحمراء**: مناطق القرحة المكتشفة
475
+ """)
476
+
477
+ analyze_btn.click(
478
+ fn=analyze_images,
479
+ inputs=[img1, img2],
480
+ outputs=[gallery, json_output]
481
+ )
482
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
483
  if __name__ == "__main__":
484
+ print("🌐 بدء تشغيل السيرفر...")
485
+ demo.launch(server_name="0.0.0.0", server_port=7860, share=True)