EngReem85 commited on
Commit
f53fb31
·
verified ·
1 Parent(s): 0846e24

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +140 -118
app.py CHANGED
@@ -2,162 +2,184 @@ 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
- # تهيئة MediaPipe Pose
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  mp_pose = mp.solutions.pose
11
  pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.7, model_complexity=1)
12
 
13
- def calculate_distance(p1, p2):
14
- return math.sqrt((p1[0]-p2[0])**2 + (p1[1]-p2[1])**2)
15
-
16
- def calculate_angle(a, b, c):
17
- try:
18
- a, b, c = np.array(a), np.array(b), np.array(c)
19
- radians = np.arctan2(c[1]-b[1], c[0]-b[0]) - np.arctan2(a[1]-b[1], a[0]-b[0])
20
- angle = abs(radians * 180.0 / np.pi)
21
- return angle if angle <= 180 else 360 - angle
22
- except:
23
- return 0
24
-
25
- def analyze_gait(video_file):
26
- if video_file is None:
27
- return "❌ يرجى رفع فيديو أولاً"
28
-
29
- # التعامل مع File من Gradio
30
- if hasattr(video_file, 'name'):
31
- video_path = video_file.name
32
- else:
33
- temp_video = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
34
- with open(temp_video.name, 'wb') as f:
35
- f.write(video_file)
36
- video_path = temp_video.name
37
- temp_video.close()
38
-
39
  cap = cv2.VideoCapture(video_path)
40
- if not cap.isOpened():
41
- return "❌ لا يمكن فتح الفيديو"
42
-
43
- total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
44
- fps = cap.get(cv2.CAP_PROP_FPS)
45
- frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
46
- frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
47
- pixel_to_meter = 1.7 / (frame_height * 0.8) # تقدير طول الشخص ~1.7 م
48
-
49
- # تخزين القياسات
50
- left_clearances, right_clearances = [], []
51
- left_angles, right_angles = [], []
52
- base_widths = []
53
- left_steps, right_steps = [], []
54
- prev_left_ankle, prev_right_ankle = None, None
55
  frames_processed = 0
56
- person_detected = False
57
-
58
- while cap.isOpened() and frames_processed < min(500, total_frames):
59
  ret, frame = cap.read()
60
  if not ret:
61
  break
62
-
63
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
64
  results = pose.process(frame_rgb)
65
  if results.pose_landmarks:
66
- person_detected = True
67
  lm = results.pose_landmarks.landmark
