A Flask API that turns a .wav recording into a voice-pathology screening result: acoustic feature extraction, an LSTM classifier, a clinical-style PDF report, and a SQLite history of every analysis.
Not a diagnostic device. The model hasn't been clinically validated — treat the output as a screening signal, not medical advice.
- Upload —
POST /analyzereceives a.wavovermultipart/form-data. - Feature extraction —
model.pyuses Librosa to pull F0 (YIN), Jitter, Shimmer, HNR, LPC formants, and 13 MFCCs, and builds the mel-spectrogram the model actually consumes. - Inference — the mel-spectrogram goes into
lsm_model3, a pre-trained bidirectional LSTM, loaded once at process start rather than per-request. Output is a 3-class softmax: Healthy / Laryngitis / Vocal Polyp. Full architecture and training details:MODEL_CARD.md. - Reporting — ReportLab and Matplotlib turn the raw features and confidence scores into a 3-page PDF (waveform, spectrogram, formant chart) plus a standalone confidence bar chart.
- Persist and respond — the classification, risk level, and file paths are written to SQLite, and the same JSON goes back to the caller.
| Framework | Flask + Flask-CORS |
| Audio / DSP | Librosa |
| Model | TensorFlow / Keras (lsm_model3, bidirectional LSTM) |
| Reporting | ReportLab, Matplotlib |
| Storage | SQLite (stdlib sqlite3) |
| Runtime | Docker, deployed on Hugging Face Spaces |
Needs Python ≤3.11 (TensorFlow 2.15's ceiling) and system audio libs:
brew install libsndfile ffmpeg # macOS
sudo apt-get install libsndfile1 ffmpeg # Ubuntu/Debiangit clone https://github.com/auraCodesKM/vocalB.git
cd vocalB
python3.11 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
python app.pyListens on http://localhost:7860. Or via Docker: docker build -t vocalwell-backend . && docker run -p 7860:7860 vocalwell-backend.
| Method | Path | Does |
|---|---|---|
POST |
/analyze |
Upload a .wav, get a diagnosis back |
GET |
/analyses |
List past analyses, newest first |
GET |
/analyses/{id} |
One analysis by id |
GET |
/plot/{name} |
The confidence bar chart (PNG) |
GET |
/report/{name} |
The PDF report — add ?download=true to force a download |
GET |
/health |
Liveness check for Railway/Render/HF Spaces |
POST /analyze is the one that matters — send the file, get this back:
curl -X POST http://localhost:7860/analyze -F "audio=@your_voice.wav"{
"Prediction": "Your voice shows moderate indication of Vocal Polyp",
"Class": "Vocal Polyp",
"RiskLevel": "moderate",
"PlotPath": "analysis_plot_1786688281.png",
"ReportPath": "voice_analysis_report_1786688281.pdf"
}Everything else — /analyses, /plot, /report — just hands back what /analyze already pointed at.
lsm_model3 is a bidirectional LSTM — InputLayer(128, 128) → Bidirectional(LSTM(128)) → Dense(3, softmax), 263,939 parameters (~1MB) — trained separately from this repo and checked in as a finished artifact, the way most serving repos keep training code and datasets out of the production path. Full layer breakdown, the compiled optimizer/loss config, and known limitations: MODEL_CARD.md.
vocalB/
├── app.py Flask routes, CORS, logging, DB wiring
├── db.py SQLite: init / insert / list / get
├── model.py feature extraction, inference, PDF generation
├── MODEL_CARD.md architecture, training config, known issues
├── requirements.txt
├── Dockerfile
├── Procfile gunicorn, for Heroku/Render
├── railway.toml
├── render.yaml
├── assets/ logo used in the PDF header
├── lsm_model3/ the trained LSTM weights
├── plots/ 56 sample confidence charts, checked in for reference
└── reports/ 56 sample PDF reports, checked in for reference
Flask, not FastAPI. There's exactly one endpoint doing real work, and it's CPU-bound on TensorFlow inference the whole time — nothing here is waiting on I/O that async would help with. Flask is the simpler tool for that shape of problem. What you lose: FastAPI's free OpenAPI docs.
SQLite, not Postgres. The history table is a single-instance log nobody else writes to concurrently — SQLite covers that with the Python standard library and no extra container. If a history write fails, it's logged and swallowed; the diagnosis already succeeded and that's the part that matters.
Model loaded once. model.py loads lsm_model3 at import time, not inside the request handler — retraining per-request isn't realistic, and reloading the weights on every call would dominate response time.
Hugging Face Spaces (where this actually runs): git remote add hf https://huggingface.co/spaces/<user>/<space> && git push hf main. The Dockerfile handles system deps and starts Gunicorn on 7860.
Railway / Render: railway.toml / render.yaml are already set up — health check on /health, persistent volumes for reports/, plots/, data/.
Frontend: auraCodesKM/VocalF — Next.js client for recording, dashboard, and reports.