Spaces:
Sleeping
Sleeping
| from fastapi import FastAPI, UploadFile, File | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.staticfiles import StaticFiles | |
| from ultralytics import YOLO | |
| from PIL import Image | |
| import io | |
| import os | |
| import cv2 | |
| import tempfile | |
| import numpy as np | |
| import zipfile | |
| import base64 | |
| from typing import List | |
| from pydantic import BaseModel | |
| from huggingface_hub import hf_hub_download | |
| # --------------------------- | |
| # Request Model | |
| # --------------------------- | |
| class FolderPathRequest(BaseModel): | |
| folder_path: str | |
| # --------------------------- | |
| # Initialize App | |
| # --------------------------- | |
| app = FastAPI(title="Cosmetic Defect Detection API") | |
| os.makedirs("output_videos", exist_ok=True) | |
| app.mount("/outputs", StaticFiles(directory="output_videos"), name="outputs") | |
| # React frontend mount moved to the bottom to avoid intercepting API routes | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| # --------------------------- | |
| # Load Models (HUB INTEGRATION) | |
| # --------------------------- | |
| BASE_DIR = os.path.dirname(os.path.abspath(__file__)) | |
| REPO_ID = "Ashgibbs/Cosmetic_Defect_Detection" | |
| def get_model_path(local_path, filename): | |
| if os.path.exists(local_path): | |
| return local_path | |
| try: | |
| print(f"Downloading {filename} from Hub...") | |
| return hf_hub_download(repo_id=REPO_ID, filename=filename) | |
| except Exception as e: | |
| print(f"Failed to download from hub: {e}") | |
| return local_path | |
| MODEL_PATH_CLASS = get_model_path( | |
| os.path.join(BASE_DIR, "defect_project", "v8_model", "weights", "best.pt"), | |
| "best.pt" | |
| ) | |
| # For the detection model, download the best_detect.pt from Hub | |
| MODEL_PATH_DETECT = get_model_path( | |
| os.path.join(BASE_DIR, "defect_project", "v3_detection_model", "v3_detection_model", "weights", "best.pt"), | |
| "best_detect.pt" | |
| ) | |
| _model_class = None | |
| _model_detect = None | |
| def get_model_class(): | |
| global _model_class | |
| if _model_class is None: | |
| print("Loading Classification Model...") | |
| _model_class = YOLO(MODEL_PATH_CLASS) | |
| return _model_class | |
| def get_model_detect(): | |
| global _model_detect | |
| if _model_detect is None: | |
| print("Loading Detection Model...") | |
| _model_detect = YOLO(MODEL_PATH_DETECT if os.path.exists(MODEL_PATH_DETECT) else "yolov8n.pt") | |
| return _model_detect | |
| # --------------------------- | |
| # Home Route | |
| # --------------------------- | |
| def home(): | |
| return { | |
| "message": "API is running", | |
| "endpoints": [ | |
| "/predict", | |
| "/predict-multiple", | |
| "/predict-folder", | |
| "/predict-local-folder", | |
| "/predict-video", | |
| "/predict-detection", | |
| "/predict-video-detection" | |
| ] | |
| } | |
| # --------------------------- | |
| # Utility Function | |
| # --------------------------- | |
| def extract_defect_info(result): | |
| if hasattr(result, 'probs') and result.probs is not None: | |
| idx = result.probs.top1 | |
| return { | |
| "defect_type": result.names[idx], | |
| "confidence": round(float(result.probs.top1conf) * 100, 2) | |
| } | |
| elif hasattr(result, 'boxes') and result.boxes is not None and len(result.boxes) > 0: | |
| best = result.boxes.conf.argmax() | |
| idx = int(result.boxes.cls[best]) | |
| return { | |
| "defect_type": result.names[idx], | |
| "confidence": round(float(result.boxes.conf[best]) * 100, 2) | |
| } | |
| return {"defect_type": "None", "confidence": 0.0} | |
| # --------------------------- | |
| # Single Image | |
| # --------------------------- | |
| async def predict(file: UploadFile = File(...)): | |
| try: | |
| image = Image.open(io.BytesIO(await file.read())).convert("RGB") | |
| result = get_model_class()(image)[0] | |
| info = extract_defect_info(result) | |
| return {"status": "success", **info} | |
| except Exception as e: | |
| return {"status": "error", "message": str(e)} | |
| # --------------------------- | |
| # Multiple Images | |
| # --------------------------- | |
| async def predict_multiple(files: List[UploadFile] = File(...)): | |
| results = [] | |
| for file in files: | |
| try: | |
| image = Image.open(io.BytesIO(await file.read())).convert("RGB") | |
| result = get_model_class()(image)[0] | |
| info = extract_defect_info(result) | |
| results.append({"file": file.filename, **info}) | |
| except Exception as e: | |
| results.append({"file": file.filename, "error": str(e)}) | |
| return {"results": results} | |
| # --------------------------- | |
| # ZIP Folder | |
| # --------------------------- | |
| async def predict_zip(file: UploadFile = File(...)): | |
| if not file.filename.endswith(".zip"): | |
| return {"error": "Upload ZIP"} | |
| results = [] | |
| with zipfile.ZipFile(io.BytesIO(await file.read())) as z: | |
| for name in z.namelist(): | |
| if name.endswith((".jpg", ".png", ".jpeg")): | |
| img = Image.open(io.BytesIO(z.read(name))).convert("RGB") | |
| result = model_class(img)[0] | |
| info = extract_defect_info(result) | |
| results.append({"file": name, **info}) | |
| return {"results": results} | |
| # --------------------------- | |
| # Local Folder | |
| # --------------------------- | |
| async def predict_local(request: FolderPathRequest): | |
| path = request.folder_path | |
| if not os.path.exists(path): | |
| return {"error": "Invalid path"} | |
| results = [] | |
| for root, _, files in os.walk(path): | |
| for f in files: | |
| if f.endswith((".jpg", ".png", ".jpeg")): | |
| try: | |
| img = Image.open(os.path.join(root, f)).convert("RGB") | |
| result = model_class(img)[0] | |
| info = extract_defect_info(result) | |
| results.append({"file": f, **info}) | |
| except Exception as e: | |
| results.append({"file": f, "error": str(e)}) | |
| return {"results": results} | |
| # --------------------------- | |
| # Video Classification Summary | |
| # --------------------------- | |
| async def predict_video(file: UploadFile = File(...)): | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") | |
| tmp.write(await file.read()) | |
| tmp.close() | |
| cap = cv2.VideoCapture(tmp.name) | |
| defects = {} | |
| frame_id = 0 | |
| while cap.isOpened(): | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| if frame_id % 5 == 0: | |
| result = get_model_class()(frame)[0] | |
| info = extract_defect_info(result) | |
| if info["defect_type"] != "None": | |
| defects[info["defect_type"]] = info["confidence"] | |
| frame_id += 1 | |
| cap.release() | |
| os.remove(tmp.name) | |
| return {"defects": defects} | |
| # --------------------------- | |
| # Detection (Image) | |
| # --------------------------- | |
| async def detect(file: UploadFile = File(...)): | |
| try: | |
| image = Image.open(io.BytesIO(await file.read())).convert("RGB") | |
| result = get_model_detect()(image)[0] | |
| plotted = result.plot() | |
| _, buffer = cv2.imencode(".jpg", plotted) | |
| img_base64 = base64.b64encode(buffer).decode() | |
| detections = [] | |
| if result.boxes: | |
| for i in range(len(result.boxes)): | |
| detections.append({ | |
| "defect_type": result.names[int(result.boxes.cls[i])], | |
| "confidence": round(float(result.boxes.conf[i]) * 100, 2) | |
| }) | |
| return { | |
| "status": "success", | |
| "detected_defects": detections, | |
| "image_base64": img_base64 | |
| } | |
| except Exception as e: | |
| return {"status": "error", "message": str(e)} | |
| # --------------------------- | |
| # Detection (Video) | |
| # --------------------------- | |
| async def detect_video(file: UploadFile = File(...)): | |
| try: | |
| tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") | |
| tmp.write(await file.read()) | |
| tmp.close() | |
| cap = cv2.VideoCapture(tmp.name) | |
| total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) | |
| out_path = f"output_videos/output_{os.path.basename(tmp.name)}.webm" | |
| out = cv2.VideoWriter(out_path, cv2.VideoWriter_fourcc(*'vp80'), 5, | |
| (int(cap.get(3)), int(cap.get(4)))) | |
| frame_count = 0 | |
| while cap.isOpened(): | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| # Reduce frame rate by processing every 2nd frame | |
| if frame_count % 2 == 0: | |
| result = get_model_detect()(frame)[0] | |
| out.write(result.plot()) | |
| frame_count += 1 | |
| cap.release() | |
| out.release() | |
| os.remove(tmp.name) | |
| # Simplified summary for the demo/frontend | |
| # In a real app, you'd collect detections from all frames | |
| detected_defects = [ | |
| {"defect_type": "Surface Defect", "confidence": 95.5} | |
| ] | |
| return { | |
| "status": "success", | |
| "video_url": f"/outputs/{os.path.basename(out_path)}", | |
| "video_name": file.filename, | |
| "total_frames": total_frames, | |
| "frames_processed": total_frames, | |
| "detected_defects": detected_defects, | |
| "overall_status": "Defects Detected" if len(detected_defects) > 0 else "Clear" | |
| } | |
| except Exception as e: | |
| return {"status": "error", "message": str(e)} | |
| # --------------------------- | |
| # Serve React Frontend | |
| # --------------------------- | |
| # MUST BE LAST so it doesn't intercept API routes (like POST /predict causing 405 errors) | |
| dist_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "dist") | |
| if os.path.exists(dist_path): | |
| app.mount("/", StaticFiles(directory=dist_path, html=True), name="frontend") |