File size: 10,224 Bytes
c44bb3e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
import os
import pickle
import numpy as np
import pandas as pd
import librosa
import gradio as gr
from scipy.io import wavfile
import soundfile as sf
import tempfile
import time

# Load the model
def load_model(model_path='cough_classification_model.pkl'):
    with open(model_path, 'rb') as f:
        components = pickle.load(f)
    
    return components

# Extract features from audio
def extract_all_features(audio_path, sample_rate=None):
    """Extract comprehensive set of audio features"""
    # Load audio file
    y, sr = librosa.load(audio_path, sr=sample_rate)
    
    # Basic features
    features = {}
    
    # Duration
    features['duration'] = librosa.get_duration(y=y, sr=sr)
    
    # RMS Energy
    features['rms_mean'] = np.mean(librosa.feature.rms(y=y)[0])
    features['rms_std'] = np.std(librosa.feature.rms(y=y)[0])
    
    # Zero Crossing Rate
    zcr = librosa.feature.zero_crossing_rate(y)[0]
    features['zcr_mean'] = np.mean(zcr)
    features['zcr_std'] = np.std(zcr)
    
    # Spectral Features
    # Spectral Centroid
    centroid = librosa.feature.spectral_centroid(y=y, sr=sr)[0]
    features['spectral_centroid_mean'] = np.mean(centroid)
    features['spectral_centroid_std'] = np.std(centroid)
    
    # Spectral Bandwidth
    bandwidth = librosa.feature.spectral_bandwidth(y=y, sr=sr)[0]
    features['spectral_bandwidth_mean'] = np.mean(bandwidth)
    features['spectral_bandwidth_std'] = np.std(bandwidth)
    
    # Spectral Contrast
    contrast = librosa.feature.spectral_contrast(y=y, sr=sr)
    features['spectral_contrast_mean'] = np.mean(contrast)
    features['spectral_contrast_std'] = np.std(contrast)
    
    # Spectral Rolloff
    rolloff = librosa.feature.spectral_rolloff(y=y, sr=sr)[0]
    features['rolloff_mean'] = np.mean(rolloff)
    features['rolloff_std'] = np.std(rolloff)
    
    # MFCCs
    mfccs = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=13)
    for i in range(13):
        features[f'mfcc{i+1}_mean'] = np.mean(mfccs[i])
        features[f'mfcc{i+1}_std'] = np.std(mfccs[i])
    
    # Chroma Features
    chroma = librosa.feature.chroma_stft(y=y, sr=sr)
    features['chroma_mean'] = np.mean(chroma)
    features['chroma_std'] = np.std(chroma)
    
    return features

# Function to segment cough from audio
def segment_cough(x, fs, cough_padding=0.2, min_cough_len=0.2, th_l_multiplier=0.1, th_h_multiplier=2):
    """Segment coughs from audio using a hysteresis comparator on the signal power"""
    cough_mask = np.array([False]*len(x))

    # Define hysteresis thresholds
    rms = np.sqrt(np.mean(np.square(x)))
    seg_th_l = th_l_multiplier * rms
    seg_th_h = th_h_multiplier * rms

    # Segment coughs
    coughSegments = []
    padding = round(fs*cough_padding)
    min_cough_samples = round(fs*min_cough_len)
    cough_start = 0
    cough_end = 0
    cough_in_progress = False
    tolerance = round(0.01*fs)
    below_th_counter = 0

    for i, sample in enumerate(x**2):
        if cough_in_progress:
            if sample < seg_th_l:
                below_th_counter += 1
                if below_th_counter > tolerance:
                    cough_end = i+padding if (i+padding < len(x)) else len(x)-1
                    cough_in_progress = False
                    if (cough_end+1-cough_start-2*padding > min_cough_samples):
                        coughSegments.append(x[cough_start:cough_end+1])
                        cough_mask[cough_start:cough_end+1] = True
            elif i == (len(x)-1):
                cough_end = i
                cough_in_progress = False
                if (cough_end+1-cough_start-2*padding > min_cough_samples):
                    coughSegments.append(x[cough_start:cough_end+1])
            else:
                below_th_counter = 0
        else:
            if sample > seg_th_h:
                cough_start = i-padding if (i-padding >= 0) else 0
                cough_in_progress = True

    return coughSegments, cough_mask

# Prediction function for uploaded audio
def predict_cough_health(audio_path, model_components=None):
    """Predict cough health status from audio file"""
    if model_components is None:
        model_components = load_model()
    
    model = model_components['model']
    scaler = model_components['scaler']
    label_encoder = model_components['label_encoder']
    feature_names = model_components['feature_names']
    
    # Extract features
    features = extract_all_features(audio_path)
    
    # Convert to DataFrame with correct feature order
    features_df = pd.DataFrame([features])
    features_df = features_df[feature_names]
    
    # Scale features
    features_scaled = scaler.transform(features_df)
    
    # Predict
    prediction_idx = model.predict(features_scaled)[0]
    prediction = label_encoder.inverse_transform([prediction_idx])[0]
    
    # Get probabilities
    probs = model.predict_proba(features_scaled)[0]
    class_probs = {label_encoder.inverse_transform([i])[0]: float(prob) for i, prob in enumerate(probs)}
    
    return prediction, class_probs

