EngReem85 commited on
Commit
913015b
·
verified ·
1 Parent(s): dd7ac53

Update app.py

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