CoolFace
Apppublic

Xav007/dyslexia-accessibility-nlp

sourceHugging Faceupdated 3mo agoView on Hugging Face
1likes
App README

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.md

Inference 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 report

Setup

bash
# 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:5000

Datasets

DatasetPurposeLocation
EMNIST LettersMLP letter classifier (Aโ€“Z)data/raw/emnist-letters-train.csv + test.csv
GamboCNN reversal detectordata/raw/Gambo/Train + Test
Synthetic (auto-generated)NLP LSTM trainingdata/raw/sequence_data.txt โ€” created automatically

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/.

ModelAccuracyROC-AUCF1-Score
MLP (Letter Classifier)88.75%โ€”0.8873 (Weighted)
CNN (Reversal Detector)91.86%0.97580.9118 (Reversal)
NLP (Sequence Anomaly)84.06%0.90220.8266 (Anomaly)

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:

ModelSignal typeCaptured by
MLPLetter identity and formation confidencePer-character classification
CNNSpatial reversal pattern in character shapeVisual / convolutional
NLPSequence-level statistical and linguistic anomalySequential / analytical

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

AreaBetaImproved
EMNIST orientationRaw (90ยฐ rotated + mirrored)Corrected before training
Feature scalingNoneStandardScaler fitted on train split, saved for inference
Train/val splitFixed slice data[:30000]Stratified train_test_split
MLP architectureSingle layer (250,)Three layers (512, 256, 128)
CNN architectureFlat Dense ANN (no Conv2D)3-block Conv2D + BatchNorm
CNN threshold0.8 (biased toward reversals)0.5 (correct sigmoid boundary)
CNN trainingNo augmentation, no callbackstorchvision transforms + early stopping + AMP
CNN frameworkTensorFlowPyTorch with GPU AMP
NLP training dataMissing file, untrainedAuto-generated with domain adaptation
NLP architectureSingle LSTM(32), max_len=5Bidirectional 2-layer LSTM, max_len=20
NLP inferenceHardcoded "sample_sequence"Real MLP-predicted letter sequence
NLP saturation100% alwaysAnalytical score + LSTM saturation gate
Reversal scoringGlobal mean (dilutes long text)Strong-binary sliding window
Character extractionFixed global threshold (128)Adaptive Gaussian + morphological close + Otsu fallback
Contour filterAbsolute pixel sizes (10โ€“100 px)Area-ratio (resolution-independent)
Model loadingReloaded from disk per request@lru_cache โ€” loaded once per process
Decision outputBinary majority voteCalibrated score [0,1] + confidence label
BenchmarkingAccuracy onlyF1, AUC, confusion matrix, reversal-pair analysis
PDF reportHand-positioned Canvas APIReportLab Platypus with score bar + per-character table
Flask sessionsShared filename (race condition on concurrent requests)UUID-keyed per-request sessions
File cleanupshutil.rmtree in finally blockDaemon thread auto-delete after 5 min
Input validationNoneExtension check + magic-byte MIME validation + size limit
Quality gateNone โ€” failed extraction scored as high dyslexia probabilityRare letter dominance + raw reversal ceiling block bad extractions before ensemble

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.