68
-
69
- # نقاط رئيسية
70
- left_ankle = [lm[mp_pose.PoseLandmark.LEFT_ANKLE].x*frame_width, lm[mp_pose.PoseLandmark.LEFT_ANKLE].y*frame_height]
71
- right_ankle = [lm[mp_pose.PoseLandmark.RIGHT_ANKLE].x*frame_width, lm[mp_pose.PoseLandmark.RIGHT_ANKLE].y*frame_height]
72
- left_knee = [lm[mp_pose.PoseLandmark.LEFT_KNEE].x*frame_width, lm[mp_pose.PoseLandmark.LEFT_KNEE].y*frame_height]
73
- right_knee = [lm[mp_pose.PoseLandmark.RIGHT_KNEE].x*frame_width, lm[mp_pose.PoseLandmark.RIGHT_KNEE].y*frame_height]
74
- left_heel = [lm[mp_pose.PoseLandmark.LEFT_HEEL].x*frame_width, lm[mp_pose.PoseLandmark.LEFT_HEEL].y*frame_height]
75
- right_heel = [lm[mp_pose.PoseLandmark.RIGHT_HEEL].x*frame_width, lm[mp_pose.PoseLandmark.RIGHT_HEEL].y*frame_height]
76
- left_foot = [lm[mp_pose.PoseLandmark.LEFT_FOOT_INDEX].x*frame_width, lm[mp_pose.PoseLandmark.LEFT_FOOT_INDEX].y*frame_height]
77
- right_foot = [lm[mp_pose.PoseLandmark.RIGHT_FOOT_INDEX].x*frame_width, lm[mp_pose.PoseLandmark.RIGHT_FOOT_INDEX].y*frame_height]
78
-
79
- # ارتفاع القدم
80
- ground_threshold = frame_height*0.92
81
- left_clear = max(0, (ground_threshold - min(left_ankle[1], left_foot[1]))*pixel_to_meter*100)
82
- right_clear = max(0, (ground_threshold - min(right_ankle[1], right_foot[1]))*pixel_to_meter*100)
83
- left_clearances.append(left_clear)
84
- right_clearances.append(right_clear)
85
-
86
- # زاوية الكاحل
87
- left_angles.append(calculate_angle(left_knee, left_ankle, left_foot))
88
- right_angles.append(calculate_angle(right_knee, right_ankle, right_foot))
89
-
90
- # عرض القاعدة
91
- base_widths.append(abs(left_ankle[0]-right_ankle[0])*pixel_to_meter)
92
-
93
- # طول الخطوة
94
- if prev_left_ankle: left_steps.append(calculate_distance(left_ankle, prev_left_ankle)*pixel_to_meter)
95
- if prev_right_ankle: right_steps.append(calculate_distance(right_ankle, prev_right_ankle)*pixel_to_meter)
96
-
97
- prev_left_ankle = left_ankle
98
- prev_right_ankle = right_ankle
99
  frames_processed += 1
100
-
101
  cap.release()
102
- try: os.unlink(video_path)
103
- except: pass
104
-
105
- if not person_detected:
106
- return "❌ لم يتم اكتشاف شخص في الفيديو"
107
-
108
- # إحصاءات دقيقة
109
- avg_left_clear, avg_right_clear = np.mean(left_clearances), np.mean(right_clearances)
110
- std_left_clear, std_right_clear = np.std(left_clearances), np.std(right_clearances)
111
- avg_left_angle, avg_right_angle = np.mean(left_angles), np.mean(right_angles)
112
- std_left_angle, std_right_angle = np.std(left_angles), np.std(right_angles)
113
- avg_base = np.mean(base_widths)
114
- avg_left_step, avg_right_step = np.mean(left_steps) if left_steps else 0, np.mean(right_steps) if right_steps else 0
115
-
116
- # تحديد الجانب المتضرر
117
- if (avg_left_clear < avg_right_clear) or (avg_left_angle < avg_right_angle):
118
- affected_side = "اليسار"
119
  else:
120
- affected_side = "اليمين"
 
 
 
 
121
 
 
 
 
 
122
  # تصنيف المشية
123
- gait_issue = False
124
- condition, severity = "غير محددة", "طبيعية"
125
-
126
- # معايير دقيقة للكشف عن المشكلات
127
- if (avg_left_clear < 8 or avg_right_clear < 8) or abs(avg_left_angle - avg_right_angle) > 10 or avg_left_step < 0.3 or avg_right_step < 0.3:
128
- gait_issue = True
129
- if avg_base > 0.22: condition = "قدم شاركوت (Charcot Foot)"; severity = "خطر شديد"
130
- elif avg_left_clear < 8 or avg_right_clear < 8: condition = "ضعف Tibialis Anterior / Foot Drop"; severity = "معتدل"
131
- else: condition = "اعتلال الأعصاب المحيطية"; severity = "معتدل"
132
-
133
- # التقرير النهائي
134
- if not gait_issue:
135
- report = f"✅ المشية طبيعية. لا توجد علامات واضحة على المشكلات.\n\n**ملاحظة:** هذا تحليل مبدئي ولا يمكن الاعتماد عليه وحده لتحديد المشكلة."
136
  else:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  report = f"""⚠️ تم اكتشاف مشكلات في المشية
138
  - الجانب المتأثر: **{affected_side}**
139
- - شدة الحالة: **{severity}**
140
  - الحالة المحتملة: **{condition}**
141
  - نوصي بمراجعة طبيب مختص.
142
  - يمكن حجز موعد مباشر **حضوري أو أونلاين** عبر التطبيق.
143
  ---
144
  **ملاحظة:** هذا تحليل مبدئي ولا يمكن الاعتماد عليه وحده لتحديد المشكلة.
145
  """
 
