Ellie5757575757 commited on
Commit
28fc64c
·
verified ·
1 Parent(s): 63d2b83

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +444 -364
app.py CHANGED
@@ -1,449 +1,529 @@
1
- import gradio as gr
2
- from flask import Flask
 
 
 
 
 
3
  import os
4
  import tempfile
5
  import logging
6
- import threading
7
- import time
 
8
 
9
  # Set up logging
10
  logging.basicConfig(level=logging.INFO)
11
  logger = logging.getLogger(__name__)
12
 
13
- # Configuration
14
- MODEL_DIR = "."
15
- SUPPORTED_AUDIO_FORMATS = [".mp3", ".mp4", ".wav", ".m4a", ".flac", ".ogg"]
 
16
 
17
  def safe_import_modules():
18
- """Safely import pipeline modules with error handling"""
19
  modules = {}
20
 
21
  try:
22
  from utils_audio import convert_to_wav
23
  modules['convert_to_wav'] = convert_to_wav
24
- logger.info("✓ utils_audio imported successfully")
25
  except Exception as e:
26
- logger.error(f"✗ Failed to import utils_audio: {e}")
27
  modules['convert_to_wav'] = None
28
 
29
  try:
30
  from to_cha import to_cha_from_wav
31
  modules['to_cha_from_wav'] = to_cha_from_wav
32
- logger.info("✓ to_cha imported successfully")
33
  except Exception as e:
34
- logger.error(f"✗ Failed to import to_cha: {e}")
35
  modules['to_cha_from_wav'] = None
36
 
37
  try:
38
  from cha_json import cha_to_json_file
39
  modules['cha_to_json_file'] = cha_to_json_file
40
- logger.info("✓ cha_json imported successfully")
41
  except Exception as e:
42
- logger.error(f"✗ Failed to import cha_json: {e}")
43
  modules['cha_to_json_file'] = None
44
 
45
  try:
46
  from output import predict_from_chajson
47
  modules['predict_from_chajson'] = predict_from_chajson
48
- logger.info("✓ output imported successfully")
49
  except Exception as e:
50
- logger.error(f"✗ Failed to import output: {e}")
51
  modules['predict_from_chajson'] = None
52
 
53
  return modules
54
 
55
  # Import modules
56
  MODULES = safe_import_modules()
 
57
 
58
- def check_model_files():
59
- """Check if required model files exist"""
60
- required_files = [
61
- "pytorch_model.bin",
62
- "config.json",
63
- "tokenizer.json",
64
- "tokenizer_config.json"
65
- ]
66
-
67
- missing_files = []
68
- for file in required_files:
69
- if not os.path.exists(os.path.join(MODEL_DIR, file)):
70
- missing_files.append(file)
71
-
72
- return len(missing_files) == 0, missing_files
73
-
74
- def run_complete_pipeline(audio_file_path: str) -> dict:
75
- """Complete pipeline: Audio → WAV → CHA → JSON → Model Prediction"""
76
-
77
- # Check if all modules are available
78
- if not all(MODULES.values()):
79
- missing = [k for k, v in MODULES.items() if v is None]
80
- return {
81
- "success": False,
82
- "error": f"Missing required modules: {missing}",
83
- "message": "Pipeline modules not available"
84
  }
85
-
86
- try:
87
- logger.info(f"Starting pipeline for: {audio_file_path}")
88
 
89
- # Step 1: Convert to WAV
90
- logger.info("Step 1: Converting audio to WAV...")
91
- wav_path = MODULES['convert_to_wav'](audio_file_path, sr=16000, mono=True)
92
- logger.info(f"WAV conversion completed: {wav_path}")
 
 
 
 
 
 
 
 
 
 
 
93
 
94
- # Step 2: Generate CHA file using Batchalign
95
- logger.info("Step 2: Generating CHA file...")
96
- cha_path = MODULES['to_cha_from_wav'](wav_path, lang="eng")
97
- logger.info(f"CHA generation completed: {cha_path}")
 
 
98
 
99
- # Step 3: Convert CHA to JSON
100
- logger.info("Step 3: Converting CHA to JSON...")
101
- chajson_path, json_data = MODULES['cha_to_json_file'](cha_path)
102
- logger.info(f"JSON conversion completed: {chajson_path}")
 
103
 
104
- # Step 4: Run aphasia classification
105
- logger.info("Step 4: Running aphasia classification...")
106
- results = MODULES['predict_from_chajson'](MODEL_DIR, chajson_path, output_file=None)
107
- logger.info("Classification completed")
108
 
