Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| 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" | |
| } | |
| } | |
| 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)}" | |
| ) | |
| 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) | |