Spaces:
Sleeping
Sleeping
| import uuid | |
| from pathlib import Path | |
| from datetime import datetime | |
| from fastapi import FastAPI, UploadFile, File, HTTPException, Request | |
| from fastapi.responses import FileResponse | |
| from . import configs as config | |
| from .models import UploadResponse, ClassifyRequest, ClassifyResponse, SegmentRequest, SegmentResponse | |
| from .services.predictor import classify_image | |
| from .services.segmenter import segment_image | |
| from .storage import ( | |
| save_image, | |
| get_image_path, | |
| get_image_bytes, | |
| save_classification_result, | |
| get_classification_result, | |
| save_segmentation_result, | |
| get_segmentation_result, | |
| ) | |
| from .monitoring.metrics import MetricsMiddleware, metrics_endpoint | |
| app = FastAPI( | |
| title="AI Image Classification API", | |
| version="3.2.0" | |
| ) | |
| app.add_middleware(MetricsMiddleware) | |
| ALLOWED_CONTENT_TYPES = {"image/jpeg", "image/png", "image/jpg"} | |
| MAX_FILE_SIZE = 10 * 1024 * 1024 | |
| async def prometheus_metrics(request: Request): | |
| return metrics_endpoint(request) | |
| async def models_status(): | |
| classification_model = config.get_classification_model() | |
| segmentation_model = config.get_segmentation_model() | |
| return { | |
| "classification_loaded": classification_model is not None, | |
| "segmentation_loaded": segmentation_model is not None, | |
| "storage_paths": { | |
| "images": str(config.IMAGES_DIR), | |
| "segments": str(config.SEGMENTS_DIR), | |
| }, | |
| } | |
| async def upload_image(file: UploadFile = File(...)): | |
| if file.content_type not in ALLOWED_CONTENT_TYPES: | |
| raise HTTPException( | |
| status_code=400, | |
| detail=f"Unsupported content type: {file.content_type}. Allowed: {ALLOWED_CONTENT_TYPES}", | |
| ) | |
| file_bytes = await file.read() | |
| if len(file_bytes) > MAX_FILE_SIZE: | |
| raise HTTPException(status_code=400, detail="File too large (10MB max)") | |
| ext = Path(file.filename or "image.jpg").suffix or ".jpg" | |
| image_id = str(uuid.uuid4()) | |
| save_image(image_id, file_bytes, ext) | |
| return UploadResponse( | |
| image_id=image_id, | |
| filename=file.filename or "image.jpg", | |
| size_bytes=len(file_bytes), | |
| content_type=file.content_type, | |
| uploaded_at=datetime.now().isoformat(), | |
| url=f"/api/images/{image_id}", | |
| ) | |
| async def get_image(image_id: str): | |
| image_path = get_image_path(image_id) | |
| if image_path is None: | |
| raise HTTPException(status_code=404, detail="Image not found") | |
| media_type = "image/jpeg" | |
| if image_path.suffix.lower() in {".png"}: | |
| media_type = "image/png" | |
| return FileResponse(path=image_path, media_type=media_type) | |
| async def classify(body: ClassifyRequest): | |
| image_bytes = get_image_bytes(body.image_id) | |
| if image_bytes is None: | |
| raise HTTPException(status_code=404, detail="Image not found") | |
| # ✅ Check for cached result | |
| existing = get_classification_result(body.image_id) | |
| if existing: | |
| return ClassifyResponse( | |
| image_id=body.image_id, | |
| prediction=existing["prediction"], | |
| confidence=existing["confidence"], | |
| model_version=existing.get("model_version", "hf_savedmodel"), | |
| status=existing.get("status", "completed"), | |
| ) | |
| # ✅ Classify without model_version | |
| try: | |
| prediction, confidence = classify_image(image_bytes) | |
| except ValueError as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| result = { | |
| "prediction": prediction, | |
| "confidence": confidence, | |
| "model_version": "hf_savedmodel", | |
| "status": "completed", | |
| } | |
| save_classification_result(body.image_id, result) | |
| return ClassifyResponse( | |
| image_id=body.image_id, | |
| **result, | |
| ) | |
| async def get_classify_result(image_id: str): | |
| result = get_classification_result(image_id) | |
| if result is None: | |
| raise HTTPException(status_code=404, detail="Classification result not found") | |
| return ClassifyResponse( | |
| image_id=image_id, | |
| prediction=result["prediction"], | |
| confidence=result["confidence"], | |
| model_version=result.get("model_version", "hf_savedmodel"), | |
| status=result.get("status", "completed"), | |
| ) | |
| async def segment(body: SegmentRequest): | |
| image_bytes = get_image_bytes(body.image_id) | |
| if image_bytes is None: | |
| raise HTTPException(status_code=404, detail="Image not found") | |
| # ✅ Check for cached result | |
| existing = get_segmentation_result(body.image_id) | |
| if existing: | |
| return SegmentResponse( | |
| image_id=body.image_id, | |
| status=existing.get("status", "completed"), | |
| masks_shape=existing.get("masks_shape"), | |
| max_confidence=existing.get("max_confidence"), | |
| result_url=existing.get("result_path"), | |
| error=existing.get("error"), | |
| ) | |
| # ✅ Run segmentation | |
| seg_result = segment_image(image_bytes) | |
| seg_path = save_segmentation_result(body.image_id, seg_result) | |
| return SegmentResponse( | |
| image_id=body.image_id, | |
| status=seg_result.get("status", "completed"), | |
| masks_shape=seg_result.get("masks_shape"), | |
| max_confidence=seg_result.get("max_confidence"), | |
| result_url=seg_path, | |
| error=seg_result.get("error"), | |
| ) | |
| async def get_segment_result(image_id: str): | |
| result = get_segmentation_result(image_id) | |
| if result is None: | |
| raise HTTPException(status_code=404, detail="Segmentation result not found") | |
| return SegmentResponse( | |
| image_id=image_id, | |
| status=result.get("status", "completed"), | |
| masks_shape=result.get("masks_shape"), | |
| max_confidence=result.get("max_confidence"), | |
| result_url=result.get("result_path"), | |
| error=result.get("error"), | |
| ) | |
| async def root(): | |
| class_ready = "READY" if config.get_classification_model() else "REQUIRED" | |
| seg_ready = "READY" if config.get_segmentation_model() else "OPTIONAL" | |
| return { | |
| "service": "AI Image Classification API", | |
| "version": "3.2.0", | |
| "classification": class_ready, | |
| "segmentation": seg_ready, | |
| "endpoints": { | |
| "upload": "POST /api/upload", | |
| "classify": "POST /api/classify", | |
| "segment": "POST /api/segment", | |
| "health": "GET /health", | |
| "metrics": "GET /metrics", | |
| "docs": "/docs", | |
| }, | |
| "models_status": "/models/status", | |
| } | |
| async def health(): | |
| return { | |
| "status": "healthy", | |
| "predict_ready": config.get_classification_model() is not None, | |
| } |