import os import uuid import tempfile from pathlib import Path from fastapi import FastAPI, UploadFile, File, Form, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import JSONResponse from fastapi.staticfiles import StaticFiles from src.model.pipeline import pipeline ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".webp", ".bmp"} MAX_FILE_SIZE_MB = 5 MAX_N_CIRCLES = 500 MIN_N_CIRCLES = 1 app = FastAPI(title="Fourier Epicycle API") app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=False, allow_methods=["POST", "GET"], allow_headers=["*"], ) @app.post("/api/fourier-data") async def get_fourier_data( file: UploadFile = File(...), n_circles: int = Form(...) ): # Validate n_circles if not (MIN_N_CIRCLES <= n_circles <= MAX_N_CIRCLES): raise HTTPException( status_code=400, detail=f"n_circles must be between {MIN_N_CIRCLES} and {MAX_N_CIRCLES}" ) # Validate file extension original_ext = Path(file.filename).suffix.lower() if file.filename else "" if original_ext not in ALLOWED_EXTENSIONS: raise HTTPException( status_code=400, detail=f"Unsupported file type. Allowed: {', '.join(ALLOWED_EXTENSIONS)}" ) # Read and validate file size contents = await file.read() if len(contents) > MAX_FILE_SIZE_MB * 1024 * 1024: raise HTTPException( status_code=413, detail=f"File too large. Maximum size is {MAX_FILE_SIZE_MB} MB" ) # Write to a safe temp file with a random name tmp_dir = tempfile.gettempdir() temp_path = os.path.join(tmp_dir, f"{uuid.uuid4().hex}{original_ext}") try: with open(temp_path, "wb") as f: f.write(contents) result = pipeline(image_path=temp_path, n_circles=n_circles) circles = [ [ float(result['magnitude'][i]), float(result['frequencies'][i]), float(result['phases'][i]) ] for i in range(len(result['frequencies'])) ] return {"circles": circles, "success": True} except HTTPException: raise except Exception: return JSONResponse( status_code=500, content={"error": "Image processing failed. Try a different image or fewer circles.", "success": False} ) finally: if os.path.exists(temp_path): os.remove(temp_path) # Serve the frontend — must be mounted AFTER API routes app.mount("/", StaticFiles(directory=".", html=True), name="static")