146
  return report
147
 
148
- # واجهة Gradio محسنة
149
- with gr.Blocks(title="تحليل المشية العصبية المحسّن") as demo:
150
- gr.Markdown("# 🩺 تحليل المشية العصبية المحسّن")
151
- gr.Markdown("رفع فيديو المشي للحصول على تقييم مبدئي للحالة.")
152
-
 
153
  with gr.Row():
154
  with gr.Column(scale=1):
155
  video_input = gr.File(label="اختر ملف الفيديو", file_types=[".mp4",".avi",".mov"], type="binary")
156
- analyze_btn = gr.Button("بدء التحليل", variant="primary")
157
  with gr.Column(scale=2):
158
  output_report = gr.Markdown(value="**سيظهر التقرير هنا**")
159
 
160
- analyze_btn.click(fn=analyze_gait, inputs=[video_input], outputs=[output_report])
161
 
162
  if __name__ == "__main__":
163
  demo.launch(server_name="0.0.0.0", server_port=7860)
 
2
  import cv2
3
  import numpy as np
4
  import mediapipe as mp
5
+ import torch
6
+ import torch.nn as nn
7
  import tempfile
8
  import os
9
  import math
10
 
11
+ # ===========================
12
+ # نموذج تصنيف المشية
13
+ # ===========================
14
+ class SimpleGaitClassifier(nn.Module):
15
+ def __init__(self, input_dim, hidden_dim=128, num_classes=2):
16
+ super().__init__()
17
+ self.net = nn.Sequential(
18
+ nn.Linear(input_dim, hidden_dim),
19
+ nn.ReLU(),
20
+ nn.Linear(hidden_dim, hidden_dim),
21
+ nn.ReLU(),
22
+ nn.Linear(hidden_dim, num_classes)
23
+ )
24
+ def forward(self, x):
25
+ return self.net(x)
26
+
27
+ input_dim = 33*3 # 33 نقطة لكل من x,y,z
28
+ model = SimpleGaitClassifier(input_dim)
29
+ model.eval() # وضع النموذج في وضع الاختبار
30
+
31
+ classes = ['Normal', 'Abnormal']
32
+
33
+ # ===========================
34
+ # MediaPipe لاستخراج النقاط
35
+ # ===========================
36
  mp_pose = mp.solutions.pose
37
  pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.7, model_complexity=1)
38
 
39
+ def extract_keypoints_from_video(video_path, max_frames=100):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  cap = cv2.VideoCapture(video_path)
41
+ keypoints_seq = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  frames_processed = 0
43
+ while cap.isOpened() and frames_processed < max_frames:
 
 
44
  ret, frame = cap.read()
45
  if not ret:
46
  break
 
47
  frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
48
  results = pose.process(frame_rgb)
49
  if results.pose_landmarks:
 
50
  lm = results.pose_landmarks.landmark
51
+ keypoints = []
52
+ for point in lm:
53
+ keypoints.extend([point.x, point.y, point.z])
54
+ keypoints_seq.append(keypoints)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  frames_processed += 1
 
56
  cap.release()
57
+ return keypoints_seq if keypoints_seq else [[0]*input_dim]
58
+
59
+ # ===========================
60
+ # تحليل المشية بعد تصنيفها
61
+ # ===========================
62
+ def analyze_gait_with_classifier(video_file):
63
+ if video_file is None:
64
+ return "❌ يرجى رفع فيديو أولاً"
65
+
66
+ # حفظ الفيديو مؤقتًا
67
+ if hasattr(video_file, 'name'):
68
+ video_path = video_file.name
 
 
 
 
 
