78anand commited on
Commit
dd60f41
·
verified ·
1 Parent(s): 7fba504

Upload folder using huggingface_hub

Browse files
app/main.py CHANGED
@@ -1,158 +1,170 @@
1
- import os
2
- import sys
3
-
4
- # --- Force Writable Paths for Hugging Face ---
5
- # This MUST happen before any other imports
6
- os.environ['HOME'] = '/tmp'
7
- os.environ['HF_HOME'] = '/tmp/huggingface'
8
- os.environ['XDG_CACHE_HOME'] = '/tmp/cache'
9
- os.environ['MPLCONFIGDIR'] = '/tmp/matplotlib'
10
- os.environ['NUMBA_CACHE_DIR'] = '/tmp/numba'
11
-
12
- # Ensure directories exist
13
- for d in ['/tmp/huggingface', '/tmp/cache', '/tmp/matplotlib', '/tmp/numba']:
14
- os.makedirs(d, exist_ok=True)
15
-
16
- import numpy as np
17
- import librosa
18
- import tensorflow as tf
19
- from flask import Flask, request, jsonify
20
- from flask_cors import CORS
21
- from tensorflow.keras.models import load_model
22
- from werkzeug.utils import secure_filename
23
-
24
- # --- Absolute Path Resolution ---
25
- # This ensures the Linux server finds the 'utils' and 'models' folders
26
- current_dir = os.path.dirname(os.path.abspath(__file__))
27
- project_root = os.path.dirname(current_dir)
28
- if project_root not in sys.path:
29
- sys.path.insert(0, project_root)
30
-
31
- try:
32
- from utils.hear_extractor import HeARExtractor
33
- from utils.audio_preprocessor import advanced_preprocess
34
- print(" Successfully imported utils package.")
35
- except ImportError as e:
36
- print(f"❌ Critical Import Error: {e}")
37
- # Show more context for debugging
38
- print(f"DEBUG: sys.path is {sys.path}")
39
- print(f"DEBUG: Attempting to look in {project_root}")
40
- sys.exit(1)
41
-
42
- # Configuration
43
- BASE_DIR = os.path.dirname(os.path.abspath(__file__))
44
- app = Flask(__name__)
45
- # Enable CORS for the entire API to allow Vercel and Flutter to connect securely
46
- CORS(app)
47
-
48
- app.config['UPLOAD_FOLDER'] = os.path.join(BASE_DIR, 'tmp', 'uploads')
49
- app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024 # 16MB limit
50
-
51
- # Ensure upload directory exists
52
- os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
53
-
54
- # Configuration
55
- MODEL_PATH = os.path.join(project_root, "models", "hear_classifier_v9_ultimate.h5")
56
-
57
- # Global variables for lazy loading
58
- extractor = None
59
- classifier_model = None
60
-
61
- def load_resources():
62
- global extractor, classifier_model
63
- if extractor is None:
64
- print("Initializing HeAR Extractor...")
65
- hf_token = os.environ.get('HF_TOKEN')
66
- extractor = HeARExtractor(token=hf_token)
67
-
68
- if classifier_model is None:
69
- print(f"Loading Ultimate Model from {MODEL_PATH}...")
70
- classifier_model = load_model(MODEL_PATH, compile=False)
71
-
72
- @app.route('/')
73
- def index():
74
- return jsonify({
75
- "status": "online",
76
- "service": "KasaHealth Diagnostic API (Ultimate V9)",
77
- "version": "1.3.0",
78
- "message": "Send audio files via POST to /predict"
79
- })
80
-
81
- @app.route('/predict', methods=['POST'])
82
- def predict():
83
- if 'audio' not in request.files:
84
- return jsonify({"error": "No audio file provided"}), 400
85
-
86
- file = request.files['audio']
87
- if file.filename == '':
88
- return jsonify({"error": "No selected file"}), 400
89
-
90
- if file:
91
- filename = secure_filename(file.filename)
92
- filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
93
- file.save(filepath)
94
-
95
- try:
96
- load_resources()
97
- y, sr = librosa.load(filepath, sr=16000)
98
-
99
- # VAD Lite
100
- rms_energy = np.mean(librosa.feature.rms(y=y))
101
- if rms_energy < 0.005:
102
- os.remove(filepath)
103
- return jsonify({"error": "No cough detected. Please record in a quieter area."}), 400
104
-
105
- # 2. Preprocess to remove phone microphone rumble and static!
106
- y_clean = advanced_preprocess(y, sr)
107
- emb = extractor.extract(y_clean)
108
-
109
- if emb is not None:
110
- X = emb[np.newaxis, ...]
111
- prob = classifier_model.predict(X, verbose=0)[0][0]
112
-
113
- # Logic: Highly sensitive (Unleashed mode)
114
- THRESHOLD = 0.50
115
-
116
- if prob > THRESHOLD:
117
- final_label = "sick"
118
- # If it's sick but barely (0.5 - 0.65), call it inconclusive
119
- is_inconclusive = bool(prob < 0.65)
120
- confidence = float(prob)
121
- else:
122
- final_label = "healthy"
123
- # If it's healthy but close to sick, call it inconclusive
124
- is_inconclusive = bool(prob > 0.35)
125
-
126
- # Convert 0.0 -> 0.5 range to 1.0 -> 0.5 healthy confidence
127
- confidence = 1.0 - float(prob)
128
-
129
- raw_label = "sick" if prob > 0.5 else "healthy"
130
-
131
- os.remove(filepath)
132
- return jsonify({
133
- "status": "success",
134
- "result": final_label,
135
- "confidence": confidence,
136
- "is_inconclusive": is_inconclusive,
137
- "raw_label": raw_label,
138
- "recommendation": get_recommendation(final_label, is_inconclusive)
139
- })
140
- else:
141
- os.remove(filepath)
142
- return jsonify({"error": "Feature extraction failed"}), 500
143
-
144
- except Exception as e:
145
- if os.path.exists(filepath): os.remove(filepath)
146
- return jsonify({"error": str(e)}), 500
147
-
148
- def get_recommendation(label, is_inconclusive):
149
- if label == "sick":
150
- return "Potential respiratory symptoms detected. We strongly recommend consulting a healthcare professional for a detailed evaluation."
151
- elif is_inconclusive:
152
- return "Acoustic signals show some variation but no strong abnormal indicators were found. Re-record in a quiet environment for more certainty."
153
- else:
154
- return "Acoustic pattern appears healthy. Continue to monitor your health and maintain good respiratory hygiene."
155
-
156
- if __name__ == '__main__':
157
- # For local development
158
- app.run(debug=True, port=5000)
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+
4
+ # --- Force Writable Paths for Hugging Face ---
5
+ os.environ['HOME'] = '/tmp'
6
+ os.environ['HF_HOME'] = '/tmp/huggingface'
7
+ os.environ['XDG_CACHE_HOME'] = '/tmp/cache'
8
+ os.environ['MPLCONFIGDIR'] = '/tmp/matplotlib'
9
+ os.environ['NUMBA_CACHE_DIR'] = '/tmp/numba'
10
+
11
+ # Ensure directories exist
12
+ for d in ['/tmp/huggingface', '/tmp/cache', '/tmp/matplotlib', '/tmp/numba']:
13
+ os.makedirs(d, exist_ok=True)
14
+
15
+ import numpy as np
16
+ import librosa
17
+ import tensorflow as tf
18
+ from flask import Flask, request, jsonify
19
+ from flask_cors import CORS
20
+ from tensorflow.keras.models import load_model
21
+ from werkzeug.utils import secure_filename
22
+ from scipy.signal import butter, lfilter
23
+
24
+ # --- Absolute Path Resolution ---
25
+ current_dir = os.path.dirname(os.path.abspath(__file__))
26
+ project_root = os.path.dirname(current_dir)
27
+ if project_root not in sys.path:
28
+ sys.path.insert(0, project_root)
29
+
30
+ try:
31
+ from utils.hear_extractor import HeARExtractor
32
+ print("✅ Successfully imported utils package.")
33
+ except ImportError as e:
34
+ print(f" Critical Import Error: {e}")
35
+ sys.exit(1)
36
+
37
+ app = Flask(__name__)
38
+ CORS(app)
39
+
40
+ app.config['UPLOAD_FOLDER'] = os.path.join('/tmp', 'uploads')
41
+ app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
42
+ os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
43
+
44
+ # Configuration: DUAL-BRAIN MODELS
45
+ # Brain 1: The Shield (V9) - Protects healthy users
46
+ # Brain 2: The Sentry (V10) - High sensitivity for sick patients
47
+ MODEL_V9_PATH = os.path.join(project_root, "models", "hear_classifier_v9_ultimate.h5")
48
+ MODEL_V10_PATH = os.path.join(project_root, "models", "hear_classifier_v10_sentry.h5")
49
+
50
+ # Global variables for lazy loading
51
+ extractor = None
52
+ shield_model = None
53
+ sentry_model = None
54
+
55
+ def load_resources():
56
+ global extractor, shield_model, sentry_model
57
+ if extractor is None:
58
+ hf_token = os.environ.get('HF_TOKEN')
59
+ extractor = HeARExtractor(token=hf_token)
60
+
61
+ if shield_model is None:
62
+ print(f"Loading Shield Model (V9)...")
63
+ shield_model = load_model(MODEL_V9_PATH, compile=False)
64
+
65
+ if sentry_model is None:
66
+ print(f"Loading Sentry Model (V10)...")
67
+ sentry_model = load_model(MODEL_V10_PATH, compile=False)
68
+
69
+ def highpass_filter(data, cutoff, fs, order=5):
70
+ nyq = 0.5 * fs
71
+ normal_cutoff = cutoff / nyq
72
+ b, a = butter(order, normal_cutoff, btype='high', analog=False)
73
+ return lfilter(b, a, data)
74
+
75
+ @app.route('/')
76
+ def index():
77
+ return jsonify({
78
+ "status": "online",
79
+ "service": "KasaHealth Dual-Brain Engine",
80
+ "version": "2.0.0 (Ensemble)",
81
+ "message": "Dual-Brain analysis ready."
82
+ })
83
+
84
+ @app.route('/predict', methods=['POST'])
85
+ def predict():
86
+ if 'audio' not in request.files:
87
+ return jsonify({"error": "No audio file"}), 400
88
+
89
+ file = request.files['audio']
90
+ filename = secure_filename(file.filename)
91
+ filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
92
+ file.save(filepath)
93
+
94
+ try:
95
+ load_resources()
96
+ y, sr = librosa.load(filepath, sr=16000)
97
+
98
+ # VAD Power check
99
+ rms_energy = np.mean(librosa.feature.rms(y=y))
100
+ if rms_energy < 0.005:
101
+ os.remove(filepath)
102
+ return jsonify({"error": "Silence detected. Please cough closer to the mic."}), 400
103
+
104
+ # Pre-process
105
+ y_clean = highpass_filter(y, 100, sr)
106
+ y_clean = y_clean / (np.max(np.abs(y_clean)) + 1e-8)
107
+
108
+ emb = extractor.extract(y_clean)
109
+ if emb is None:
110
+ os.remove(filepath)
111
+ return jsonify({"error": "Failed to extract acoustic features."}), 500
112
+
113
+ X = emb[np.newaxis, ...]
114
+
115
+ # --- Dual-Brain Analysis ---
116
+ p9 = shield_model.predict(X, verbose=0)[0][0]
117
+ p10 = sentry_model.predict(X, verbose=0)[0][0]
118
+
119
+ # Logic Mapping to Frontend Risk Meter:
120
+ # result: 'healthy' or 'sick'
121
+ # confidence: drives the meter (High/Med/Low)
122
+
123
+ is_sick_sentry = bool(p10 > 0.50)
124
+ is_sick_shield = bool(p9 > 0.55) # Shield is stricter
125
+
126
+ if not is_sick_sentry:
127
+ # Both agree it is likely fine
128
+ final_label = "healthy"
129
+ confidence = float(1.0 - p10)
130
+ # Force high healthy confidence for Low Risk trigger
131
+ if confidence < 0.70: confidence = 0.75
132
+ is_inconclusive = False
133
+
134
+ elif is_sick_sentry and not is_sick_shield:
135
+ # Sentry catches something, but Shield isn't sure
136
+ final_label = "sick"
137
+ # In app.js: sick + conf < 0.75 = MEDIUM RISK
138
+ confidence = 0.60
139
+ is_inconclusive = True
140
+
141
+ else:
142
+ # Both models detect strong signs
143
+ final_label = "sick"
144
+ # In app.js: sick + conf >= 0.75 = HIGH RISK
145
+ confidence = 0.90
146
+ is_inconclusive = False
147
+
148
+ os.remove(filepath)
149
+ return jsonify({
150
+ "status": "success",
151
+ "result": final_label,
152
+ "confidence": confidence,
153
+ "is_inconclusive": is_inconclusive,
154
+ "scores": { "shield": float(p9), "sentry": float(p10) },
155
+ "recommendation": get_recommendation(final_label, is_inconclusive)
156
+ })
157
+
158
+ except Exception as e:
159
+ if os.path.exists(filepath): os.remove(filepath)
160
+ return jsonify({"error": str(e)}), 500
161
+
162
+ def get_recommendation(label, is_inconclusive):
163
+ if label == "sick":
164
+ if is_inconclusive:
165
+ return "Minor acoustic anomalies detected. We recommend re-testing in a quiet environment or monitoring symptoms."
166
+ return "Significant respiratory indicators found. We strongly recommend consulting a healthcare professional."
167
+ return "Clear acoustic signature. Maintain good respiratory hygiene."
168
+
169
+ if __name__ == '__main__':
170
+ app.run(host='0.0.0.0', port=7860)
models/hear_classifier_v10_sentry.h5 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a1ec482f249e5ef8b6ebd970373c13014658f3e7c1e65c89014d5e6e5ac65206
3
+ size 14787768
test_real_pipeline.py CHANGED
@@ -19,7 +19,7 @@ from utils.audio_preprocessor import advanced_preprocess
19
  AUDIO_ROOT = r"c:\Users\ASUS\lung_ai_project\data\coughvid_public\organized"
