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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +196 -126
app.py CHANGED
@@ -2,17 +2,18 @@ import gradio as gr
2
  import cv2
3
  import numpy as np
4
  import mediapipe as mp
5
- import tempfile
6
- import os
7
- import math
8
 
 
 
 
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):
@@ -25,162 +26,221 @@ def _angle(a, b, c):
25
  except:
26
  return 0.0
27
 
28
- def _safe_mean(x): return float(np.mean(x)) if x else 0.0
29
- def _safe_std(x): return float(np.std(x)) if x else 0.0
 
 
 
 
 
 
 
 
 
 
 
 
30
 
31
  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 #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>"
48
 
 
49
  if hasattr(video_file, "name"):
50
  video_path = video_file.name
51
  else:
52
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
53
- with open(tmp.name, "wb") as f:
54
- f.write(video_file)
55
  video_path = tmp.name
56
 
57
  cap = cv2.VideoCapture(video_path)
58
  if not cap.isOpened():
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
 
77
- while cap.isOpened() and frames_processed < min(1200, total_frames or 1200):
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"
@@ -189,8 +249,8 @@ def analyze_gait(video_file):
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"
@@ -200,38 +260,50 @@ def analyze_gait(video_file):
200
  </div>
201
  """
202
  else:
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):
237
  video_in = gr.File(label="📂 اختر فيديو المشي", file_types=[".mp4", ".avi", ".mov"], type="binary")
@@ -239,7 +311,6 @@ with gr.Blocks(title="تحليل المشية العصبية - v9 (دقيق جد
239
  with gr.Column(scale=1):
240
  gauge = gr.HTML("<div></div>")
241
  out_html = gr.HTML("<i>النتيجة ستظهر هنا بعد التحليل...</i>")
242
-
243
  analyze_btn.click(fn=analyze_gait, inputs=[video_in], outputs=[out_html, gauge])
244
 
245
  if __name__ == "__main__":
@@ -249,6 +320,5 @@ if __name__ == "__main__":
249
 
250
 
251
 
252
-
253
 
254
 
 
2
  import cv2
3
  import numpy as np
4
  import mediapipe as mp
5
+ import tempfile, os, math
 
 
6
 
7
+ # =========================
8
+ # إعداد Mediapipe Pose
9
+ # =========================
10
  mp_pose = mp.solutions.pose
11
  pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.75, model_complexity=2)
12
 
13
+ # =========================
14
  # دوال مساعدة
15
+ # =========================
16
+ def _dist(p1, p2):
17
  return math.hypot(p1[0]-p2[0], p1[1]-p2[1])
18
 
19
  def _angle(a, b, c):
 
26
  except:
27
  return 0.0
28
 
29
+ def _safe_mean(x): return float(np.mean(x)) if len(x) else 0.0
30
+ def _safe_std(x): return float(np.std(x)) if len(x) else 0.0
31
+
32
+ def _symmetry_index(a_mean, b_mean, eps=1e-6):
33
+ # Robinson/SI مبسّط: 2*(R-L)/(R+L) كنسبة مئوية
34
+ return 100.0 * (2.0 * (a_mean - b_mean) / (a_mean + b_mean + eps))
35
+
36
+ def _symmetry_angle(a_mean, b_mean, eps=1e-6):
37
+ # Zifchock/SA مبسّطة: تحويل تماثل لنطاق زاوي
38
+ r = (a_mean + eps) / (b_mean + eps)
39
+ return abs(45.0 * (r - 1) / (r + 1))
40
+
41
+ def _norm01(x, lo, hi):
42
+ return max(0.0, min(1.0, (x - lo) / (hi - lo + 1e-6)))
43
 
44
  def _gauge_html(norm_score):
45
  pct = int(max(0, min(1, norm_score)) * 100)
46
  color = "#4caf50" if pct < 35 else "#fbc02d" if pct < 65 else "#c62828"
47
+ return f"""
48
  <div style="width:100%;background:#eee;border-radius:10px;height:16px;overflow:hidden;border:1px solid #ccc;">
