Pranjal2510 commited on
Commit
9b4e272
·
1 Parent(s): 8dd058f

Baby-Cry-Analysis Api

Browse files
Files changed (4) hide show
  1. README.md +164 -0
  2. app.py +194 -0
  3. inference.py +86 -0
  4. requirements.txt +7 -0
README.md CHANGED
@@ -11,3 +11,167 @@ license: mit
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  ---
12
 
13
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
14
+ # 👶 Baby Cry AI Service
15
+
16
+ A Flask microservice that analyzes baby cry audio to identify the reason for crying using an ensemble of Hugging Face models.
17
+
18
+ ## 🎯 Features
19
+
20
+ - **Ensemble Model Approach**: Combines supervised and zero-shot classification for improved accuracy
21
+ - **Single Load Architecture**: Models loaded once at startup for optimal performance
22
+ - **Docker Ready**: Production-ready containerized deployment
23
+ - **REST API**: Simple POST endpoint for audio analysis
24
+
25
+ ## 🏗️ Architecture
26
+
27
+ ```
28
+ ┌─────────────────────────────────────────────────────────────┐
29
+ │ Baby Cry AI Service │
30
+ ├─────────────────────────────────────────────────────────────┤
31
+ │ app.py (Flask API) │
32
+ │ └── POST /analyze-cry │
33
+ │ └── inference.py │
34
+ │ ├── Supervised Model (Wiam/baby-cry-*) │
35
+ │ └── Zero-Shot Model (laion/clap-htsat-unfused) │
36
+ └─────────────────────────────────────────────────────────────┘
37
+ ```
38
+
39
+ ## 🚀 Quick Start
40
+
41
+ ### Local Development
42
+
43
+ ```bash
44
+ # Install dependencies
45
+ pip install -r requirements.txt
46
+
47
+ # Run the service
48
+ python app.py
49
+ ```
50
+
51
+ ### Docker
52
+
53
+ ```bash
54
+ # Build image
55
+ docker build -t baby-cry-ai .
56
+
57
+ # Run container
58
+ docker run -p 5000:5000 baby-cry-ai
59
+ ```
60
+
61
+ ## 📡 API Reference
62
+
63
+ ### Health Check
64
+
65
+ ```http
66
+ GET /health
67
+ ```
68
+
69
+ **Response:**
70
+ ```json
71
+ {
72
+ "status": "healthy",
73
+ "service": "baby-cry-ai"
74
+ }
75
+ ```
76
+
77
+ ### Analyze Cry
78
+
79
+ ```http
80
+ POST /analyze-cry
81
+ Content-Type: multipart/form-data
82
+ ```
83
+
84
+ **Request:**
85
+ - `audio`: Audio file (WAV, MP3, OGG, FLAC, M4A, WebM)
86
+
87
+ **Response:**
88
+ ```json
89
+ {
90
+ "cry_detected": true,
91
+ "top_reason": "hunger",
92
+ "scores": {
93
+ "hunger": 0.45,
94
+ "belly_pain": 0.20,
95
+ "tired": 0.15,
96
+ "discomfort": 0.12,
97
+ "burping": 0.08
98
+ },
99
+ "disclaimer": "AI-generated suggestion, not a medical diagnosis. Please consult a healthcare professional for medical advice."
100
+ }
101
+ ```
102
+
103
+ ### Example Usage
104
+
105
+ ```bash
106
+ # Using curl
107
+ curl -X POST http://localhost:5000/analyze-cry \
108
+ -F "audio=@baby_cry.wav"
109
+
110
+ # Using Python requests
111
+ import requests
112
+
113
+ with open("baby_cry.wav", "rb") as f:
114
+ response = requests.post(
115
+ "http://localhost:5000/analyze-cry",
116
+ files={"audio": f}
117
+ )
118
+ print(response.json())
119
+ ```
120
+
121
+ ## 🏷️ Cry Categories
122
+
123
+ | Label | Description |
124
+ |-------|-------------|
125
+ | `hunger` | Baby is hungry (rhythmic "neh" sound) |
126
+ | `belly_pain` | Stomach discomfort (sharp, high-pitched) |
127
+ | `tired` | Baby needs sleep (heavy, yawning cry) |
128
+ | `discomfort` | General discomfort (fussy, whiny) |
129
+ | `burping` | Needs to burp (repetitive sounds) |
130
+
131
+ ## 🧠 Models Used
132
+
133
+ 1. **Supervised Model**: `Wiam/baby-cry-classification-finetuned-babycry-v4`
134
+ - Fine-tuned specifically for baby cry classification
135
+
136
+ 2. **Zero-Shot Model**: `laion/clap-htsat-unfused`
137
+ - CLAP model for audio-text matching
138
+ - Provides additional context via natural language prompts
139
+
140
+ ## 📁 Project Structure
141
+
142
+ ```
143
+ baby-cry-ai-service/
144
+ ├── app.py # Flask entry point
145
+ ├── inference.py # Model loading & inference logic
146
+ ├── requirements.txt # Python dependencies
147
+ ├── Dockerfile # Container configuration
148
+ ├── .env.example # Environment variables template
149
+ └── README.md # Documentation
150
+ ```
151
+
152
+ ## ⚙️ Environment Variables
153
+
154
+ | Variable | Default | Description |
155
+ |----------|---------|-------------|
156
+ | `PORT` | `5000` | Server port |
157
+ | `FLASK_DEBUG` | `false` | Enable debug mode |
158
+
159
+ ## 🔧 Production Deployment
160
+
161
+ For production, the Docker image uses Gunicorn with:
162
+ - Single worker (due to model memory requirements)
163
+ - 120s timeout for large audio files
164
+ - Health check endpoint
165
+
166
+ ```bash
167
+ # Production run with custom port
168
+ docker run -p 8080:5000 -e PORT=5000 baby-cry-ai
169
+ ```
170
+
171
+ ## ⚠️ Disclaimer
172
+
173
+ This service provides AI-generated suggestions only and should **NOT** be used as a substitute for professional medical advice. Always consult with a healthcare professional for concerns about your baby's health.
174
+
175
+ ## 📄 License
176
+
177
+ MIT License
app.py ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Baby Cry Analysis - Flask API for Hugging Face Spaces
3
+ Exposes REST endpoint for audio-based baby cry classification.
4
+ Wrapped with Gradio to keep the Space alive.
5
+ """
6
+
7
+ from flask import Flask, request, jsonify
8
+ import tempfile
9
+ import os
10
+ import logging
11
+ import threading
12
+
13
+ # Configure logging
14
+ logging.basicConfig(level=logging.INFO)
15
+ logger = logging.getLogger(__name__)
16
+
17
+ # Import inference module (model loads at import time)
18
+ from inference import analyze_cry, preload_model
19
+
20
+ # Preload model at startup
21
+ preload_model()
22
+
23
+ app = Flask(__name__)
24
+
25
+ # Maximum file size: 16MB
26
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
27
+
28
+ # Allowed audio extensions
29
+ ALLOWED_EXTENSIONS = {'wav', 'mp3', 'ogg', 'flac', 'm4a', 'webm'}
30
+
31
+
32
+ def allowed_file(filename: str) -> bool:
33
+ """Check if file extension is allowed."""
34
+ return '.' in filename and \
35
+ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
36
+
37
+
38
+ @app.route("/health", methods=["GET"])
39
+ def health():
40
+ """Health check endpoint."""
41
+ return jsonify({"status": "healthy", "service": "baby-cry-ai"})
42
+
43
+
44
+ @app.route("/analyze-cry", methods=["POST"])
45
+ def analyze():
46
+ """
47
+ Analyze baby cry audio file.
48
+
49
+ Expects multipart/form-data with 'audio' file field.
50
+
51
+ Returns:
52
+ JSON with cry analysis results:
53
+ - cry_detected: boolean
54
+ - top_reason: string (hunger, belly_pain, tired, discomfort, burping)
55
+ - scores: object with confidence scores per label
56
+ - disclaimer: legal disclaimer string
57
+ """
58
+ # Validate request has audio file
59
+ if "audio" not in request.files:
60
+ return jsonify({
61
+ "error": "No audio file provided",
62
+ "message": "Please upload an audio file with key 'audio'"
63
+ }), 400
64
+
65
+ audio = request.files["audio"]
66
+
67
+ # Validate filename exists
68
+ if audio.filename == '':
69
+ return jsonify({
70
+ "error": "Empty filename",
71
+ "message": "No file selected"
72
+ }), 400
73
+
74
+ # Validate file extension
75
+ if not allowed_file(audio.filename):
76
+ return jsonify({
77
+ "error": "Invalid file type",
78
+ "message": f"Allowed types: {', '.join(ALLOWED_EXTENSIONS)}"
79
+ }), 400
80
+
81
+ # Get original extension for temp file
82
+ ext = audio.filename.rsplit('.', 1)[1].lower()
83
+
84
+ try:
85
+ # Save to temporary file for processing
86
+ with tempfile.NamedTemporaryFile(delete=False, suffix=f".{ext}") as tmp:
87
+ audio.save(tmp.name)
88
+ temp_path = tmp.name
89
+
90
+ logger.info(f"Processing audio file: {temp_path}")
91
+
92
+ # Run inference
93
+ result = analyze_cry(temp_path)
94
+
95
+ # Add disclaimer
96
+ result["disclaimer"] = "AI-generated suggestion, not a medical diagnosis. Please consult a healthcare professional for medical advice."
97
+
98
+ logger.info(f"Analysis successful: {result['top_reason']}")
99
+ return jsonify(result)
100
+
101
+ except Exception as e:
102
+ logger.error(f"Analysis failed: {str(e)}", exc_info=True)
103
+ return jsonify({
104
+ "error": "Analysis failed",
105
+ "message": str(e)
106
+ }), 500
107
+
108
+ finally:
109
+ # Clean up temp file
110
+ if 'temp_path' in locals() and os.path.exists(temp_path):
111
+ os.unlink(temp_path)
112
+
113
+
114
+ @app.errorhandler(413)
115
+ def too_large(e):
116
+ """Handle file too large error."""
117
+ return jsonify({
118
+ "error": "File too large",
119
+ "message": "Maximum file size is 16MB"
120
+ }), 413
121
+
122
+
123
+ def run_flask():
124
+ """Run Flask app in background thread."""
125
+ app.run(host="0.0.0.0", port=7860, debug=False, use_reloader=False)
126
+
127
+
128
+ # Start Flask in background thread
129
+ flask_thread = threading.Thread(target=run_flask, daemon=True)
130
+ flask_thread.start()
131
+
132
+ # Gradio UI to keep the Space alive
133
+ import gradio as gr
134
+
135
+ with gr.Blocks() as demo:
136
+ gr.Markdown("""
137
+ # 👶 Baby Cry Analysis API
138
+
139
+ This Space hosts a REST API for analyzing baby cries using machine learning.
140
+
141
+ ## 🔗 API Endpoints
142
+
143
+ ### Health Check
144
+ ```
145
+ GET /health
146
+ ```
147
+
148
+ ### Analyze Cry
149
+ ```
150
+ POST /analyze-cry
151
+ Content-Type: multipart/form-data
152
+ Body: audio=<file>
153
+ ```
154
+
155
+ **Supported formats:** WAV, MP3, OGG, FLAC, M4A, WebM
156
+
157
+ ## 📡 Example Usage
158
+
159
+ ```python
160
+ import requests
161
+
162
+ with open("baby_cry.wav", "rb") as f:
163
+ response = requests.post(
164
+ "https://YOUR-SPACE.hf.space/analyze-cry",
165
+ files={"audio": f}
166
+ )
167
+ print(response.json())
168
+ ```
169
+
170
+ ## 📊 Response Format
171
+
172
+ ```json
173
+ {
174
+ "cry_detected": true,
175
+ "top_reason": "hunger",
176
+ "scores": {
177
+ "hunger": 0.45,
178
+ "belly_pain": 0.20,
179
+ "tired": 0.15,
180
+ "discomfort": 0.12,
181
+ "burping": 0.08
182
+ },
183
+ "disclaimer": "AI-generated suggestion..."
184
+ }
185
+ ```
186
+
187
+ ---
188
+ ⚠️ *This is an AI-generated suggestion, not a medical diagnosis.*
189
+ """)
190
+
191
+ demo.launch(server_name="0.0.0.0", server_port=7861)
192
+
193
+
194
+
inference.py ADDED
@@ -0,0 +1,86 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Baby Cry Analysis - Inference Module
3
+ Optimized for Hugging Face Spaces with eager model loading.
4
+ """
5
+
6
+ import logging
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Model loaded at module level for single load
11
+ _model = None
12
+
13
+ # Supported cry reason labels
14
+ LABELS = ["hunger", "belly_pain", "tired", "discomfort", "burping"]
15
+
16
+
17
+ def preload_model():
18
+ """
19
+ Eagerly load the model at startup.
20
+ Called once when the app starts to avoid cold start delays.
21
+ """
22
+ global _model
23
+ if _model is None:
24
+ logger.info("🔄 Loading baby cry classification model at startup...")
25
+ from transformers import pipeline
26
+ _model = pipeline(
27
+ "audio-classification",
28
+ model="Wiam/baby-cry-classification-finetuned-babycry-v4"
29
+ )
30
+ logger.info("✅ Model loaded successfully!")
31
+ return _model
32
+
33
+
34
+ def get_model():
35
+ """Get the loaded model instance."""
36
+ global _model
37
+ if _model is None:
38
+ return preload_model()
39
+ return _model
40
+
41
+
42
+ def analyze_cry(audio_path: str) -> dict:
43
+ """
44
+ Analyze a baby cry audio file using the supervised classification model.
45
+
46
+ Args:
47
+ audio_path: Path to the audio file (WAV format preferred)
48
+
49
+ Returns:
50
+ Dictionary containing:
51
+ - cry_detected: boolean indicating if a cry was detected
52
+ - top_reason: the most likely reason for crying
53
+ - scores: confidence scores for each label
54
+ """
55
+ # Get preloaded model
56
+ model = get_model()
57
+
58
+ # Get supervised model predictions
59
+ results = model(audio_path)
60
+
61
+ # Build scores dictionary from model output
62
+ scores = {}
63
+ for r in results:
64
+ label = r["label"]
65
+ if label in LABELS:
66
+ scores[label] = round(r["score"], 4)
67
+
68
+ # Ensure all labels have a score (default 0 if missing)
69
+ for label in LABELS:
70
+ if label not in scores:
71
+ scores[label] = 0.0
72
+
73
+ # Determine top prediction
74
+ top_label = max(scores, key=scores.get)
75
+ top_confidence = scores[top_label]
76
+
77
+ # Cry detection threshold
78
+ cry_detected = top_confidence >= 0.1
79
+
80
+ return {
81
+ "cry_detected": cry_detected,
82
+ "top_reason": top_label if cry_detected else None,
83
+ "scores": scores
84
+ }
85
+
86
+
requirements.txt ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ flask
2
+ torch
3
+ transformers
4
+ librosa
5
+ numpy
6
+ soundfile
7
+ gradio