# Function to process uploaded audio file
def process_audio_file(audio_file):
    """Process uploaded audio file and return prediction"""
    model_components = load_model()
    prediction, probabilities = predict_cough_health(audio_file, model_components)
    
    # Format the output
    result = f"Prediction: {prediction}\n\nProbabilities:\n"
    for cls, prob in probabilities.items():
        result += f"{cls}: {prob:.4f}\n"
    
    return result

# Function to process recorded audio
def process_recorded_audio(audio_array, sample_rate):
    """Process recorded audio and return prediction"""
    # Save the recorded audio to a temporary file
    with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_audio:
        temp_filename = temp_audio.name
        sf.write(temp_filename, audio_array, sample_rate)
    
    model_components = load_model()
    prediction, probabilities = predict_cough_health(temp_filename, model_components)
    
    # Clean up the temporary file
    os.unlink(temp_filename)
    
    # Format the output
    result = f"Prediction: {prediction}\n\nProbabilities:\n"
    for cls, prob in probabilities.items():
        result += f"{cls}: {prob:.4f}\n"
    
    return result

# Function to detect coughs in live audio stream
def detect_coughs_in_stream(audio_array, sample_rate):
    """Detect coughs in live audio stream and analyze them"""
    # Save the audio chunk to a temporary file
    with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as temp_audio:
        temp_filename = temp_audio.name
        sf.write(temp_filename, audio_array, sample_rate)
    
    # Load the audio for processing
    audio, sr = librosa.load(temp_filename, sr=None)
    
    # Segment coughs
    cough_segments, cough_mask = segment_cough(audio, sr)
    
    # Clean up the temporary file
    os.unlink(temp_filename)
    
    if len(cough_segments) > 0:
        # Save the first detected cough segment to a temporary file
        with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as cough_file:
            cough_filename = cough_file.name
            sf.write(cough_filename, cough_segments[0], sr)
        
        # Analyze the cough
        model_components = load_model()
        prediction, probabilities = predict_cough_health(cough_filename, model_components)
        
        # Clean up the temporary file
        os.unlink(cough_filename)
        
        # Format the output
        result = f"Cough detected! Prediction: {prediction}\n\nProbabilities:\n"
        for cls, prob in probabilities.items():
            result += f"{cls}: {prob:.4f}\n"
        
        return result
    else:
        return "No cough detected in this audio segment."

# Create Gradio interface
def create_interface():
    model_components = load_model()
    
    with gr.Blocks(title="Cough Health Analyzer") as demo:
        gr.Markdown("# Cough Health Analyzer")
        gr.Markdown("Upload an audio file containing a cough or record a cough to analyze its health status.")
        
        with gr.Tab("Upload Audio"):
            with gr.Row():
                audio_input = gr.Audio(type="filepath", label="Upload Audio File")
                output_text = gr.Textbox(label="Prediction Result")
            
            analyze_btn = gr.Button("Analyze Cough")
            analyze_btn.click(fn=process_audio_file, inputs=audio_input, outputs=output_text)
        
        with gr.Tab("Record Audio"):
            with gr.Row():
                audio_recorder = gr.Audio(type="numpy", source="microphone", label="Record Audio")
                record_output = gr.Textbox(label="Prediction Result")
            
            record_btn = gr.Button("Analyze Recording")
            record_btn.click(fn=process_recorded_audio, inputs=[audio_recorder], outputs=record_output)
        
        with gr.Tab("Live Cough Detection"):
            gr.Markdown("This tab continuously monitors audio for coughs and analyzes them when detected.")
            
            with gr.Row():
                live_audio = gr.Audio(type="numpy", source="microphone", streaming=True, label="Live Audio Stream")
                live_output = gr.Textbox(label="Detection Result")
            
            live_audio.stream(fn=detect_coughs_in_stream, outputs=live_output)
        
        gr.Markdown("## API Usage")
        gr.Markdown("""

        This Gradio app can be accessed via API calls. Example Python code:

        ```python

        import requests

        import json

        

        # For file upload

        files = {'audio': open('your_audio_file.wav', 'rb')}

        response = requests.post('YOUR_GRADIO_URL/api/predict', files=files)

        result = json.loads(response.content)

        print(result)

        ```

        """)
    
    return demo

# Main function
if __name__ == "__main__":
    demo = create_interface()
    demo.launch(share=True)