EngReem85 commited on
Commit
f92dac8
·
verified ·
1 Parent(s): e1069b0

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -111
app.py CHANGED
@@ -9,10 +9,10 @@ import math
9
  mp_pose = mp.solutions.pose
10
  pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.75, model_complexity=2)
11
 
12
- # ===========================
13
  # دوال مساعدة
14
- # ===========================
15
- def _dist(p1, p2):
16
  return math.hypot(p1[0]-p2[0], p1[1]-p2[1])
17
 
18
  def _angle(a, b, c):
@@ -32,16 +32,16 @@ def _gauge_html(norm_score):
32
  pct = int(max(0, min(1, norm_score)) * 100)
33
  color = "#4caf50" if pct < 35 else "#fbc02d" if pct < 65 else "#c62828"
34
  bar = f"""
35
- <div style="width:100%;background:#eee;border-radius:10px;height:16px;overflow:hidden;border:1px solid #ddd;">
36
  <div style="width:{pct}%;height:100%;background:{color};transition:width 1s;"></div>
37
  </div>
38
  <div style="font-size:12px;color:#555;margin-top:6px">درجة الخطورة: {pct}%</div>
39
  """
40
  return bar
41
 
42
- # ===========================
43
- # التحليل الرئيسي للفيديو
44
- # ===========================
45
  def analyze_gait(video_file):
46
  if video_file is None:
47
  return "<div>❌ يرجى رفع فيديو أولًا.</div>", "<div></div>"
@@ -59,17 +59,18 @@ def analyze_gait(video_file):
59
  return "<div>❌ لا يمكن فتح الفيديو.</div>", "<div></div>"
60
 
61
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
62
- fps = float(cap.get(cv2.CAP_PROP_FPS) or 30.0)
63
  W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 640)
64
  H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 480)
65
  px2m = 1.7 / (H * 0.8)
 
66
 
67
- L_clear, R_clear = [], []
68
- L_angle, R_angle = [], []
69
- base_px_seq = []
70
- torso_tilt_seq, torso_side_lean_seq = [], []
 
71
 
72
- ground_y = H * 0.92
73
  frames_processed = 0
74
  person_detected = False
75
 
@@ -77,141 +78,123 @@ def analyze_gait(video_file):
77
  ret, frame = cap.read()
78
  if not ret: break
79
  frames_processed += 1
 
 
 
 
 
80
 
81
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
82
  res = pose.process(frame_rgb)
83
- if not res.pose_landmarks: continue
84
-
85
  person_detected = True
86
  lm = res.pose_landmarks.landmark
87
- def xy(idx): return [lm[idx].x * W, lm[idx].y * H]
88
-
89
- # النقاط
90
- L_ank = xy(mp_pose.PoseLandmark.LEFT_ANKLE.value)
91
- R_ank = xy(mp_pose.PoseLandmark.RIGHT_ANKLE.value)
92
- L_knee = xy(mp_pose.PoseLandmark.LEFT_KNEE.value)
93
- R_knee = xy(mp_pose.PoseLandmark.RIGHT_KNEE.value)
94
- L_foot = xy(mp_pose.PoseLandmark.LEFT_FOOT_INDEX.value)
95
- R_foot = xy(mp_pose.PoseLandmark.RIGHT_FOOT_INDEX.value)
96
- L_hip = xy(mp_pose.PoseLandmark.LEFT_HIP.value)
97
- R_hip = xy(mp_pose.PoseLandmark.RIGHT_HIP.value)
98
- L_sh = xy(mp_pose.PoseLandmark.LEFT_SHOULDER.value)
99
- R_sh = xy(mp_pose.PoseLandmark.RIGHT_SHOULDER.value)
100
-
101
- # ارتفاع القدم بالسنتيمتر
102
- Lc = max(0, (ground_y - min(L_ank[1], L_foot[1])) * px2m * 100)
103
- Rc = max(0, (ground_y - min(R_ank[1], R_foot[1])) * px2m * 100)
104
- L_clear.append(Lc)
105
- R_clear.append(Rc)
106
-
107
- # زوايا الكاحل
108
- La = _angle(L_knee, L_ank, L_foot)
109
- Ra = _angle(R_knee, R_ank, R_foot)
110
- L_angle.append(La)
111
- R_angle.append(Ra)
112
-
113
- # المسافة الأفقية بين الكاحلين
114
- base_px_seq.append(abs(L_ank[0]-R_ank[0]))
115
-
116
- # ميل الجذع للأمام والخلف
117
  mid_sh = [(L_sh[0]+R_sh[0])/2, (L_sh[1]+R_sh[1])/2]
