MetalGuard-AI / app.py
Ashgibbs's picture
Upload folder using huggingface_hub
7068f81 verified
Raw
History Blame Contribute Delete
10.3 kB
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
# ---------------------------
@app.get("/api")
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
# ---------------------------
@app.post("/predict")
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
# ---------------------------
@app.post("/predict-multiple")
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
# ---------------------------
@app.post("/predict-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
# ---------------------------
@app.post("/predict-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
# ---------------------------
@app.post("/predict-video")
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)
# ---------------------------
@app.post("/predict-detection")
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)
# ---------------------------
@app.post("/predict-video-detection")
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")