assignment / app.py
rezvan98
Medical image processing backend API
ddbe9e9
Raw
History Blame Contribute Delete
6.83 kB
"""
MedTech Image Processing Backend API
Provides endpoints for processing medical images with arterial and venous phase filters
"""
from fastapi import FastAPI, File, UploadFile, Form, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import Response
import cv2
import numpy as np
from io import BytesIO
from PIL import Image
import logging
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Initialize FastAPI app
app = FastAPI(
title="MedTech Image Processing API",
description="Backend API for processing medical images",
version="1.0.0"
)
# Configure CORS to allow requests from GitHub Pages and localhost
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://*.github.io",
"http://localhost:*",
"http://127.0.0.1:*",
"*" # Allow all origins for development - restrict in production
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
def increase_contrast(img: np.ndarray) -> np.ndarray:
"""
Apply CLAHE (Contrast Limited Adaptive Histogram Equalization) to enhance contrast
Used for arterial phase processing
Args:
img: Input image as numpy array (BGR format)
Returns:
Enhanced image as numpy array
"""
try:
# Convert to LAB color space for better contrast enhancement
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
# Apply CLAHE to L channel (lightness)
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
l_enhanced = clahe.apply(l)
# Merge channels back and convert to BGR
enhanced_lab = cv2.merge([l_enhanced, a, b])
enhanced = cv2.cvtColor(enhanced_lab, cv2.COLOR_LAB2BGR)
logger.info("Contrast enhancement applied successfully")
return enhanced
except Exception as e:
logger.error(f"Error in contrast enhancement: {str(e)}")
raise
def apply_gaussian_blur(img: np.ndarray) -> np.ndarray:
"""
Apply Gaussian blur for smoothing
Used for venous phase processing
Args:
img: Input image as numpy array (BGR format)
Returns:
Blurred image as numpy array
"""
try:
# Apply Gaussian blur with 15x15 kernel
blurred = cv2.GaussianBlur(img, (15, 15), 0)
logger.info("Gaussian blur applied successfully")
return blurred
except Exception as e:
logger.error(f"Error in Gaussian blur: {str(e)}")
raise
def read_image_file(file_bytes: bytes) -> np.ndarray:
"""
Read image file bytes and convert to OpenCV format
Args:
file_bytes: Raw image file bytes
Returns:
Image as numpy array in BGR format
"""
try:
# Convert bytes to PIL Image
image = Image.open(BytesIO(file_bytes))
# Convert to RGB if needed
if image.mode != 'RGB':
image = image.convert('RGB')
# Convert PIL Image to numpy array
img_array = np.array(image)
# Convert RGB to BGR for OpenCV
img_bgr = cv2.cvtColor(img_array, cv2.COLOR_RGB2BGR)
logger.info(f"Image loaded successfully: shape={img_bgr.shape}")
return img_bgr
except Exception as e:
logger.error(f"Error reading image file: {str(e)}")
raise HTTPException(status_code=400, detail=f"Invalid image file: {str(e)}")
def encode_image(img: np.ndarray) -> bytes:
"""
Encode OpenCV image to PNG bytes
Args:
img: Image as numpy array (BGR format)
Returns:
PNG encoded bytes
"""
try:
# Encode image to PNG format
success, buffer = cv2.imencode('.png', img)
if not success:
raise Exception("Failed to encode image")
return buffer.tobytes()
except Exception as e:
logger.error(f"Error encoding image: {str(e)}")
raise
@app.get("/")
async def root():
"""Health check endpoint"""
return {
"status": "online",
"service": "MedTech Image Processing API",
"version": "1.0.0",
"endpoints": {
"/process": "POST - Process medical images with arterial or venous phase filters"
}
}
@app.post("/process")
async def process_image(
image: UploadFile = File(..., description="Medical image file (JPG/PNG)"),
phase: str = Form(..., description="Processing phase: 'arterial' or 'venous'")
):
"""
Process medical image with selected phase filter
Args:
image: Uploaded image file (JPG/PNG)
phase: Processing phase - 'arterial' (contrast enhancement) or 'venous' (blur)
Returns:
Processed image as PNG
"""
logger.info(f"Processing request received - Phase: {phase}")
# Validate phase parameter
if phase not in ['arterial', 'venous']:
raise HTTPException(
status_code=400,
detail="Invalid phase. Must be 'arterial' or 'venous'"
)
# Validate file type
if not image.content_type.startswith('image/'):
raise HTTPException(
status_code=400,
detail="File must be an image (JPG/PNG)"
)
try:
# Read uploaded file
file_bytes = await image.read()
logger.info(f"File received: {image.filename}, size: {len(file_bytes)} bytes")
# Convert to OpenCV format
img = read_image_file(file_bytes)
# Apply processing based on phase
if phase == 'arterial':
processed_img = increase_contrast(img)
logger.info("Arterial phase processing completed")
else: # venous
processed_img = apply_gaussian_blur(img)
logger.info("Venous phase processing completed")
# Encode processed image
output_bytes = encode_image(processed_img)
# Return processed image
return Response(
content=output_bytes,
media_type="image/png",
headers={
"Content-Disposition": f"inline; filename=processed_{image.filename}",
"X-Processing-Phase": phase
}
)
except HTTPException:
raise
except Exception as e:
logger.error(f"Unexpected error processing image: {str(e)}")
raise HTTPException(
status_code=500,
detail=f"Error processing image: {str(e)}"
)
@app.get("/health")
async def health_check():
"""Detailed health check endpoint"""
return {
"status": "healthy",
"opencv_version": cv2.__version__,
"numpy_version": np.__version__
}
if __name__ == "__main__":
import uvicorn
# Run server on port 7860 (Hugging Face Spaces default)
uvicorn.run(app, host="0.0.0.0", port=7860)