Spaces:
Sleeping
Sleeping
File size: 9,566 Bytes
931c451 6c159a9 931c451 6c159a9 18b1375 a9160bc ebc55f0 017d265 33bd29b 017d265 ebc55f0 f4bfc2c bd78e69 33bd29b f53fb31 f4bfc2c 8b15264 33bd29b f4bfc2c 18b1375 f4bfc2c bd78e69 f53fb31 18b1375 6a4b483 bd78e69 f53fb31 0846e24 bd78e69 33bd29b bd78e69 8c11e9d 6a4b483 bd78e69 18b1375 bd78e69 33bd29b 18b1375 bd78e69 33bd29b 6a4b483 8b15264 33bd29b 18b1375 bd78e69 33bd29b fb93dd1 33bd29b f4bfc2c fb93dd1 33bd29b e1069b0 6a4b483 8b15264 6a4b483 18b1375 33bd29b bd78e69 18b1375 33bd29b 18b1375 33bd29b 18b1375 33bd29b a16f17d 33bd29b a16f17d 33bd29b a16f17d 33bd29b 18b1375 33bd29b 18b1375 8d32ced e0e487a e1069b0 e0e487a 8d32ced e0e487a e1069b0 8d32ced 33bd29b 8d32ced 18b1375 33bd29b 8d32ced 0ab681f e0e487a 8d32ced 8b15264 062cac5 18b1375 79a0a0b ebc55f0 8d32ced e0e487a 062cac5 f4bfc2c a9324fd f92dac8 18b1375 246bee3 18b1375 33bd29b 18b1375 f92dac8 e0e487a 515e044 79a0a0b 63bf8a6 18b1375 bfeb176 f61cac5 246bee3 bd78e69 f4bfc2c 18b1375 062cac5 9d35a29 bfeb176 f61cac5 649f6b1 699d8ac | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 | import gradio as gr
import cv2
import numpy as np
import mediapipe as mp
import tempfile
import os
import math
# إعداد Mediapipe Pose
mp_pose = mp.solutions.pose
pose = mp_pose.Pose(static_image_mode=False, min_detection_confidence=0.8, model_complexity=2)
def _dist(p1, p2):
return math.hypot(p1[0]-p2[0], p1[1]-p2[1])
def _angle(a, b, c):
try:
a, b, c = np.array(a), np.array(b), np.array(c)
ang = abs(math.degrees(
math.atan2(c[1]-b[1], c[0]-b[0]) - math.atan2(a[1]-b[1], a[0]-b[0])
))
return ang if ang <= 180 else 360 - ang
except:
return 0.0
def _safe_mean(x): return float(np.mean(x)) if len(x) else 0.0
def _safe_std(x): return float(np.std(x)) if len(x) else 0.0
def _gauge_html(norm_score):
pct = int(max(0, min(1, norm_score)) * 100)
color = "#4caf50" if pct < 35 else "#fbc02d" if pct < 65 else "#c62828"
return f"""
<div style="width:100%;background:#eee;border-radius:10px;height:16px;overflow:hidden;border:1px solid #ccc;">
<div style="width:{pct}%;height:100%;background:{color};transition:width .8s;"></div>
</div>
<div style="font-size:12px;color:#555;margin-top:6px;direction:rtl;text-align:right">درجة الخطورة: {pct}%</div>
"""
def analyze_gait(video_file):
if video_file is None:
return "<div style='direction:rtl;text-align:right'>❌ يرجى رفع فيديو أولًا.</div>", "<div></div>"
if hasattr(video_file, "name"):
video_path = video_file.name
else:
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4")
with open(tmp.name, "wb") as f: f.write(video_file)
video_path = tmp.name
cap = cv2.VideoCapture(video_path)
if not cap.isOpened():
return "<div style='direction:rtl;text-align:right'>❌ لا يمكن فتح الفيديو.</div>", "<div></div>"
W, H = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
px2m = 1.7 / (H * 0.8)
ground_y = H * 0.92
L_clear, R_clear, L_angle, R_angle = [], [], [], []
base_seq, tilt_seq, side_lean_seq = [], [], []
frames, person_detected = 0, False
while cap.isOpened() and frames < 1000:
ret, frame = cap.read()
if not ret: break
frames += 1
res = pose.process(cv2.cvtColor(frame, cv2.COLOR_BGR2RGB))
if not res.pose_landmarks: continue
person_detected = True
lm = res.pose_landmarks.landmark
def xy(i): return [lm[i].x * W, lm[i].y * H]
L_ank, R_ank = xy(27), xy(28)
L_knee, R_knee = xy(25), xy(26)
L_foot, R_foot = xy(31), xy(32)
L_hip, R_hip = xy(23), xy(24)
L_sh, R_sh = xy(11), xy(12)
Lc = max(0, (ground_y - min(L_ank[1], L_foot[1])) * px2m * 100)
Rc = max(0, (ground_y - min(R_ank[1], R_foot[1])) * px2m * 100)
L_clear.append(Lc); R_clear.append(Rc)
L_angle.append(_angle(L_knee, L_ank, L_foot))
R_angle.append(_angle(R_knee, R_ank, R_foot))
base_seq.append(abs(L_ank[0]-R_ank[0]))
mid_sh = [(L_sh[0]+R_sh[0])/2, (L_sh[1]+R_sh[1])/2]
mid_hip= [(L_hip[0]+R_hip[0])/2, (L_hip[1]+R_hip[1])/2]
vec = np.array([mid_sh[0]-mid_hip[0], mid_sh[1]-mid_hip[1]])
tilt = abs(90 - abs(math.degrees(math.atan2(abs(vec[1]), abs(vec[0])+1e-6))))
tilt_seq.append(tilt)
side_lean_seq.append(mid_sh[0]-mid_hip[0])
cap.release()
try: os.unlink(video_path)
except: pass
if not person_detected:
return "<div style='direction:rtl;text-align:right'>❌ لم يتم اكتشاف شخص في الفيديو. يُرجى إعادة التصوير بزاوية أمامية واضحة.</div>", "<div></div>"
avg_Lc, avg_Rc = _safe_mean(L_clear), _safe_mean(R_clear)
std_Lc, std_Rc = _safe_std(L_clear), _safe_std(R_clear)
avg_La, avg_Ra = _safe_mean(L_angle), _safe_mean(R_angle)
avg_tilt = _safe_mean(tilt_seq)
avg_lean = _safe_mean(side_lean_seq)
base_ratio = _safe_mean(base_seq)/W
diff_clear = abs(avg_Lc - avg_Rc)
diff_angle = abs(avg_La - avg_Ra)
var_clear = max(std_Lc, std_Rc)
sym_clear = abs((avg_Lc - avg_Rc) / (avg_Lc + avg_Rc + 1e-6)) * 100
sym_angle = abs((avg_La - avg_Ra) / (avg_La + avg_Ra + 1e-6)) * 100
low_ratio_L = np.mean(np.array(L_clear) < 4)
low_ratio_R = np.mean(np.array(R_clear) < 4)
score = 0
red_flags = 0
if min(avg_Lc, avg_Rc) < 4 or low_ratio_L > 0.4 or low_ratio_R > 0.4:
score += 3.5; red_flags += 1
if var_clear > 8 or diff_angle > 15 or sym_angle > 18:
score += 3; red_flags += 1
if base_ratio > 0.24 and avg_tilt > 8:
score += 3.5; red_flags += 1
if sym_clear > 25: score += 1.5
if abs(avg_lean) > W*0.025: score += 1.5
if diff_clear > 5 and diff_angle > 10: score += 1.0
score = min(score, 10)
norm_score = score / 10.0
# الجهة المتضررة
if avg_Lc < avg_Rc - 2 and avg_La < avg_Ra:
side = "الجهة اليسرى"
elif avg_Rc < avg_Lc - 2 and avg_Ra < avg_La:
side = "الجهة اليمنى"
else:
side = "غير محددة بوضوح"
# الحالة المحتملة
if red_flags >= 3 and base_ratio > 0.25:
condition = "قدم شاركوت (Charcot Foot)"
elif min(avg_Lc, avg_Rc) < 4 or low_ratio_L > 0.4 or low_ratio_R > 0.4:
condition = "ضعف العضلة الظنبوبية (Foot Drop)"
elif var_clear > 8 or sym_angle > 15:
condition = "اعتلال الأعصاب المحيطية أو السكري"
else:
condition = "مشية طبيعية"
# مستوى الخطورة + زر الحجز عند الحاجة
if red_flags >= 2 or norm_score >= 0.7:
level, color, desc = "🔴 عالية الخطورة", "#c62828", "تم رصد مؤشرات متعددة لخلل واضح في المشية."
booking = """
<div style='margin-top:10px;direction:rtl;text-align:right'>
<a href="https://example.com/book" target="_blank"
style="background:#2e7d32;color:#fff;padding:10px 16px;border-radius:8px;text-decoration:none;font-weight:600;">
احجز موعد مباشر (حضوري أو أونلاين)
</a>
</div>"""
elif norm_score >= 0.45:
level, color, desc = "🟡 متوسطة الخطورة", "#fbc02d", "مؤشرات تستدعي متابعة طبية دقيقة."
booking = """
<div style='margin-top:10px;direction:rtl;text-align:right'>
<a href="https://example.com/book" target="_blank"
style="background:#2e7d32;color:#fff;padding:10px 16px;border-radius:8px;text-decoration:none;font-weight:600;">
احجز استشارة طبية
</a>
</div>"""
else:
level, color, desc = "🟢 منخفضة الخطورة", "#2e7d32", "المشية ضمن النطاق السليم."
booking = ""
html = f"""
<div style='direction:rtl;text-align:right;color:{color};font-weight:700;font-size:18px'>{level}</div>
<div style='direction:rtl;text-align:right'> الجانب المتأثر: <b>{side}</b></div>
<div style='direction:rtl;text-align:right'> الحالة المحتملة: <b>{condition}</b></div>
<div style='direction:rtl;text-align:right'> درجة الخطورة: <b>{score:.1f}/10</b></div>
<div style='direction:rtl;text-align:right'>{desc}</div>
{booking}
<div style='font-size:13px;color:#555;margin-top:8px;direction:rtl;text-align:right'>
⚠️ التحليل يعتمد على مؤشرات متعددة ولا يُغني عن الفحص الطبي.</div>
"""
return html, _gauge_html(norm_score)
instructions = """
<div style='direction:rtl;text-align:right'>
<h3> تعليمات التصوير لضمان دقة التحليل:</h3>
<ol>
<li>ضع الكاميرا على <b>بعد 2 إلى 3 أمتار</b> وارتفاع الركبة تقريبًا.</li>
<li>استخدم <b>إضاءة أمامية قوية</b> وخلفية بسيطة.</li>
<li>صوّر <b>من الأمام أو الجانب</b> مع ظهور الساقين بالكامل.</li>
<li>اطلب من الشخص أن <b>يمشي بشكل طبيعي</b> لمسافة 3–5 أمتار لمدة 15–30 ثانية.</li>
<li>تجنّب الملابس التي <b>تغطي الركبة أو الكاحل</b>.</li>
<li>ثبّت الهاتف أثناء التصوير لتقليل الاهتزاز.</li>
</ol>
</div>
"""
# ✅ ثيم بلون أخضر كامل
green_theme = gr.themes.Soft(
primary_hue="green",
secondary_hue="green",
neutral_hue="gray"
)
with gr.Blocks(title="تحليل المشية ", theme=green_theme) as demo:
gr.HTML(instructions)
with gr.Row():
with gr.Column(scale=1):
video_in = gr.File(label="📂 اختر فيديو المشي", file_types=[".mp4", ".avi", ".mov"], type="binary")
analyze_btn = gr.Button(" بدء التحليل", elem_id="green_btn")
with gr.Column(scale=1):
gauge = gr.HTML("<div></div>")
out_html = gr.HTML("<i style='direction:rtl;text-align:right'>النتيجة ستظهر هنا بعد التحليل...</i>")
analyze_btn.click(fn=analyze_gait, inputs=[video_in], outputs=[out_html, gauge])
if __name__ == "__main__":
demo.launch()
|