From 6a3f0632844f46b584c2b642aea47f2ce6e77c91 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Mon, 17 Nov 2025 20:38:54 +0530 Subject: [PATCH 1/5] 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/5] 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/5] 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/5] 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 From e3413710de69258e72474c22fcec622a5ec59354 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Wed, 19 Nov 2025 09:07:25 +0530 Subject: [PATCH 5/5] Add simple error handling: fixed model path, startup exceptions, CORS validation, and global model loading --- model-lab/app/main.py | 46 ++++++++++++++++++++++++++++++------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/model-lab/app/main.py b/model-lab/app/main.py index 61815f0..03c6592 100644 --- a/model-lab/app/main.py +++ b/model-lab/app/main.py @@ -1,23 +1,31 @@ from fastapi import FastAPI, File, UploadFile, HTTPException, Form +from fastapi.middleware.cors import CORSMiddleware +from pathlib import Path +from dotenv import load_dotenv from PIL import Image import io import os +import logging + from app.model import Model -from pathlib import Path -from fastapi.middleware.cors import CORSMiddleware -from dotenv import load_dotenv +# Load environment variables load_dotenv() +# Configure logging +logging.basicConfig(level=logging.INFO) +logger = logging.getLogger(__name__) + +# Initialize FastAPI app app = FastAPI(title="Face Sentiment API") -# Path to the model (fixed typo: onxx_models → onnx_models) +# ✅ Path to the model (fixed typo: onxx_models → onnx_models) BASE_DIR = Path(__file__).resolve().parent.parent MODEL_PATH = BASE_DIR / "models" / "onnx_models" / "emotion-ferplus-8.onnx" if not MODEL_PATH.exists(): raise FileNotFoundError(f"Model file not found: {MODEL_PATH}") -# Validate CORS origins properly +# ✅ Validate CORS origins properly ALLOWED_ORIGINS = os.getenv("CORS_ALLOWED_ORIGINS", "http://localhost:3000").split(",") if not ALLOWED_ORIGINS or ALLOWED_ORIGINS == [""]: raise RuntimeError("No allowed origins configured in CORS_ALLOWED_ORIGINS") @@ -35,25 +43,35 @@ 2: Model(model_path=MODEL_PATH, model_option=2) # ONNX with CV2 } +# ✅ Prediction endpoint with corrected error handling @app.post("/predict") async def predict(file: UploadFile = File(...), model_option: int = Form(1)): + # Validate file type if not file.content_type.startswith("image/"): raise HTTPException(status_code=400, detail="File must be an image") + # Read file bytes 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 - # ✅ 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: - # ✅ 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 + # Validate model option + model = emotion_models.get(model_option) + if not model: + raise HTTPException(status_code=400, detail="Invalid model option") + + # Run prediction + prediction = model.predict(image) + return {"prediction": prediction} - return {"emotion": emotion, "probabilities": prob} + except HTTPException: + # Preserve explicit HTTP errors + raise + except Exception as e: + # Log internal error for debugging + logger.exception("Unexpected error during prediction") + # Return generic message to client + raise HTTPException(status_code=500, detail="Internal server error")