import os import base64 import json import requests import re import numpy as np import tensorflow as tf from flask import Flask, request, render_template, make_response, session from werkzeug.utils import secure_filename import cv2 from flask_limiter import Limiter from flask_limiter.util import get_remote_address from weasyprint import HTML import datetime import time import csv import traceback import markdown # REQUIRED: For robust Markdown to HTML conversion import secrets os.environ['HF_HOME'] = '/tmp/hf_home' os.environ['HF_HUB_CACHE'] = '/tmp/hf_cache' os.environ['HUGGINGFACE_HUB_CACHE'] = '/tmp/hf_cache' os.environ['TRANSFORMERS_CACHE'] = '/tmp/transformers_cache' os.makedirs('/tmp/hf_home', exist_ok=True) os.makedirs('/tmp/hf_cache', exist_ok=True) os.makedirs('/tmp/transformers_cache', exist_ok=True) # --- Initialization & Configuration --- app = Flask(__name__) # IMPORTANT: Replace "your-secret-key-here" with a long, random string. app.secret_key = secrets.token_hex(32) OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY") OPENROUTER_API_URL = "https://openrouter.ai/api/v1/chat/completions" YOUR_SITE_URL = "https://huggingface.co/spaces/Arihant0008/Pneumonia-Detector-App" YOUR_SITE_NAME = "Pneumonia Detection AI" HF_TOKEN = os.environ.get("HF_TOKEN") # limiter = Limiter( # get_remote_address, # app=app, # default_limits=["10 per day"], # storage_uri="memory://" # ) UPLOAD_FOLDER = 'static/uploads/' app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER app.config['MAX_CONTENT_LENGTH'] = 50 * 1024 * 1024 ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg'} # --- CNN Model Loading (IMPROVED WITH PERSISTENT CACHE) --- model = None try: from huggingface_hub import hf_hub_download HF_TOKEN_2 = os.environ.get("HF_TOKEN_2") if HF_TOKEN_2: print("🔄 Attempting to download model from private HF repository...") # Use default HF cache (more persistent) MODEL_PATH = hf_hub_download( repo_id="Arihant0008/pneumonia-resnet50-model", filename="ResNet50_Pneumonia_model.keras", token=HF_TOKEN_2 ) print(f"📥 Model path: {MODEL_PATH}") # Load model model = tf.keras.models.load_model(MODEL_PATH, compile=False) print(f"✅ CNN Model loaded from HF repo") else: print("❌ ERROR: HF_TOKEN_2 not found in environment") except FileNotFoundError: print("⚠️ Model file not found, downloading again...") try: MODEL_PATH = hf_hub_download( repo_id="Arihant0008/pneumonia-resnet50-model", filename="ResNet50_Pneumonia_model.keras", token=HF_TOKEN_2, force_download=True # Force re-download ) model = tf.keras.models.load_model(MODEL_PATH, compile=False) print(f"✅ Model re-downloaded and loaded") except Exception as e: print(f"❌ Re-download failed: {e}") model = None except Exception as e: print(f"❌ Error loading CNN model: {e}") import traceback traceback.print_exc() model = None if model is None: print("❌❌❌ CRITICAL: Model not loaded!") else: print("✅✅✅ Model ready!") # Model configuration labels = ['PNEUMONIA', 'NORMAL'] img_size = 128 # --- Helper Functions --- def allowed_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS def image_to_base64(filepath): try: with open(filepath, "rb") as image_file: return base64.b64encode(image_file.read()).decode('utf-8') except Exception as e: print(f"❌ Error encoding image to Base64: {e}") return None def preprocess_image(image_path): try: img = cv2.imread(image_path, cv2.IMREAD_COLOR) if img is None: return None img = cv2.resize(img, (img_size, img_size)) img = img / 255.0 return np.expand_dims(img, axis=0) except Exception as e: print(f"❌ Error preprocessing image: {e}") return None @app.route('/submit_interest', methods=['POST']) # @limiter.limit("10 per hour") def submit_interest(): try: # Get form data user_name = request.form.get('user_name', '').strip() user_email = request.form.get('user_email', '').strip() user_message = request.form.get('user_message', '').strip() # Validate inputs if not user_name or not user_email or not user_message: return render_template('index.html', error='All fields are required in the interest form.') # Create timestamp timestamp = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') # Save to local CSV file (temporary) interest_file = '/tmp/user_interests.csv' file_exists = os.path.exists(interest_file) with open(interest_file, 'a', newline='', encoding='utf-8') as csvfile: fieldnames = ['timestamp', 'name', 'email', 'message'] writer = csv.DictWriter(csvfile, fieldnames=fieldnames) # Write header if file is new if not file_exists: writer.writeheader() # Write user data writer.writerow({ 'timestamp': timestamp, 'name': user_name, 'email': user_email, 'message': user_message }) print(f"✅ Interest submitted locally: {user_name} ({user_email})") # ========== NEW: Upload to Hugging Face Dataset ========== if HF_TOKEN: try: from huggingface_hub import HfApi hf_api = HfApi() # Upload the CSV to your dataset repository hf_api.upload_file( path_or_fileobj=interest_file, path_in_repo='user_interests.csv', repo_id='Arihant0008/AI-interests', # ⬅️ Change if you used different name repo_type='dataset', token=HF_TOKEN, commit_message=f'New submission from {user_name}' ) print(f"✅ Uploaded to HF Dataset: {user_name}") except Exception as upload_error: print(f"⚠️ HF Dataset upload failed (data still saved locally): {upload_error}") # Don't fail the whole request - data is still saved locally else: print("⚠️ HF_TOKEN not found - skipping dataset upload") # ========== END NEW CODE ========== return render_template('index.html', interest_success=True) except Exception as e: print(f"❌ Error saving interest: {e}") traceback.print_exc() return render_template('index.html', error=f'Submission error: {str(e)}') @app.route('/check_csv') def check_csv(): """Debug route to check CSV status""" interest_file = '/tmp/user_interests.csv' if os.path.exists(interest_file): with open(interest_file, 'r', encoding='utf-8') as f: content = f.read() return f"
{content}"
else:
return f"Looking for: {os.path.abspath(interest_file)}
Files in /tmp: {os.listdir('/tmp')}
" @app.route('/download_csv') def download_csv(): """Download user interests CSV""" from flask import send_file interest_file = '/tmp/user_interests.csv' if os.path.exists(interest_file): return send_file( interest_file, mimetype='text/csv', as_attachment=True, download_name=f'user_interests_{datetime.datetime.now().strftime("%Y%m%d")}.csv' ) else: return "No submissions yet.", 404 # --- AI Report Generation --- def get_ai_synthesis_report(image_base64, resnet_label, resnet_confidence, today_date): if not OPENROUTER_API_KEY: return "API service unavailable", "OpenRouter API key not configured.
", False headers = { "Content-Type": "application/json", "Authorization": f"Bearer {OPENROUTER_API_KEY}", "HTTP-Referer": YOUR_SITE_URL, "X-Title": YOUR_SITE_NAME } # FIXED: Enhanced prompt with prominent DATE display user_prompt = f""" You are a board-certified radiologist and AI medical imaging specialist with over 15 years of experience in chest X-ray interpretation and pneumonia diagnosis. **GENERATE A COMPREHENSIVE RADIOLOGY REPORT WITH THE FOLLOWING EXACT STRUCTURE AND FORMATTING:** # CHEST X-RAY ANALYSIS REPORT **Report Date: {today_date}** **Model: ResNet50 Deep Learning Architecture** --- ## PATIENT INFORMATION & STUDY DETAILS - **Study Date:** {today_date} - **Imaging Modality:** Posterior-Anterior (PA) Chest X-ray - **AI Classification Result:** {resnet_label} - **Model Confidence Level:** {resnet_confidence} - **Analysis System:** ResNet50 Convolutional Neural Network --- ## MEDICAL DISCLAIMER - This AI-generated report is for educational and informational purposes only - This analysis does NOT constitute a clinical diagnosis or medical advice - All findings require confirmation by qualified healthcare professionals - Seek immediate medical attention for concerning symptoms - This report should be used as a supplementary tool alongside clinical evaluation --- ## EXECUTIVE SUMMARY Provide a concise 2-3 sentence clinical summary incorporating the CNN prediction ({resnet_label} at {resnet_confidence} confidence) and your comprehensive visual analysis of the chest radiograph. --- ## SYSTEMATIC RADIOLOGICAL ANALYSIS --- ### A. Technical Assessment - **Image Quality:** Assess penetration, inspiration depth, patient positioning, and overall technical adequacy - **Anatomical Coverage:** Evaluate whether all relevant chest structures are adequately visualized - **Artifacts:** Identify any technical artifacts, overlapping structures, or positioning issues ### B. Cardiac Assessment - **Cardiac Silhouette:** Analyze heart size, shape, and cardiothoracic ratio - **Cardiac Borders:** Evaluate right heart border, left heart border, and aortic knob - **Mediastinal Structures:** Assess mediastinal width, tracheal position, and hilar anatomy - **Cardiovascular Abnormalities:** Note any cardiomegaly, mediastinal shift, or vascular congestion ### C. Pulmonary Parenchymal Analysis - **Lung Fields:** Systematically evaluate both upper, middle, and lower lung zones - **Aeration Pattern:** Assess lung expansion, symmetry, and overall aeration - **Opacities:** Identify and characterize any consolidations, ground-glass opacities, or infiltrates - **Pneumonia-Specific Findings:** * Alveolar consolidation patterns * Air bronchograms presence/absence * Lobar vs bronchopneumonia distribution * Pleural involvement assessment - **Other Parenchymal Abnormalities:** Nodules, masses, cavitations, or interstitial changes ### D. Pleural Space Evaluation - **Pleural Effusions:** Assess for fluid collections, quantify if present - **Pneumothorax:** Evaluate for air in pleural space - **Pleural Thickening:** Note any pleural abnormalities or calcifications ### E. Skeletal and Soft Tissue Assessment - **Chest Wall:** Examine ribs, clavicles, and thoracic spine for fractures or abnormalities - **Soft Tissues:** Evaluate for subcutaneous emphysema or masses - **Diaphragm:** Assess diaphragmatic contours and position ## CLINICAL CORRELATION AND INTERPRETATION --- ### Primary Findings Discussion Based on the CNN analysis showing **{resnet_label}** with **{resnet_confidence}** confidence: - **If PNEUMONIA detected:** Provide detailed analysis of consolidation patterns, distribution (lobar/bronchopneumonia), severity assessment, and potential complications - **If NORMAL detected:** Confirm normal anatomical structures, clear lung fields, and absence of pathological findings ### Differential Diagnosis Considerations List 3-5 relevant differential diagnoses based on imaging findings, including: - Most likely diagnosis based on imaging pattern - Alternative diagnoses to consider - Clinical correlation needed for definitive diagnosis ### Severity Assessment - **Mild:** Limited involvement, normal cardiac silhouette - **Moderate:** More extensive involvement, possible complications - **Severe:** Extensive bilateral involvement, signs of respiratory compromise ## CLINICAL RECOMMENDATIONS --- ### Immediate Actions - Urgency level based on findings (routine follow-up vs urgent evaluation) - Need for supplemental oxygen or respiratory support - Isolation precautions if infectious pneumonia suspected ### Diagnostic Workup - **Laboratory Tests:** CBC with differential, blood cultures, sputum culture, inflammatory markers (CRP, procalcitonin) - **Additional Imaging:** Consider CT chest if findings unclear, ultrasound for pleural effusions - **Microbiological Studies:** Sputum gram stain and culture, blood cultures, urinary antigens ### Treatment Considerations - **Outpatient vs Inpatient:** Management setting recommendations - **Antibiotic Therapy:** Empirical treatment suggestions based on pattern - **Supportive Care:** Oxygen therapy, fluid management, bronchodilators if indicated - **Monitoring Parameters:** Vital signs, oxygen saturation, clinical response ### Follow-up Recommendations - **Timeline:** When to repeat imaging (typically 6-8 weeks post-treatment for pneumonia) - **Clinical Monitoring:** Symptom resolution, functional improvement - **Red Flag Symptoms:** When to seek immediate medical attention ## RISK STRATIFICATION AND PROGNOSIS - **Risk Factors:** Age, comorbidities, extent of disease - **Prognostic Indicators:** Based on imaging extent and pattern - **Expected Recovery Timeline:** Typical resolution patterns ## QUALITY ASSURANCE NOTES - **AI Model Limitations:** Acknowledge CNN model constraints and potential false positives/negatives - **Correlation Needed:** Emphasize importance of clinical correlation with symptoms, vital signs, and laboratory data - **Second Opinion:** Recommend radiologist review for complex cases --- ## SIMPLIFIED PATIENT-FRIENDLY SUMMARY **Plain Language Explanation for Non-Medical Readers:** - This chest X-ray was analyzed using an advanced AI system called ResNet50. - The system predicts: **{resnet_label}** with **{resnet_confidence}% confidence**. - If pneumonia is detected, it means there may be an infection in the lungs causing cloudy areas on the X-ray. - If the result is normal, it means the lungs, heart, and chest structures look healthy. - This report is not a final diagnosis. A doctor must review the findings and decide the next steps. - If you feel unwell—especially with fever, cough, or breathing problems—please see a healthcare provider immediately. Generate a comprehensive, detailed report following this structure while maintaining the highest standards of medical accuracy and clinical utility. """ report_models = [ # Tier 1: Best for medical analysis (primary) "qwen/qwen2.5-vl-32b-instruct:free", "google/gemini-2.0-flash-exp:free", "meta-llama/llama-4-maverick:free", # Tier 2: Good general performance (secondary) "x-ai/grok-4-fast:free", "meta-llama/llama-4-scout:free", # Tier 3: Reliable fallbacks (tertiary) "mistralai/mistral-small-3.2-24b-instruct:free", "google/gemma-3-12b-it:free", "qwen/qwen2.5-vl-72b-instruct:free" ] for i, model_name in enumerate(report_models): payload = { "model": model_name, "messages": [{ "role": "user", "content": [ {"type": "text", "text": user_prompt}, {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}} ] }], "temperature": 0.1, "max_tokens": 4000 } try: # Progressive timeout strategy timeout = 60 + (i * 15) # 60s, 75s, 90s, etc. print(f"📊 Generating report with model {i+1}/{len(report_models)}: {model_name}") time.sleep(1) # Rate limiting courtesy response = requests.post(OPENROUTER_API_URL, headers=headers, data=json.dumps(payload), timeout=timeout) if response.status_code == 200: json_response = response.json() raw_content = json_response.get('choices', [{}])[0].get('message', {}).get('content', '') if raw_content and len(raw_content) > 200: # Ensure substantial content html_content = markdown.markdown(raw_content) print(f"✅ Report generated successfully with {model_name}") return raw_content, f"Unable to generate detailed report - all models failed.
", False # --- Download Report Route (CRITICAL FIXES HERE) --- @app.route('/download_report', methods=['POST']) # @limiter.limit("10 per day") def download_report(): print("🔍 DEBUG: Processing download request.") # 1. CRITICAL: Try to get the raw report content directly from the POST form data, then fall back to session. raw_report = request.form.get('raw_report_content', session.get('last_report', 'API service unavailable')) # 2. Check if we are truly in fallback mode if raw_report == 'API service unavailable' or not raw_report.strip(): print("⚠️ Failed to retrieve full report content (API error). Generating minimal fallback PDF.") # Fallback logic: Retrieves minimal data from session cnn_label = session.get('last_cnn_label', 'UNKNOWN') cnn_confidence = session.get('last_cnn_confidence', 'N/A') today_date = datetime.datetime.now().strftime("%d-%m-%Y") # Fallback content in Markdown format raw_report = f""" # Medical Analysis Report (Fallback Mode) Date: {today_date} ## AI Service Unavailable The detailed AI synthesis service is currently unreachable. This report contains only the raw machine learning prediction results. ## CNN Analysis Results - **Classification:** {cnn_label} - **Confidence Level:** {cnn_confidence} - **Important Notice:** This is an automated prediction, not a clinical diagnosis. Consult a qualified medical professional for definitive evaluation and treatment recommendations. """ filename_label = cnn_label is_fallback = True else: print("✅ Full report content successfully retrieved for PDF generation.") filename_label = session.get('last_cnn_label', 'AI_Report') is_fallback = False try: # 3. Use markdown library to convert the RAW report content to HTML for the PDF report_html_content = markdown.markdown(raw_report) # Consistent PDF Styling for professional look pdf_style = f""" """ # 4. Construct the full HTML for WeasyPrint full_html = f"