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"""
درجة الخطورة: {pct}%
""" def analyze_gait(video_file): if video_file is None: return "
❌ يرجى رفع فيديو أولًا.
", "
" 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 "
❌ لا يمكن فتح الفيديو.
", "
" 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 "
❌ لم يتم اكتشاف شخص في الفيديو. يُرجى إعادة التصوير بزاوية أمامية واضحة.
", "
" 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 = """
احجز موعد مباشر (حضوري أو أونلاين)
""" elif norm_score >= 0.45: level, color, desc = "🟡 متوسطة الخطورة", "#fbc02d", "مؤشرات تستدعي متابعة طبية دقيقة." booking = """
احجز استشارة طبية
""" else: level, color, desc = "🟢 منخفضة الخطورة", "#2e7d32", "المشية ضمن النطاق السليم." booking = "" html = f"""
{level}
الجانب المتأثر: {side}
الحالة المحتملة: {condition}
درجة الخطورة: {score:.1f}/10
{desc}
{booking}
⚠️ التحليل يعتمد على مؤشرات متعددة ولا يُغني عن الفحص الطبي.
""" return html, _gauge_html(norm_score) instructions = """

تعليمات التصوير لضمان دقة التحليل:

  1. ضع الكاميرا على بعد 2 إلى 3 أمتار وارتفاع الركبة تقريبًا.
  2. استخدم إضاءة أمامية قوية وخلفية بسيطة.
  3. صوّر من الأمام أو الجانب مع ظهور الساقين بالكامل.
  4. اطلب من الشخص أن يمشي بشكل طبيعي لمسافة 3–5 أمتار لمدة 15–30 ثانية.
  5. تجنّب الملابس التي تغطي الركبة أو الكاحل.
  6. ثبّت الهاتف أثناء التصوير لتقليل الاهتزاز.
""" # ✅ ثيم بلون أخضر كامل 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("
") out_html = gr.HTML("النتيجة ستظهر هنا بعد التحليل...") analyze_btn.click(fn=analyze_gait, inputs=[video_in], outputs=[out_html, gauge]) if __name__ == "__main__": demo.launch()