109
- # Cleanup temporary files
110
- try:
111
- os.unlink(wav_path)
112
- os.unlink(cha_path)
113
- os.unlink(chajson_path)
114
- except Exception as cleanup_error:
115
- logger.warning(f"Cleanup error: {cleanup_error}")
116
-
117
- return {
118
- "success": True,
119
- "results": results,
120
- "message": "Pipeline completed successfully"
121
  }
122
 
123
- except Exception as e:
124
- logger.error(f"Pipeline error: {str(e)}")
125
- import traceback
126
- traceback.print_exc()
127
- return {
128
- "success": False,
129
- "error": str(e),
130
- "message": f"Pipeline failed: {str(e)}"
131
  }
132
-
133
- def process_audio_input(audio_file):
134
- """Process audio file and return formatted results"""
135
- try:
136
- if audio_file is None:
137
- return "❌ Error: No audio file uploaded"
138
 
139
- # Check if pipeline is available
140
- if not all(MODULES.values()):
141
- missing_modules = [k for k, v in MODULES.items() if v is None]
142
- return f"❌ Error: Audio processing pipeline not available. Missing required modules: {', '.join(missing_modules)}"
143
 
144
- # Check file format
145
- file_path = audio_file
146
- if hasattr(audio_file, 'name'):
147
- file_path = audio_file.name
 
 
 
 
 
148
 
149
- from pathlib import Path
150
- file_ext = Path(file_path).suffix.lower()
151
- if file_ext not in SUPPORTED_AUDIO_FORMATS:
152
- return f"❌ Error: Unsupported file format {file_ext}. Supported: {', '.join(SUPPORTED_AUDIO_FORMATS)}"
153
 
154
- # Run the complete pipeline
155
- pipeline_result = run_complete_pipeline(file_path)
 
156
 
157
- if not pipeline_result["success"]:
158
- return f"❌ Pipeline Error: {pipeline_result['message']}\n\nDetails: {pipeline_result.get('error', '')}"
 
 
 
 
 
 
 
 
 
159
 
160
- # Format results
161
- results = pipeline_result["results"]
 
162
 
