From 6a3f0632844f46b584c2b642aea47f2ce6e77c91 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Mon, 17 Nov 2025 20:38:54 +0530 Subject: [PATCH 1/4] Add .gitattributes to enforce LF endings --- .gitattributes | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e69de29 From 55f0ad9d84eefa6754792d54ca66b9a963418243 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Mon, 17 Nov 2025 20:42:56 +0530 Subject: [PATCH 2/4] Add error handling to FastAPI app --- model-lab/app/main.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/model-lab/app/main.py b/model-lab/app/main.py index bb63904..7b5316e 100644 --- a/model-lab/app/main.py +++ b/model-lab/app/main.py @@ -6,15 +6,20 @@ from pathlib import Path from fastapi.middleware.cors import CORSMiddleware from dotenv import load_dotenv + load_dotenv() app = FastAPI(title="Face Sentiment API") # path to the model BASE_DIR = Path(__file__).resolve().parent.parent -MODEL_PATH = BASE_DIR / "models"/ "onxx_models" / "emotion-ferplus-8.onnx" +MODEL_PATH = BASE_DIR / "models" / "onxx_models" / "emotion-ferplus-8.onnx" +if not MODEL_PATH.exists(): + raise HTTPException(status_code=500, detail="Model file is not found") ALLOWED_ORIGINS = os.getenv("CORS_ALLOWED_ORIGINS", "http://localhost:3000").split(",") +if not ALLOWED_ORIGINS: + raise HTTPException(status_code=500, detail="No allowed origins configured") app.add_middleware( CORSMiddleware, @@ -27,12 +32,17 @@ async def predict(file: UploadFile = File(...), model_option: int = Form(1)): if not file.content_type.startswith("image/"): raise HTTPException(status_code=400, detail="File must be an image") + file_bytes = await file.read() try: image = Image.open(io.BytesIO(file_bytes)) except (OSError, ValueError) as err: raise HTTPException(status_code=400, detail="Invalid or corrupted image file") from err - emotion_model = Model(model_path=MODEL_PATH, model_option=model_option) - emotion, prob = emotion_model.predict(pil_image=image) - - return {"emotion": emotion, "probabilities": prob} \ No newline at end of file + + try: + emotion_model = Model(model_path=MODEL_PATH, model_option=model_option) + emotion, prob = emotion_model.predict(pil_image=image) + except Exception as e: + raise HTTPException(status_code=500, detail=f"Model inference failed: {e}") + + return {"emotion": emotion, "probabilities": prob} From 0e155d417c581a40afd28ffc4a334196a20d7db7 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Tue, 18 Nov 2025 22:17:32 +0530 Subject: [PATCH 3/4] Enhance FastAPI app with new improvements --- model-lab/app/main.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/model-lab/app/main.py b/model-lab/app/main.py index 7b5316e..b39a71e 100644 --- a/model-lab/app/main.py +++ b/model-lab/app/main.py @@ -11,15 +11,16 @@ app = FastAPI(title="Face Sentiment API") -# path to the model +# Path to the model (fixed typo: onxx_models → onnx_models) BASE_DIR = Path(__file__).resolve().parent.parent -MODEL_PATH = BASE_DIR / "models" / "onxx_models" / "emotion-ferplus-8.onnx" +MODEL_PATH = BASE_DIR / "models" / "onnx_models" / "emotion-ferplus-8.onnx" if not MODEL_PATH.exists(): - raise HTTPException(status_code=500, detail="Model file is not found") + raise FileNotFoundError(f"Model file not found: {MODEL_PATH}") +# Validate CORS origins properly ALLOWED_ORIGINS = os.getenv("CORS_ALLOWED_ORIGINS", "http://localhost:3000").split(",") -if not ALLOWED_ORIGINS: - raise HTTPException(status_code=500, detail="No allowed origins configured") +if not ALLOWED_ORIGINS or ALLOWED_ORIGINS == [""]: + raise RuntimeError("No allowed origins configured in CORS_ALLOWED_ORIGINS") app.add_middleware( CORSMiddleware, @@ -28,6 +29,9 @@ allow_headers=["Content-Type"], ) +# Load model once at startup (not per request) +emotion_model = Model(model_path=MODEL_PATH, model_option=1) + @app.post("/predict") async def predict(file: UploadFile = File(...), model_option: int = Form(1)): if not file.content_type.startswith("image/"): @@ -40,9 +44,10 @@ async def predict(file: UploadFile = File(...), model_option: int = Form(1)): raise HTTPException(status_code=400, detail="Invalid or corrupted image file") from err try: - emotion_model = Model(model_path=MODEL_PATH, model_option=model_option) + # Use already loaded model, but allow overriding option if needed + emotion_model.model_option = model_option emotion, prob = emotion_model.predict(pil_image=image) except Exception as e: - raise HTTPException(status_code=500, detail=f"Model inference failed: {e}") + raise HTTPException(status_code=500, detail=f"Model inference failed: {e}") from e return {"emotion": emotion, "probabilities": prob} From f04dd677cda9c63f04ae08ec4ccce7cda7781d21 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Tue, 18 Nov 2025 22:28:31 +0530 Subject: [PATCH 4/4] Add error handling to FastAPI app --- model-lab/app/main.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/model-lab/app/main.py b/model-lab/app/main.py index b39a71e..61815f0 100644 --- a/model-lab/app/main.py +++ b/model-lab/app/main.py @@ -29,8 +29,11 @@ allow_headers=["Content-Type"], ) -# Load model once at startup (not per request) -emotion_model = Model(model_path=MODEL_PATH, model_option=1) +# ✅ Pre-load both model options at startup to avoid race conditions +emotion_models = { + 1: Model(model_path=MODEL_PATH, model_option=1), # HuggingFace ViT + 2: Model(model_path=MODEL_PATH, model_option=2) # ONNX with CV2 +} @app.post("/predict") async def predict(file: UploadFile = File(...), model_option: int = Form(1)): @@ -43,10 +46,13 @@ async def predict(file: UploadFile = File(...), model_option: int = Form(1)): except (OSError, ValueError) as err: raise HTTPException(status_code=400, detail="Invalid or corrupted image file") from err + # ✅ Validate model_option before using + if model_option not in emotion_models: + raise HTTPException(status_code=400, detail=f"Invalid model_option: {model_option}") + try: - # Use already loaded model, but allow overriding option if needed - emotion_model.model_option = model_option - emotion, prob = emotion_model.predict(pil_image=image) + # ✅ Select the correct preloaded model without mutating shared state + emotion, prob = emotion_models[model_option].predict(pil_image=image) except Exception as e: raise HTTPException(status_code=500, detail=f"Model inference failed: {e}") from e