118
  mid_hip= [(L_hip[0]+R_hip[0])/2, (L_hip[1]+R_hip[1])/2]
119
- vec = np.array([mid_sh[0]-mid_hip[0], mid_sh[1]-mid_hip[1]])
120
- tilt = abs(90 - abs(math.degrees(math.atan2(abs(vec[1]), abs(vec[0])+1e-6))))
121
  torso_tilt_seq.append(tilt)
122
-
123
- # ميل جانبي للجذع (لتحديد انحراف نحو اليمين أو اليسار)
124
- torso_side = (L_sh[0]+R_sh[0])/2 - (L_hip[0]+R_hip[0])/2
125
- torso_side_lean_seq.append(torso_side)
126
 
127
  cap.release()
128
  try: os.unlink(video_path)
129
  except: pass
130
 
131
  if not person_detected:
132
- return "<div>❌ لم يتم اكتشاف شخص في الفيديو.</div>", "<div></div>"
133
 
134
- # ===========================
135
- # الإحصاءات المحسَّنة
136
- # ===========================
137
  avg_Lc, avg_Rc = _safe_mean(L_clear), _safe_mean(R_clear)
138
  std_Lc, std_Rc = _safe_std(L_clear), _safe_std(R_clear)
139
  avg_La, avg_Ra = _safe_mean(L_angle), _safe_mean(R_angle)
140
- avg_base_px = _safe_mean(base_px_seq)
141
  avg_tilt = _safe_mean(torso_tilt_seq)
142
- avg_side_lean = _safe_mean(torso_side_lean_seq)
 
143
  var_clear = max(std_Lc, std_Rc)
144
  diff_clear = abs(avg_Lc - avg_Rc)
145
  diff_angle = abs(avg_La - avg_Ra)
146
- px_base_ratio = avg_base_px / W
147
 
148
- # زاوية التصوير
149
- view = "frontal" if px_base_ratio > 0.14 else "side"
150
 
151
- # نسبة الإطارات اللي فيها انخفاض شديد
152
- low_ratio_L = sum(np.array(L_clear)<3.5)/len(L_clear)
153
- low_ratio_R = sum(np.array(R_clear)<3.5)/len(R_clear)
 
154
 
155
- # ===========================
156
- # منطق الصرامة العالية
157
- # ===========================
158
  score = 0
159
  strong_flags = 0
160
 
161
- # ضعف القدم (Foot Drop)
162
- if (min(avg_Lc, avg_Rc)<3.5) or (low_ratio_L>0.4 or low_ratio_R>0.4):
163
  score += 3.5; strong_flags += 1
164
 
165
- # Neuropathy
166
- if (var_clear>9 and diff_angle>15) or (diff_clear>6 and var_clear>8):
167
  score += 3; strong_flags += 1
168
 
169
  # Charcot
170
- if (px_base_ratio>0.25 and avg_tilt>10):
171
  score += 3.5; strong_flags += 1
172
 
173
- # ميل جانبي واضح للجذع
174
- if abs(avg_side_lean) > W*0.03:
175
- score += 1.5
176
-
177
- # اختلاف كبير بين القدمين
178
- if diff_clear > 7:
179
  score += 1.5
180
 
181
  score = min(score, 10)
182
- norm_score = score / 10
183
 
184
- # ===========================
185
- # تحديد الجانب المتأثر
186
- # ===========================
187
- if avg_Lc < avg_Rc - 2.5 and avg_La < avg_Ra:
188
  side = "اليسار"
189
- elif avg_Rc < avg_Lc - 2.5 and avg_Ra < avg_La:
190
  side = "اليمين"
191
  else:
192
- side = "كلا الجانبين / غير محدد"
193
 
194
- # ===========================
195
- # التقييم النهائي
196
- # ===========================
197
  if norm_score >= 0.7 or strong_flags >= 2:
198
- level, color, desc = "🔴 عالية الخطورة", "#c62828", "تم رصد مؤشرات قوية لخلل في المشية."
199
  booking_html = """
200
  <div style="margin-top:10px">
201
  <a href="https://example.com/book" target="_blank"
202
- style="background:#007bff;color:#fff;padding:10px 16px;border-radius:8px;
203
- text-decoration:none;font-weight:600;display:inline-block;">
204
  احجز موعد مباشر (حضوري أو أونلاين)
205
  </a>
206
  </div>
207
  """
