Kh0128/Aphasia_Classificifier
0
1#!/usr/bin/env python32"""3Lightweight Aphasia Classification App4Optimized for Hugging Face Spaces with lazy loading and fallbacks5"""6 7import os8 9# Configure environment for CPU-only and memory optimization10os.environ['CUDA_VISIBLE_DEVICES'] = '' # Force CPU-only11os.environ['PYTORCH_CUDA_ALLOC_CONF'] = 'max_split_size_mb:128'12os.environ['OMP_NUM_THREADS'] = '2' # Limit CPU threads13os.environ['MKL_NUM_THREADS'] = '2'14os.environ['NUMEXPR_NUM_THREADS'] = '2'15os.environ['TOKENIZERS_PARALLELISM'] = 'false' # Avoid tokenizer warnings16 17# Batchalign specific settings18os.environ['BATCHALIGN_CACHE'] = '/tmp/batchalign_cache'19os.environ['HF_HUB_CACHE'] = '/tmp/hf_cache' # Use tmp for model cache20os.environ['TRANSFORMERS_CACHE'] = '/tmp/transformers_cache'21 22# Whisper settings for CPU optimization23os.environ['WHISPER_CACHE'] = '/tmp/whisper_cache'24 25print("๐ง Environment configured for CPU-only processing")26print("๐พ Model caches set to /tmp/ to save space")27 28 29from flask import Flask, request, render_template_string, jsonify30import os31import tempfile32import logging33import json34import threading35import time36from pathlib import Path37 38# Set up logging39logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')40logger = logging.getLogger(__name__)41 42app = Flask(__name__)43app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 # 50MB max (reduced)44 45print("๐ Starting Lightweight Aphasia Classification System")46 47# Global state48MODULES = {}49MODELS_LOADED = False50LOADING_STATUS = "Starting up..."51 52def lazy_import_modules():53 """Import modules only when needed"""54 global MODULES, MODELS_LOADED, LOADING_STATUS55 56 if MODELS_LOADED:57 return True58 59 try:60 LOADING_STATUS = "Loading audio processing..."61 logger.info("Importing utils_audio...")62 from utils_audio import convert_to_wav63 MODULES['convert_to_wav'] = convert_to_wav64 logger.info("โ Audio processing loaded")65 66 LOADING_STATUS = "Loading speech analysis..."67 logger.info("Importing to_cha...")68 from to_cha import to_cha_from_wav69 MODULES['to_cha_from_wav'] = to_cha_from_wav70 logger.info("โ Speech analysis loaded")71 72 LOADING_STATUS = "Loading data conversion..."73 logger.info("Importing cha_json...")74 from cha_json import cha_to_json_file75 MODULES['cha_to_json_file'] = cha_to_json_file76 logger.info("โ Data conversion loaded")77 78 LOADING_STATUS = "Loading AI model..."79 logger.info("Importing output...")80 from output import predict_from_chajson81 MODULES['predict_from_chajson'] = predict_from_chajson82 logger.info("โ AI model loaded")83 84 MODELS_LOADED = True85 LOADING_STATUS = "Ready!"86 logger.info("๐ All modules loaded successfully!")87 return True88 89 except Exception as e:90 logger.error(f"Failed to load modules: {e}")91 LOADING_STATUS = f"Error: {str(e)}"92 return False93 94def background_loader():95 """Load modules in background thread"""96 logger.info("Starting background module loading...")97 lazy_import_modules()98 99# Start loading modules in background100loading_thread = threading.Thread(target=background_loader, daemon=True)101loading_thread.start()102 103# HTML Template (simplified)104HTML_TEMPLATE = """105<!DOCTYPE html>106<html lang="en">107<head>108 <meta charset="UTF-8">109 <meta name="viewport" content="width=device-width, initial-scale=1.0">110 <title>๐ง Aphasia Classification</title>111 <style>112 body {113 font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;114 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);115 min-height: 100vh;116 padding: 20px;117 margin: 0;118 }119 120 .container {121 max-width: 800px;122 margin: 0 auto;123 background: white;124 border-radius: 20px;125 box-shadow: 0 20px 60px rgba(0,0,0,0.1);126 overflow: hidden;127 }128 129 .header {130 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);131 color: white;132 padding: 40px 30px;133 text-align: center;134 }135 136 .content {137 padding: 40px 30px;138 }139 140 .status {141 background: #f8f9fa;142 border-radius: 10px;143 padding: 20px;144 margin-bottom: 30px;145 border-left: 4px solid #28a745;146 }147 148 .status.loading {149 border-left-color: #ffc107;150 }151 152 .status.error {153 border-left-color: #dc3545;154 }155 156 .upload-section {157 background: #f8f9fa;158 border-radius: 15px;159 padding: 30px;160 text-align: center;161 margin-bottom: 30px;162 }163 164 .file-input {165 display: none;166 }167 168 .file-label {169 display: inline-block;170 background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);171 color: white;172 padding: 15px 30px;173 border-radius: 50px;174 cursor: pointer;175 font-weight: 600;176 transition: transform 0.2s ease;177 }178 179 .file-label:hover {180 transform: translateY(-2px);181 }182 183 .analyze-btn {184 background: #28a745;185 color: white;186 border: none;187 padding: 15px 40px;188 border-radius: 50px;189 font-weight: 600;190 cursor: pointer;191 margin-top: 20px;192 transition: all 0.2s ease;193 }194 195 .analyze-btn:disabled {196 background: #6c757d;197 cursor: not-allowed;198 }199 200 .results {201 background: #f8f9fa;202 border-radius: 15px;203 padding: 30px;204 margin-top: 30px;205 display: none;206 white-space: pre-wrap;207 font-family: monospace;208 }209 210 .loading {211 text-align: center;212 padding: 40px;213 display: none;214 }215 216 .spinner {217 border: 4px solid #f3f3f3;218 border-top: 4px solid #667eea;219 border-radius: 50%;220 width: 50px;221 height: 50px;222 animation: spin 1s linear infinite;223 margin: 0 auto 20px;224 }225 226 @keyframes spin {227 0% { transform: rotate(0deg); }228 100% { transform: rotate(360deg); }229 }230 231 .refresh-btn {232 background: #17a2b8;233 color: white;234 border: none;235 padding: 10px 20px;236 border-radius: 25px;237 cursor: pointer;238 margin-left: 10px;239 }240 </style>241</head>242<body>243 <div class="container">244 <div class="header">245 <h1>๐ง Aphasia Classification</h1>246 <p>AI-powered speech analysis for aphasia identification</p>247 </div>248 249 <div class="content">250 <div class="status" id="statusBox">251 <h3 id="statusTitle">๐ System Status</h3>252 <div id="statusText">{{ status_message }}</div>253 <button class="refresh-btn" onclick="checkStatus()">Refresh Status</button>254 </div>255 256 <div class="upload-section">257 <h3>๐ Upload Audio File</h3>258 <p>Upload speech audio for aphasia classification</p>259 260 <form id="uploadForm" enctype="multipart/form-data">261 <input type="file" id="audioFile" name="audio" class="file-input" accept="audio/*" required>262 <label for="audioFile" class="file-label">263 ๐ต Choose Audio File264 </label>265 <br>266 <button type="submit" class="analyze-btn" id="analyzeBtn">267 ๐ Analyze Speech268 </button>269 </form>270 271 <p style="color: #666; margin-top: 15px; font-size: 0.9em;">272 Supported: MP3, WAV, M4A (max 50MB)273 </p>274 </div>275 276 <div class="loading" id="loading">277 <div class="spinner"></div>278 <h3>๐ Processing Audio...</h3>279 <p>This may take 2-5 minutes. Please be patient.</p>280 </div>281 282 <div class="results" id="results"></div>283 </div>284 </div>285 286 <script>287 // Check status periodically288 function checkStatus() {289 fetch('/status')290 .then(response => response.json())291 .then(data => {292 const statusBox = document.getElementById('statusBox');293 const statusTitle = document.getElementById('statusTitle');294 const statusText = document.getElementById('statusText');295 296 if (data.ready) {297 statusBox.className = 'status';298 statusTitle.textContent = '๐ข System Ready';299 statusText.textContent = 'All components loaded. Ready to process audio files.';300 } else {301 statusBox.className = 'status loading';302 statusTitle.textContent = '๐ก Loading...';303 statusText.textContent = data.status;304 }305 })306 .catch(error => {307 const statusBox = document.getElementById('statusBox');308 statusBox.className = 'status error';309 document.getElementById('statusTitle').textContent = '๐ด Error';310 document.getElementById('statusText').textContent = 'Failed to check status';311 });312 }313 314 // Check status every 5 seconds315 setInterval(checkStatus, 5000);316 317 // Form submission318 document.getElementById('uploadForm').addEventListener('submit', async function(e) {319 e.preventDefault();320 321 const fileInput = document.getElementById('audioFile');322 const loading = document.getElementById('loading');323 const results = document.getElementById('results');324 const analyzeBtn = document.getElementById('analyzeBtn');325 326 if (!fileInput.files[0]) {327 alert('Please select an audio file');328 return;329 }330 331 // Check if system is ready332 const statusCheck = await fetch('/status');333 const status = await statusCheck.json();334 335 if (!status.ready) {336 alert('System is still loading. Please wait and try again.');337 return;338 }339 340 // Show loading341 loading.style.display = 'block';342 results.style.display = 'none';343 analyzeBtn.disabled = true;344 analyzeBtn.textContent = 'Processing...';345 346 try {347 const formData = new FormData();348 formData.append('audio', fileInput.files[0]);349 350 const response = await fetch('/analyze', {351 method: 'POST',352 body: formData353 });354 355 const data = await response.json();356 357 loading.style.display = 'none';358 359 if (data.success) {360 results.textContent = data.result;361 results.style.borderLeft = '4px solid #28a745';362 } else {363 results.textContent = 'Error: ' + data.error;364 results.style.borderLeft = '4px solid #dc3545';365 }366 367 results.style.display = 'block';368 369 } catch (error) {370 loading.style.display = 'none';371 results.textContent = 'Network error: ' + error.message;372 results.style.borderLeft = '4px solid #dc3545';373 results.style.display = 'block';374 }375 376 analyzeBtn.disabled = false;377 analyzeBtn.textContent = '๐ Analyze Speech';378 });379 380 // File selection feedback381 document.getElementById('audioFile').addEventListener('change', function(e) {382 const label = document.querySelector('.file-label');383 if (e.target.files[0]) {384 label.textContent = 'โ ' + e.target.files[0].name;385 } else {386 label.textContent = '๐ต Choose Audio File';387 }388 });389 </script>390</body>391</html>392"""393 394@app.route('/')395def index():396 """Main page"""397 return render_template_string(HTML_TEMPLATE, status_message=LOADING_STATUS)398 399@app.route('/status')400def status():401 """Status check endpoint"""402 return jsonify({403 'ready': MODELS_LOADED,404 'status': LOADING_STATUS,405 'modules_loaded': len(MODULES)406 })407 408@app.route('/analyze', methods=['POST'])409def analyze_audio():410 """Process uploaded audio - only if models are loaded"""411 try:412 # Check if system is ready413 if not MODELS_LOADED:414 return jsonify({415 'success': False, 416 'error': f'System still loading: {LOADING_STATUS}'417 })418 419 # Check file upload420 if 'audio' not in request.files:421 return jsonify({'success': False, 'error': 'No audio file uploaded'})422 423 audio_file = request.files['audio']424 if audio_file.filename == '':425 return jsonify({'success': False, 'error': 'No file selected'})426 427 # Save uploaded file428 with tempfile.NamedTemporaryFile(delete=False, suffix=os.path.splitext(audio_file.filename)[1]) as tmp_file:429 audio_file.save(tmp_file.name)430 temp_path = tmp_file.name431 432 try:433 logger.info("๐ต Starting audio processing...")434 435 # Step 1: Convert to WAV436 logger.info("Converting to WAV...")437 wav_path = MODULES['convert_to_wav'](temp_path, sr=16000, mono=True)438 439 # Step 2: Generate CHA440 logger.info("Generating CHA file...")441 cha_path = MODULES['to_cha_from_wav'](wav_path, lang="eng")442 443 # Step 3: Convert to JSON444 logger.info("Converting to JSON...")445 json_path, _ = MODULES['cha_to_json_file'](cha_path)446 447 # Step 4: Classification448 logger.info("Running classification...")449 results = MODULES['predict_from_chajson'](".", json_path, output_file=None)450 451 # Cleanup452 for temp_file in [temp_path, wav_path, cha_path, json_path]:453 try:454 os.unlink(temp_file)455 except:456 pass457 458 # Format results459 if "predictions" in results and results["predictions"]:460 pred = results["predictions"][0]461 462 classification = pred["prediction"]["predicted_class"]463 confidence = pred["prediction"]["confidence_percentage"]464 description = pred["class_description"]["name"]465 severity = pred["additional_predictions"]["predicted_severity_level"]466 fluency = pred["additional_predictions"]["fluency_rating"]467 468 result_text = f"""๐ง APHASIA CLASSIFICATION RESULTS469 470๐ฏ Classification: {classification}471๐ Confidence: {confidence}472๐ Type: {description}473๐ Severity: {severity}/3474๐ฃ๏ธ Fluency: {fluency}475 476๐ Top 3 Probabilities:"""477 478 prob_dist = pred["probability_distribution"]479 for i, (atype, info) in enumerate(list(prob_dist.items())[:3], 1):480 result_text += f"\n{i}. {atype}: {info['percentage']}"481 482 result_text += f"""483 484๐ Description:485{pred["class_description"]["description"]}486 487โ
Processing completed successfully!488"""489 490 return jsonify({'success': True, 'result': result_text})491 else:492 return jsonify({'success': False, 'error': 'No predictions generated'})493 494 except Exception as e:495 # Cleanup on error496 try:497 os.unlink(temp_path)498 except:499 pass500 raise e501 502 except Exception as e:503 logger.error(f"Processing error: {e}")504 return jsonify({'success': False, 'error': str(e)})505 506if __name__ == '__main__':507 port = int(os.environ.get('PORT', 7860))508 print(f"๐ Starting on port {port}")509 print("๐ Models loading in background...")510 511 app.run(host='0.0.0.0', port=port, debug=False, threaded=True)