49
+ <div style="width:{pct}%;height:100%;background:{color};transition:width .8s;"></div>
50
  </div>
51
  <div style="font-size:12px;color:#555;margin-top:6px">درجة الخطورة: {pct}%</div>
52
  """
 
53
 
54
+ # =========================
55
  # التحليل الرئيسي
56
+ # =========================
57
  def analyze_gait(video_file):
58
  if video_file is None:
59
  return "<div>❌ يرجى رفع فيديو أولًا.</div>", "<div></div>"
60
 
61
+ # حفظ الفيديو مؤقتًا
62
  if hasattr(video_file, "name"):
63
  video_path = video_file.name
64
  else:
65
  tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
66
+ with open(tmp.name, "wb") as f: f.write(video_file)
 
67
  video_path = tmp.name
68
 
69
  cap = cv2.VideoCapture(video_path)
70
  if not cap.isOpened():
71
  return "<div>❌ لا يمكن فتح الفيديو.</div>", "<div></div>"
72
 
 
73
  W = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH) or 640)
74
  H = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT) or 480)
75
+ fps = float(cap.get(cv2.CAP_PROP_FPS) or 30.0)
76
+ px2m = 1.7 / (H * 0.8) # تطبيع تقريبي للطول
77
  ground_y = H * 0.92
78
 
79
+ # سلاسل زمنية
80
+ L_clear, R_clear = [], []
81
+ L_ang, R_ang = [], []
82
+ base_px_seq, torso_tilt_seq, torso_side_seq = [], [], []
83
+
84
+ # طاقة الحركة (للاستقرار العام)
85
  motion_energy = []
86
+ prev_gray = None
87
 
88
+ frames = 0
89
+ detected = False
 
90
 
91
+ while cap.isOpened() and frames < 1400:
92
  ret, frame = cap.read()
93
  if not ret: break
94
+ frames += 1
95
+
96
+ g = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
97
+ if prev_gray is not None:
98
+ diff = cv2.absdiff(g, prev_gray)
99
+ motion_energy.append(float(np.mean(diff)))
100
+ prev_gray = g
101
+
102
+ res = pose.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
103
+ if not res.pose_landmarks:
104
  continue
105
+ detected = True
106
  lm = res.pose_landmarks.landmark
107
 
108
  def xy(i): return [lm[i].x*W, lm[i].y*H]
109
+
110
+ # نقاط حرجة (وفق Mediapipe Pose Landmarks)
111
+ L_ank, R_ank = xy( mp_pose.PoseLandmark.LEFT_ANKLE.value ), xy( mp_pose.PoseLandmark.RIGHT_ANKLE.value )
112
+ L_foot,R_foot= xy( mp_pose.PoseLandmark.LEFT_FOOT_INDEX.value),xy( mp_pose.PoseLandmark.RIGHT_FOOT_INDEX.value)
113
+ L_knee,R_knee= xy( mp_pose.PoseLandmark.LEFT_KNEE.value ), xy( mp_pose.PoseLandmark.RIGHT_KNEE.value )
114
+ L_hip, R_hip = xy( mp_pose.PoseLandmark.LEFT_HIP.value ), xy( mp_pose.PoseLandmark.RIGHT_HIP.value )
115
+ L_sh, R_sh = xy( mp_pose.PoseLandmark.LEFT_SHOULDER.value ),xy( mp_pose.PoseLandmark.RIGHT_SHOULDER.value )
116
+
117
+ # ارتفاع القدم (سم): أقرب نقطة (كاحل/مقدمة القدم) إلى الأرض
118
+ Lc = max(0, (ground_y - min(L_ank[1], L_foot[1])) * px2m * 100)
119
+ Rc = max(0, (ground_y - min(R_ank[1], R_foot[1])) * px2m * 100)
120
  L_clear.append(Lc); R_clear.append(Rc)
121
 
122
  # زاوية الكاحل
123
+ La = _angle(L_knee, L_ank, L_foot)
124
+ Ra = _angle(R_knee, R_ank, R_foot)
125
+ L_ang.append(La); R_ang.append(Ra)
126
 
127
+ # قاعدة القدمين (بكسل) + تقدير الميل
128
+ base_px_seq.append(abs(L_ank[0]-R_ank[0]))
129
  mid_sh = [(L_sh[0]+R_sh[0])/2, (L_sh[1]+R_sh[1])/2]
130
  mid_hip= [(L_hip[0]+R_hip[0])/2, (L_hip[1]+R_hip[1])/2]
131
+ vec = np.array([mid_sh[0]-mid_hip[0], mid_sh[1]-mid_hip[1]])
132
+ tilt = abs(90 - abs(math.degrees(math.atan2(abs(vec[1]), abs(vec[0])+1e-6))))
133
  torso_tilt_seq.append(tilt)
134
  torso_side_seq.append(mid_sh[0]-mid_hip[0])
 
135
 
136
  cap.release()
137
  try: os.unlink(video_path)
138
  except: pass
139
 
140
+ if not detected or frames < 30:
141
+ return "<div>❌ لم يتم التقاط معالم كافية. يرجى إعادة التصوير وفق التعليمات أدناه.</div>", "<div></div>"
142
 
143
+ # =========================
144
+ # إحصاءات أساسية
145
+ # =========================
146
  avg_Lc, avg_Rc = _safe_mean(L_clear), _safe_mean(R_clear)
147
+ std_Lc, std_Rc = _safe_std(L_clear), _safe_std(R_clear)
148
+ avg_La, avg_Ra = _safe_mean(L_ang), _safe_mean(R_ang)
 
 
 
149
  var_clear = max(std_Lc, std_Rc)
150
+ diff_clear= abs(avg_Lc - avg_Rc)
151
+ diff_angle= abs(avg_La - avg_Ra)
152
+ base_ratio= _safe_mean(base_px_seq)/max(1,W)
153
+ avg_tilt = _safe_mean(torso_tilt_seq)
154
+ side_lean = _safe_mean(torso_side_seq)
155
+ motion_cv = (np.std(motion_energy)/ (np.mean(motion_energy)+1e-6)) if len(motion_energy) else 0.0
156
+
157
+ # عرض أمامي/جانبي (مع ترجيح للأمامي كما طلبتِ)
158
+ view = "frontal" if base_ratio > 0.15 else "side"
159
+
160
+ # =========================
161
+ # مؤشرات زمنية متقدمة
162
+ # =========================
163
+ Lc_arr = np.array(L_clear, dtype=float)
164
+ Rc_arr = np.array(R_clear, dtype=float)
165
+
166
+ # 1) نسبة زمن انخفاض الارتفاع لكل قدم
167
+ low_thr = 3.5 # سم
168
+ ratio_low_L = float(np.mean(Lc_arr < low_thr))
169
+ ratio_low_R = float(np.mean(Rc_arr < low_thr))
170
+
171
+ # 2) تأخر زمني بين القدمين (cross-correlation) كدليل Foot Drop
172
+ # نستخدم الإزاحة التي تعظم الارتباط بين L_clear و R_clear
173
+ def lag_cc(a, b, max_lag=15):
174
+ if len(a) < 5 or len(b) < 5: return 0
175
+ a = (a - np.mean(a)) / (np.std(a)+1e-6)
176
+ b = (b - np.mean(b)) / (np.std(b)+1e-6)
177
+ best_lag, best_cc = 0, -1
178
+ for lag in range(-max_lag, max_lag+1):
179
+ if lag < 0:
180
+ cc = np.mean(a[:lag] * b[-lag:])
181
+ elif lag > 0:
182
+ cc = np.mean(a[lag:] * b[:-lag])
183
+ else:
184
+ cc = np.mean(a * b)
185
+ if cc > best_cc:
186
+ best_cc, best_lag = cc, lag
187
+ return best_lag
188
+ lag = lag_cc(Lc_arr, Rc_arr, max_lag=round(0.3*fps)) # ~0.3 ثانية كحد أقصى
189
+
190
+ # 3) مؤشرات تماثل مبسّطة (SI/SA)
191
+ si_clear = abs(_symmetry_index(avg_Lc, avg_Rc)) # كلما ارتفع كانت لا تماثل أكبر
192
+ sa_angle = _symmetry_angle(avg_La, avg_Ra)
193
+
194
+ # 4) عدم استقرار الجذع (تذبذب)
195
+ torso_sway = _safe_std(torso_side_seq) / max(1.0, W) # نسبي لعرض الإطار
196
+
197
+ # =========================
198
+ # تصنيف صارم ومتوازن
199
+ # =========================
200
+ score = 0.0
201
+ strong = 0
202
+
203
+ # Foot Drop: انخفاض مستمر + تأخر زمني + زاوية منخفضة نسبياً
204
+ fd_evidence = (min(avg_Lc, avg_Rc) < 3.5) or (ratio_low_L > 0.45 or ratio_low_R > 0.45)
205
+ fd_delay = abs(lag)/max(1.0,fps) > 0.08 # تأخر > 80ms
206
+ if (fd_evidence and fd_delay) or (fd_evidence and diff_angle < 25):
207
+ score += 3.5; strong += 1
208
+
209
+ # Neuropathy: تذبذب ارتفاع واضح + لا تماثل زاوي/زمني
210
+ if (var_clear > 9 and sa_angle > 6) or (si_clear > 18 and motion_cv > 0.22):
211
+ score += 3.0; strong += 1
212
+
213
+ # Charcot: قاعدة أوسع + ميل جذعي ملحوظ (مفيد أكثر للأمامي)
214
+ base_m = base_ratio * W * px2m
215
+ if (view == "frontal" and base_m > 0.27 and (avg_tilt > 9 or torso_sway > 0.02)) or (base_m > 0.30):
216
+ score += 3.5; strong += 1
217
+
218
+ # عوامل داعمة
219
+ if diff_clear > 6: score += 1.0
220
+ if abs(side_lean) > W*0.03: score += 1.0
221
+ if sa_angle > 8: score += 0.5
222
+
223
+ # ترجيح للأمامي لأنك ذكرت أنه الأكثر شيوعاً
224
+ if view == "frontal": score *= 1.12
225
+
226
+ score = min(score, 10.0)
227
+ norm_score = score / 10.0
228
+
229
+ # تحديد الجانب المتضرر (تجميعي)
230
+ # يعتمد على: انخفاض المتوسط + طول زمن الانخفاض + إشارة التأخر الزمني
231
+ left_weight = (avg_Rc - avg_Lc) + 20*(ratio_low_L - ratio_low_R) + (1 if lag > 0 else 0) # lag>0 يعني L يتأخر
232
+ right_weight = (avg_Lc - avg_Rc) + 20*(ratio_low_R - ratio_low_L) + (1 if lag < 0 else 0)
233
+
234
+ if left_weight > right_weight + 3:
235
  side = "اليسار"
236
+ elif right_weight > left_weight + 3:
237
  side = "اليمين"
238
  else:
239
  side = "غير محدد بوضوح"
240
 
241
+ # قرار نهائي (أكثر صرامة)
242
+ if norm_score >= 0.68 or strong >= 2:
243
+ level, color, desc = "🔴 عالية الخطورة", "#c62828", "تم رصد مؤشرات زمنية ومكانية قوية لخلل في المشية."
 
 
244
  booking_html = """
