EngReem85 commited on
Commit
0ab681f
·
verified ·
1 Parent(s): 062cac5

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +58 -90
app.py CHANGED
@@ -15,9 +15,7 @@ pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.7, model
15
  # ===========================
16
  # دوال مساعدة
17
  # ===========================
18
- def _dist(p1, p2):
19
- return math.hypot(p1[0]-p2[0], p1[1]-p2[1])
20
-
21
  def _angle(a, b, c):
22
  try:
23
  a, b, c = np.array(a), np.array(b), np.array(c)
@@ -62,26 +60,21 @@ def analyze_gait(video_file):
62
  L_angle, R_angle = [], []
63
  L_step, R_step = [], []
64
  base_widths = []
65
- step_events_L, step_events_R = 0, 0
66
  prev_L_ank, prev_R_ank = None, None
67
  frames_processed, person_detected = 0, False
68
  ground_y = H * 0.92
69
 
70
  while cap.isOpened() and frames_processed < min(1000, total_frames or 1000):
71
  ret, frame = cap.read()
72
- if not ret:
73
- break
74
  frames_processed += 1
75
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
76
  res = pose.process(frame_rgb)
77
- if not res.pose_landmarks:
78
- continue
79
 
80
  person_detected = True
81
  lm = res.pose_landmarks.landmark
82
-
83
- def xy(idx):
84
- return [lm[idx].x * W, lm[idx].y * H]
85
 
86
  L_ank = xy(mp_pose.PoseLandmark.LEFT_ANKLE.value)
87
  R_ank = xy(mp_pose.PoseLandmark.RIGHT_ANKLE.value)
@@ -89,47 +82,34 @@ def analyze_gait(video_file):
89
  R_knee = xy(mp_pose.PoseLandmark.RIGHT_KNEE.value)
90
  L_foot = xy(mp_pose.PoseLandmark.LEFT_FOOT_INDEX.value)
91
  R_foot = xy(mp_pose.PoseLandmark.RIGHT_FOOT_INDEX.value)
92
- L_hip = xy(mp_pose.PoseLandmark.LEFT_HIP.value)
93
- R_hip = xy(mp_pose.PoseLandmark.RIGHT_HIP.value)
94
 
95
- # ارتفاع القدم
96
  Lc = max(0, (ground_y - min(L_ank[1], L_foot[1])) * px2m * 100)
97
  Rc = max(0, (ground_y - min(R_ank[1], R_foot[1])) * px2m * 100)
98
  L_clear.append(Lc); R_clear.append(Rc)
99
 
100
- # زاوية الكاحل والركبة والورك
101
  La = _angle(L_knee, L_ank, L_foot)
102
  Ra = _angle(R_knee, R_ank, R_foot)
103
- Lk = _angle(L_hip, L_knee, L_ank)
104
- Rk = _angle(R_hip, R_knee, R_ank)
105
- L_angle.append(La + Lk / 2)
106
- R_angle.append(Ra + Rk / 2)
107
 
108
- # تباعد القدمين
109
  base_widths.append(abs(L_ank[0]-R_ank[0]) * px2m)
110
 
111
- # طول الخطوة
112
  if prev_L_ank is not None:
113
- d = _dist(L_ank, prev_L_ank) * px2m
114
- L_step.append(d)
115
- if d > 0.03: step_events_L += 1
116
  if prev_R_ank is not None:
117
- d = _dist(R_ank, prev_R_ank) * px2m
118
- R_step.append(d)
119
- if d > 0.03: step_events_R += 1
120
  prev_L_ank, prev_R_ank = L_ank, R_ank
121
 
122
  cap.release()
123
- try:
124
- os.unlink(video_path)
125
- except:
126
- pass
127
 
128
  if not person_detected:
129
  return "<div>❌ لم يتم اكتشاف شخص في الفيديو.</div>", None
130
 
131
  # ===========================
132
- # الإحصاءات الأساسية
133
  # ===========================
134
  avg_Lc, avg_Rc = _safe_mean(L_clear), _safe_mean(R_clear)
135
  std_Lc, std_Rc = _safe_std(L_clear), _safe_std(R_clear)
@@ -137,96 +117,84 @@ def analyze_gait(video_file):
137
  avg_Lstep, avg_Rstep = _safe_mean(L_step), _safe_mean(R_step)
138
  avg_base = _safe_mean(base_widths)
139
  duration_s = (frames_processed / fps) if fps > 0 else frames_processed / 30
140
- total_steps = (step_events_L + step_events_R) / 2
141
- cadence = (total_steps / duration_s) * 60 if duration_s > 0 else 0
142
 