208
  elif norm_score >= 0.45:
209
- level, color, desc = "🟡 متوسطة الخطورة", "#fbc02d", "تم رصد مؤشرات تحتاج متابعة طبية."
210
  booking_html = """
211
  <div style="margin-top:10px">
212
  <a href="https://example.com/book" target="_blank"
213
- style="background:#fbc02d;color:#000;padding:10px 16px;border-radius:8px;
214
- text-decoration:none;font-weight:600;display:inline-block;">
215
  احجز استشارة طبية
216
  </a>
217
  </div>
@@ -220,34 +203,34 @@ def analyze_gait(video_file):
220
  level, color, desc = "🟢 طبيعية", "#2e7d32", "المشية ضمن الحدود الطبيعية."
221
  booking_html = ""
222
 
223
- # الحالة
224
- if strong_flags >= 2 and (px_base_ratio>0.25):
225
- condition = "قدم شاركوت (Charcot Foot)"
226
- elif low_ratio_L>0.4 or low_ratio_R>0.4:
227
- condition = "ضعف العضلة الظنبوبية (Foot Drop)"
228
- elif (var_clear>8 and diff_angle>15):
229
- condition = "اعتلال الأعصاب المحيطية / السكري"
230
- else:
231
- condition = "خلل بسيط غير محدد"
232
-
233
  html = f"""
234
  <div style='color:{color};font-weight:700;font-size:18px'>{level}</div>
235
  <div>👁️ زاوية التصوير: <b>{'أمامية' if view=='frontal' else 'جانبية'}</b></div>
236
  <div>📍 الجانب المتأثر: <b>{side}</b></div>
237
- <div>🩺 الحالة المحتملة: <b>{condition}</b></div>
238
  <div>📊 درجة الخطورة: <b>{score:.1f}/10</b></div>
239
  <div>{desc}</div>
240
  {booking_html}
241
- <div style='font-size:13px;color:#555;margin-top:8px'>⚠️ هذا التحليل يعتمد على أنماط زمنية وزوايا متعددة، ولا يُغني عن الفحص الطبي.</div>
242
  """
243
  return html, _gauge_html(norm_score)
244
 
245
- # ===========================
246
- # واجهة Gradio
247
- # ===========================
248
- with gr.Blocks(title="تحليل المشية العصبية - v8 (دقيق وصارم)") as demo:
249
- gr.Markdown("## 🩺 نظام تحليل المشية العصبية لإصدار 8)")
250
- gr.Markdown("🔹 النظام يستخدم خوارزميات متقدمة لتحليل المشية بدقة عالية.<br>🔹 يحدد الجانب المتأثر ويقترح حجز موعد عند الحاجة.")
 
 
 
 
 
 
 
 
 
 
251
 
252
  with gr.Row():
253
  with gr.Column(scale=1):
 
9
  mp_pose = mp.solutions.pose
10
  pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.75, model_complexity=2)
11
 
12
+ # ===================================
13
  # دوال مساعدة
14
+ # ===================================
15
+ def _dist(p1, p2):
16
  return math.hypot(p1[0]-p2[0], p1[1]-p2[1])
17
 
18
  def _angle(a, b, c):
 
32
  pct = int(max(0, min(1, norm_score)) * 100)
33
  color = "#4caf50" if pct < 35 else "#fbc02d" if pct < 65 else "#c62828"
34
  bar = f"""
35
+ <div style="width:100%;background:#eee;border-radius:10px;height:16px;overflow:hidden;border:1px solid #ccc;">
36
  <div style="width:{pct}%;height:100%;background:{color};transition:width 1s;"></div>
37
  </div>
38
  <div style="font-size:12px;color:#555;margin-top:6px">درجة الخطورة: {pct}%</div>
