Xav007/dyslexia-accessibility-nlp
title: Dyslexia Accessibility NLP emoji: ๐ colorFrom: blue colorTo: indigo sdk: gradio appfile: app/main.py pinned: false sdkversion: 6.18.0 ---
Dyslexia Accessibility NLP
A multi-model deep learning pipeline for dyslexia screening from handwriting images. Three heterogeneous models โ a scikit-learn MLP letter classifier, a PyTorch CNN reversal detector, and a PyTorch Bidirectional LSTM sequence anomaly detector โ are fused via a clinically motivated weighted ensemble and served through a Flask web application with structured PDF reporting.
This project is framed as a research capstone on multi-model fusion for social impact, not a production tool. Every architectural and mathematical decision is motivated and documented below.
Project Structure
dyslexia-accessibility-nlp/
โ
โโโ beta versions/ โ Original iterative development history
โ โโโ Letter_Classification/ ยท Scratch MLP (NumPy/Numba), GridSearch,
โ โ โโโ scripts/ output predictions at 10k/30k/88.8k samples
โ โ โโโ output/
โ โโโ Dyslexic_Detection/ ยท TF/Keras CNN training notebook,
โ โ โโโ testing_tf.ipynb checkpoint .h5 models
โ โโโ nlp_module.py ยท Original incomplete LSTM module
โ โโโ README.md
โ
โโโ data/ โ Data loading, preprocessing, augmentation
โ โโโ preprocessing.py ยท EMNIST loader (orientation fix),
โ โ stratified splits, StandardScaler
โ โโโ augmentation.py ยท torchvision transforms + PyTorch DataLoader
โ โโโ nlp_data_generator.py ยท Synthetic sequence generator with
โ MLP confusion noise (domain adaptation)
โ
โโโ models/ โ Model definitions and training scripts
โ โโโ mlp_classifier.py ยท 3-layer MLP (512โ256โ128), Adam,
โ โ sklearn early stopping
โ โโโ cnn_classifier.py ยท 3-block Conv2D CNN, BatchNorm, Dropout,
โ โ GlobalAvgPool, PyTorch AMP (GPU)
โ โโโ nlp_sequence.py ยท Bidirectional 2-layer LSTM,
โ โ PyTorch AMP (GPU)
โ โโโ ensemble.py ยท Strong-binary sliding window ensemble
โ + analytical NLP pattern scorer
โ
โโโ pipeline/ โ Inference and report generation
โ โโโ character_extraction.py ยท Adaptive threshold OpenCV pipeline
โ โ with Otsu fallback
โ โโโ inference.py ยท Unified entry point โ all 3 models
โ โ + analytical/LSTM NLP blend
โ โโโ report_generator.py ยท ReportLab Platypus PDF report
โ
โโโ evaluation/
โ โโโ benchmark.py ยท F1, AUC, confusion matrix,
โ reversal-pair analysis (b/d, p/q, n/u, m/w)
โ
โโโ app/
| โโโ images/ ยท some images to test
| โโโ reports/ ยท some sample reports on the test images
โ โโโ main.py ยท Flask app โ UUID sessions, MIME
โ โ validation, auto-expiring PDF reports
โ โโโ models/ ยท Trained model files (gitignored)
โ โ โโโ mlp_model.pkl
โ โ โโโ mlp_scaler.pkl
โ โ โโโ pattern_classifier.pt
โ โ โโโ sequence_anomaly.pt
โ โโโ templates/
โ โโโ index.html ยท Single-page frontend
โ
โโโ output/ โ Runtime output (gitignored)
โ โโโ characters/ ยท Temp per-session character crops
โ โโโ reports/ ยท Generated PDFs (auto-deleted, 5 min)
โ โโโ benchmarks/ ยท JSON benchmark results
โ
โโโ data/raw/ โ Raw datasets (gitignored)
โ โโโ emnist-letters-train.csv
โ โโโ emnist-letters-test.csv
โ โโโ Gambo/
โ โโโ Train/{Normal,Reversal}/
โ โโโ Test/{Normal,Reversal}/
โ
โโโ gpu_config.py โ PyTorch GPU detection + logging
โโโ config.py โ Single source of truth: all paths,
โ hyperparameters, thresholds
โโโ train_all.py โ Master training orchestrator
โโโ requirements.txt
โโโ .gitignore
โโโ README.mdInference Pipeline
Upload image
โ
โผ
character_extraction.py
Greyscale โ Gaussian denoise
โ Adaptive Gaussian threshold (handles uneven lighting)
โ Morphological closing (reconnects broken strokes)
โ Contour filter: area-ratio [0.00005, 0.20] + aspect [0.05, 5.0]
โ Otsu fallback if adaptive finds nothing
โ Sort left-to-right (reading order)
โ Resize โ 28ร28 (MLP) and 64ร64 (CNN)
โ
โผ (per character, batched)
MLP (sklearn, CPU)
StandardScaler โ 3-layer MLP โ letter (AโZ) + softmax confidence
โ
CNN (PyTorch, GPU)
Conv2D ร 3 blocks โ GlobalAvgPool โ sigmoid โ reversal probability [0,1]
โ
โผ
Quality Gate (pipeline/inference.py)
Check 1 โ rare letter dominance:
rare_ratio = count(z,x,q,j,w,v) / n
if rare_ratio > 0.55 โ Inconclusive
(noise/background regions produce these letters; real text never dominates with them)
Check 2 โ raw CNN reversal ceiling:
if mean(cnn_probs) > 0.65 โ Inconclusive
(worst-case genuine dyslexic text stays below 0.65; noise regions exceed it)
โ
โผ
NLP component (hybrid)
Analytical score:
strong_count (CNN โฅ 0.85) / max(n ร 0.08, 5)
โ clinically normalised against 8% expected reversal rate
โ does not saturate from cursive noise (50โ80% CNN)
vs true reversals (90โ100% CNN)
LSTM blend (when retrained on noise-aware data):
if 0.03 < lstm_output < 0.97:
nlp = 0.70 ร analytical + 0.30 ร lstm
else:
nlp = analytical only โ saturation check gates broken LSTM output
โ
โผ
Ensemble (models/ensemble.py)
CNN component โ strong-binary sliding window:
binary = (reversal_probs >= 0.85)
window = 15% of n, clamped to [5, 20] characters
local_weight = clip(0.20 + (n โ 10) ร 0.007, 0.20, 0.80)
cnn_component = (1 โ local_w) ร global_strong_rate
+ local_w ร sliding_peak
ensemble_score = 0.55 ร cnn_component
+ 0.40 ร nlp_component
+ 0.05 ร mlp_uncertainty
threshold = 0.40
โ
โผ
DiagnosisResult โ JSON response + PDF reportSetup
# 1. Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
# 2. Install dependencies
pip install -r requirements.txt
# PyTorch with CUDA (recommended โ CPU fallback works but CNN/NLP will be slow):
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121
# 3. Place datasets (see Datasets section)
# 4. Train models
python train_all.py # all three
python train_all.py 1 # MLP only (sklearn, CPU, ~5 min)
python train_all.py 2 # CNN only (PyTorch, GPU, ~30โ60 min)
python train_all.py 3 # NLP only (PyTorch, GPU, ~10 min)
python train_all.py 2 3 # CNN + NLP (skip MLP if already trained)
# Optional flags
python train_all.py 2 3 --smoke-test # tiny data slice, fast validation
python train_all.py 2 3 --no-mixed-precision # disable AMP if you see NaN losses
python train_all.py 2 3 --skip-eval # skip benchmark after training
# 5. Run the app
python app/main.py
# โ http://localhost:5000Datasets
EMNIST note: The CSV format stores images rotated 90ยฐ clockwise and horizontally mirrored. preprocessing.py undoes both transforms (np.rot90(k=3) + np.fliplr) before any training โ without this the MLP trains on visually incorrect characters.
NLP note: Delete data/raw/sequence_data.txt and run python train_all.py 3 to regenerate training data with domain adaptation noise before retraining the LSTM.
Model Performance
Benchmarked on held-out test splits. Full metrics including per-class F1 and confusion matrices in output/benchmarks/.
Reversal-pair confusion is tracked explicitly in the MLP benchmark โ the b/d, p/q, n/u, and m/w pairs are the most clinically significant confusions and are reported separately from overall accuracy.
Key Design Decisions
Why strong-binary reversal threshold (CNN โฅ 0.85)?
The CNN's raw reversal probability for cursive strokes, ink bleed, and touching letters consistently lands between 50โ80%. Genuine letter reversals (bโd, pโq) consistently land at 90โ100%. A hard threshold at 0.85 separates signal from noise without any learned parameter โ it is a principled operating point derived from the CNN's ROC curve.
Using a global mean across all characters dilutes genuine reversal clusters in long paragraphs. A writer who reverses 20 out of 100 characters scores a mean of 20% โ clinically significant, but the mean hides it. The sliding window captures the densest local burst and blends it with the global rate, where the blend weight shifts toward local as text length grows (n=10 โ localw=0.20, n=100 โ localw=0.80). This is motivated by clinical literature describing dyslexic errors as localised clusters rather than uniformly distributed noise.
Why the analytical NLP score instead of raw LSTM output?
The LSTM trained on clean synthetic sequences but receives the MLP's noisy letter predictions at inference. The MLP misclassifies ~10% of characters โ within visually similar groups (c/e, i/j, u/v, n/m). To the LSTM, every real input looks anomalous regardless of dyslexia status, saturating near 100% for all inputs.
The analytical score addresses this by counting only strong reversals (CNN โฅ 0.85) normalised against clinical expectation (8% of characters are strongly reversed in diagnosed dyslexic writers, per Isa et al. 2019). It cannot saturate from cursive noise and gives a meaningful zero for clean writing.
The LSTM remains in the architecture and contributes 30% when its output is in a valid range (0.03โ0.97), gated by a saturation check. It will become the dominant NLP signal once retrained on noise-aware data.
Why domain adaptation in NLP training data?
This is a training-inference distribution mismatch. The fix (_simulate_mlp_noise()) applies structured MLP confusion substitutions to both training classes before dyslexic transformations are added to the anomalous class. This ensures the LSTM sees the same noise floor at training time as at inference, forcing it to learn the boundary between baseline MLP noise (normal) and dyslexic patterns above that baseline โ the exact distinction it needs to make.
Why ensemble fusion over a single model?
Each model captures a distinct and complementary signal:
No single modality is sufficient. A writer who reverses letters cleanly (high MLP confidence, high CNN reversal) would be missed by a sequence-only model. A writer with poor handwriting quality (low MLP confidence) but no reversals would be over-flagged by a CNN-only model. The ensemble with domain-motivated weights handles both cases.
Why not horizontal flip augmentation for the CNN?
Deliberately excluded. A horizontally flipped 'b' is a 'd' โ which is exactly the reversal pattern being detected. Including it as augmentation would teach the model that both orientations are equivalent, destroying its ability to detect reversals. This is a domain-specific augmentation choice motivated by the nature of the classification task.
Why a quality gate before the ensemble?
When character extraction fails โ due to a noisy image, extreme angle, faint ink, or cluttered background โ the downstream models process garbage and produce confidently wrong outputs. The ensemble has no way to know the input was invalid.
Two independent checks catch this before any scoring happens:
Rare letter dominance: Background noise and stroke fragments get misclassified as z, x, q, j, w, v โ letters with distinctive shapes that match common noise patterns. These letters make up under 3% of normal English text. If they exceed 55% of predictions, the extractor grabbed non-character regions. This check is deliberately inverted from checking for common letter presence โ short real words like "KITE FAMILY" legitimately contain few common letters (e, t, a, o, i, n...) but will never be dominated by rare ones.
Raw reversal ceiling: Even worst-case dyslexic short text โ 4 of 10 characters genuinely reversed plus moderate CNN responses on visually ambiguous letters โ stays below 0.65 raw mean. Noise regions consistently exceed this. The threshold provides headroom for genuine edge cases while blocking haywire extractions.
Both thresholds are set conservatively to avoid false Inconclusive results on legitimate images, and both are configurable in QualityGateConfig in config.py. ---
Key Improvements Over Beta Versions
Referenced Research
- Alqahtani, N. D., et al. (2023). "Detection of Dyslexia Through Images of Handwriting using Hybrid AI Approach." International Journal of Advanced Computer Science and Applications (IJACSA).
- Alqahtani, N. D., et al. (2023). "Deep Learning Applications for Dyslexia Prediction." Applied Sciences.
- Isa, I. S., et al. (2019). "Automated Detection of Dyslexia Symptom Based on Handwriting Image for Primary School Children." Procedia Computer Science.
- Cohen, G., et al. (2017). "EMNIST: an extension of MNIST to handwritten letters." arXiv:1702.05373.
Disclaimer
This tool is a screening aid for educational and research purposes only. It does not constitute a medical or psychological diagnosis. Always consult a qualified educational psychologist for clinical assessment.
