Update app.py
Browse files
app.py
CHANGED
|
@@ -9,6 +9,7 @@ from fastapi import FastAPI, UploadFile, File, HTTPException
|
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
| 10 |
from typing import Optional, Dict, Any, List
|
| 11 |
import warnings
|
|
|
|
| 12 |
|
| 13 |
warnings.filterwarnings('ignore')
|
| 14 |
|
|
@@ -208,6 +209,36 @@ except Exception as e:
|
|
| 208 |
yamnet_model = None
|
| 209 |
|
| 210 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
# ========================================
|
| 212 |
# KAGGLE METHOD - FINE-TUNED MODEL
|
| 213 |
# ========================================
|
|
@@ -587,7 +618,6 @@ def analyze_audio_segments(y, sr, model, segment_duration=10):
|
|
| 587 |
def count_precise_apneas(y, sr, segment_results, min_apnea=1, max_apnea=60):
|
| 588 |
"""
|
| 589 |
ACCURATE APNEA DETECTION
|
| 590 |
-
|
| 591 |
RULES:
|
| 592 |
1. Apnea is only valid when a confirmed snoring segment (HIGH or MEDIUM)
|
| 593 |
exists IMMEDIATELY before AND immediately after the silence gap.
|
|
@@ -601,12 +631,10 @@ def count_precise_apneas(y, sr, segment_results, min_apnea=1, max_apnea=60):
|
|
| 601 |
b) ML score check: gap audio scored by the fine-tuned model must be
|
| 602 |
< 0.40 (model must agree it is NOT snoring).
|
| 603 |
4. If total snoring count = 0, apnea is forced to 0.
|
| 604 |
-
|
| 605 |
Risk Levels:
|
| 606 |
- 10-15 seconds: LOW
|
| 607 |
- 15-20 seconds: MEDIUM
|
| 608 |
- 20+ seconds: HIGH
|
| 609 |
-
|
| 610 |
Returns: (apnea_count, apnea_events_for_annotations)
|
| 611 |
"""
|
| 612 |
print("\n🎯 APNEA DETECTION (Accurate - Immediate snoring before & after required)")
|
|
@@ -845,7 +873,6 @@ def validate_sleep_recording(y, sr):
|
|
| 845 |
def calculate_apnea_risk(apnea_events: List[Dict[str, Any]]) -> str:
|
| 846 |
"""
|
| 847 |
Calculate apnea risk based on the longest apnea duration
|
| 848 |
-
|
| 849 |
Rules:
|
| 850 |
- NO: No apnea detected
|
| 851 |
- LOW: Apnea duration 10-15 seconds
|
|
@@ -880,7 +907,8 @@ async def root():
|
|
| 880 |
"counting_feature": "Enabled - counts individual snores within detected segments" if new_counting_model else "Disabled",
|
| 881 |
"apnea_detection": "HYBRID (Fast screening + ML validation)",
|
| 882 |
"model_accuracy": "95.33%",
|
| 883 |
-
"threshold_note": "Fine-tuned threshold: 0.55 (NO LOW confidence detection)"
|
|
|
|
| 884 |
}
|
| 885 |
|
| 886 |
|
|
@@ -911,9 +939,9 @@ async def analyze_audio(file: UploadFile = File(...)):
|
|
| 911 |
print(f" {'✅ PASS' if is_valid else '⚠️ WARNING'}: {validation_reason}")
|
| 912 |
validation_warning = None if is_valid else validation_reason
|
| 913 |
|
| 914 |
-
# 3.
|
| 915 |
-
|
| 916 |
-
|
| 917 |
|
| 918 |
# 4. ANALYZE with STRICT DUAL MODELS + NEW COUNTING
|
| 919 |
print("\n🛡️ Using STRICT detection (HIGH and MEDIUM confidence only)...")
|
|
@@ -1011,6 +1039,7 @@ async def analyze_audio(file: UploadFile = File(...)):
|
|
| 1011 |
"fine_tuned_threshold": 0.55,
|
| 1012 |
"validation_approach": "Strict - only detecting HIGH and MEDIUM confidence snores",
|
| 1013 |
"apnea_detection_method": "Hybrid (Fast screening + ML validation)",
|
|
|
|
| 1014 |
"events": annotations
|
| 1015 |
}
|
| 1016 |
|
|
|
|
| 9 |
from fastapi.middleware.cors import CORSMiddleware
|
| 10 |
from typing import Optional, Dict, Any, List
|
| 11 |
import warnings
|
| 12 |
+
import noisereduce as nr
|
| 13 |
|
| 14 |
warnings.filterwarnings('ignore')
|
| 15 |
|
|
|
|
| 209 |
yamnet_model = None
|
| 210 |
|
| 211 |
|
| 212 |
+
# ========================================
|
| 213 |
+
# NOISE REDUCTION AND AUDIO PREPROCESSING
|
| 214 |
+
# ========================================
|
| 215 |
+
def clean_and_prepare_audio(y, sr):
|
| 216 |
+
"""
|
| 217 |
+
Step 1: Apply noise reduction to clean the audio
|
| 218 |
+
Step 2: Format audio to proper shape for models
|
| 219 |
+
"""
|
| 220 |
+
print(" 🧹 Applying noise reduction...")
|
| 221 |
+
try:
|
| 222 |
+
# Apply noise reduction using spectral gating method
|
| 223 |
+
y_clean = nr.reduce_noise(y=y, sr=sr, stationary=True, prop_decrease=0.8)
|
| 224 |
+
print(" ✅ Noise reduction completed")
|
| 225 |
+
except Exception as e:
|
| 226 |
+
print(f" ⚠️ Noise reduction failed: {e}, using original audio")
|
| 227 |
+
y_clean = y
|
| 228 |
+
|
| 229 |
+
# Normalize audio to proper amplitude range
|
| 230 |
+
print(" 📐 Formatting audio shape for models...")
|
| 231 |
+
y_clean = y_clean.astype(np.float32)
|
| 232 |
+
|
| 233 |
+
# Normalize to [-1, 1] range
|
| 234 |
+
max_val = np.max(np.abs(y_clean))
|
| 235 |
+
if max_val > 0:
|
| 236 |
+
y_clean = y_clean / max_val
|
| 237 |
+
|
| 238 |
+
print(" ✅ Audio prepared and shaped correctly")
|
| 239 |
+
return y_clean
|
| 240 |
+
|
| 241 |
+
|
| 242 |
# ========================================
|
| 243 |
# KAGGLE METHOD - FINE-TUNED MODEL
|
| 244 |
# ========================================
|
|
|
|
| 618 |
def count_precise_apneas(y, sr, segment_results, min_apnea=1, max_apnea=60):
|
| 619 |
"""
|
| 620 |
ACCURATE APNEA DETECTION
|
|
|
|
| 621 |
RULES:
|
| 622 |
1. Apnea is only valid when a confirmed snoring segment (HIGH or MEDIUM)
|
| 623 |
exists IMMEDIATELY before AND immediately after the silence gap.
|
|
|
|
| 631 |
b) ML score check: gap audio scored by the fine-tuned model must be
|
| 632 |
< 0.40 (model must agree it is NOT snoring).
|
| 633 |
4. If total snoring count = 0, apnea is forced to 0.
|
|
|
|
| 634 |
Risk Levels:
|
| 635 |
- 10-15 seconds: LOW
|
| 636 |
- 15-20 seconds: MEDIUM
|
| 637 |
- 20+ seconds: HIGH
|
|
|
|
| 638 |
Returns: (apnea_count, apnea_events_for_annotations)
|
| 639 |
"""
|
| 640 |
print("\n🎯 APNEA DETECTION (Accurate - Immediate snoring before & after required)")
|
|
|
|
| 873 |
def calculate_apnea_risk(apnea_events: List[Dict[str, Any]]) -> str:
|
| 874 |
"""
|
| 875 |
Calculate apnea risk based on the longest apnea duration
|
|
|
|
| 876 |
Rules:
|
| 877 |
- NO: No apnea detected
|
| 878 |
- LOW: Apnea duration 10-15 seconds
|
|
|
|
| 907 |
"counting_feature": "Enabled - counts individual snores within detected segments" if new_counting_model else "Disabled",
|
| 908 |
"apnea_detection": "HYBRID (Fast screening + ML validation)",
|
| 909 |
"model_accuracy": "95.33%",
|
| 910 |
+
"threshold_note": "Fine-tuned threshold: 0.55 (NO LOW confidence detection)",
|
| 911 |
+
"noise_reduction": "Enabled - audio cleaned before analysis"
|
| 912 |
}
|
| 913 |
|
| 914 |
|
|
|
|
| 939 |
print(f" {'✅ PASS' if is_valid else '⚠️ WARNING'}: {validation_reason}")
|
| 940 |
validation_warning = None if is_valid else validation_reason
|
| 941 |
|
| 942 |
+
# 3. CLEAN AND PREPARE AUDIO (NEW STEP)
|
| 943 |
+
print("\n🧹 Cleaning and preparing audio...")
|
| 944 |
+
y_clean = clean_and_prepare_audio(y, sr)
|
| 945 |
|
| 946 |
# 4. ANALYZE with STRICT DUAL MODELS + NEW COUNTING
|
| 947 |
print("\n🛡️ Using STRICT detection (HIGH and MEDIUM confidence only)...")
|
|
|
|
| 1039 |
"fine_tuned_threshold": 0.55,
|
| 1040 |
"validation_approach": "Strict - only detecting HIGH and MEDIUM confidence snores",
|
| 1041 |
"apnea_detection_method": "Hybrid (Fast screening + ML validation)",
|
| 1042 |
+
"noise_reduction_applied": True,
|
| 1043 |
"events": annotations
|
| 1044 |
}
|
| 1045 |
|