245
  <div style="margin-top:10px">
246
  <a href="https://example.com/book" target="_blank"
 
249
  </a>
250
  </div>
251
  """
252
+ elif norm_score >= 0.48:
253
+ level, color, desc = "🟡 متوسطة الخطورة", "#f9a825", "مؤشرات ملحوظة تستدعي متابعة طبية وقائية."
254
  booking_html = """
255
  <div style="margin-top:10px">
256
  <a href="https://example.com/book" target="_blank"
 
260
  </div>
261
  """
262
  else:
263
+ level, color, desc = "🟢 طبيعية", "#2e7d32", "المشية ضمن الحدود الطبيعية بحسب المعطيات الزمنية والمكانية."
264
  booking_html = ""
265
 
266
+ # ترجيح الحالة
267
+ if strong >= 2 and base_m > 0.27:
268
+ condition = "قدم شاركوت (Charcot Foot)"
269
+ elif fd_evidence:
270
+ condition = "ضعف العضلة الظنبوبية (Foot Drop)"
271
+ elif (var_clear > 9 and sa_angle > 6) or (si_clear > 18 and motion_cv > 0.22):
272
+ condition = "اعتلال الأعصاب المحيطية / السكري"
273
+ else:
274
+ condition = "خلل بسيط غير محدد"
275
+
276
  html = f"""
