File size: 8,505 Bytes
66aa4ff 77811fe 66aa4ff 265ec44 7d0356e 1a80857 66aa4ff 265ec44 669181c 265ec44 669181c 66aa4ff 265ec44 77811fe 265ec44 669181c 265ec44 669181c 77811fe 669181c 77811fe 669181c 77811fe 669181c 1a80857 14f39e7 669181c 265ec44 669181c 265ec44 669181c 265ec44 669181c 77811fe 1a80857 14f39e7 669181c 1a80857 66aa4ff 1a80857 14f39e7 669181c 77811fe 669181c 14f39e7 669181c 77811fe 669181c 77811fe 669181c 77811fe 669181c 77811fe 669181c 77811fe 14f39e7 77811fe 669181c b0b2248 77811fe 669181c 66aa4ff 14f39e7 669181c b0b2248 66aa4ff b0b2248 | 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 | import gradio as gr
import torch
from transformers import AutoTokenizer, AutoModelForSequenceClassification
import re
model_path = "."
tokenizer = AutoTokenizer.from_pretrained(model_path, local_files_only=True)
model = AutoModelForSequenceClassification.from_pretrained(model_path, local_files_only=True)
id2label = {
0: 'Fluency Disorder (تأتأة)',
1: 'Language Disorder (تأخر لغة)',
2: 'Motor Speech Disorder (اضطراب حركي)',
3: 'Other/Medical (أخرى/طبية)',
4: 'Social Communication/ASD (تواصل واجتماعي)',
5: 'Speech Sound Disorder (مخارج الحروف)'
}
# (Data Source: Amayreh & Dyson 1998 + ASHA) ---
SOUND_NORMS = {
# المجموعة الأولى (تكتمل حوالي 3 سنوات)
'ب': 3, 'م': 3, 'ن': 3, 'و': 3, 'ه': 3, 'د': 3, 'ت': 3, 'ك': 3, 'ف': 3, 'ل': 3, 'ي': 3,
# المجموعة الثانية (تكتمل بين 4 - 5 سنوات)
'س': 5, 'ز': 5, 'ش': 5, 'ج': 5,
# المجموعة الثالثة (تكتمل حوالي 6 سنوات)
'ر': 6, 'ث': 6, 'ذ': 6, 'ص': 6, 'ض': 6, 'ط': 6, 'ظ': 6, 'ع': 6, 'غ': 6, 'خ': 6
}
# (Phonological Processes)
PHONO_PROCESSES = {
'fronting': {'age': 3.5, 'keywords': ['كاف', 'قاف', 'غين', 'خاء'], 'desc': 'التقديم (Fronting): استبدال الأصوات الخلفية بأمامية (مثل: كتاب -> تتاب) طبيعي حتى 3.5 سنوات.'},
'gliding': {'age': 6.0, 'keywords': ['راء', 'لام', 'واو', 'ياء'], 'desc': 'الانزلاق (Gliding): إبدال الراء أو اللام بـ (واو/ياء) عملية طبيعية قد تستمر حتى 6 سنوات.'},
'final_deletion': {'age': 3.0, 'keywords': ['اخر الكلمة', 'يحذف', 'نهاية'], 'desc': 'حذف الحرف الأخير (Final Consonant Deletion) طبيعي حتى عمر 3 سنوات فقط.'},
'stopping': {'age': 4.0, 'keywords': ['فاء', 'سين', 'شين', 'صاد'], 'desc': 'الوقف (Stopping): استبدال الأصوات الاحتكاكية بأصوات انفجارية طبيعي حتى 3-4 سنوات.'}
}
# (Logic Layer)
def extract_age(text):
text = text.replace('١', '1').replace('٢', '2').replace('٣', '3').replace('٤', '4').replace('٥', '5').replace('٦', '6')
match_half = re.search(r'(\d+)\s*(ونصف|و نص)', text)
if match_half:
return float(match_half.group(1)) + 0.5
numbers = re.findall(r'\d+', text)
if numbers: return float(numbers[0])
text = text.lower()
if 'سنتين' in text or 'عامين' in text: return 2.0
if 'سنة' in text or 'عام' in text: return 1.0
if 'ثلاث' in text: return 3.0
if 'أربع' in text: return 4.0
if 'خمس' in text: return 5.0
if 'ست' in text: return 6.0
return None
def check_clinical_logic(age, text, predicted_label):
text = text.lower()
if age is None:
return predicted_label, None
# Gliding
if any(k in text for k in PHONO_PROCESSES['gliding']['keywords']) and ('يبدل' in text or 'يقلب' in text or 'ما يقول' in text):
if age < PHONO_PROCESSES['gliding']['age']:
return "Phonological Process (عملية نطقية)", PHONO_PROCESSES['gliding']['desc']
# Fronting
if any(k in text for k in PHONO_PROCESSES['fronting']['keywords']) and ('يبدل' in text or 'تاء' in text or 'دال' in text):
if age <= PHONO_PROCESSES['fronting']['age']:
return "Phonological Process (عملية نطقية)", PHONO_PROCESSES['fronting']['desc']
# 2. Sound Norms
for sound, norm_age in SOUND_NORMS.items():
patterns = [f"حرف ال{sound}", f"حرف {sound}", f"صوت ال{sound}", f"ال{sound}"]
if any(p in text for p in patterns):
if age < norm_age:
return f"Age Appropriate Error (خطأ تطوري طبيعي)", f"حسب معايير (Amayreh & Dyson)، اكتساب صوت ({sound}) يكتمل عند {norm_age} سنوات. وعمر الطفل ({age}) سنوات، فالأمر طبيعي."
if age < 4 and any(w in text for w in ['قصة', 'سالفة', 'يسرد']):
return "Normal Development (تطور طبيعي)", "مهارة السرد القصصي تكتمل عادة بعد عمر 4 سنوات."
if (2 <= age <= 4) and any(w in text for w in ['تأتأة', 'يعلق', 'يكرر']):
return "Developmental Disfluency (تأتأة نمائية)", "التأتأة الطبيعية شائعة بين 2-4 سنوات وتختفي غالباً تلقائياً."
return predicted_label, None
# -----------------------------------------
def predict_speech_issue(text):
if not text.strip(): return "الرجاء كتابة وصف الحالة"
# AI Prediction
inputs = tokenizer(text, return_tensors="pt", truncation=True, padding=True)
with torch.no_grad():
outputs = model(**inputs)
probs = torch.nn.functional.softmax(outputs.logits, dim=-1)
pred_id = torch.argmax(probs).item()
confidence = probs[0][pred_id].item()
ai_label = id2label[pred_id]
# Clinical Logic Check
age = extract_age(text)
final_label, logic_reason = check_clinical_logic(age, text, ai_label)
# Formatting
if logic_reason:
advice = logic_reason
color = "#27ae60" # Green
badge = "✅ تحليل نمائي (Evidence-Based)"
confidence_display = "استناداً للمعايير العلمية"
else:
# Default AI Recommendations
recommendations = {
'Speech Sound Disorder (مخارج الحروف)': "ينصح بتقييم مخارج الحروف وفحص أعضاء النطق (Oral Motor Exam).",
'Language Disorder (تأخر لغة)': "ينصح بعمل اختبار لغة شامل (مثل PLS-4 أو معرب).",
'Social Communication/ASD (تواصل واجتماعي)': "ينصح بمراجعة طبيب نمو وسلوك وعمل مقياس كارز أو ADOS.",
'Fluency Disorder (تأتأة)': "ينصح بتقييم الطلاقة ومراقبة البيئة التواصلية.",
'Motor Speech Disorder (اضطراب حركي)': "يحتاج تقييم دقيق (Apraxia/Dysarthria) من قبل مختص.",
'Other/Medical (أخرى/طبية)': "⛔ يفضل استشارة طبيب أنف وأذن وحنجرة (ENT) لفحص السمع أو الحبال الصوتية."
}
advice = recommendations.get(final_label, "يرجى استشارة مختص.")
color = "#c0392b" if "Medical" in final_label else "#2980b9"
badge = "🤖 تصنيف الذكاء الاصطناعي"
confidence_display = f"{confidence:.1%}"
return f"""
<div style="direction: rtl; text-align: right; padding: 20px; background-color: #fff; border-radius: 12px; border-right: 6px solid {color}; box-shadow: 0 4px 15px rgba(0,0,0,0.05);">
<div style="display: flex; justify-content: space-between; align-items: center;">
<h2 style="color: {color}; margin: 0;">{final_label}</h2>
<span style="background-color: #f1f2f6; padding: 6px 12px; border-radius: 20px; font-size: 0.8em; color: #555;">{badge}</span>
</div>
<p style="color: #7f8c8d; margin-top: 5px;"><b>الثقة:</b> {confidence_display}</p>
<hr style="border-top: 1px dashed #ddd; margin: 15px 0;">
<div style="background-color: #fcfcfc; padding: 10px; border-radius: 8px;">
<p style="font-size: 1.1em; color: #2c3e50; line-height: 1.6;"><b>💡 الرأي المختص:</b><br>{advice}</p>
</div>
<p style="font-size: 0.7em; color: #95a5a6; margin-top: 15px;">* المصادر: Amayreh & Dyson (1998), ASHA Guidelines.</p>
</div>
"""
with gr.Blocks(theme=gr.themes.Soft()) as demo:
gr.Markdown("<h1 style='text-align: center'>🇸🇦 أداة الفرز الذكية لاضطرابات النطق - المراعية للهجات السعودية</h1>")
input_txt = gr.Textbox(label="وصف الحالة (يرجى ذكر العمر)", placeholder="مثال: ولدي عمره 3 سنوات ونص ويبدل الكاف تاء")
btn = gr.Button("تحليل الحالة", variant="primary")
output = gr.HTML(label="التقرير")
btn.click(fn=predict_speech_issue, inputs=input_txt, outputs=output)
if __name__ == "__main__":
demo.launch() |