api / app /main.py
Omarelrayes's picture
Update app/main.py
67a7d05 verified
Raw
History Blame Contribute Delete
7.1 kB
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
@app.get("/metrics")
async def prometheus_metrics(request: Request):
return metrics_endpoint(request)
@app.get("/models/status")
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),
},
}
@app.post("/api/upload", status_code=201, response_model=UploadResponse)
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}",
)
@app.get("/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)
@app.post("/api/classify", response_model=ClassifyResponse)
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,
)
@app.get("/api/classify/{image_id}", response_model=ClassifyResponse)
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"),
)
@app.post("/api/segment", response_model=SegmentResponse)
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"),
)
@app.get("/api/segment/{image_id}", response_model=SegmentResponse)
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"),
)
@app.get("/")
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",
}
@app.get("/health")
async def health():
return {
"status": "healthy",
"predict_ready": config.get_classification_model() is not None,
}