Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- .claude/settings.local.json +7 -0
- .dockerignore +17 -0
- .gitattributes +16 -35
- .gitignore +32 -0
- Dockerfile +29 -0
- Emotion_Anaysis.ipynb +0 -0
- LICENSE +21 -0
- anxiety_app/__init__.py +15 -0
- anxiety_app/config.py +37 -0
- anxiety_app/logging_config.py +17 -0
- anxiety_app/routes.py +69 -0
- anxiety_app/services/__init__.py +1 -0
- anxiety_app/services/face_emotion.py +132 -0
- anxiety_app/services/recorder.py +36 -0
- anxiety_app/services/voice_emotion.py +65 -0
- anxiety_app/static/Presentation2.jpg +0 -0
- anxiety_app/static/bgm.svg +0 -0
- anxiety_app/static/face-detection.svg +0 -0
- anxiety_app/static/face.js +78 -0
- anxiety_app/static/index.css +41 -0
- anxiety_app/static/microphone.svg +0 -0
- anxiety_app/static/styles.css +92 -0
- anxiety_app/static/voice.js +48 -0
- anxiety_app/templates/face.html +36 -0
- anxiety_app/templates/index.html +20 -0
- anxiety_app/templates/phq9.html +23 -0
- anxiety_app/templates/voice.html +33 -0
- app.py +10 -0
- models/fer.h5 +0 -0
- models/fer.json +1 -0
- models/haarcascade_frontalface_default.xml +0 -0
- models/voice_model.h5 +0 -0
- models/voice_model.json +1 -0
- requirements-modern.txt +23 -0
.claude/settings.local.json
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"permissions": {
|
| 3 |
+
"allow": [
|
| 4 |
+
"PowerShell(cd \"d:\\\\ALL PROGRAM PROJECTS\\\\ML\\\\Detection-of-Anxiety-and-Depression-main\\\\Detection-of-Anxiety-and-Depression-main\"; $env:FLASK_APP=\"app.py\"; python -c \"import app\" 2>&1 | Select-Object -First 30)"
|
| 5 |
+
]
|
| 6 |
+
}
|
| 7 |
+
}
|
.dockerignore
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
venv/
|
| 2 |
+
.venv/
|
| 3 |
+
C:/venvs/
|
| 4 |
+
__pycache__/
|
| 5 |
+
*.pyc
|
| 6 |
+
.git/
|
| 7 |
+
.github/
|
| 8 |
+
tests/
|
| 9 |
+
training/
|
| 10 |
+
docs/
|
| 11 |
+
Speech-Emotion-Analyzer/
|
| 12 |
+
fer2013.csv
|
| 13 |
+
output10.wav
|
| 14 |
+
output.txt
|
| 15 |
+
install_log.txt
|
| 16 |
+
*.whl
|
| 17 |
+
.pytest_cache/
|
.gitattributes
CHANGED
|
@@ -1,35 +1,16 @@
|
|
| 1 |
-
|
| 2 |
-
*
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
*.
|
| 6 |
-
*.
|
| 7 |
-
*.
|
| 8 |
-
*.
|
| 9 |
-
*.
|
| 10 |
-
*.
|
| 11 |
-
*.
|
| 12 |
-
*.
|
| 13 |
-
*.
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
-
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
-
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
-
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
-
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
-
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
-
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
-
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
-
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
-
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
-
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
-
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
-
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
-
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
-
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
-
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
-
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
-
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
-
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
| 1 |
+
# Normalize line endings: let Git handle text automatically
|
| 2 |
+
* text=auto
|
| 3 |
+
|
| 4 |
+
# Explicitly mark binary assets so Git never tries to diff/convert them
|
| 5 |
+
*.h5 binary
|
| 6 |
+
*.whl binary
|
| 7 |
+
*.png binary
|
| 8 |
+
*.jpg binary
|
| 9 |
+
*.jpeg binary
|
| 10 |
+
*.wav binary
|
| 11 |
+
*.xml binary
|
| 12 |
+
*.ipynb binary
|
| 13 |
+
*.svg binary
|
| 14 |
+
anxiety_app/static/Presentation2.jpg filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
models/fer.h5 filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
models/voice_model.h5 filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
.gitignore
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Virtual environments
|
| 2 |
+
venv/
|
| 3 |
+
.venv/
|
| 4 |
+
env/
|
| 5 |
+
ENV/
|
| 6 |
+
|
| 7 |
+
# Python cache / build
|
| 8 |
+
__pycache__/
|
| 9 |
+
*.py[cod]
|
| 10 |
+
*$py.class
|
| 11 |
+
*.egg-info/
|
| 12 |
+
.eggs/
|
| 13 |
+
build/
|
| 14 |
+
dist/
|
| 15 |
+
|
| 16 |
+
# Runtime artifacts produced by the app
|
| 17 |
+
output.txt
|
| 18 |
+
/output10.wav
|
| 19 |
+
install_log.txt
|
| 20 |
+
|
| 21 |
+
# Training artifacts / datasets (not committed)
|
| 22 |
+
results/
|
| 23 |
+
data/
|
| 24 |
+
*.npy
|
| 25 |
+
fer2013.csv
|
| 26 |
+
balanced-all.csv
|
| 27 |
+
|
| 28 |
+
# IDE / OS
|
| 29 |
+
.vscode/
|
| 30 |
+
.idea/
|
| 31 |
+
.DS_Store
|
| 32 |
+
Thumbs.db
|
Dockerfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Container image for the multimodal affect-screening app.
|
| 2 |
+
# Works locally (docker run) and on Hugging Face Spaces (Docker SDK, port 7860).
|
| 3 |
+
FROM python:3.11-slim
|
| 4 |
+
|
| 5 |
+
# System libs: ffmpeg (decode browser webm/ogg audio), libGL + glib (OpenCV).
|
| 6 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 7 |
+
ffmpeg libgl1 libglib2.0-0 \
|
| 8 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 9 |
+
|
| 10 |
+
ENV PYTHONUNBUFFERED=1 \
|
| 11 |
+
TF_CPP_MIN_LOG_LEVEL=3 \
|
| 12 |
+
TF_USE_LEGACY_KERAS=1 \
|
| 13 |
+
TF_ENABLE_ONEDNN_OPTS=0 \
|
| 14 |
+
HF_HOME=/tmp/hf \
|
| 15 |
+
PORT=7860
|
| 16 |
+
|
| 17 |
+
WORKDIR /app
|
| 18 |
+
|
| 19 |
+
# Install Python deps first for better layer caching.
|
| 20 |
+
COPY requirements-modern.txt .
|
| 21 |
+
RUN pip install --no-cache-dir -r requirements-modern.txt
|
| 22 |
+
|
| 23 |
+
# App code + models.
|
| 24 |
+
COPY . .
|
| 25 |
+
|
| 26 |
+
EXPOSE 7860
|
| 27 |
+
|
| 28 |
+
# Single worker (TF model is loaded lazily per process); threads handle concurrency.
|
| 29 |
+
CMD ["sh", "-c", "gunicorn --bind 0.0.0.0:${PORT:-7860} --workers 1 --threads 4 --timeout 180 app:app"]
|
Emotion_Anaysis.ipynb
ADDED
|
Binary file (40.7 kB). View file
|
|
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2026 Parth Ashtikar
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
anxiety_app/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Application factory for the multimodal affect-screening app."""
|
| 2 |
+
from flask import Flask
|
| 3 |
+
from flask_bootstrap import Bootstrap
|
| 4 |
+
|
| 5 |
+
from .logging_config import configure_logging
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def create_app():
|
| 9 |
+
configure_logging()
|
| 10 |
+
app = Flask(__name__) # templates/ and static/ live inside this package
|
| 11 |
+
Bootstrap(app)
|
| 12 |
+
|
| 13 |
+
from .routes import main
|
| 14 |
+
app.register_blueprint(main)
|
| 15 |
+
return app
|
anxiety_app/config.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Central configuration: filesystem paths and runtime settings.
|
| 2 |
+
|
| 3 |
+
Paths are resolved relative to the project root so the app works regardless of
|
| 4 |
+
the current working directory.
|
| 5 |
+
"""
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
# <repo root>/anxiety_app/config.py -> <repo root>
|
| 9 |
+
BASE_DIR = Path(__file__).resolve().parent.parent
|
| 10 |
+
MODELS_DIR = BASE_DIR / "models"
|
| 11 |
+
|
| 12 |
+
# Model artifacts
|
| 13 |
+
FACE_MODEL_JSON = MODELS_DIR / "fer.json"
|
| 14 |
+
FACE_MODEL_WEIGHTS = MODELS_DIR / "fer.h5"
|
| 15 |
+
HAAR_CASCADE = MODELS_DIR / "haarcascade_frontalface_default.xml"
|
| 16 |
+
VOICE_MODEL_JSON = MODELS_DIR / "voice_model.json"
|
| 17 |
+
VOICE_MODEL_WEIGHTS = MODELS_DIR / "voice_model.h5"
|
| 18 |
+
|
| 19 |
+
# Where microphone recordings are written
|
| 20 |
+
RECORDING_PATH = BASE_DIR / "output10.wav"
|
| 21 |
+
|
| 22 |
+
# Audio recording parameters
|
| 23 |
+
SAMPLE_RATE = 44100
|
| 24 |
+
CHANNELS = 2
|
| 25 |
+
CHUNK = 1024
|
| 26 |
+
RECORD_SECONDS = 4
|
| 27 |
+
|
| 28 |
+
# FER2013 emotion classes in the model's native (Kaggle) order. The app maps
|
| 29 |
+
# "fear" -> Anxiety and "sad" -> Depressed as supplementary affective signals
|
| 30 |
+
# (NOT a clinical diagnosis).
|
| 31 |
+
FACE_EMOTIONS = ("angry", "disgust", "fear", "happy", "sad", "surprise", "neutral")
|
| 32 |
+
|
| 33 |
+
# Voice model output classes (gender + emotion).
|
| 34 |
+
VOICE_CLASSES = (
|
| 35 |
+
"Male Angry", "Male Calm", "Male Anxious", "Male Happy", "Male Depressed",
|
| 36 |
+
"Female Angry", "Female Calm", "Female Anxious", "Female Happy", "Female Depressed",
|
| 37 |
+
)
|
anxiety_app/logging_config.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Logging setup shared across the app."""
|
| 2 |
+
import logging
|
| 3 |
+
|
| 4 |
+
_CONFIGURED = False
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def configure_logging(level=logging.INFO):
|
| 8 |
+
"""Configure root logging once with a concise, timestamped format."""
|
| 9 |
+
global _CONFIGURED
|
| 10 |
+
if _CONFIGURED:
|
| 11 |
+
return
|
| 12 |
+
logging.basicConfig(
|
| 13 |
+
level=level,
|
| 14 |
+
format="%(asctime)s %(levelname)-7s %(name)s | %(message)s",
|
| 15 |
+
datefmt="%H:%M:%S",
|
| 16 |
+
)
|
| 17 |
+
_CONFIGURED = True
|
anxiety_app/routes.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""HTTP routes for the app, grouped in a single blueprint.
|
| 2 |
+
|
| 3 |
+
Capture happens in the browser (getUserMedia / MediaRecorder); frames and audio
|
| 4 |
+
are POSTed to JSON API endpoints. Heavy ML services are imported lazily inside
|
| 5 |
+
handlers so importing this module (e.g. for fast route tests) does not pull in
|
| 6 |
+
TensorFlow/OpenCV.
|
| 7 |
+
"""
|
| 8 |
+
import logging
|
| 9 |
+
|
| 10 |
+
from flask import Blueprint, jsonify, render_template, request
|
| 11 |
+
|
| 12 |
+
logger = logging.getLogger(__name__)
|
| 13 |
+
|
| 14 |
+
main = Blueprint("main", __name__)
|
| 15 |
+
|
| 16 |
+
TITLE = "Anxiety and Depression Detection"
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
@main.route("/")
|
| 20 |
+
@main.route("/home")
|
| 21 |
+
def index():
|
| 22 |
+
return render_template("index.html")
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@main.route("/qstn")
|
| 26 |
+
def phq():
|
| 27 |
+
return render_template("phq9.html", data=TITLE)
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
@main.route("/face")
|
| 31 |
+
def face():
|
| 32 |
+
return render_template("face.html", data=TITLE)
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
@main.route("/voice")
|
| 36 |
+
def voice():
|
| 37 |
+
return render_template("voice.html", data="Allow the mic, then record a few seconds.")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
# ---------------------------------------------------------------- JSON API ---
|
| 41 |
+
|
| 42 |
+
@main.post("/api/face")
|
| 43 |
+
def api_face():
|
| 44 |
+
"""Classify faces in one browser-captured frame (multipart 'frame')."""
|
| 45 |
+
from .services import face_emotion
|
| 46 |
+
|
| 47 |
+
file = request.files.get("frame")
|
| 48 |
+
if file is None:
|
| 49 |
+
return jsonify(error="no frame uploaded"), 400
|
| 50 |
+
try:
|
| 51 |
+
return jsonify(faces=face_emotion.classify_frame(file.read()))
|
| 52 |
+
except Exception as exc: # noqa: BLE001
|
| 53 |
+
logger.exception("Face frame classification failed")
|
| 54 |
+
return jsonify(error=str(exc)), 500
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
@main.post("/api/voice")
|
| 58 |
+
def api_voice():
|
| 59 |
+
"""Classify a browser-recorded audio blob (multipart 'audio')."""
|
| 60 |
+
from .services import voice_emotion
|
| 61 |
+
|
| 62 |
+
file = request.files.get("audio")
|
| 63 |
+
if file is None:
|
| 64 |
+
return jsonify(error="no audio uploaded"), 400
|
| 65 |
+
try:
|
| 66 |
+
return jsonify(label=voice_emotion.analyze_upload(file))
|
| 67 |
+
except Exception as exc: # noqa: BLE001
|
| 68 |
+
logger.exception("Voice analysis failed")
|
| 69 |
+
return jsonify(error=str(exc)), 500
|
anxiety_app/services/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
"""Inference and capture services for the app."""
|
anxiety_app/services/face_emotion.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Facial emotion recognition from a live webcam feed.
|
| 2 |
+
|
| 3 |
+
Loads the FER2013 CNN once (lazily) and classifies each detected face. Emotions
|
| 4 |
+
are tallied across the session; the dominant affective signal is returned as a
|
| 5 |
+
supplementary cue (Anxious / Depressed / Normal) -- NOT a clinical diagnosis.
|
| 6 |
+
|
| 7 |
+
Preprocessing: per-image standardization (subtract the face's mean, divide by
|
| 8 |
+
its std) to match how the model was trained. This fixes a train/serve skew in
|
| 9 |
+
the original code (which divided by 255) and roughly doubles accuracy.
|
| 10 |
+
"""
|
| 11 |
+
import logging
|
| 12 |
+
|
| 13 |
+
import numpy as np
|
| 14 |
+
|
| 15 |
+
from .. import config
|
| 16 |
+
|
| 17 |
+
logger = logging.getLogger(__name__)
|
| 18 |
+
|
| 19 |
+
# Heavy deps (tf_keras, cv2) are imported lazily so the package can be imported
|
| 20 |
+
# (e.g. for fast route tests) without TensorFlow/OpenCV present.
|
| 21 |
+
_model = None
|
| 22 |
+
_cascade = None
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def _load():
|
| 26 |
+
global _model, _cascade
|
| 27 |
+
if _model is None:
|
| 28 |
+
import cv2
|
| 29 |
+
from tf_keras.models import model_from_json
|
| 30 |
+
|
| 31 |
+
logger.info("Loading face model from %s", config.FACE_MODEL_JSON)
|
| 32 |
+
_model = model_from_json(config.FACE_MODEL_JSON.read_text())
|
| 33 |
+
_model.load_weights(str(config.FACE_MODEL_WEIGHTS))
|
| 34 |
+
_cascade = cv2.CascadeClassifier(str(config.HAAR_CASCADE))
|
| 35 |
+
return _model, _cascade
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def preprocess(face_gray):
|
| 39 |
+
"""48x48 grayscale -> standardized (1, 48, 48, 1) batch."""
|
| 40 |
+
import cv2
|
| 41 |
+
|
| 42 |
+
face = cv2.resize(face_gray, (48, 48)).astype("float32")
|
| 43 |
+
face = (face - face.mean()) / (face.std() + 1e-7) # per-image standardization
|
| 44 |
+
return face.reshape(1, 48, 48, 1)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def classify_face(face_gray):
|
| 48 |
+
"""Return the predicted FER2013 emotion label for one grayscale face crop."""
|
| 49 |
+
model, _ = _load()
|
| 50 |
+
preds = model.predict(preprocess(face_gray), verbose=0)
|
| 51 |
+
return config.FACE_EMOTIONS[int(np.argmax(preds[0]))]
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# Map raw emotions to the app's affective signal (NOT a diagnosis).
|
| 55 |
+
_SIGNAL = {"fear": "Anxious", "sad": "Depressed"}
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def emotion_to_signal(emotion):
|
| 59 |
+
return _SIGNAL.get(emotion, "Normal")
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def classify_frame(image_bytes):
|
| 63 |
+
"""Detect faces in an encoded image (e.g. a browser JPEG) and classify each.
|
| 64 |
+
|
| 65 |
+
Returns a list of {"box": [x, y, w, h], "emotion": str, "signal": str}.
|
| 66 |
+
Used by the browser-capture API so no server-side camera is required.
|
| 67 |
+
"""
|
| 68 |
+
import cv2
|
| 69 |
+
|
| 70 |
+
_, cascade = _load()
|
| 71 |
+
arr = np.frombuffer(image_bytes, np.uint8)
|
| 72 |
+
img = cv2.imdecode(arr, cv2.IMREAD_COLOR)
|
| 73 |
+
if img is None:
|
| 74 |
+
return []
|
| 75 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
| 76 |
+
results = []
|
| 77 |
+
for (x, y, w, h) in cascade.detectMultiScale(gray, 1.32, 5):
|
| 78 |
+
emotion = classify_face(gray[y:y + h, x:x + w])
|
| 79 |
+
results.append({
|
| 80 |
+
"box": [int(x), int(y), int(w), int(h)],
|
| 81 |
+
"emotion": emotion,
|
| 82 |
+
"signal": emotion_to_signal(emotion),
|
| 83 |
+
})
|
| 84 |
+
return results
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def _verdict(counts):
|
| 88 |
+
"""Map tallied emotions to a dominant affective signal."""
|
| 89 |
+
signal = {
|
| 90 |
+
"Anxious": counts.get("fear", 0),
|
| 91 |
+
"Depressed": counts.get("sad", 0),
|
| 92 |
+
"Normal": sum(v for k, v in counts.items() if k not in ("fear", "sad")),
|
| 93 |
+
}
|
| 94 |
+
return max(signal, key=signal.get)
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def detect_emotion(camera_index=0):
|
| 98 |
+
"""Open the webcam, classify faces until 'q' is pressed, return the verdict.
|
| 99 |
+
|
| 100 |
+
NOTE: opens a native OpenCV window; requires a local display + webcam.
|
| 101 |
+
"""
|
| 102 |
+
import cv2
|
| 103 |
+
|
| 104 |
+
model, cascade = _load()
|
| 105 |
+
cap = cv2.VideoCapture(camera_index)
|
| 106 |
+
if not cap.isOpened():
|
| 107 |
+
raise RuntimeError("Could not open the webcam (camera index %s)." % camera_index)
|
| 108 |
+
|
| 109 |
+
counts = {}
|
| 110 |
+
try:
|
| 111 |
+
while True:
|
| 112 |
+
ok, frame = cap.read()
|
| 113 |
+
if not ok:
|
| 114 |
+
continue
|
| 115 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 116 |
+
for (x, y, w, h) in cascade.detectMultiScale(gray, 1.32, 5):
|
| 117 |
+
cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 0, 0), 3)
|
| 118 |
+
emotion = classify_face(gray[y:y + h, x:x + w])
|
| 119 |
+
counts[emotion] = counts.get(emotion, 0) + 1
|
| 120 |
+
cv2.putText(frame, emotion, (x, y), cv2.FONT_HERSHEY_SIMPLEX,
|
| 121 |
+
1, (0, 0, 255), 2)
|
| 122 |
+
cv2.imshow("Facial emotion analysis", cv2.resize(frame, (1000, 700)))
|
| 123 |
+
cv2.setWindowProperty("Facial emotion analysis", cv2.WND_PROP_TOPMOST, 1)
|
| 124 |
+
if cv2.waitKey(10) == ord("q"):
|
| 125 |
+
break
|
| 126 |
+
finally:
|
| 127 |
+
cap.release()
|
| 128 |
+
cv2.destroyAllWindows()
|
| 129 |
+
|
| 130 |
+
verdict = _verdict(counts)
|
| 131 |
+
logger.info("Face session counts=%s -> %s", counts, verdict)
|
| 132 |
+
return verdict
|
anxiety_app/services/recorder.py
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Microphone recording helper (server-side capture via PyAudio)."""
|
| 2 |
+
import logging
|
| 3 |
+
import wave
|
| 4 |
+
|
| 5 |
+
from .. import config
|
| 6 |
+
|
| 7 |
+
logger = logging.getLogger(__name__)
|
| 8 |
+
|
| 9 |
+
|
| 10 |
+
def record_to_wav(path=None, seconds=None):
|
| 11 |
+
"""Record a fixed-length clip from the default microphone to a WAV file.
|
| 12 |
+
|
| 13 |
+
NOTE: captures on the machine running the server; requires a local mic.
|
| 14 |
+
"""
|
| 15 |
+
import pyaudio
|
| 16 |
+
|
| 17 |
+
path = str(path or config.RECORDING_PATH)
|
| 18 |
+
seconds = seconds or config.RECORD_SECONDS
|
| 19 |
+
fmt = pyaudio.paInt16
|
| 20 |
+
|
| 21 |
+
audio = pyaudio.PyAudio()
|
| 22 |
+
stream = audio.open(format=fmt, channels=config.CHANNELS, rate=config.SAMPLE_RATE,
|
| 23 |
+
input=True, frames_per_buffer=config.CHUNK)
|
| 24 |
+
logger.info("Recording %ss to %s", seconds, path)
|
| 25 |
+
frames = [stream.read(config.CHUNK)
|
| 26 |
+
for _ in range(int(config.SAMPLE_RATE / config.CHUNK * seconds))]
|
| 27 |
+
stream.stop_stream()
|
| 28 |
+
stream.close()
|
| 29 |
+
audio.terminate()
|
| 30 |
+
|
| 31 |
+
with wave.open(path, "wb") as wf:
|
| 32 |
+
wf.setnchannels(config.CHANNELS)
|
| 33 |
+
wf.setsampwidth(audio.get_sample_size(fmt))
|
| 34 |
+
wf.setframerate(config.SAMPLE_RATE)
|
| 35 |
+
wf.writeframes(b"".join(frames))
|
| 36 |
+
return path
|
anxiety_app/services/voice_emotion.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Speech emotion recognition from a recorded WAV file.
|
| 2 |
+
|
| 3 |
+
Loads the RAVDESS-trained 1-D CNN once (lazily) and predicts one of 10
|
| 4 |
+
gender x emotion classes from MFCC features. Gender is part of this single
|
| 5 |
+
model -- there is no separate gender network.
|
| 6 |
+
"""
|
| 7 |
+
import logging
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
|
| 11 |
+
from .. import config
|
| 12 |
+
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
_model = None
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def _load():
|
| 19 |
+
global _model
|
| 20 |
+
if _model is None:
|
| 21 |
+
from tf_keras.models import model_from_json
|
| 22 |
+
|
| 23 |
+
logger.info("Loading voice model from %s", config.VOICE_MODEL_JSON)
|
| 24 |
+
_model = model_from_json(config.VOICE_MODEL_JSON.read_text())
|
| 25 |
+
_model.load_weights(str(config.VOICE_MODEL_WEIGHTS))
|
| 26 |
+
return _model
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def extract_features(wav_path):
|
| 30 |
+
"""Return the mean MFCC feature vector the model expects."""
|
| 31 |
+
import librosa
|
| 32 |
+
|
| 33 |
+
X, sample_rate = librosa.load(str(wav_path), res_type="kaiser_fast",
|
| 34 |
+
duration=2.5, sr=22050 * 2, offset=0.5)
|
| 35 |
+
mfccs = np.mean(librosa.feature.mfcc(y=X, sr=sample_rate, n_mfcc=13), axis=0)
|
| 36 |
+
return np.expand_dims(np.expand_dims(mfccs, axis=0), axis=2)
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def analyze(wav_path=None):
|
| 40 |
+
"""Classify a recording and return its gender+emotion label."""
|
| 41 |
+
wav_path = wav_path or config.RECORDING_PATH
|
| 42 |
+
model = _load()
|
| 43 |
+
preds = model.predict(extract_features(wav_path), batch_size=32, verbose=0)
|
| 44 |
+
label = config.VOICE_CLASSES[int(preds.argmax(axis=1)[0])]
|
| 45 |
+
logger.info("Voice prediction for %s -> %s", wav_path, label)
|
| 46 |
+
return label
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def analyze_upload(file_storage):
|
| 50 |
+
"""Classify an uploaded audio blob (e.g. browser MediaRecorder webm/wav).
|
| 51 |
+
|
| 52 |
+
librosa decodes via soundfile (wav) or audioread/ffmpeg (webm/ogg), so the
|
| 53 |
+
container ships ffmpeg. Returns the gender+emotion label.
|
| 54 |
+
"""
|
| 55 |
+
import os
|
| 56 |
+
import tempfile
|
| 57 |
+
|
| 58 |
+
suffix = os.path.splitext(file_storage.filename or "")[1] or ".webm"
|
| 59 |
+
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
|
| 60 |
+
try:
|
| 61 |
+
file_storage.save(tmp.name)
|
| 62 |
+
tmp.close()
|
| 63 |
+
return analyze(tmp.name)
|
| 64 |
+
finally:
|
| 65 |
+
os.unlink(tmp.name)
|
anxiety_app/static/Presentation2.jpg
ADDED
|
anxiety_app/static/bgm.svg
ADDED
|
|
anxiety_app/static/face-detection.svg
ADDED
|
|
anxiety_app/static/face.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Browser webcam capture -> POST frames to /api/face -> draw boxes + tally signals.
|
| 2 |
+
(function () {
|
| 3 |
+
const video = document.getElementById("video");
|
| 4 |
+
const overlay = document.getElementById("overlay");
|
| 5 |
+
const ctx = overlay.getContext("2d");
|
| 6 |
+
const startBtn = document.getElementById("startBtn");
|
| 7 |
+
const stopBtn = document.getElementById("stopBtn");
|
| 8 |
+
const statusEl = document.getElementById("status");
|
| 9 |
+
const resultEl = document.getElementById("result");
|
| 10 |
+
|
| 11 |
+
const capture = document.createElement("canvas");
|
| 12 |
+
const counts = { Anxious: 0, Depressed: 0, Normal: 0 };
|
| 13 |
+
let stream = null, timer = null, busy = false;
|
| 14 |
+
|
| 15 |
+
startBtn.onclick = async () => {
|
| 16 |
+
try {
|
| 17 |
+
stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
| 18 |
+
video.srcObject = stream;
|
| 19 |
+
Object.keys(counts).forEach((k) => (counts[k] = 0));
|
| 20 |
+
resultEl.textContent = "";
|
| 21 |
+
startBtn.disabled = true;
|
| 22 |
+
stopBtn.disabled = false;
|
| 23 |
+
statusEl.textContent = "Detecting… keep your face in view.";
|
| 24 |
+
timer = setInterval(tick, 500);
|
| 25 |
+
} catch (e) {
|
| 26 |
+
statusEl.textContent = "Could not access webcam: " + e.message;
|
| 27 |
+
}
|
| 28 |
+
};
|
| 29 |
+
|
| 30 |
+
stopBtn.onclick = () => {
|
| 31 |
+
if (timer) clearInterval(timer);
|
| 32 |
+
timer = null;
|
| 33 |
+
if (stream) stream.getTracks().forEach((t) => t.stop());
|
| 34 |
+
startBtn.disabled = false;
|
| 35 |
+
stopBtn.disabled = true;
|
| 36 |
+
ctx.clearRect(0, 0, overlay.width, overlay.height);
|
| 37 |
+
const total = counts.Anxious + counts.Depressed + counts.Normal;
|
| 38 |
+
const verdict = Object.keys(counts).reduce((a, b) => (counts[a] >= counts[b] ? a : b));
|
| 39 |
+
resultEl.textContent = total ? "Result: " + verdict : "No face detected — try again.";
|
| 40 |
+
statusEl.textContent = "Stopped.";
|
| 41 |
+
};
|
| 42 |
+
|
| 43 |
+
async function tick() {
|
| 44 |
+
if (busy || video.readyState < 2) return;
|
| 45 |
+
busy = true;
|
| 46 |
+
capture.width = video.videoWidth;
|
| 47 |
+
capture.height = video.videoHeight;
|
| 48 |
+
capture.getContext("2d").drawImage(video, 0, 0);
|
| 49 |
+
try {
|
| 50 |
+
const blob = await new Promise((r) => capture.toBlob(r, "image/jpeg", 0.7));
|
| 51 |
+
const fd = new FormData();
|
| 52 |
+
fd.append("frame", blob, "frame.jpg");
|
| 53 |
+
const res = await fetch("/api/face", { method: "POST", body: fd });
|
| 54 |
+
const data = await res.json();
|
| 55 |
+
draw(data.faces || []);
|
| 56 |
+
} catch (e) {
|
| 57 |
+
/* drop the occasional failed frame */
|
| 58 |
+
}
|
| 59 |
+
busy = false;
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
function draw(faces) {
|
| 63 |
+
const sx = overlay.width / (video.videoWidth || overlay.width);
|
| 64 |
+
const sy = overlay.height / (video.videoHeight || overlay.height);
|
| 65 |
+
ctx.clearRect(0, 0, overlay.width, overlay.height);
|
| 66 |
+
ctx.lineWidth = 2;
|
| 67 |
+
ctx.strokeStyle = "#00b3ff";
|
| 68 |
+
ctx.fillStyle = "#00b3ff";
|
| 69 |
+
ctx.font = "18px sans-serif";
|
| 70 |
+
faces.forEach((f) => {
|
| 71 |
+
const [x, y, w, h] = f.box;
|
| 72 |
+
ctx.strokeRect(x * sx, y * sy, w * sx, h * sy);
|
| 73 |
+
const label = f.emotion + " (" + f.signal + ")";
|
| 74 |
+
ctx.fillText(label, x * sx, y * sy > 20 ? y * sy - 6 : y * sy + 16);
|
| 75 |
+
if (counts[f.signal] !== undefined) counts[f.signal]++;
|
| 76 |
+
});
|
| 77 |
+
}
|
| 78 |
+
})();
|
anxiety_app/static/index.css
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body{
|
| 2 |
+
background: #172326;
|
| 3 |
+
|
| 4 |
+
}
|
| 5 |
+
ul {
|
| 6 |
+
list-style-type: none;
|
| 7 |
+
margin: 0;
|
| 8 |
+
padding: 0;
|
| 9 |
+
overflow: hidden;
|
| 10 |
+
background-color: #333;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
li {
|
| 14 |
+
float: left;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
li a {
|
| 18 |
+
display: block;
|
| 19 |
+
color: white;
|
| 20 |
+
text-align: center;
|
| 21 |
+
padding: 14px 16px;
|
| 22 |
+
text-decoration: none;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
li a:hover {
|
| 26 |
+
background-color: #111;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
h2{
|
| 30 |
+
position: absolute;
|
| 31 |
+
left: 360px;
|
| 32 |
+
top: 300px;
|
| 33 |
+
font-family: Roboto;
|
| 34 |
+
font-style: normal;
|
| 35 |
+
font-weight: 900;
|
| 36 |
+
font-size: 55px;
|
| 37 |
+
line-height: 75px;
|
| 38 |
+
text-align: center;
|
| 39 |
+
color: #fffdfd;
|
| 40 |
+
}
|
| 41 |
+
|
anxiety_app/static/microphone.svg
ADDED
|
|
anxiety_app/static/styles.css
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
body{
|
| 2 |
+
background: #172326;
|
| 3 |
+
|
| 4 |
+
}
|
| 5 |
+
|
| 6 |
+
img{
|
| 7 |
+
position: absolute;
|
| 8 |
+
width: 486px;
|
| 9 |
+
height: 426px;
|
| 10 |
+
left: 94px;
|
| 11 |
+
top: 130px;
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
h2{
|
| 15 |
+
position: absolute;
|
| 16 |
+
left: 730px;
|
| 17 |
+
top: 130px;
|
| 18 |
+
font-family: Roboto;
|
| 19 |
+
font-style: normal;
|
| 20 |
+
font-weight: 900;
|
| 21 |
+
font-size: 55px;
|
| 22 |
+
line-height: 75px;
|
| 23 |
+
text-align: center;
|
| 24 |
+
color: #fffdfd;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.detect_btn {
|
| 28 |
+
position: absolute;
|
| 29 |
+
width: 410px;
|
| 30 |
+
height: 90px;
|
| 31 |
+
left: 790px;
|
| 32 |
+
top: 400px;
|
| 33 |
+
background: #474AF0;
|
| 34 |
+
border-radius: 66px;
|
| 35 |
+
font-family: Roboto;
|
| 36 |
+
font-style: normal;
|
| 37 |
+
font-weight: 900;
|
| 38 |
+
font-size: 48px;
|
| 39 |
+
line-height: 56px;
|
| 40 |
+
text-align: center;
|
| 41 |
+
color: #E5E5E5;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.icon{
|
| 45 |
+
|
| 46 |
+
position: absolute;
|
| 47 |
+
width: 54px;
|
| 48 |
+
height: 54px;
|
| 49 |
+
left: 834px;
|
| 50 |
+
z-index: 1;
|
| 51 |
+
top: 415px;
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
p {
|
| 57 |
+
position: absolute;
|
| 58 |
+
width: 154px;
|
| 59 |
+
height: 28px;
|
| 60 |
+
left: 910px;
|
| 61 |
+
top: 520px;
|
| 62 |
+
|
| 63 |
+
font-family: Roboto;
|
| 64 |
+
font-style: normal;
|
| 65 |
+
font-weight: normal;
|
| 66 |
+
font-size: 20px;
|
| 67 |
+
line-height: 28px;
|
| 68 |
+
|
| 69 |
+
color: #FFFFFF;
|
| 70 |
+
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.voice_detect_btn {
|
| 74 |
+
position: absolute;
|
| 75 |
+
width: 410px;
|
| 76 |
+
height: 90px;
|
| 77 |
+
left: 790px;
|
| 78 |
+
top: 400px;
|
| 79 |
+
background: #F04747;
|
| 80 |
+
border-radius: 66px;
|
| 81 |
+
font-family: Roboto;
|
| 82 |
+
font-style: normal;
|
| 83 |
+
font-weight: 900;
|
| 84 |
+
font-size: 48px;
|
| 85 |
+
line-height: 56px;
|
| 86 |
+
text-align: center;
|
| 87 |
+
color: #E5E5E5;
|
| 88 |
+
}
|
| 89 |
+
|
| 90 |
+
a:hover {
|
| 91 |
+
background-color:#F04747;
|
| 92 |
+
}
|
anxiety_app/static/voice.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Browser mic capture (MediaRecorder) -> POST blob to /api/voice -> show label.
|
| 2 |
+
(function () {
|
| 3 |
+
const recBtn = document.getElementById("recBtn");
|
| 4 |
+
const statusEl = document.getElementById("vstatus");
|
| 5 |
+
const resultEl = document.getElementById("vresult");
|
| 6 |
+
const player = document.getElementById("player");
|
| 7 |
+
const SECONDS = 4;
|
| 8 |
+
|
| 9 |
+
recBtn.onclick = async () => {
|
| 10 |
+
let stream;
|
| 11 |
+
try {
|
| 12 |
+
stream = await navigator.mediaDevices.getUserMedia({ audio: true });
|
| 13 |
+
} catch (e) {
|
| 14 |
+
statusEl.textContent = "Could not access microphone: " + e.message;
|
| 15 |
+
return;
|
| 16 |
+
}
|
| 17 |
+
const chunks = [];
|
| 18 |
+
const recorder = new MediaRecorder(stream);
|
| 19 |
+
recorder.ondataavailable = (e) => e.data.size && chunks.push(e.data);
|
| 20 |
+
recorder.onstop = async () => {
|
| 21 |
+
stream.getTracks().forEach((t) => t.stop());
|
| 22 |
+
const type = recorder.mimeType || "audio/webm";
|
| 23 |
+
const blob = new Blob(chunks, { type });
|
| 24 |
+
player.src = URL.createObjectURL(blob);
|
| 25 |
+
player.style.display = "block";
|
| 26 |
+
statusEl.textContent = "Analyzing…";
|
| 27 |
+
const ext = type.includes("ogg") ? "ogg" : "webm";
|
| 28 |
+
const fd = new FormData();
|
| 29 |
+
fd.append("audio", blob, "recording." + ext);
|
| 30 |
+
try {
|
| 31 |
+
const res = await fetch("/api/voice", { method: "POST", body: fd });
|
| 32 |
+
const data = await res.json();
|
| 33 |
+
resultEl.textContent = data.label ? "Result: " + data.label
|
| 34 |
+
: "Error: " + (data.error || "unknown");
|
| 35 |
+
statusEl.textContent = "Done.";
|
| 36 |
+
} catch (e) {
|
| 37 |
+
statusEl.textContent = "Request failed: " + e.message;
|
| 38 |
+
}
|
| 39 |
+
recBtn.disabled = false;
|
| 40 |
+
};
|
| 41 |
+
|
| 42 |
+
recBtn.disabled = true;
|
| 43 |
+
resultEl.textContent = "";
|
| 44 |
+
statusEl.textContent = "Recording " + SECONDS + "s…";
|
| 45 |
+
recorder.start();
|
| 46 |
+
setTimeout(() => recorder.state !== "inactive" && recorder.stop(), SECONDS * 1000);
|
| 47 |
+
};
|
| 48 |
+
})();
|
anxiety_app/templates/face.html
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "bootstrap/base.html" %}
|
| 2 |
+
{% block styles %}
|
| 3 |
+
{{super()}}
|
| 4 |
+
<link rel="stylesheet" href="{{url_for('static', filename='styles.css')}}">
|
| 5 |
+
<link rel="preconnect" href="https://fonts.gstatic.com">
|
| 6 |
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@900&display=swap" rel="stylesheet">
|
| 7 |
+
{% endblock %}
|
| 8 |
+
|
| 9 |
+
{% block content %}
|
| 10 |
+
<ul style="list-style-type: none; margin: 0; padding: 0; overflow: hidden; background-color: #333;">
|
| 11 |
+
<li style="float: left;"><a class="active" href="home" style="display: block; color: white;padding: 14px 16px;">Home</a></li>
|
| 12 |
+
<li style="float: left;"><a href="face" style="display: block; color: white;padding: 14px 16px;">Face</a></li>
|
| 13 |
+
<li style="float: left;"><a href="voice" style="display: block; color: white;padding: 14px 16px;">Voice</a></li>
|
| 14 |
+
<li style="float: left;"><a href="qstn" style="display: block; color: white;padding: 14px 16px;">PHQ-9</a></li>
|
| 15 |
+
</ul>
|
| 16 |
+
|
| 17 |
+
<h2 class="heading">Facial Emotion Detection</h2>
|
| 18 |
+
|
| 19 |
+
<div style="text-align:center;">
|
| 20 |
+
<div style="position:relative; display:inline-block;">
|
| 21 |
+
<video id="video" autoplay playsinline muted width="640" height="480" style="background:#000;border-radius:8px;"></video>
|
| 22 |
+
<canvas id="overlay" width="640" height="480" style="position:absolute; left:0; top:0;"></canvas>
|
| 23 |
+
</div>
|
| 24 |
+
<div style="margin:14px;">
|
| 25 |
+
<button id="startBtn" class="detect_btn">Start camera</button>
|
| 26 |
+
<button id="stopBtn" class="detect_btn" disabled>Stop & get result</button>
|
| 27 |
+
</div>
|
| 28 |
+
<p id="status">Click “Start camera” and allow webcam access.</p>
|
| 29 |
+
<h3 id="result"></h3>
|
| 30 |
+
<p style="color:#888; max-width:640px; margin:10px auto; font-size:0.9em;">
|
| 31 |
+
Emotion signals are a demo cue, <strong>not a diagnosis</strong>. For screening use the PHQ-9.
|
| 32 |
+
</p>
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<script src="{{url_for('static', filename='face.js')}}"></script>
|
| 36 |
+
{% endblock %}
|
anxiety_app/templates/index.html
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "bootstrap/base.html" %}
|
| 2 |
+
|
| 3 |
+
{% block styles %}
|
| 4 |
+
{{super()}}
|
| 5 |
+
<link rel="stylesheet" href="{{url_for('static', filename='index.css')}}">
|
| 6 |
+
<link rel="preconnect" href="https://fonts.gstatic.com">
|
| 7 |
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@900&display=swap" rel="stylesheet">
|
| 8 |
+
{% endblock %}
|
| 9 |
+
|
| 10 |
+
{% block content%}
|
| 11 |
+
<ul>
|
| 12 |
+
<li><a class="active" href="#home">Home</a></li>
|
| 13 |
+
<li><a href="face">Face</a></li>
|
| 14 |
+
<li><a href="voice">Voice</a></li>
|
| 15 |
+
</ul>
|
| 16 |
+
<h2>Anxiety and Depression Detection</h2>
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
{% endblock %}
|
anxiety_app/templates/phq9.html
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "bootstrap/base.html" %}
|
| 2 |
+
{% block styles %}
|
| 3 |
+
{{super()}}
|
| 4 |
+
<link rel="stylesheet" href="{{url_for('static', filename='styles.css')}}">
|
| 5 |
+
<link rel="preconnect" href="https://fonts.gstatic.com">
|
| 6 |
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@900&display=swap" rel="stylesheet">
|
| 7 |
+
{% endblock %}
|
| 8 |
+
|
| 9 |
+
{% block content%}
|
| 10 |
+
<ul style="list-style-type: none; margin: 0; padding: 0; overflow: hidden; background-color: #333;">
|
| 11 |
+
<li style="float: left;"><a class="active" href="home" style="display: block; color: white;padding: 14px 16px;">Home</a></li>
|
| 12 |
+
<li style="float: left;"><a href="face" style="display: block; color: white;padding: 14px 16px;">Face</a></li>
|
| 13 |
+
<li style="float: left;"><a href="voice" style="display: block; color: white;padding: 14px 16px;">Voice</a></li>
|
| 14 |
+
</ul>
|
| 15 |
+
<img src="{{url_for('static',filename = 'Presentation2.jpg')}}">
|
| 16 |
+
<h2 class="heading">{{data}}</br></h2>
|
| 17 |
+
<img class="icon" src="{{url_for('static',filename = 'face-detection.svg')}}">
|
| 18 |
+
<a href="expression"><button class="detect_btn">Detect</button></a>
|
| 19 |
+
<p>Press q to quit</p>
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
{% endblock %}
|
anxiety_app/templates/voice.html
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{% extends "bootstrap/base.html" %}
|
| 2 |
+
{% block styles %}
|
| 3 |
+
{{super()}}
|
| 4 |
+
<link rel="stylesheet" href="{{url_for('static', filename='styles.css')}}">
|
| 5 |
+
<link rel="preconnect" href="https://fonts.gstatic.com">
|
| 6 |
+
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@900&display=swap" rel="stylesheet">
|
| 7 |
+
{% endblock %}
|
| 8 |
+
|
| 9 |
+
{% block content %}
|
| 10 |
+
<ul style="list-style-type: none; margin: 0; padding: 0; overflow: hidden; background-color: #333;">
|
| 11 |
+
<li style="float: left;"><a class="active" href="home" style="display: block; color: white;padding: 14px 16px;">Home</a></li>
|
| 12 |
+
<li style="float: left;"><a href="face" style="display: block; color: white;padding: 14px 16px;">Face</a></li>
|
| 13 |
+
<li style="float: left;"><a href="voice" style="display: block; color: white;padding: 14px 16px;">Voice</a></li>
|
| 14 |
+
<li style="float: left;"><a href="qstn" style="display: block; color: white;padding: 14px 16px;">PHQ-9</a></li>
|
| 15 |
+
</ul>
|
| 16 |
+
|
| 17 |
+
<h2 class="heading">Voice Emotion Detection</h2>
|
| 18 |
+
|
| 19 |
+
<div style="text-align:center;">
|
| 20 |
+
<img class="icon" src="{{url_for('static', filename='microphone.svg')}}" alt="microphone" style="width:90px;">
|
| 21 |
+
<div style="margin:14px;">
|
| 22 |
+
<button id="recBtn" class="voice_detect_btn">Record 4s</button>
|
| 23 |
+
</div>
|
| 24 |
+
<p id="vstatus">{{data}}</p>
|
| 25 |
+
<h3 id="vresult"></h3>
|
| 26 |
+
<audio id="player" controls style="display:none; margin:10px auto;"></audio>
|
| 27 |
+
<p style="color:#888; max-width:640px; margin:10px auto; font-size:0.9em;">
|
| 28 |
+
Predicts gender + emotion from your voice. A demo cue, <strong>not a diagnosis</strong>.
|
| 29 |
+
</p>
|
| 30 |
+
</div>
|
| 31 |
+
|
| 32 |
+
<script src="{{url_for('static', filename='voice.js')}}"></script>
|
| 33 |
+
{% endblock %}
|
app.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Entry point: create the Flask app and run the development server.
|
| 2 |
+
|
| 3 |
+
python app.py # or: flask --app app run
|
| 4 |
+
"""
|
| 5 |
+
from anxiety_app import create_app
|
| 6 |
+
|
| 7 |
+
app = create_app()
|
| 8 |
+
|
| 9 |
+
if __name__ == "__main__":
|
| 10 |
+
app.run(host="127.0.0.1", port=5000, debug=False)
|
models/fer.h5
ADDED
|
Binary file (132 Bytes). View file
|
|
|
models/fer.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"class_name": "Sequential", "config": {"name": "sequential", "layers": [{"class_name": "InputLayer", "config": {"batch_input_shape": [null, 48, 48, 1], "dtype": "float32", "sparse": false, "ragged": false, "name": "conv2d_input"}}, {"class_name": "Conv2D", "config": {"name": "conv2d", "trainable": true, "batch_input_shape": [null, 48, 48, 1], "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Conv2D", "config": {"name": "conv2d_1", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "MaxPooling2D", "config": {"name": "max_pooling2d", "trainable": true, "dtype": "float32", "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}}, {"class_name": "Dropout", "config": {"name": "dropout", "trainable": true, "dtype": "float32", "rate": 0.5, "noise_shape": null, "seed": null}}, {"class_name": "Conv2D", "config": {"name": "conv2d_2", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Conv2D", "config": {"name": "conv2d_3", "trainable": true, "dtype": "float32", "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_1", "trainable": true, "dtype": "float32", "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}}, {"class_name": "Dropout", "config": {"name": "dropout_1", "trainable": true, "dtype": "float32", "rate": 0.5, "noise_shape": null, "seed": null}}, {"class_name": "Conv2D", "config": {"name": "conv2d_4", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Conv2D", "config": {"name": "conv2d_5", "trainable": true, "dtype": "float32", "filters": 128, "kernel_size": [3, 3], "strides": [1, 1], "padding": "valid", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_2", "trainable": true, "dtype": "float32", "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}}, {"class_name": "Flatten", "config": {"name": "flatten", "trainable": true, "dtype": "float32", "data_format": "channels_last"}}, {"class_name": "Dense", "config": {"name": "dense", "trainable": true, "dtype": "float32", "units": 1024, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dropout", "config": {"name": "dropout_2", "trainable": true, "dtype": "float32", "rate": 0.2, "noise_shape": null, "seed": null}}, {"class_name": "Dense", "config": {"name": "dense_1", "trainable": true, "dtype": "float32", "units": 1024, "activation": "relu", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Dropout", "config": {"name": "dropout_3", "trainable": true, "dtype": "float32", "rate": 0.2, "noise_shape": null, "seed": null}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "dtype": "float32", "units": 7, "activation": "softmax", "use_bias": true, "kernel_initializer": {"class_name": "GlorotUniform", "config": {"seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}]}, "keras_version": "2.5.0", "backend": "tensorflow"}
|
models/haarcascade_frontalface_default.xml
ADDED
|
Binary file (963 kB). View file
|
|
|
models/voice_model.h5
ADDED
|
Binary file (132 Bytes). View file
|
|
|
models/voice_model.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"class_name": "Sequential", "config": [{"class_name": "Conv1D", "config": {"name": "conv1d_7", "trainable": true, "batch_input_shape": [null, 216, 1], "dtype": "float32", "filters": 128, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_8", "trainable": true, "activation": "relu"}}, {"class_name": "Conv1D", "config": {"name": "conv1d_8", "trainable": true, "filters": 128, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_9", "trainable": true, "activation": "relu"}}, {"class_name": "Dropout", "config": {"name": "dropout_3", "trainable": true, "rate": 0.1}}, {"class_name": "MaxPooling1D", "config": {"name": "max_pooling1d_2", "trainable": true, "strides": [8], "pool_size": [8], "padding": "valid"}}, {"class_name": "Conv1D", "config": {"name": "conv1d_9", "trainable": true, "filters": 128, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_10", "trainable": true, "activation": "relu"}}, {"class_name": "Conv1D", "config": {"name": "conv1d_10", "trainable": true, "filters": 128, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_11", "trainable": true, "activation": "relu"}}, {"class_name": "Conv1D", "config": {"name": "conv1d_11", "trainable": true, "filters": 128, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_12", "trainable": true, "activation": "relu"}}, {"class_name": "Dropout", "config": {"name": "dropout_4", "trainable": true, "rate": 0.2}}, {"class_name": "Conv1D", "config": {"name": "conv1d_12", "trainable": true, "filters": 128, "kernel_size": [5], "strides": [1], "padding": "same", "dilation_rate": [1], "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_13", "trainable": true, "activation": "relu"}}, {"class_name": "Flatten", "config": {"name": "flatten_2", "trainable": true}}, {"class_name": "Dense", "config": {"name": "dense_2", "trainable": true, "units": 10, "activation": "linear", "use_bias": true, "kernel_initializer": {"class_name": "VarianceScaling", "config": {"scale": 1.0, "mode": "fan_avg", "distribution": "uniform", "seed": null}}, "bias_initializer": {"class_name": "Zeros", "config": {}}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}}, {"class_name": "Activation", "config": {"name": "activation_14", "trainable": true, "activation": "softmax"}}], "keras_version": "2.0.6", "backend": "tensorflow"}
|
requirements-modern.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Modernized dependency set (Option C) — Python 3.13
|
| 2 |
+
# Uses current TensorFlow + the legacy `tf-keras` package so the original
|
| 3 |
+
# Keras 2.0.6 model files (fer.json/h5, model.json + .h5) load unchanged.
|
| 4 |
+
#
|
| 5 |
+
# Setup:
|
| 6 |
+
# py -3.13 -m venv venv
|
| 7 |
+
# venv\Scripts\activate
|
| 8 |
+
# pip install -r requirements-modern.txt
|
| 9 |
+
# python app.py # or: flask run
|
| 10 |
+
#
|
| 11 |
+
# PyAudio installs from PyPI wheels on 3.13 (the bundled cp39 .whl files are not used).
|
| 12 |
+
|
| 13 |
+
tensorflow==2.21.0
|
| 14 |
+
tf-keras==2.21.0
|
| 15 |
+
opencv-python
|
| 16 |
+
librosa
|
| 17 |
+
pandas
|
| 18 |
+
scikit-learn
|
| 19 |
+
soundfile
|
| 20 |
+
flask
|
| 21 |
+
flask-bootstrap
|
| 22 |
+
pyaudio
|
| 23 |
+
gunicorn
|