69
  else:
70
+ temp_video = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
71
+ with open(temp_video.name, 'wb') as f:
72
+ f.write(video_file)
73
+ video_path = temp_video.name
74
+ temp_video.close()
75
 
76
+ # استخراج النقاط
77
+ keypoints_seq = extract_keypoints_from_video(video_path)
78
+ x = torch.tensor(keypoints_seq, dtype=torch.float32).mean(dim=0).unsqueeze(0)
79
+
80
  # تصنيف المشية
81
+ with torch.no_grad():
82
+ output = model(x)
83
+ pred_class = torch.argmax(output, dim=1).item()
84
+
85
+ gait_label = classes[pred_class]
86
+
87
+ if gait_label == 'Normal':
88
+ report = "✅ المشية طبيعية. لا توجد علامات واضحة على المشكلات.\n\n**ملاحظة:** هذا تحليل مبدئي ولا يمكن الاعتماد عليه وحده لتحديد المشكلة."
 
 
 
 
 
89
  else:
90
+ # تحليل أساسي لتحديد الجانب المتضرر والحالة
91
+ # نفس فكرة ارتفاع القدم وزوايا الكاحل
92
+ cap = cv2.VideoCapture(video_path)
93
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
94
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
95
+ pixel_to_meter = 1.7 / (frame_height*0.8)
96
+
97
+ left_clearances, right_clearances = [], []
98
+ left_angles, right_angles = [], []
99
+ prev_left_ankle, prev_right_ankle = None, None
100
+ left_steps, right_steps = [], []
101
+ frames_processed = 0
102
+ person_detected = False
103
+
104
+ while cap.isOpened() and frames_processed < min(500, int(cap.get(cv2.CAP_PROP_FRAME_COUNT))):
105
+ ret, frame = cap.read()
106
+ if not ret:
107
+ break
108
+ frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
109
+ results = pose.process(frame_rgb)
110
+ if results.pose_landmarks:
111
+ person_detected = True
112
+ lm = results.pose_landmarks.landmark
113
+ left_ankle = [lm[mp_pose.PoseLandmark.LEFT_ANKLE].x*frame_width, lm[mp_pose.PoseLandmark.LEFT_ANKLE].y*frame_height]
114
+ right_ankle = [lm[mp_pose.PoseLandmark.RIGHT_ANKLE].x*frame_width, lm[mp_pose.PoseLandmark.RIGHT_ANKLE].y*frame_height]
115
+ left_knee = [lm[mp_pose.PoseLandmark.LEFT_KNEE].x*frame_width, lm[mp_pose.PoseLandmark.LEFT_KNEE].y*frame_height]
116
+ right_knee = [lm[mp_pose.PoseLandmark.RIGHT_KNEE].x*frame_width, lm[mp_pose.PoseLandmark.RIGHT_KNEE].y*frame_height]
117
+ left_heel = [lm[mp_pose.PoseLandmark.LEFT_HEEL].x*frame_width, lm[mp_pose.PoseLandmark.LEFT_HEEL].y*frame_height]
118
+ right_heel = [lm[mp_pose.PoseLandmark.RIGHT_HEEL].x*frame_width, lm[mp_pose.PoseLandmark.RIGHT_HEEL].y*frame_height]
119
+ left_foot = [lm[mp_pose.PoseLandmark.LEFT_FOOT_INDEX].x*frame_width, lm[mp_pose.PoseLandmark.LEFT_FOOT_INDEX].y*frame_height]
120
+ right_foot = [lm[mp_pose.PoseLandmark.RIGHT_FOOT_INDEX].x*frame_width, lm[mp_pose.PoseLandmark.RIGHT_FOOT_INDEX].y*frame_height]
121
+
122
+ ground_threshold = frame_height*0.92
123
+ left_clear = max(0,(ground_threshold - min(left_ankle[1], left_foot[1]))*pixel_to_meter*100)
124
+ right_clear = max(0,(ground_threshold - min(right_ankle[1], right_foot[1]))*pixel_to_meter*100)
125
+ left_clearances.append(left_clear)
126
+ right_clearances.append(right_clear)
127
+
128
+ def calc_angle(a,b,c):
129
+ a,b,c = np.array(a), np.array(b), np.array(c)
130
+ angle = abs(math.degrees(math.atan2(c[1]-b[1],c[0]-b[0])-math.atan2(a[1]-b[1],a[0]-b[0])))
131
+ return angle if angle<=180 else 360-angle
132
+
133
+ left_angles.append(calc_angle(left_knee,left_ankle,left_foot))
134
+ right_angles.append(calc_angle(right_knee,right_ankle,right_foot))
135
+
136
+ if prev_left_ankle: left_steps.append(calculate_distance(left_ankle, prev_left_ankle)*pixel_to_meter)
137
+ if prev_right_ankle: right_steps.append(calculate_distance(right_ankle, prev_right_ankle)*pixel_to_meter)
138
+ prev_left_ankle, prev_right_ankle = left_ankle, right_ankle
139
+ frames_processed += 1
140
+
141
+ cap.release()
142
+ try: os.unlink(video_path)
143
+ except: pass
144
+
145
+ avg_left_clear, avg_right_clear = np.mean(left_clearances), np.mean(right_clearances)
146
+ avg_left_angle, avg_right_angle = np.mean(left_angles), np.mean(right_angles)
147
+ avg_left_step, avg_right_step = np.mean(left_steps) if left_steps else 0, np.mean(right_steps) if right_steps else 0
148
+
149
+ affected_side = "اليسار" if avg_left_clear<avg_right_clear or avg_left_angle<avg_right_angle else "اليمين"
150
+
151
+ if avg_left_clear<8 or avg_right_clear<8:
152
+ condition="ضعف Tibialis Anterior / Foot Drop"
153
+ elif abs(avg_left_angle-avg_right_angle)>10 or avg_left_step<0.3 or avg_right_step<0.3:
154
+ condition="اعتلال الأعصاب المحيطية"
155
+ else:
156
+ condition="قدم شاركوت (Charcot Foot)"
157
+
158
  report = f"""⚠️ تم اكتشاف مشكلات في المشية
159
  - الجانب المتأثر: **{affected_side}**
 
160
  - الحالة المحتملة: **{condition}**
161
  - نوصي بمراجعة طبيب مختص.
162
  - يمكن حجز موعد مباشر **حضوري أو أونلاين** عبر التطبيق.
163
  ---
164
  **ملاحظة:** هذا تحليل مبدئي ولا يمكن الاعتماد عليه وحده لتحديد المشكلة.
165
  """
166
+
167
  return report
168
 
169
+ # ===========================
170
+ # واجهة Gradio
171
+ # ===========================
172
+ with gr.Blocks(title="تصنيف المشية العصبية") as demo:
173
+ gr.Markdown("# 🩺 تحليل المشية العصبية باستخدام نموذج بسيط")
174
+ gr.Markdown("رفع فيديو المشي للحصول على تقييم مبدئي.")
175
  with gr.Row():
176
  with gr.Column(scale=1):
177
  video_input = gr.File(label="اختر ملف الفيديو", file_types=[".mp4",".avi",".mov"], type="binary")
178
+ analyze_btn = gr.Button("تحليل المشية", variant="primary")
179
  with gr.Column(scale=2):
180
  output_report = gr.Markdown(value="**سيظهر التقرير هنا**")
181
 
182
+ analyze_btn.click(fn=analyze_gait_with_classifier, inputs=[video_input], outputs=[output_report])
183
 
184
  if __name__ == "__main__":
185
  demo.launch(server_name="0.0.0.0", server_port=7860)