163
- if "predictions" in results and len(results["predictions"]) > 0:
164
- first_pred = results["predictions"][0]
165
-
166
- if "error" in first_pred:
167
- return f"❌ Classification Error: {first_pred['error']}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
- # Format main result
170
- predicted_class = first_pred["prediction"]["predicted_class"]
171
- confidence = first_pred["prediction"]["confidence_percentage"]
172
- class_name = first_pred["class_description"]["name"]
173
- description = first_pred["class_description"]["description"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- # Additional metrics
176
- additional_info = first_pred["additional_predictions"]
177
- severity_level = additional_info["predicted_severity_level"]
178
- fluency_score = additional_info["fluency_score"]
179
- fluency_rating = additional_info["fluency_rating"]
180
 
181
- # Format probability distribution (top 3)
182
- prob_dist = first_pred["probability_distribution"]
183
- top_3 = list(prob_dist.items())[:3]
184
 
185
- result_text = f"""🧠 **APHASIA CLASSIFICATION RESULTS**
186
-
187
- 🎯 **Primary Classification:** {predicted_class}
188
- 📊 **Confidence:** {confidence}
189
- 📋 **Type:** {class_name}
 
 
 
 
 
 
 
 
190
 
191
- 📈 **Additional Metrics:**
192
- Severity Level: {severity_level}/3
193
- • Fluency Score: {fluency_score:.3f} ({fluency_rating})
194
-
195
- 📊 **Top 3 Probability Rankings:**
196
- """
197
- for i, (aphasia_type, info) in enumerate(top_3, 1):
198
- result_text += f"{i}. {aphasia_type}: {info['percentage']}\n"
199
 
200
- result_text += f"""
201
- 📝 **Clinical Description:**
202
- {description}
203
-
204
- 📊 **Processing Summary:**
205
- • Total sentences analyzed: {results.get('total_sentences', 'N/A')}
206
- • Average confidence: {results.get('summary', {}).get('average_confidence', 'N/A')}
207
- • Average fluency: {results.get('summary', {}).get('average_fluency_score', 'N/A')}
208
- """
209
 
210
- return result_text
211
-
212
- else:
213
- return "❌ No predictions generated. The audio file may not contain analyzable speech."
214
 
215
- except Exception as e:
216
- logger.error(f"Processing error: {str(e)}")
217
- import traceback
218
- traceback.print_exc()
219
- return f"❌ Processing Error: {str(e)}\n\nPlease check the logs for more details."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
220
 
221
- def process_text_input(text_input):
222
- """Process text input directly (fallback option)"""
 
223
  try:
224
- if not text_input or not text_input.strip():
225
- return "❌ Error: Please enter some text for analysis"
226
-
227
- # Check if prediction module is available
228
- if MODULES['predict_from_chajson'] is None:
229
- return "❌ Error: Text analysis not available. Missing prediction module."
230
-
231
- # Create a simple JSON structure for text-only input
232
- import json
233
- temp_json = {
234
- "sentences": [{
235
- "sentence_id": "S1",
236
- "aphasia_type": "UNKNOWN",
237
- "dialogues": [{
238
- "INV": [],
239
- "PAR": [{
240
- "tokens": text_input.split(),
241
- "word_pos_ids": [0] * len(text_input.split()),
242
- "word_grammar_ids": [[0, 0, 0]] * len(text_input.split()),
243
- "word_durations": [0.0] * len(text_input.split()),
244
- "utterance_text": text_input
245
- }]
246
- }]
247
- }],
248
- "text_all": text_input
249
- }
250
 
251
- # Save to temporary file
252
- with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f:
253
- json.dump(temp_json, f, ensure_ascii=False, indent=2)
254
- temp_json_path = f.name
255
 
256
- # Run prediction
257
- results = MODULES['predict_from_chajson'](MODEL_DIR, temp_json_path, output_file=None)
 
 
258
 
259
- # Cleanup
260
- try:
261
- os.unlink(temp_json_path)
262
- except:
263
- pass
264
 
265
- # Format results
266
- if "predictions" in results and len(results["predictions"]) > 0:
267
- first_pred = results["predictions"][0]
 
 
 
 
 
 
 
 
 
 
 
268
 
269
- predicted_class = first_pred["prediction"]["predicted_class"]
270
- confidence = first_pred["prediction"]["confidence_percentage"]
271
- description = first_pred["class_description"]["description"]
272
- severity = first_pred["additional_predictions"]["predicted_severity_level"]
273
- fluency = first_pred["additional_predictions"]["fluency_rating"]
274
 
275
- return f"""🧠 **TEXT ANALYSIS RESULTS**
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
- 🎯 **Predicted:** {predicted_class}
278
- 📊 **Confidence:** {confidence}
279
- 📈 **Severity:** {severity}/3
280
- 🗣️ **Fluency:** {fluency}
281
 
282
- 📝 **Description:**
 
 
 
 
 
 
 
283
  {description}
284
 
285
- ℹ️ **Note:** Text-based analysis provides limited accuracy compared to audio analysis.
 
 
 
286
  """
287
- else:
288
- return "❌ No predictions generated from text input"
289
-
 
 
 
 
 
 
 
 
 
 
290
  except Exception as e:
291
- logger.error(f"Text processing error: {str(e)}")
292
- return f"❌ Error: {str(e)}"
293
-
294
- def create_gradio_app():
295
- """Create the Gradio interface"""
296
-
297
- # Check system status
298
- model_available, missing_files = check_model_files()
299
- pipeline_available = all(MODULES.values())
300
-
301
- status_message = "🟢 System Ready" if model_available and pipeline_available else "🔴 System Issues"
302
-
303
- status_details = []
304
- if not model_available:
305
- status_details.append(f"Missing model files: {', '.join(missing_files)}")
306
- if not pipeline_available:
307
- missing_modules = [k for k, v in MODULES.items() if v is None]
308
- status_details.append(f"Missing modules: {', '.join(missing_modules)}")
309
-
310
- # Create simple interfaces to avoid JSON schema issues
311
- audio_demo = gr.Interface(
312
- fn=process_audio_input,
313
- inputs=gr.File(label="Upload Audio File", file_types=["audio"]),
314
- outputs=gr.Textbox(label="Analysis Results", lines=25),
315
- title="🎵 Audio Analysis",
316
- description="Upload MP3, MP4, WAV, M4A, FLAC, or OGG files"
317
- )
318
-
319
- text_demo = gr.Interface(
320
- fn=process_text_input,
321
- inputs=gr.Textbox(label="Enter Text", lines=5, placeholder="Enter speech transcription..."),
322
- outputs=gr.Textbox(label="Analysis Results", lines=15),
323
- title="📝 Text Analysis",
324
- description="Enter text for direct analysis (less accurate than audio)"
325
- )
326
-
327
- # Combine interfaces using TabbedInterface
328
- demo = gr.TabbedInterface(
329
- [audio_demo, text_demo],
330
- ["Audio Analysis", "Text Analysis"],
331
- title="🧠 Aphasia Classification System",
332
- theme=gr.themes.Soft()
333
- )
334
-
335
- return demo
336
 
337
- def create_flask_app():
338
- """Create Flask app that serves Gradio"""
339
-
340
- # Create Flask app
341
- flask_app = Flask(__name__)
342
-
343
- # Create Gradio app
344
- gradio_app = create_gradio_app()
345
-
346
- # Mount Gradio app on Flask
347
- gradio_app.queue() # Enable queuing for better performance
348
-
349
- # Get the underlying FastAPI app from Gradio
350
- gradio_fastapi_app = gradio_app.app
351
-
352
- # Add a health check endpoint
353
- @flask_app.route('/health')
354
- def health_check():
355
- model_available, missing_files = check_model_files()
356
- pipeline_available = all(MODULES.values())
357
-
358
- return {
359
- "status": "healthy" if model_available and pipeline_available else "unhealthy",
360
- "model_available": model_available,
361
- "pipeline_available": pipeline_available,
362
- "missing_files": missing_files if not model_available else [],
363
- "missing_modules": [k for k, v in MODULES.items() if v is None] if not pipeline_available else []
364
- }
365
-
366
- # Add info endpoint
367
- @flask_app.route('/info')
368
- def info():
369
- return {
370
- "title": "Aphasia Classification System",
371
- "description": "AI-powered aphasia type classification from audio",
372
- "supported_formats": SUPPORTED_AUDIO_FORMATS,
373
- "endpoints": {
374
- "/": "Main Gradio interface",
375
- "/health": "Health check",
376
- "/info": "System information"
377
- }
378
- }
379
-
380
- return flask_app, gradio_app
381
 
382
- def run_gradio_on_flask():
383
- """Run Gradio app mounted on Flask"""
384
-
385
- logger.info("Starting Aphasia Classification System with Flask + Gradio...")
386
-
387
- # Create Flask and Gradio apps
388
- flask_app, gradio_app = create_flask_app()
389
-
390
- # Detect environment
391
  port = int(os.environ.get('PORT', 7860))
392
  host = os.environ.get('HOST', '0.0.0.0')
393
 
394
- # Check if we're in a cloud environment
395
- is_cloud = any(os.getenv(indicator) for indicator in [
396
- 'SPACE_ID', 'PAPERSPACE_NOTEBOOK_REPO_ID',
397
- 'COLAB_GPU', 'KAGGLE_KERNEL_RUN_TYPE'
398
- ])
399
-
400
- logger.info(f"Environment - Cloud: {is_cloud}, Host: {host}, Port: {port}")
401
-
402
- def run_gradio():
403
- """Run Gradio in a separate thread"""
404
- try:
405
- gradio_app.launch(
406
- server_name=host,
407
- server_port=port,
408
- share=is_cloud, # Auto-enable share in cloud environments
409
- show_error=True,
410
- quiet=False,
411
- prevent_thread_lock=True # Important for running with Flask
412
- )
413
- except Exception as e:
414
- logger.error(f"Failed to start Gradio: {e}")
415
-
416
- # Start Gradio in background thread
417
- gradio_thread = threading.Thread(target=run_gradio, daemon=True)
418
- gradio_thread.start()
419
 
420
- # Give Gradio time to start
421
- time.sleep(2)
422
-
423
- logger.info(f"✓ Gradio app started on {host}:{port}")
424
- logger.info("✓ Flask health endpoints available at /health and /info")
425
-
426
- # Keep the main thread alive
427
- try:
428
- while True:
429
- time.sleep(1)
430
- except KeyboardInterrupt:
431
- logger.info("Shutting down...")
432
-
433
- if __name__ == "__main__":
434
- try:
435
- run_gradio_on_flask()
436
- except Exception as e:
437
- logger.error(f"Failed to start application: {e}")
438
- import traceback
439
- traceback.print_exc()
440
-
441
- # Fallback to basic Gradio if Flask setup fails
442
- logger.info("Falling back to basic Gradio interface...")
443
- demo = create_gradio_app()
444
- demo.launch(
445
- server_name="0.0.0.0",
446
- server_port=7860,
447
- share=True,
448
- show_error=True
449
- )
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ Pure Flask App for Aphasia Classification
4
+ No Gradio dependency - works anywhere!
5
+ """
6
+
7
+ from flask import Flask, request, render_template_string, jsonify, send_from_directory
8
  import os
9
  import tempfile
10
  import logging
11
+ import json
12
+ from pathlib import Path
13
+ import traceback
14
 
15
  # Set up logging
16
  logging.basicConfig(level=logging.INFO)
17
  logger = logging.getLogger(__name__)
18
 
19
+ app = Flask(__name__)
20
+ app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024 # 100MB max
21
+
22
+ print("🚀 Starting Aphasia Classification System (Flask)")
23
 
24
  def safe_import_modules():
25
+ """Import pipeline modules safely"""
26
  modules = {}
27
 
28
  try:
29
  from utils_audio import convert_to_wav
30
  modules['convert_to_wav'] = convert_to_wav
31
+ logger.info("✓ utils_audio imported")
32
  except Exception as e:
33
+ logger.error(f"✗ utils_audio failed: {e}")
34
  modules['convert_to_wav'] = None
35
 
36
  try:
37
  from to_cha import to_cha_from_wav
38
  modules['to_cha_from_wav'] = to_cha_from_wav
39
+ logger.info("✓ to_cha imported")
40
  except Exception as e:
41
+ logger.error(f"✗ to_cha failed: {e}")
42
  modules['to_cha_from_wav'] = None
43
 
44
  try:
45
  from cha_json import cha_to_json_file
46
  modules['cha_to_json_file'] = cha_to_json_file
47
+ logger.info("✓ cha_json imported")
48
  except Exception as e:
49
+ logger.error(f"✗ cha_json failed: {e}")
50
  modules['cha_to_json_file'] = None
51
 
52
  try:
53
  from output import predict_from_chajson
54
  modules['predict_from_chajson'] = predict_from_chajson
55
+ logger.info("✓ output imported")
56
  except Exception as e:
57
+ logger.error(f"✗ output failed: {e}")
58
  modules['predict_from_chajson'] = None
59
 
60
  return modules
61
 
62
  # Import modules
63
  MODULES = safe_import_modules()
64
+ MODEL_DIR = "."
65
 
66
+ # HTML Template
67
+ HTML_TEMPLATE = """
68
+ <!DOCTYPE html>
69
+ <html lang="en">
70
+ <head>
71
+ <meta charset="UTF-8">
72
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
73
+ <title>🧠 Aphasia Classification System</title>
74
+ <style>
75
+ * {
76
+ margin: 0;
77
+ padding: 0;
78
+ box-sizing: border-box;
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  }
 
 
 
80
 
81
+ body {
82
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
83
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
84
+ min-height: 100vh;
85
+ padding: 20px;
86
+ }
87
+
88
+ .container {
89
+ max-width: 800px;
90
+ margin: 0 auto;
91
+ background: white;
92
+ border-radius: 20px;
93
+ box-shadow: 0 20px 60px rgba(0,0,0,0.1);
94
+ overflow: hidden;
95
+ }
96
 
97
+ .header {
98
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
99
+ color: white;
100
+ padding: 40px 30px;
101
+ text-align: center;
102
+ }
103
 
104
+ .header h1 {
105
+ font-size: 2.5em;
106
+ margin-bottom: 10px;
107
+ font-weight: 700;
108
+ }
109
 
110
+ .header p {
111
+ font-size: 1.1em;
112
+ opacity: 0.9;
113
+ }
114
 
115
+ .content {
116
+ padding: 40px 30px;
 
 
 
 
 
 
 
 
 
 
117
  }
118
 
119
+ .status {
120
+ background: #f8f9fa;
121
+ border-radius: 10px;
122
+ padding: 20px;
123
+ margin-bottom: 30px;
124
+ border-left: 4px solid #28a745;
 
 
125
  }
 
 
 
 
 
 
126
 
127
+ .status h3 {
128
+ color: #28a745;
129
+ margin-bottom: 10px;
130
+ }
131
 
132
+ .upload-section {
133
+ background: #f8f9fa;
134
+ border-radius: 15px;
135
+ padding: 30px;
136
+ margin-bottom: 30px;
137
+ border: 2px dashed #dee2e6;
138
+ text-align: center;
139
+ transition: all 0.3s ease;
140
+ }
141
 
142
+ .upload-section:hover {
143
+ border-color: #667eea;
144
+ background: #f0f4ff;
145
+ }
146
 
147
+ .file-input {
148
+ display: none;
149
+ }
150
 
151
+ .file-label {
152
+ display: inline-block;
153
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
154
+ color: white;
155
+ padding: 15px 30px;
156
+ border-radius: 50px;
157
+ cursor: pointer;
158
+ font-size: 1.1em;
159
+ font-weight: 600;
160
+ transition: transform 0.2s ease;
161
+ }
162
 
163
+ .file-label:hover {
164
+ transform: translateY(-2px);
165
+ }
166
 
167
+ .analyze-btn {
168
+ background: #28a745;
169
+ color: white;
170
+ border: none;
171
+ padding: 15px 40px;
172
+ border-radius: 50px;
173
+ font-size: 1.1em;
174
+ font-weight: 600;
175
+ cursor: pointer;
176
+ margin-top: 20px;
177
+ transition: all 0.2s ease;
178
+ }
179
+
180
+ .analyze-btn:hover {
181
+ background: #218838;
182
+ transform: translateY(-2px);
183
+ }
184
+
185
+ .analyze-btn:disabled {
186
+ background: #6c757d;
187
+ cursor: not-allowed;
188
+ transform: none;
189
+ }
190
+
191
+ .results {
192
+ background: #f8f9fa;
193
+ border-radius: 15px;
194
+ padding: 30px;
195
+ margin-top: 30px;
196
+ display: none;
197
+ }
198
+
199
+ .results.success {
200
+ border-left: 4px solid #28a745;
201
+ }
202
+
203
+ .results.error {
204
+ border-left: 4px solid #dc3545;
205
+ background: #fff5f5;
206
+ }
207
+
208
+ .loading {
209
+ text-align: center;
210
+ padding: 40px;
211
+ display: none;
212
+ }
213
+
214
+ .spinner {
215
+ border: 4px solid #f3f3f3;
216
+ border-top: 4px solid #667eea;
217
+ border-radius: 50%;
218
+ width: 50px;
219
+ height: 50px;
220
+ animation: spin 1s linear infinite;
221
+ margin: 0 auto 20px;
222
+ }
223
+
224
+ @keyframes spin {
225
+ 0% { transform: rotate(0deg); }
226
+ 100% { transform: rotate(360deg); }
227
+ }
228
+
229
+ .supported-formats {
230
+ text-align: center;
231
+ color: #6c757d;
232
+ margin-top: 15px;
233
+ font-size: 0.9em;
234
+ }
235
+
236
+ .about {
237
+ background: #fff;
238
+ border-radius: 15px;
239
+ padding: 30px;
240
+ margin-top: 30px;
241
+ border: 1px solid #dee2e6;
242
+ }
243
+
244
+ .about h3 {
245
+ color: #333;
246
+ margin-bottom: 15px;
247
+ }
248
+
249
+ .about p {
250
+ color: #666;
251
+ line-height: 1.6;
252
+ margin-bottom: 10px;
253
+ }
254
+ </style>
255
+ </head>
256
+ <body>
257
+ <div class="container">
258
+ <div class="header">
259
+ <h1>🧠 Aphasia Classification</h1>
260
+ <p>Advanced AI-powered speech analysis for aphasia type identification</p>
261
+ </div>
262
+
263
+ <div class="content">
264
+ <div class="status">
265
+ <h3>{{ status_title }}</h3>
266
+ <div>{{ status_details | safe }}</div>
267
+ </div>
268
 
269
+ <div class="upload-section">
270
+ <h3>📁 Upload Audio File</h3>
271
+ <p>Select an audio file containing speech for analysis</p>
272
+
273
+ <form id="uploadForm" enctype="multipart/form-data">
274
+ <input type="file" id="audioFile" name="audio" class="file-input" accept="audio/*" required>
275
+ <label for="audioFile" class="file-label">
276
+ 🎵 Choose Audio File
277
+ </label>
278
+ <br>
279
+ <button type="submit" class="analyze-btn" id="analyzeBtn">
280
+ 🔍 Analyze Speech
281
+ </button>
282
+ </form>
283
+
284
+ <div class="supported-formats">
285
+ Supported: MP3, WAV, MP4, M4A, FLAC, OGG
286
+ </div>
287
+ </div>
288
 
289
+ <div class="loading" id="loading">
290
+ <div class="spinner"></div>
291
+ <h3>🔄 Analyzing Audio...</h3>
292
+ <p>This may take 1-3 minutes depending on file size</p>
293
+ </div>
294
 
295
+ <div class="results" id="results">
296
+ <div id="resultsContent"></div>
297
+ </div>
298
 
299
+ <div class="about">
300
+ <h3>About This System</h3>
301
+ <p>This AI system analyzes speech patterns to classify different types of aphasia, including:</p>
302
+ <p><strong>• Broca's Aphasia:</strong> Non-fluent speech with preserved comprehension</p>
303
+ <p><strong>• Wernicke's Aphasia:</strong> Fluent but often meaningless speech</p>
304
+ <p><strong>• Anomic Aphasia:</strong> Word-finding difficulties</p>
305
+ <p><strong>• Conduction Aphasia:</strong> Fluent speech with poor repetition</p>
306
+ <p><strong>• Global Aphasia:</strong> Severe impairment in all language areas</p>
307
+ <br>
308
+ <p><em>Note: This tool is for research and educational purposes. Always consult healthcare professionals for clinical decisions.</em></p>
309
+ </div>
310
+ </div>
311
+ </div>
312
 
313
+ <script>
314
+ document.getElementById('uploadForm').addEventListener('submit', async function(e) {
315
+ e.preventDefault();
 
 
 
 
 
316
 
317
+ const fileInput = document.getElementById('audioFile');
318
+ const analyzeBtn = document.getElementById('analyzeBtn');
319
+ const loading = document.getElementById('loading');
320
+ const results = document.getElementById('results');
321
+ const resultsContent = document.getElementById('resultsContent');
 
 
 
 
322
 
323
+ if (!fileInput.files[0]) {
324
+ alert('Please select an audio file first');
325
+ return;
326
+ }
327
 
328
+ // Show loading
329
+ loading.style.display = 'block';
330
+ results.style.display = 'none';
331
+ analyzeBtn.disabled = true;
332
+ analyzeBtn.textContent = '🔄 Processing...';
333
+
334
+ try {
335
+ const formData = new FormData();
336
+ formData.append('audio', fileInput.files[0]);
337
+
338
+ const response = await fetch('/analyze', {
339
+ method: 'POST',
340
+ body: formData
341
+ });
342
+
343
+ const data = await response.json();
344
+
345
+ // Hide loading
346
+ loading.style.display = 'none';
347
+
348
+ if (data.success) {
349
+ resultsContent.innerHTML = '<pre style="white-space: pre-wrap; font-family: inherit;">' + data.result + '</pre>';
350
+ results.className = 'results success';
351
+ } else {
352
+ resultsContent.innerHTML = '<h3 style="color: #dc3545;">❌ Error</h3><p>' + data.error + '</p>';
353
+ results.className = 'results error';
354
+ }
355
+
356
+ results.style.display = 'block';
357
+
358
+ } catch (error) {
359
+ loading.style.display = 'none';
360
+ resultsContent.innerHTML = '<h3 style="color: #dc3545;">❌ Network Error</h3><p>Failed to process request: ' + error.message + '</p>';
361
+ results.className = 'results error';
362
+ results.style.display = 'block';
363
+ }
364
+
365
+ // Reset button
366
+ analyzeBtn.disabled = false;
367
+ analyzeBtn.textContent = '🔍 Analyze Speech';
368
+ });
369
+
370
+ // Update file label when file is selected
371
+ document.getElementById('audioFile').addEventListener('change', function(e) {
372
+ const label = document.querySelector('.file-label');
373
+ if (e.target.files[0]) {
374
+ label.textContent = '✓ ' + e.target.files[0].name;
375
+ } else {
376
+ label.textContent = '🎵 Choose Audio File';
377
+ }
378
+ });
379
+ </script>
380
+ </body>
381
+ </html>
382
+ """
383
+
384
+ @app.route('/')
385
+ def index():
386
+ """Main page"""
387
+ # Check system status
388
+ modules_ready = all(MODULES.values())
389
+ missing_modules = [k for k, v in MODULES.items() if v is None]
390
+
391
+ if modules_ready:
392
+ status_title = "🟢 System Ready"
393
+ status_details = "All components loaded successfully. Ready to process audio files."
394
+ else:
395
+ status_title = "🟡 System Loading"
396
+ status_details = f"Missing modules: {', '.join(missing_modules)}<br>Some features may not be available."
397
+
398
+ return render_template_string(HTML_TEMPLATE,
399
+ status_title=status_title,
400
+ status_details=status_details)
401
 
402
+ @app.route('/analyze', methods=['POST'])
403
+ def analyze_audio():
404
+ """Process uploaded audio file"""
405
  try:
406
+ # Check if file was uploaded
407
+ if 'audio' not in request.files:
408
+ return jsonify({'success': False, 'error': 'No audio file uploaded'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
409
 
410
+ audio_file = request.files['audio']
411
+ if audio_file.filename == '':
412
+ return jsonify({'success': False, 'error': 'No file selected'})
 
413
 
414
+ # Check if modules are available
415
+ if not all(MODULES.values()):
416
+ missing = [k for k, v in MODULES.items() if v is None]
417
+ return jsonify({'success': False, 'error': f'System not ready. Missing: {", ".join(missing)}'})
418
 
419
+ # Save uploaded file temporarily
420
+ with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(audio_file.filename)[1]) as tmp_file:
421
+ audio_file.save(tmp_file.name)
422
+ temp_audio_path = tmp_file.name
 
423
 
424
+ try:
425
+ logger.info("🎵 Starting audio processing pipeline...")
426
+
427
+ # Step 1: Convert to WAV
428
+ wav_path = MODULES['convert_to_wav'](temp_audio_path, sr=16000, mono=True)
429
+ logger.info("✓ Audio converted to WAV")
430
+
431
+ # Step 2: Generate CHA file
432
+ cha_path = MODULES['to_cha_from_wav'](wav_path, lang="eng")
433
+ logger.info("✓ CHA file generated")
434
+
435
+ # Step 3: Convert CHA to JSON
436
+ json_path, _ = MODULES['cha_to_json_file'](cha_path)
437
+ logger.info("✓ JSON conversion completed")
438
 
439
+ # Step 4: Run classification
440
+ results = MODULES['predict_from_chajson'](MODEL_DIR, json_path, output_file=None)
441
+ logger.info("✓ Classification completed")
 
 
442
 
443
+ # Clean up temporary files
444
+ for temp_file in [temp_audio_path, wav_path, cha_path, json_path]:
445
+ try:
446
+ os.unlink(temp_file)
447
+ except:
448
+ pass
449
+
450
+ # Format results
451
+ if "predictions" in results and results["predictions"]:
452
+ pred = results["predictions"][0]
453
+
454
+ if "error" in pred:
455
+ return jsonify({'success': False, 'error': f'Classification error: {pred["error"]}'})
456
+
457
+ # Format the result text
458
+ classification = pred["prediction"]["predicted_class"]
459
+ confidence = pred["prediction"]["confidence_percentage"]
460
+ type_name = pred["class_description"]["name"]
461
+ description = pred["class_description"]["description"]
462
+ severity = pred["additional_predictions"]["predicted_severity_level"]
463
+ fluency = pred["additional_predictions"]["fluency_rating"]
464
+
465
+ result_text = f"""🧠 APHASIA CLASSIFICATION RESULTS
466
+
467
+ 🎯 Primary Classification: {classification}
468
+ 📊 Confidence: {confidence}
469
+ 📋 Type: {type_name}
470
+ 📈 Severity Level: {severity}/3
471
+ 🗣️ Fluency Rating: {fluency}
472
 
473
+ 📊 Top 3 Probability Rankings:"""
 
 
 
474
 
475
+ # Add probability distribution
476
+ prob_dist = pred["probability_distribution"]
477
+ for i, (atype, info) in enumerate(list(prob_dist.items())[:3], 1):
478
+ result_text += f"\n{i}. {atype}: {info['percentage']}"
479
+
480
+ result_text += f"""
481
+
482
+ 📝 Clinical Description:
483
  {description}
484
 
485
+ 📊 Processing Summary:
486
+ • Total sentences analyzed: {results.get('total_sentences', 'N/A')}
487
+ • Average confidence: {results.get('summary', {}).get('average_confidence', 'N/A')}
488
+ • Processing completed successfully
489
  """
490
+
491
+ return jsonify({'success': True, 'result': result_text})
492
+ else:
493
+ return jsonify({'success': False, 'error': 'No predictions generated from the audio file'})
494
+
495
+ except Exception as e:
496
+ # Clean up temp file on error
497
+ try:
498
+ os.unlink(temp_audio_path)
499
+ except:
500
+ pass
501
+ raise e
502
+
503
  except Exception as e:
504
+ logger.error(f"Processing error: {e}")
505
+ traceback.print_exc()
506
+ return jsonify({'success': False, 'error': f'Processing failed: {str(e)}'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
 
508
+ @app.route('/health')
509
+ def health_check():
510
+ """Health check endpoint"""
511
+ modules_ready = all(MODULES.values())
512
+ return jsonify({
513
+ 'status': 'healthy' if modules_ready else 'degraded',
514
+ 'modules': {k: v is not None for k, v in MODULES.items()},
515
+ 'ready': modules_ready
516
+ })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
517
 
518
+ if __name__ == '__main__':
519
+ # Get port from environment (for Hugging Face Spaces)
 
 
 
 
 
 
 
520
  port = int(os.environ.get('PORT', 7860))
521
  host = os.environ.get('HOST', '0.0.0.0')
522
 
523
+ print(f"🚀 Starting Flask app on {host}:{port}")
524
+ print("📋 Modules status:")
525
+ for name, module in MODULES.items():
526
+ status = "✓" if module else "❌"
527
+ print(f" {status} {name}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
528
 
529
+ app.run(host=host, port=port, debug=False)