CoolFace
Apppublic

Kh0128/Aphasia_Classificifier

sourceHugging Faceupdated 1y agoView on Hugging Face
0likes
app.py511 linesDownload Raw Back to root
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)