277
+ <div style="color:{color};font-weight:700;font-size:18px">{level}</div>
278
  <div>👁️ زاوية التصوير: <b>{'أمامية' if view=='frontal' else 'جانبية'}</b></div>
279
  <div>📍 الجانب المتأثر: <b>{side}</b></div>
280
+ <div>🩺 الحالة المحتملة: <b>{condition}</b></div>
281
+ <div>📊 درجة المؤشرات: <b>{score:.1f}/10</b></div>
282
  <div>{desc}</div>
283
  {booking_html}
284
+ <div style='font-size:13px;color:#555;margin-top:8px'>⚠️ التحليل تقديري يعتمد على معالم المشي والزمن ولا يُغني عن التشخيص السريري.</div>
285
  """
286
  return html, _gauge_html(norm_score)
287
 
288
+ # =========================
289
+ # تعليمات تصوير داخل الواجهة
290
+ # =========================
291
  instructions = """
292
+ ### 🎥 تعليمات تصوير دقيقة (لتحليل أوثق):
293
+ 1) ضع الكاميرا على **بعد 23 م** من الشخص وعلى **ارتفاع الركبة تقريبًا**.
294
+ 2) **إضاءة أمامية جيدة**، وخلفية بسيطة، وتجنّب الظلال القوية.
295
+ 3) صوّر **زاوية أمامية واضحة** (أو جانبية إن تعذّر مع إظهار الجسم من **الورك حتى القدم** كاملين.
296
+ 4) اطلب من الشخص **المشي بشكل طبيعي 3–5 أمتار ذهابًا وإيابًا** لمدة **15–30 ثانية**.
297
+ 5) تجنّب الملابس الطويلة/الفضفاضة التي **تحجب الركبة والكاحل**.
298
+ 6) ثبّت الهاتف (على حامل إن أمكن) لتقليل اهتزاز الكاميرا.
299
  """
300
 
301
+ # =========================
302
+ # واجهة Gradio
303
+ # =========================
304
+ with gr.Blocks(title="تحليل المشية العصبية - v10 (زمني/مكاني صارم)") as demo:
305
+ gr.Markdown("## 🩺 نظام تحليل المشية العصبية – الإصدار v10")
306
  gr.Markdown(instructions)
 
307
  with gr.Row():
308
  with gr.Column(scale=1):
309
  video_in = gr.File(label="📂 اختر فيديو المشي", file_types=[".mp4", ".avi", ".mov"], type="binary")
 
311
  with gr.Column(scale=1):
312
  gauge = gr.HTML("<div></div>")
313
  out_html = gr.HTML("<i>النتيجة ستظهر هنا بعد التحليل...</i>")
 
314
  analyze_btn.click(fn=analyze_gait, inputs=[video_in], outputs=[out_html, gauge])
315
 
316
  if __name__ == "__main__":
 
320
 
321
 
322
 
 
323
 
324