# Lung Cancer Classification API with Grad-CAM A production-ready Flask REST API for classifying lung cancer types using DenseNet121 with Grad-CAM visualization. Validates medical images, provides explainable AI predictions, and returns base64-encoded visualizations. ## Features - ✅ **DenseNet121 Classification**: 4-class lung cancer type prediction (Adenocarcinoma, Small Cell, Large Cell, Squamous Cell) - ✅ **Grad-CAM Visualization**: Visual explanation of model predictions via heatmap overlays - ✅ **CT Scan Validation**: Automatic rejection of non-medical (color) images - ✅ **Dual Preprocessing**: Attempts both normalized and non-normalized preprocessing for robust predictions - ✅ **REST API**: Flask with Swagger UI documentation - ✅ **CORS Support**: Mobile and cross-origin requests enabled - ✅ **Production Ready**: Gunicorn WSGI server, environment variables, Docker-compatible - ✅ **JSON Responses**: Base64-encoded images for mobile integration ## Installation ### Local Development ```bash # Clone the repository git clone cd Grad-Cam-Backend # Create virtual environment python3.11 -m venv .venv source .venv/bin/activate # On Windows: .venv\Scripts\activate # Install dependencies pip install -r requirements.txt ``` ### Docker (Optional) ```bash docker build -t lung-cancer-api . docker run -p 5001:5001 lung-cancer-api ``` ## Usage ### Running Locally ```bash # Development (debug mode enabled) DEBUG=true python app.py # Production (debug mode disabled) python app.py # Or use Gunicorn gunicorn app:app --bind 0.0.0.0:5001 ``` ### Running Tests Classify a single image using the command-line tool: ```bash source .venv/bin/activate python test.py /path/to/ct_scan.jpg ``` Example output: ``` ============================================================ 🩺 LUNG CANCER CLASSIFICATION - DenseNet121 ============================================================ 📄 Image: ct_scan.jpg 📏 Size: (512, 512, 3) 🔍 Running classification... ============================================================ 📊 CLASSIFICATION RESULTS ============================================================ Adenocarcinoma (Class A) ████████░░░░░░░░░░░░ 45.23% Small Cell (Class B) ██████████████░░░░░░ 72.15% Large Cell (Class E) ███░░░░░░░░░░░░░░░░░ 12.50% Squamous Cell (Class G) ██░░░░░░░░░░░░░░░░░░ 8.12% ============================================================ 🏥 FINAL DIAGNOSIS ============================================================ Classification: Small Cell (Class B) Confidence: 72.15% ============================================================ ✅ Result saved to 'classification_result.png' ``` ### REST API **Base URL**: `http://localhost:5001` #### 1. Health Check ```bash GET /health ``` Response: `{"status": "healthy", "model_loaded": true}` #### 2. CT Scan Validation (No Classification) ```bash POST /validate-ct Content-Type: multipart/form-data file: ``` Response: ```json { "is_ct_scan": true, "color_score": 4.5, "message": "Valid CT scan - grayscale image detected" } ``` #### 3. Full Analysis with Grad-CAM ```bash POST /analyze Content-Type: multipart/form-data file: ``` Response (on success): ```json { "success": true, "prediction": "Small Cell (Class B)", "confidence": 72.15, "all_confidences": { "Adenocarcinoma (Class A)": 45.23, "Small Cell (Class B)": 72.15, "Large Cell (Class E)": 12.50, "Squamous Cell (Class G)": 8.12 }, "original_image": "base64_encoded_jpeg_string", "heatmap_image": "base64_encoded_gradcam_heatmap" } ``` ### Swagger UI Interactive API documentation available at: **http://localhost:5001/docs** ## Deployment ### Railway.app Deployment 1. **Create a Railway account** at https://railway.app 2. **Connect your GitHub repository** - Go to Railway dashboard - Click "New Project" → "Deploy from GitHub repo" - Select this repository 3. **Configure environment variables** in Railway dashboard: ``` DEBUG=False PORT=5001 ``` 4. **Deploy** - Railway automatically detects `Procfile` and deploys the app - Your API will be available at `https://.up.railway.app` ### Heroku Deployment (Legacy) ```bash heroku create heroku config:set DEBUG=False git push heroku main ``` ## API Validation ### CT Scan Validation Threshold - **Valid CT scans**: Color score < 6.0 (grayscale images) - **Rejected**: Color score ≥ 6.0 (color photos, non-medical images) Color score measures RGB channel variance: - Pure grayscale: 0-2 - Real CT scans: 2-6 - Color photos: 7-100 ### Confidence Threshold - **Accepted**: Confidence ≥ 50% - **Rejected**: Confidence < 50% (uncertain predictions) ## Configuration ### Environment Variables Create a `.env` file (or set via environment): ```bash # Flask settings DEBUG=False # Set to True for development PORT=5001 # Server port (defaults to 5001) # Optional overrides # MODEL_PATH=models/densenet_final_classification.pth ``` ## Model Architecture ``` DenseNet121 (Feature Extractor) ↓ [2176 channels] ↓ Classifier: - ReLU Activation - Linear(2176 → 4) [outputs class logits] ↓ Softmax Probabilities ↓ 4-Class Output: [Adenocarcinoma, Small Cell, Large Cell, Squamous Cell] ``` ## Preprocessing Pipeline The API uses **dual preprocessing** for robustness: 1. **Normalized**: ImageNet normalization (mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) 2. **Non-normalized**: Raw tensor scaling Algorithm selects whichever produces higher max confidence, adapting to different training conditions. ## Grad-CAM Explanation Grad-CAM (Gradient-weighted Class Activation Mapping) highlights regions the model focuses on: - **Red/Hot regions**: Model focuses heavily (high confidence) - **Blue/Cool regions**: Model has low attention (less relevant) - **Alpha blending**: 0.4 transparency for visualization Extracted from: `model.features.norm5` (last fully-connected layer before classifier) ## Error Handling | Status | Error | Solution | |--------|-------|----------| | 400 | "No file uploaded" | Ensure multipart/form-data with 'file' field | | 400 | "Invalid input: image does not appear to be a CT scan" | Use actual CT scan (grayscale medical image) | | 400 | "Confidence too low" | Model uncertain; try another image | | 500 | "Error processing image" | Check server logs; verify model file exists | ## File Structure ``` Grad-Cam-Backend/ ├── app.py # Main Flask application ├── test.py # Command-line classification tool ├── models/ │ └── densenet_final_classification.pth # DenseNet weights ├── requirements.txt # Python dependencies ├── Procfile # Railway deployment config ├── runtime.txt # Python version (3.11.14) ├── .env.example # Example environment variables ├── .gitignore # Git ignore patterns └── README.md # This file ``` ## Class Labels | Index | Class | Description | |-------|-------|-------------| | 0 | Adenocarcinoma (Class A) | Most common; develops in glandular cells | | 1 | Small Cell (Class B) | Aggressive; fast-growing variant | | 2 | Large Cell (Class E) | Rare; large undifferentiated cells | | 3 | Squamous Cell (Class G) | Develops in flat cells lining airways | ## Performance Tuning ### Increase Sensitivity ```python CONFIDENCE_THRESHOLD = 0.3 # Lower from 0.5 to accept more predictions # In app.py line ~180 ``` ### Adjust CT Validation ```python is_valid = color_score < 8.0 # Raise from 6.0 if rejecting true CTs with color compression # In app.py line ~157 ``` ## Troubleshooting ### Model fails to load ``` RuntimeError: Error(s) in loading state_dict for DenseNet: Missing key(s) in state_dict: ... ``` **Solution**: Ensure `models/densenet_final_classification.pth` exists and matches architecture in `load_model()`. ### Port already in use ```bash # Find process using port 5001 lsof -i :5001 # Kill the process kill -9 # Or use different port PORT=5002 python app.py ``` ### Module import errors ```bash # Ensure all dependencies installed pip install -r requirements.txt # Verify virtual environment active source .venv/bin/activate ``` ## API Examples ### Python Client ```python import requests import base64 from PIL import Image from io import BytesIO API_URL = "http://localhost:5001" # Upload and classify CT scan with open("ct_scan.jpg", "rb") as f: files = {"file": f} response = requests.post(f"{API_URL}/analyze", files=files) result = response.json() print(f"Diagnosis: {result['prediction']}") print(f"Confidence: {result['confidence']}%") # Decode and view heatmap heatmap_data = base64.b64decode(result['heatmap_image']) heatmap_img = Image.open(BytesIO(heatmap_data)) heatmap_img.show() ``` ### JavaScript/Flutter Client ```javascript const formData = new FormData(); formData.append('file', imageFile); const response = await fetch('http://localhost:5001/analyze', { method: 'POST', body: formData }); const result = await response.json(); console.log(`Diagnosis: ${result.prediction}`); console.log(`Confidence: ${result.confidence}%`); // Display heatmap from base64 const img = new Image(); img.src = `data:image/jpeg;base64,${result.heatmap_image}`; ``` ## Dependencies - **Flask 3.0.0**: REST framework - **Gunicorn 21.2.0**: WSGI server - **PyTorch 2.1.0**: Deep learning - **TorchVision 0.16.0**: Computer vision - **OpenCV 4.8.1.78**: Image processing - **Pillow 10.1.0**: Image I/O - **NumPy 1.24.3**: Array operations - **Flask-CORS 4.0.0**: Cross-origin support - **Flasgger 0.9.7.1**: Swagger API docs ## License Proprietary - Medical Research Use Only ## Support For issues, email: support@lungcancerapi.com --- **Last Updated**: March 8, 2026 **Version**: 1.0.0 **Status**: Production Ready ✅