Spaces:
Runtime error
Runtime error
| import io | |
| import json | |
| import numpy as np | |
| import torch | |
| from fastapi import FastAPI, UploadFile, File, Form, HTTPException | |
| from fastapi.responses import StreamingResponse | |
| from PIL import Image, ImageDraw | |
| from transformers import SamModel, SamProcessor | |
| app = FastAPI(title="MedSAM Image Segmentation Service") | |
| # Global device initialization (Fall back gracefully to CPU on standard HF spaces) | |
| device = "cuda" if torch.cuda.is_available() else "cpu" | |
| print(f"Loading MedSAM model onto: {device}...") | |
| processor = SamProcessor.from_pretrained("flaviagiammarino/medsam-vit-base") | |
| model = SamModel.from_pretrained("flaviagiammarino/medsam-vit-base").to(device) | |
| model.eval() | |
| print("Model loaded successfully.") | |
| def health_check(): | |
| return {"status": "healthy", "device": device} | |
| async def segment_image( | |
| file: UploadFile = File(...), | |
| bbox: str = Form(..., description="JSON string array format: '[xmin, ymin, xmax, ymax]'") | |
| ): | |
| # 1. Parse bounding box coordinates | |
| try: | |
| box_coords = json.loads(bbox) | |
| if not isinstance(box_coords, list) or len(box_coords) != 4: | |
| raise ValueError | |
| except ValueError: | |
| raise HTTPException(status_code=400, detail="Bounding box format must be '[xmin, ymin, xmax, ymax]'") | |
| # 2. Read image | |
| try: | |
| image_bytes = await file.read() | |
| raw_image = Image.open(io.BytesIO(image_bytes)).convert("RGB") | |
| except Exception: | |
| raise HTTPException(status_code=400, detail="Invalid image file format uploaded.") | |
| # 3. Process image and run inference | |
| try: | |
| inputs = processor( | |
| raw_image, | |
| input_boxes=[[box_coords]], | |
| return_tensors="pt" | |
| ).to(device) | |
| with torch.no_grad(): | |
| outputs = model(**inputs) | |
| # Post-process low resolution logits into a concrete mask array | |
| masks = processor.image_processor.post_process_masks( | |
| outputs.pred_masks.cpu(), | |
| inputs["original_sizes"].cpu(), | |
| inputs["reshaped_input_sizes"].cpu() | |
| ) | |
| # Binary mask array (True/False boolean configuration) | |
| mask_array = masks[0][0][0].numpy() | |
| except Exception as e: | |
| raise HTTPException( | |
| status_code=500, | |
| detail=f"MedSAM core processing exception: {str(e)}" | |
| ) | |
| # 4. Generate the composite visual output (matching reference target) | |
| # Create an overlay layout where True items highlight yellow at an alpha state | |
| yellow_overlay = Image.new( | |
| "RGBA", | |
| raw_image.size, | |
| (251, 252, 30, 150) | |
| ) | |
| mask_image = Image.fromarray( | |
| (mask_array * 255).astype(np.uint8), | |
| mode="L" | |
| ) | |
| # Composite the isolated mask layout with the underlying source picture | |
| segmented_image = Image.composite( | |
| yellow_overlay, | |
| raw_image.convert("RGBA"), | |
| mask_image | |
| ) | |
| # Draw the diagnostic bounding box outline border matching your input configuration | |
| draw = ImageDraw.Draw(segmented_image) | |
| draw.rectangle( | |
| box_coords, | |
| outline="blue", | |
| width=3 | |
| ) | |
| # Stream back final constructed image payload | |
| buffer = io.BytesIO() | |
| segmented_image.convert("RGB").save( | |
| buffer, format="PNG" | |
| ) | |
| buffer.seek(0) | |
| return StreamingResponse( | |
| buffer, | |
| media_type="image/png" | |
| ) |