20
  SICK_DIR = os.path.join(AUDIO_ROOT, "sick")
21
  HEALTHY_DIR = os.path.join(AUDIO_ROOT, "healthy")
22
- MODEL_PATH = os.path.join(PROJECT_ROOT, "models", "hear_classifier_v9_ultimate.h5")
23
 
24
  META_PATH = r"c:\Users\ASUS\lung_ai_project\data\coughvid_public\metadata_compiled.csv"
25
  import pandas as pd
@@ -76,8 +76,16 @@ def test_pipeline():
76
  # API rejects quiet audio. For testing, skip.
77
  continue
78
 
79
- # 3. API Preprocess
80
- y_clean = y
 
 
 
 
 
 
 
 
81
 
82
  # 4. API Extract
83
  emb = extractor.extract(y_clean)
 
19
  AUDIO_ROOT = r"c:\Users\ASUS\lung_ai_project\data\coughvid_public\organized"
20
  SICK_DIR = os.path.join(AUDIO_ROOT, "sick")
21
  HEALTHY_DIR = os.path.join(AUDIO_ROOT, "healthy")
22
+ MODEL_PATH = os.path.join(PROJECT_ROOT, "models", "hear_classifier_v10_sentry.h5")
23
 
24
  META_PATH = r"c:\Users\ASUS\lung_ai_project\data\coughvid_public\metadata_compiled.csv"
25
  import pandas as pd
 
76
  # API rejects quiet audio. For testing, skip.
77
  continue
78
 
79
+ # 3. API Preprocess (Gentle Field Preprocess)
80
+ from scipy.signal import butter, lfilter
81
+ def highpass_filter(data, cutoff, fs, order=5):
82
+ nyq = 0.5 * fs
83
+ normal_cutoff = cutoff / nyq
84
+ b, a = butter(order, normal_cutoff, btype='high', analog=False)
85
+ return lfilter(b, a, data)
86
+
87
+ y_clean = highpass_filter(y, 100, sr)
88
+ y_clean = y_clean / (np.max(np.abs(y_clean)) + 1e-8)
89
 
90
  # 4. API Extract
91
  emb = extractor.extract(y_clean)