39
  """
40
  return bar
41
 
42
+ # ===================================
43
+ # التحليل الرئيسي
44
+ # ===================================
45
  def analyze_gait(video_file):
46
  if video_file is None:
47
  return "<div>❌ يرجى رفع فيديو أولًا.</div>", "<div></div>"
 
59
  return "<div>❌ لا يمكن فتح الفيديو.</div>", "<div></div>"
60
 
61
  total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT) or 0)
 
62
  W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 640)
63
  H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 480)
64
  px2m = 1.7 / (H * 0.8)
65
+ ground_y = H * 0.92
66
 
67
+ # قوائم القياسات
68
+ L_clear, R_clear, L_angle, R_angle = [], [], [], []
69
+ torso_tilt_seq, torso_side_seq = [], []
70
+ base_seq = []
71
+ motion_energy = []
72
 
73
+ prev_frame = None
74
  frames_processed = 0
75
  person_detected = False
76
 
 
78
  ret, frame = cap.read()
79
  if not ret: break
80
  frames_processed += 1
81
+ frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
82
+ if prev_frame is not None:
83
+ diff = cv2.absdiff(frame_gray, prev_frame)
84
+ motion_energy.append(np.sum(diff))
85
+ prev_frame = frame_gray
86
 
87
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
88
  res = pose.process(frame_rgb)
89
+ if not res.pose_landmarks:
90
+ continue
91
  person_detected = True
92
  lm = res.pose_landmarks.landmark
93
+
94
+ def xy(i): return [lm[i].x*W, lm[i].y*H]
95
+ L_ank, R_ank = xy(27), xy(28)
96
+ L_knee, R_knee = xy(25), xy(26)
97
+ L_foot, R_foot = xy(31), xy(32)
98
+ L_hip, R_hip = xy(23), xy(24)
99
+ L_sh, R_sh = xy(11), xy(12)
100
+
101
+ # ارتفاع القدم
102
+ Lc = max(0, (ground_y - min(L_ank[1], L_foot[1]))*px2m*100)
103
+ Rc = max(0, (ground_y - min(R_ank[1], R_foot[1]))*px2m*100)
104
+ L_clear.append(Lc); R_clear.append(Rc)
105
+
106
+ # زاوية الكاحل
107
+ L_angle.append(_angle(L_knee, L_ank, L_foot))
108
+ R_angle.append(_angle(R_knee, R_ank, R_foot))
109
+
110
+ # ميل الجذع
 
 
 
 
 
 
 
 
 
 
 
 
111
  mid_sh = [(L_sh[0]+R_sh[0])/2, (L_sh[1]+R_sh[1])/2]
112
  mid_hip= [(L_hip[0]+R_hip[0])/2, (L_hip[1]+R_hip[1])/2]
113
+ tilt = abs(90 - abs(math.degrees(math.atan2(abs(mid_sh[1]-mid_hip[1]), abs(mid_sh[0]-mid_hip[0])+1e-6))))
 
114
  torso_tilt_seq.append(tilt)
115
+ torso_side_seq.append(mid_sh[0]-mid_hip[0])
116
+ base_seq.append(abs(L_ank[0]-R_ank[0]))
 
 
117
 
118
  cap.release()
119
  try: os.unlink(video_path)
120
  except: pass
121
 
122
  if not person_detected:
123
+ return "<div>❌ لم يتم اكتشاف شخص في الفيديو. يرجى إعادة التصوير وفق التعليمات.</div>", "<div></div>"
124
 
125
+ # ===================================
126
+ # التحليل المتقدم
127
+ # ===================================
128
  avg_Lc, avg_Rc = _safe_mean(L_clear), _safe_mean(R_clear)
129
  std_Lc, std_Rc = _safe_std(L_clear), _safe_std(R_clear)
130
  avg_La, avg_Ra = _safe_mean(L_angle), _safe_mean(R_angle)
 
131
  avg_tilt = _safe_mean(torso_tilt_seq)
132
+ side_lean = _safe_mean(torso_side_seq)
133
+ avg_base = _safe_mean(base_seq)
134
  var_clear = max(std_Lc, std_Rc)
135
  diff_clear = abs(avg_Lc - avg_Rc)
136
  diff_angle = abs(avg_La - avg_Ra)
137
+ motion_index = np.std(motion_energy)/np.mean(motion_energy) if motion_energy else 0
138
 
139
+ view = "frontal" if (avg_base/W) > 0.15 else "side"
 
140
 
141
+ # نسب زمنية
142
+ n = max(1, len(L_clear))
143
+ low_ratio_L = sum(np.array(L_clear)<3.5)/n
144
+ low_ratio_R = sum(np.array(R_clear)<3.5)/n
145
 
146
+ # ===================================
147
+ # منطق أكثر صرامة + وزن ديناميكي
148
+ # ===================================
149
  score = 0
150
  strong_flags = 0
151
 
152
+ # ضعف القدم
153
+ if (min(avg_Lc, avg_Rc)<3.5) or (low_ratio_L>0.45 or low_ratio_R>0.45):
154
  score += 3.5; strong_flags += 1
155
 
156
+ # neuropathy
157
+ if (var_clear>8 and diff_angle>15) or (motion_index>0.25 and diff_clear>5):
158
  score += 3; strong_flags += 1
159
 
160
  # Charcot
161
+ if (avg_base*px2m>0.26 and avg_tilt>9):
162
  score += 3.5; strong_flags += 1
163
 
164
+ # ميل الجذع الجانبي
165
+ if abs(side_lean) > W*0.03:
 
 
 
 
166
  score += 1.5
167
 
168
  score = min(score, 10)
169
+ norm_score = score/10
170
 
171
+ # تحديد الجانب المتضرر
172
+ if avg_Lc < avg_Rc-2.5 and avg_La < avg_Ra:
 
 
173
  side = "اليسار"
174
+ elif avg_Rc < avg_Lc-2.5 and avg_Ra < avg_La:
175
  side = "اليمين"
176
  else:
177
+ side = "غير محدد بوضوح"
178
 
179
+ # ===================================
180
+ # النتيجة النهائية
181
+ # ===================================
182
  if norm_score >= 0.7 or strong_flags >= 2:
183
+ level, color, desc = "🔴 عالية الخطورة", "#c62828", "تم رصد مؤشرات واضحة لخلل في المشية."
184
  booking_html = """
