| 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 (مخارج الحروف)' |
| } |
|
|
| |
|
|
| SOUND_NORMS = { |
| |
| 'ب': 3, 'م': 3, 'ن': 3, 'و': 3, 'ه': 3, 'د': 3, 'ت': 3, 'ك': 3, 'ف': 3, 'ل': 3, 'ي': 3, |
| |
| 'س': 5, 'ز': 5, 'ش': 5, 'ج': 5, |
| |
| 'ر': 6, 'ث': 6, 'ذ': 6, 'ص': 6, 'ض': 6, 'ط': 6, 'ظ': 6, 'ع': 6, 'غ': 6, 'خ': 6 |
| } |
|
|
| |
|
|
| 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 سنوات.'} |
| } |
|
|
| |
|
|
| 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 |
|
|
|
|
| |
| 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'] |
|
|
| |
| 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'] |
|
|
| |
| 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 "الرجاء كتابة وصف الحالة" |
| |
| |
| 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] |
| |
| |
| age = extract_age(text) |
| final_label, logic_reason = check_clinical_logic(age, text, ai_label) |
| |
| |
| if logic_reason: |
| advice = logic_reason |
| color = "#27ae60" |
| badge = "✅ تحليل نمائي (Evidence-Based)" |
| confidence_display = "استناداً للمعايير العلمية" |
| else: |
| |
| 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() |