ChestSense / README.md
NoumanUsman's picture
Upload folder using huggingface_hub
52e8264 verified
|
Raw
History Blame Contribute Delete
10.3 kB

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

# Clone the repository
git clone <repo-url>
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)

docker build -t lung-cancer-api .
docker run -p 5001:5001 lung-cancer-api

Usage

Running Locally

# 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:

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)

POST /validate-ct
Content-Type: multipart/form-data

file: <image_file>

Response:

{
  "is_ct_scan": true,
  "color_score": 4.5,
  "message": "Valid CT scan - grayscale image detected"
}

3. Full Analysis with Grad-CAM

POST /analyze
Content-Type: multipart/form-data

file: <image_file>

Response (on success):

{
  "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://<your-project>.up.railway.app

Heroku Deployment (Legacy)

heroku create <app-name>
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):

# 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

CONFIDENCE_THRESHOLD = 0.3  # Lower from 0.5 to accept more predictions
# In app.py line ~180

Adjust CT Validation

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

# Find process using port 5001
lsof -i :5001

# Kill the process
kill -9 <PID>

# Or use different port
PORT=5002 python app.py

Module import errors

# Ensure all dependencies installed
pip install -r requirements.txt

# Verify virtual environment active
source .venv/bin/activate

API Examples

Python Client

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

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 βœ