143
  # ===========================
144
- # تحليل شامل بدرجات متعددة
145
  # ===========================
146
- score = 0.0
147
- features = {}
148
-
149
- # اختلاف الزوايا بين الجانبين
150
  diff_angle = abs(avg_La - avg_Ra)
151
- features["angle_diff"] = diff_angle
152
- score += 1.0 if diff_angle > 15 else 0.5 if diff_angle > 10 else 0
153
-
154
- # تذبذب الارتفاع
155
  var_clear = max(std_Lc, std_Rc)
156
- features["stability"] = var_clear
157
- score += 1.5 if var_clear > 10 else 0.5 if var_clear > 6 else 0
158
-
159
- # طول الخطوة
160
- avg_step = (avg_Lstep + avg_Rstep) / 2
161
- features["step_length"] = avg_step
162
- score += 1.5 if avg_step < 0.16 else 0.5 if avg_step < 0.22 else 0
163
-
164
- # عرض القاعدة
165
- features["base_width"] = avg_base
166
- score += 1.5 if avg_base > 0.30 else 0.5 if avg_base > 0.25 else 0
167
-
168
- # معدل الخطوات
169
- features["cadence"] = cadence
170
- score += 1.5 if cadence < 55 else 0.5 if cadence < 65 else 0
171
-
172
- # الارتفاع الأدنى للقدم
173
  min_clear = min(avg_Lc, avg_Rc)
174
- features["min_clearance"] = min_clear
175
- score += 1.5 if min_clear < 4 else 0.5 if min_clear < 6 else 0
176
-
177
- # فرق الاتساع الجانبي
178
- asym_clear = abs(avg_Lc - avg_Rc)
179
- features["clearance_asym"] = asym_clear
180
- score += 1.0 if asym_clear > 5 else 0.5 if asym_clear > 3 else 0
181
 
182
- # زاوية الركبة (ميلها)
183
- knee_sym = abs(L_angle[-1] - R_angle[-1]) if L_angle and R_angle else 0
184
- score += 1.0 if knee_sym > 10 else 0
 
 
 
 
 
 
 
 
 
 
 
185
 
186
  # تجاهل المشي الطليعي المنتظم
187
- if avg_Lc > 10 and avg_Rc > 10 and var_clear < 5 and diff_angle < 12:
188
  return (
189
  "<div style='color:#2e7d32;font-weight:600'>✅ المشية طليعية طبيعية.</div>"
190
- "<div>تم التعرف على نمط مشية طليعية منتظمة دون مؤشرات لخلل.</div>"
191
- "<div style='font-size:13px;color:#555;margin-top:8px'>⚠️ يوصى فقط بالمتابعة الدورية في حال وجود ألم أو اختلال توازن.</div>",
192
- 1.0
193
  )
194
 
195
- # ===========================
196
- # التقييم النهائي
197
- # ===========================
198
- max_score = 10.0
199
- norm_score = min(score / max_score, 1.0)
200
 
201
- if norm_score < 0.3:
202
- level, color, desc = "🟢 طبيعي", "#2e7d32", "المشية ضمن الحدود الطبيعية."
203
  elif norm_score < 0.6:
204
- level, color, desc = "🟡 متوسطة الخطورة", "#f9a825", "بعض الاختلافات الطفيفة، يُفضّل المراقبة فقط."
 
 
 
 
 
 
 
 
 
 
205
  else:
206
- level, color, desc = "🔴 غير طبيعية", "#c62828", "مؤشرات واضحة لخلل في المشية تتطلب مراجعة طبية."
207
 
208
  html = f"""
209
  <div style='color:{color};font-weight:700;font-size:18px'>{level}</div>
210
- <div>درجة الخطورة الكلية: <b>{score:.1f}/10</b></div>
 
211
  <div>{desc}</div>
212
- <div style='font-size:13px;color:#555;margin-top:8px'>⚠️ يعتمد التحليل على الفيديو فقط ولا يُغني عن الفحص الطبي المتخصص.</div>
213
  """
214
  return html, norm_score
215
 
216
  # ===========================
217
  # واجهة Gradio
218
  # ===========================
219
- with gr.Blocks(title="تحليل المشية العصبية - الإصدار الذكي") as demo:
220
- gr.Markdown("## 🩺 نظام تحليل المشية العصبية (الإصدار الذكي)")
221
- gr.Markdown("ارفع فيديو جانبي للمشي (15–30 ثانية) وسيقدّم النظام تقييمًا تدريجيًا للحالة المحتملة.")
222
 