185
  <div style="margin-top:10px">
186
  <a href="https://example.com/book" target="_blank"
187
+ style="background:#007bff;color:#fff;padding:10px 16px;border-radius:8px;text-decoration:none;font-weight:600;">
 
188
  احجز موعد مباشر (حضوري أو أونلاين)
189
  </a>
190
  </div>
191
  """
192
  elif norm_score >= 0.45:
193
+ level, color, desc = "🟡 متوسطة الخطورة", "#fbc02d", "مؤشرات تستدعي متابعة طبية."
194
  booking_html = """
195
  <div style="margin-top:10px">
196
  <a href="https://example.com/book" target="_blank"
197
+ style="background:#fbc02d;color:#000;padding:10px 16px;border-radius:8px;text-decoration:none;font-weight:600;">
 
198
  احجز استشارة طبية
199
  </a>
200
  </div>
 
203
  level, color, desc = "🟢 طبيعية", "#2e7d32", "المشية ضمن الحدود الطبيعية."
204
  booking_html = ""
205
 
 
 
 
 
 
 
 
 
 
 
206
  html = f"""
207
  <div style='color:{color};font-weight:700;font-size:18px'>{level}</div>
208
  <div>👁️ زاوية التصوير: <b>{'أمامية' if view=='frontal' else 'جانبية'}</b></div>
209
  <div>📍 الجانب المتأثر: <b>{side}</b></div>
210
+ <div>🩺 الحالة المحتملة: <b>{'ضعف العضلة الظنبوبية' if strong_flags>=1 else 'غير محددة بدقة'}</b></div>
211
  <div>📊 درجة الخطورة: <b>{score:.1f}/10</b></div>
212
  <div>{desc}</div>
213
  {booking_html}
214
+ <div style='font-size:13px;color:#555;margin-top:8px'>⚠️ التحليل يعتمد على أنماط الحركة والزوايا الزمنية، ولا يُغني عن الفحص السريري.</div>
215
  """
216
  return html, _gauge_html(norm_score)
217
 
218
+ # ===================================
219
+ # واجهة Gradio + تعليمات تصوير
220
+ # ===================================
221
+ instructions = """
222
+ ### 🎥 تعليمات التصوير لضمان دقة التحليل:
223
+ 1️⃣ ضع الكاميرا على **بعد 2 إلى 3 أمتار** من الشخص، بارتفاع الركبة تقريبًا.
224
+ 2️⃣ استخدم **إضاءة جيدة** بدون ظلال قوية.
225
+ 3️⃣ اطلب من المريض أن **يمشي بشكل طبيعي لمسافة 3 أمتار ذهابًا وإيابًا**.
226
+ 4️⃣ لا ترتدي ملابس طويلة تغطي الركبة أو الكاحل.
227
+ 5️⃣ يُفضّل أن يكون الفيديو **من زاوية أمامية واضحة**.
228
+ 6️⃣ المدة المثالية للفيديو: **من 15 إلى 30 ثانية**.
229
+ """
230
+
231
+ with gr.Blocks(title="تحليل المشية العصبية - v9 (دقيق جدًا)") as demo:
232
+ gr.Markdown("## 🩺 نظام تحليل المشية العصبية – الإصدار 9")
233
+ gr.Markdown(instructions)
234
 
235
  with gr.Row():
236
  with gr.Column(scale=1):