| import os |
| import base64 |
| import cv2 |
| import numpy as np |
|
|
| MODEL_DIR = os.path.join(os.path.dirname(__file__), "models") |
| MODEL_PATH = os.path.join(MODEL_DIR, "best.onnx") |
| _model = None |
|
|
| CLASS_NAMES = {0: "comedone", 1: "nodules", 2: "papules", 3: "pustules"} |
| CLASS_ID_TO_NAME = {0: "comedone", 1: "nodules", 2: "papules", 3: "pustules"} |
|
|
| CLASS_COLORS = { |
| "comedone": (0, 200, 255), |
| "nodules": (0, 0, 255), |
| "papules": (0, 165, 255), |
| "pustules": (255, 0, 0), |
| } |
|
|
|
|
| def get_model(): |
| from ultralytics import YOLO |
| global _model |
| if _model is None: |
| _model = YOLO(MODEL_PATH) |
| return _model |
|
|
|
|
| def draw_annotations(image_bgr: np.ndarray, results) -> str: |
| """Gambar bbox + label pada gambar, return sebagai base64 JPEG string.""" |
| annotated = image_bgr.copy() |
|
|
| if results.boxes is not None and len(results.boxes) > 0: |
| boxes = results.boxes.xyxy.cpu().numpy() |
| confs = results.boxes.conf.cpu().numpy() |
| cls_ids = results.boxes.cls.cpu().numpy().astype(int) |
|
|
| for box, conf, cls_id in zip(boxes, confs, cls_ids): |
| cls_name = CLASS_ID_TO_NAME.get(cls_id, "unknown") |
| color = CLASS_COLORS.get(cls_name, (0, 255, 0)) |
| x1, y1, x2, y2 = map(int, box) |
|
|
| cv2.rectangle(annotated, (x1, y1), (x2, y2), color, 2) |
|
|
| label = f"{cls_name} {conf:.2f}" |
| (tw, th), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.5, 1) |
| cv2.rectangle(annotated, (x1, y1 - th - 6), (x1 + tw + 4, y1), color, -1) |
| cv2.putText(annotated, label, (x1 + 2, y1 - 3), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1) |
|
|
| _, buffer = cv2.imencode('.jpg', annotated, [cv2.IMWRITE_JPEG_QUALITY, 85]) |
| return base64.b64encode(buffer).decode('utf-8') |
|
|
|
|
| def detect_acne(image_bgr: np.ndarray, conf_threshold: float = 0.05, iou_threshold: float = 0.35) -> dict: |
| """ |
| Deteksi jerawat pada gambar (numpy array BGR). |
| Return dict berisi list deteksi, ringkasan, dan gambar anotasi (base64). |
| """ |
| model = get_model() |
|
|
| if model is None: |
| return { |
| "detections": [], |
| "detected_classes": [], |
| "summary": {}, |
| "total_detections": 0, |
| "model_loaded": False, |
| "annotated_image": None, |
| } |
|
|
| results = model.predict( |
| source=image_bgr, |
| conf=conf_threshold, |
| iou=iou_threshold, |
| verbose=False, |
| imgsz=640, |
| )[0] |
|
|
| detections = [] |
| summary: dict[str, int] = {} |
|
|
| if results.boxes is not None and len(results.boxes) > 0: |
| for box, conf, cls_id in zip( |
| results.boxes.xyxy.tolist(), |
| results.boxes.conf.tolist(), |
| results.boxes.cls.tolist(), |
| ): |
| cls_name = CLASS_ID_TO_NAME.get(int(cls_id), "unknown") |
| summary[cls_name] = summary.get(cls_name, 0) + 1 |
| detections.append({ |
| "class": cls_name, |
| "confidence": round(float(conf), 4), |
| "bbox_xyxy": [round(v, 4) for v in box], |
| }) |
|
|
| annotated_b64 = draw_annotations(image_bgr, results) |
|
|
| return { |
| "detections": detections, |
| "detected_classes": list(summary.keys()), |
| "summary": summary, |
| "total_detections": len(detections), |
| "model_loaded": True, |
| "annotated_image": annotated_b64, |
| "conf_threshold": conf_threshold, |
| "iou_threshold": iou_threshold, |
| } |
|
|