223
  with gr.Row():
224
  with gr.Column(scale=1):
225
  video_in = gr.File(label="📂 اختر فيديو المشي", file_types=[".mp4", ".avi", ".mov"], type="binary")
226
  analyze_btn = gr.Button("🔍 بدء التحليل", variant="primary")
227
  with gr.Column(scale=1):
228
- gauge = gr.Number(label="⚙️ مقياس الخطورة (0-1)", interactive=False)
229
- out_html = gr.HTML("<i>ستظهر النتيجة هنا بعد التحليل...</i>")
230
 
231
  analyze_btn.click(fn=analyze_gait, inputs=[video_in], outputs=[out_html, gauge])
232
 
 
15
  # ===========================
16
  # دوال مساعدة
17
  # ===========================
18
+ def _dist(p1, p2): return math.hypot(p1[0]-p2[0], p1[1]-p2[1])
 
 
19
  def _angle(a, b, c):
20
  try:
21
  a, b, c = np.array(a), np.array(b), np.array(c)
 
60
  L_angle, R_angle = [], []
61
  L_step, R_step = [], []
62
  base_widths = []
 
63
  prev_L_ank, prev_R_ank = None, None
64
  frames_processed, person_detected = 0, False
65
  ground_y = H * 0.92
66
 
67
  while cap.isOpened() and frames_processed < min(1000, total_frames or 1000):
68
  ret, frame = cap.read()
69
+ if not ret: break
 
70
  frames_processed += 1
71
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
72
  res = pose.process(frame_rgb)
73
+ if not res.pose_landmarks: continue
 
74
 
75
  person_detected = True
76
  lm = res.pose_landmarks.landmark
77
+ def xy(idx): return [lm[idx].x * W, lm[idx].y * H]
 
 
78
 
79
  L_ank = xy(mp_pose.PoseLandmark.LEFT_ANKLE.value)
80
  R_ank = xy(mp_pose.PoseLandmark.RIGHT_ANKLE.value)
 
82
  R_knee = xy(mp_pose.PoseLandmark.RIGHT_KNEE.value)
83
  L_foot = xy(mp_pose.PoseLandmark.LEFT_FOOT_INDEX.value)
84
  R_foot = xy(mp_pose.PoseLandmark.RIGHT_FOOT_INDEX.value)
 
 
85
 
86
+ # الارتفاع بالسنتيمتر
87
  Lc = max(0, (ground_y - min(L_ank[1], L_foot[1])) * px2m * 100)
88
  Rc = max(0, (ground_y - min(R_ank[1], R_foot[1])) * px2m * 100)
89
  L_clear.append(Lc); R_clear.append(Rc)
90
 
91
+ # زاوية الكاحل
92
  La = _angle(L_knee, L_ank, L_foot)
93
  Ra = _angle(R_knee, R_ank, R_foot)
94
+ L_angle.append(La); R_angle.append(Ra)
 
 
 
95
 
 
96
  base_widths.append(abs(L_ank[0]-R_ank[0]) * px2m)
97
 
 
98
  if prev_L_ank is not None:
99
+ L_step.append(_dist(L_ank, prev_L_ank) * px2m)
 
 
100
  if prev_R_ank is not None:
101
+ R_step.append(_dist(R_ank, prev_R_ank) * px2m)
 
 
102
  prev_L_ank, prev_R_ank = L_ank, R_ank
103
 
104
  cap.release()
105
+ try: os.unlink(video_path)
106
+ except: pass
 
 
107
 
108
  if not person_detected:
109
  return "<div>❌ لم يتم اكتشاف شخص في الفيديو.</div>", None
110
 
111
  # ===========================
112
+ # إحصاءات أساسية
113
  # ===========================
114
  avg_Lc, avg_Rc = _safe_mean(L_clear), _safe_mean(R_clear)
115
  std_Lc, std_Rc = _safe_std(L_clear), _safe_std(R_clear)
 
117
  avg_Lstep, avg_Rstep = _safe_mean(L_step), _safe_mean(R_step)
118
  avg_base = _safe_mean(base_widths)
119
  duration_s = (frames_processed / fps) if fps > 0 else frames_processed / 30
120
+ cadence = ((len(L_step) + len(R_step)) / 2) / duration_s * 60 if duration_s > 0 else 0
 
121
 
122
  # ===========================
123
+ # ملامح الأنماط العصبية
124
  # ===========================
 
 
 
 
125
  diff_angle = abs(avg_La - avg_Ra)
126
+ diff_clear = abs(avg_Lc - avg_Rc)
 
 
 
