Spaces:
Running
Running
Update app.py
Browse files
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 #
|
| 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 |
-
|
| 68 |
-
L_angle, R_angle = [], []
|
| 69 |
-
|
| 70 |
-
|
|
|
|
| 71 |
|
| 72 |
-
|
| 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:
|
| 84 |
-
|
| 85 |
person_detected = True
|
| 86 |
lm = res.pose_landmarks.landmark
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
| 92 |
-
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
|
| 101 |
-
|
| 102 |
-
|
| 103 |
-
|
| 104 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
| 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 |
-
|
| 147 |
|
| 148 |
-
|
| 149 |
-
view = "frontal" if px_base_ratio > 0.14 else "side"
|
| 150 |
|
| 151 |
-
# نسب
|
| 152 |
-
|
| 153 |
-
|
|
|
|
| 154 |
|
| 155 |
-
# ===========================
|
| 156 |
-
# منطق
|
| 157 |
-
# ===========================
|
| 158 |
score = 0
|
| 159 |
strong_flags = 0
|
| 160 |
|
| 161 |
-
# ضعف القدم
|
| 162 |
-
if (min(avg_Lc, avg_Rc)<3.5) or (low_ratio_L>0.
|
| 163 |
score += 3.5; strong_flags += 1
|
| 164 |
|
| 165 |
-
#
|
| 166 |
-
if (var_clear>
|
| 167 |
score += 3; strong_flags += 1
|
| 168 |
|
| 169 |
# Charcot
|
| 170 |
-
if (
|
| 171 |
score += 3.5; strong_flags += 1
|
| 172 |
|
| 173 |
-
# ميل
|
| 174 |
-
if abs(
|
| 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
|
| 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
|
| 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>{
|
| 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'>⚠️
|
| 242 |
"""
|
| 243 |
return html, _gauge_html(norm_score)
|
| 244 |
|
| 245 |
-
# ===========================
|
| 246 |
-
# واجهة Gradio
|
| 247 |
-
# ===========================
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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):
|