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()