Spaces:
Sleeping
Sleeping
| """ | |
| detection.py — Core YOLO inference engine. | |
| - run_detection_single() : one model on one image | |
| - run_detection_combined() : ALL 6 models, keep only the single | |
| highest-confidence box overall | |
| - extract_video_frames() : samples frames every N seconds from a video | |
| """ | |
| import os | |
| import cv2 | |
| import random | |
| import logging | |
| from pathlib import Path | |
| from typing import Dict, List, Any | |
| from model_loader import load_model, get_model_info, get_severity, MODEL_REGISTRY | |
| logger = logging.getLogger(__name__) | |
| SEVERITY_COLOR_BGR = { | |
| "Critical": (0, 0, 220), | |
| "High": (0, 100, 255), | |
| "Medium": (0, 200, 255), | |
| "Low": (0, 200, 0), | |
| } | |
| VIDEO_FRAME_INTERVAL_SECONDS = 2 | |
| def run_detection_single(image_path: str, model_key: str, output_dir: str, image_id: str, conf_threshold: float = 0.45) -> Dict[str, Any]: | |
| model = load_model(model_key) | |
| if model is None: | |
| detections = _mock_detections(model_key, conf_threshold) | |
| else: | |
| detections = _run_yolo(model, model_key, image_path, conf_threshold) | |
| return _finalize_result(image_path, detections, output_dir, image_id, model_key) | |
| def run_detection_combined(image_path: str, output_dir: str, image_id: str, conf_threshold: float = 0.45) -> Dict[str, Any]: | |
| all_detections: List[Dict] = [] | |
| for model_key in MODEL_REGISTRY.keys(): | |
| model = load_model(model_key) | |
| if model is None: | |
| dets = _mock_detections(model_key, conf_threshold) | |
| else: | |
| dets = _run_yolo(model, model_key, image_path, conf_threshold) | |
| all_detections.extend(dets) | |
| best = [max(all_detections, key=lambda d: d["confidence"])] if all_detections else [] | |
| return _finalize_result(image_path, best, output_dir, image_id, "combined (todos los modelos)") | |
| def extract_video_frames(video_path: str, output_dir: str, video_id: str) -> List[Dict]: | |
| cap = cv2.VideoCapture(video_path) | |
| if not cap.isOpened(): | |
| raise ValueError(f"Cannot open video file: {video_path}") | |
| fps = cap.get(cv2.CAP_PROP_FPS) or 25 | |
| frame_interval = max(1, int(fps * VIDEO_FRAME_INTERVAL_SECONDS)) | |
| frames_dir = Path(output_dir) / "video_frames" | |
| frames_dir.mkdir(parents=True, exist_ok=True) | |
| results = [] | |
| frame_idx = 0 | |
| saved_idx = 0 | |
| while True: | |
| ret, frame = cap.read() | |
| if not ret: | |
| break | |
| if frame_idx % frame_interval == 0: | |
| timestamp_sec = round(frame_idx / fps, 1) | |
| frame_id = f"{video_id}_frame{saved_idx:03d}" | |
| frame_path = frames_dir / f"{frame_id}.jpg" | |
| cv2.imwrite(str(frame_path), frame, [cv2.IMWRITE_JPEG_QUALITY, 90]) | |
| results.append({"frame_path": str(frame_path), "frame_id": frame_id, "timestamp_sec": timestamp_sec}) | |
| saved_idx += 1 | |
| frame_idx += 1 | |
| cap.release() | |
| logger.info("Extracted %d frames from video %s", len(results), video_id) | |
| return results | |
| def _run_yolo(model, model_key: str, image_path: str, conf_threshold: float) -> List[Dict]: | |
| results = model(image_path, conf=conf_threshold, iou=0.45, imgsz=640, device="cpu", verbose=False) | |
| detections = [] | |
| for r in results: | |
| boxes = r.boxes | |
| if boxes is None: | |
| continue | |
| for box in boxes: | |
| cls_id = int(box.cls[0]) | |
| cls_name = model.names.get(cls_id, f"class_{cls_id}") | |
| conf = float(box.conf[0]) | |
| x1, y1, x2, y2 = box.xyxy[0].tolist() | |
| detections.append({ | |
| "class": cls_name, "class_name": cls_name, | |
| "confidence": round(conf, 4), | |
| "severity": get_severity(model_key, cls_name), | |
| "bbox": [round(x1), round(y1), round(x2), round(y2)], | |
| "model_used": model_key, | |
| }) | |
| return detections | |
| def _mock_detections(model_key: str, conf_threshold: float) -> List[Dict]: | |
| info = get_model_info(model_key) or {} | |
| classes = info.get("classes", ["defect"]) | |
| n = random.randint(1, 3) | |
| detections = [] | |
| for _ in range(n): | |
| cls = random.choice(classes) | |
| conf = round(random.uniform(max(conf_threshold, 0.4), 0.95), 3) | |
| x1, y1 = random.randint(20, 200), random.randint(20, 200) | |
| x2, y2 = x1 + random.randint(80, 250), y1 + random.randint(80, 250) | |
| detections.append({ | |
| "class": cls, "class_name": cls, "confidence": conf, | |
| "severity": get_severity(model_key, cls), | |
| "bbox": [x1, y1, x2, y2], "model_used": model_key, | |
| }) | |
| return detections | |
| def _finalize_result(image_path: str, detections: List[Dict], output_dir: str, image_id: str, model_label: str) -> Dict[str, Any]: | |
| img = cv2.imread(image_path) | |
| if img is None: | |
| raise ValueError(f"Cannot read image: {image_path}") | |
| annotated = img.copy() | |
| for det in detections: | |
| x1, y1, x2, y2 = det["bbox"] | |
| color = SEVERITY_COLOR_BGR.get(det["severity"], (255, 255, 255)) | |
| cv2.rectangle(annotated, (x1, y1), (x2, y2), color, 2) | |
| label = f"{det['class_name'].replace('_', ' ')} {det['confidence']:.0%}" | |
| (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.55, 2) | |
| cv2.rectangle(annotated, (x1, y1 - th - 8), (x1 + tw + 6, y1), color, -1) | |
| cv2.putText(annotated, label, (x1 + 3, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 2) | |
| out_name = f"{image_id}_annotated.jpg" | |
| out_path = Path(output_dir) / out_name | |
| cv2.imwrite(str(out_path), annotated, [cv2.IMWRITE_JPEG_QUALITY, 92]) | |
| severity_summary = {"Critical": 0, "High": 0, "Medium": 0, "Low": 0} | |
| for d in detections: | |
| severity_summary[d["severity"]] = severity_summary.get(d["severity"], 0) + 1 | |
| overall_severity = "Low" | |
| for sev in ["Critical", "High", "Medium", "Low"]: | |
| if severity_summary.get(sev, 0) > 0: | |
| overall_severity = sev | |
| break | |
| return { | |
| "detections": detections, | |
| "total_defects": len(detections), | |
| "severity_summary": severity_summary, | |
| "overall_severity": overall_severity, | |
| "annotated_image": f"/outputs/{out_name}", | |
| "model_used": model_label, | |
| } | |