127
  var_clear = max(std_Lc, std_Rc)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
128
  min_clear = min(avg_Lc, avg_Rc)
129
+ avg_step = (avg_Lstep + avg_Rstep) / 2
 
 
 
 
 
 
130
 
131
+ # ==== تحديد الحالة المحتملة ====
132
+ foot_drop_like = (min_clear < 4 and diff_angle < 30)
133
+ neuropathy_like = (var_clear > 10 and diff_angle > 15 and cadence < 70)
134
+ charcot_like = (avg_base > 0.28 and var_clear > 8 and diff_angle > 10)
135
+
136
+ # ==== حساب درجة الخطورة الكلية ====
137
+ score = 0
138
+ score += 2.5 if foot_drop_like else 0
139
+ score += 2.0 if neuropathy_like else 0
140
+ score += 2.5 if charcot_like else 0
141
+ score += 1.0 if avg_step < 0.18 else 0
142
+ score += 0.5 if cadence < 60 else 0
143
+ score += 1.0 if diff_clear > 5 else 0
144
+ score += 0.5 if var_clear > 7 else 0
145
 
146
  # تجاهل المشي الطليعي المنتظم
147
+ if avg_Lc > 10 and avg_Rc > 10 and var_clear < 5:
148
  return (
149
  "<div style='color:#2e7d32;font-weight:600'>✅ المشية طليعية طبيعية.</div>"
150
+ "<div>تم التعرف على مشية طليعية مستقرة دون مؤشرات لخلل عصبي.</div>"
151
+ "<div style='font-size:13px;color:#555;margin-top:8px'>⚠️ المتابعة فقط عند وجود ألم أو خلل توازن.</div>",
152
+ 0.1
153
  )
154
 
155
+ # ==== تقييم الحالة ====
156
+ norm_score = min(score / 8, 1.0)
 
 
 
157
 
158
+ if norm_score < 0.35:
159
+ level, color, desc = "🟢 طبيعي", "#2e7d32", "المشية ضمن النطاق الطبيعي."
160
  elif norm_score < 0.6:
161
+ level, color, desc = "🟡 متوسطة الخطورة", "#fbc02d", "هناك بعض الاختلافات الطفيفة فقط."
162
+ else:
163
+ level, color, desc = "🔴 عالية الخطورة", "#c62828", "تم رصد مؤشرات قوية لخلل في المشية."
164
+
165
+ # نوع الحالة المرجّحة
166
+ if charcot_like:
167
+ condition = "قدم شاركوت (Charcot Foot)"
168
+ elif foot_drop_like:
169
+ condition = "ضعف العضلة الظنبوبية (Foot Drop)"
170
+ elif neuropathy_like:
171
+ condition = "اعتلال الأعصاب المحيطية / السكري"
172
  else:
173
+ condition = "غير محددة بدقة"
174
 
175
  html = f"""
176
  <div style='color:{color};font-weight:700;font-size:18px'>{level}</div>
177
+ <div>🩺 الحالة المحتملة: <b>{condition}</b></div>
178
+ <div>📊 درجة الخطورة: <b>{score:.1f}/8</b></div>
179
  <div>{desc}</div>
180
+ <div style='font-size:13px;color:#555;margin-top:8px'>⚠️ هذا تحليل مبدئي يعتمد على نمط المشي فقط ولا يُغني عن التشخيص الطبي.</div>
181
  """
182
  return html, norm_score
183
 
184
  # ===========================
185
  # واجهة Gradio
186
  # ===========================
187
+ with gr.Blocks(title="تحليل المشية العصبية - الإصدار المتوازن") as demo:
188
+ gr.Markdown("## 🩺 نظام تحليل المشية العصبية (الإصدار المتوازن)")
189
+ gr.Markdown("ارفع فيديو جانبي للمشي (15–30 ثانية) وسيقوم النظام بتصنيف الحالة مع درجة الخطورة.")
190
 
191
  with gr.Row():
192
  with gr.Column(scale=1):
193
  video_in = gr.File(label="📂 اختر فيديو المشي", file_types=[".mp4", ".avi", ".mov"], type="binary")
194
  analyze_btn = gr.Button("🔍 بدء التحليل", variant="primary")
195
  with gr.Column(scale=1):
196
+ gauge = gr.Number(label="⚙️ درجة الخطورة (0-1)", interactive=False)
197
+ out_html = gr.HTML("<i>النتيجة ستظهر هنا...</i>")
198
 
199
  analyze_btn.click(fn=analyze_gait, inputs=[video_in], outputs=[out_html, gauge])
200