hllerdgn's picture
Fix 404 by adding redirect to docs
2174500
Raw
History Blame Contribute Delete
7.23 kB
"""
API.PY - FastAPI Wheat Disease Analysis API
Endpoints:
POST /analyze -> Classification and quality analysis
GET /health -> System status
GET /classes -> Supported classes
GET /docs -> Swagger UI (automatic)
"""
import sys
import time
import json
from pathlib import Path
from typing import Optional
from contextlib import asynccontextmanager
from fastapi import FastAPI, File, UploadFile, HTTPException, Query
from fastapi.responses import JSONResponse, RedirectResponse
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field
project_root = Path(__file__).resolve().parent
if str(project_root) not in sys.path:
sys.path.append(str(project_root))
import config
from pipeline import WheatDiseasePipeline, PipelineResult
# ============================================================================
# APP INITIALIZATION
# ============================================================================
# Pipeline object (loaded at startup)
pipeline: Optional[WheatDiseasePipeline] = None
startup_error: Optional[str] = None
@asynccontextmanager
async def lifespan(app: FastAPI):
"""Load pipeline on startup."""
global pipeline, startup_error
print("Pipeline yukleniyor...")
try:
pipeline = WheatDiseasePipeline(
cls_checkpoint = str(config.MODEL_CHECKPOINT_PATH),
cls_mapping = str(config.MODELS_DIR / "class_mapping.json"),
device = str(config.DEVICE),
cls_conf = config.CONFIDENCE_THRESHOLD,
)
print("Pipeline hazir, API isteklere acik.")
except Exception as e:
startup_error = str(e)
print(f"Pipeline yuklenemedi: {e}")
yield
print("API kapatiliyor.")
app = FastAPI(
title = "Wheat Disease Detection API",
description = (
"Wheat leaf and head disease classification API.\n\n"
"**Supported 15 Classes:** Aphid, Blast, Black Rust, Brown Rust, "
"Common Root Rot, Fusarium Head Blight, Healthy, Leaf Blight, "
"Mildew, Mite, Septoria, Smut, Stem fly, Tan spot, Yellow Rust\n\n"
"**Model:** Swin Transformer (Tiny)"
),
version = "1.0.0",
lifespan = lifespan,
)
# CORS
app.add_middleware(
CORSMiddleware,
allow_origins = ["*"],
allow_credentials = True,
allow_methods = ["*"],
allow_headers = ["*"],
)
# ============================================================================
# SCHEMAS (Pydantic)
# ============================================================================
class ClassificationResult(BaseModel):
predicted_class : str
confidence : float
is_certain : bool
top3_predictions: list
class QualityInfo(BaseModel):
is_valid : bool
blur_score : float
warnings : list
rejection_reason: Optional[str]
class MetaInfo(BaseModel):
processing_time_ms: float
image_size : dict
class AnalyzeResponse(BaseModel):
classification : ClassificationResult
quality : QualityInfo
meta : MetaInfo
class HealthResponse(BaseModel):
status : str
pipeline_ready: bool
model_loaded : bool
device : str
num_classes : int
error : Optional[str] = None
# ============================================================================
# HELPERS
# ============================================================================
def _check_pipeline():
"""Raise 503 if pipeline is not ready."""
if pipeline is None:
raise HTTPException(
status_code=503,
detail={
"error" : "Pipeline yuklenemedi",
"message": startup_error or "Bilinmeyen hata",
"hint" : "Model dosyalarinin var oldugundan emin olun.",
},
)
def _validate_image(file: UploadFile):
"""Validate uploaded file is an image."""
allowed = {"image/jpeg", "image/jpg", "image/png",
"image/bmp", "image/tiff", "image/webp"}
if file.content_type not in allowed:
raise HTTPException(
status_code=415,
detail=f"Desteklenmeyen format: {file.content_type}. "
f"Kabul edilenler: {', '.join(allowed)}",
)
# Size limit: 20MB
MAX_SIZE = 20 * 1024 * 1024
if file.size and file.size > MAX_SIZE:
raise HTTPException(
status_code=413,
detail=f"Dosya cok buyuk ({file.size/1024/1024:.1f} MB). Maksimum: 20 MB",
)
# ============================================================================
# ENDPOINTS
# ============================================================================
@app.get("/", include_in_schema=False)
async def root():
return RedirectResponse(url="/docs")
@app.get(
"/health",
response_model=HealthResponse,
summary="System Status",
tags=["System"],
)
async def health():
"""Returns API and model status."""
ready = pipeline is not None
return HealthResponse(
status = "ok" if ready else "degraded",
pipeline_ready= ready,
model_loaded = ready,
device = str(config.DEVICE),
num_classes = config.NUM_CLASSES,
error = startup_error,
)
@app.get(
"/classes",
summary="Supported Disease Classes",
tags=["Info"],
)
async def get_classes():
"""Lists 15 disease classes."""
mapping_path = config.MODELS_DIR / "class_mapping.json"
if mapping_path.exists():
with open(mapping_path, "r", encoding="utf-8") as f:
idx_to_class = json.load(f)
classes = [{"id": int(k), "name": v} for k, v in sorted(idx_to_class.items(), key=lambda x: int(x[0]))]
else:
classes = [{"id": i, "name": n} for i, n in enumerate(config.DATASET_CLASSES)]
return {
"num_classes": len(classes),
"classes" : classes,
}
@app.post(
"/analyze",
response_model=AnalyzeResponse,
summary="Image Analysis",
tags=["Analysis"],
)
async def analyze(
file : UploadFile = File(..., description="Wheat image (jpg/png)"),
skip_quality : bool = Query(False, description="Skip quality filter"),
):
"""Runs analysis on uploaded image."""
_check_pipeline()
_validate_image(file)
image_bytes = await file.read()
if not image_bytes:
raise HTTPException(status_code=400, detail="Empty file")
try:
result: PipelineResult = pipeline.run(image_bytes, skip_quality=skip_quality)
except Exception as e:
raise HTTPException(status_code=500, detail=f"Analysis error: {str(e)}")
return JSONResponse(content=pipeline.result_to_dict(result))
# ============================================================================
# RUN
# ============================================================================
if __name__ == "__main__":
import uvicorn
print("Wheat Disease API baslatiliyor...")
uvicorn.run(
"api:app",
host = config.API_HOST,
port = config.API_PORT,
reload = config.API_DEBUG,
workers = 1,
)