Upload folder using huggingface_hub
Browse files- handler.py +86 -6
- requirements.txt +1 -0
handler.py
CHANGED
|
@@ -1,13 +1,76 @@
|
|
| 1 |
-
# handler.py —
|
| 2 |
-
|
| 3 |
import io, base64, requests
|
|
|
|
| 4 |
from PIL import Image, ImageDraw
|
| 5 |
from ultralytics import YOLO
|
| 6 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
def _to_ints(xyxy):
|
| 8 |
x1, y1, x2, y2 = xyxy
|
| 9 |
return int(x1), int(y1), int(x2), int(y2)
|
| 10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
class EndpointHandler:
|
| 12 |
def __init__(self, path: str = ""):
|
| 13 |
self.model = YOLO(f"{path}/best.pt")
|
|
@@ -53,11 +116,11 @@ class EndpointHandler:
|
|
| 53 |
|
| 54 |
if r.boxes is not None:
|
| 55 |
for b in r.boxes:
|
| 56 |
-
xyxy = b.xyxy[0].tolist()
|
| 57 |
confv = float(b.conf[0])
|
| 58 |
x1, y1, x2, y2 = _to_ints(xyxy)
|
| 59 |
boxes_out.append({
|
| 60 |
-
"cls": "diseased",
|
| 61 |
"conf": confv,
|
| 62 |
"x1": x1, "y1": y1, "x2": x2, "y2": y2,
|
| 63 |
})
|
|
@@ -67,7 +130,24 @@ class EndpointHandler:
|
|
| 67 |
is_diseased = len(boxes_out) > 0
|
| 68 |
|
| 69 |
# 4) Annotate ảnh -> base64 (nếu có box)
|
| 70 |
-
annotated_b64: Optional[str] = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 71 |
|
| 72 |
return {
|
| 73 |
"is_diseased": is_diseased,
|
|
@@ -77,5 +157,5 @@ class EndpointHandler:
|
|
| 77 |
"image_height": H,
|
| 78 |
"threshold_conf": conf,
|
| 79 |
"boxes": boxes_out,
|
| 80 |
-
"
|
| 81 |
}
|
|
|
|
| 1 |
+
# handler.py — thêm các import mới
|
| 2 |
+
import os
|
| 3 |
import io, base64, requests
|
| 4 |
+
from typing import Any, Dict, Optional
|
| 5 |
from PIL import Image, ImageDraw
|
| 6 |
from ultralytics import YOLO
|
| 7 |
|
| 8 |
+
# ==== Gemini ====
|
| 9 |
+
from google import genai
|
| 10 |
+
from google.genai import types
|
| 11 |
+
|
| 12 |
def _to_ints(xyxy):
|
| 13 |
x1, y1, x2, y2 = xyxy
|
| 14 |
return int(x1), int(y1), int(x2), int(y2)
|
| 15 |
|
| 16 |
+
def _annotate_to_b64(img: Image.Image, boxes_out):
|
| 17 |
+
if not boxes_out:
|
| 18 |
+
return None
|
| 19 |
+
draw = ImageDraw.Draw(img)
|
| 20 |
+
for b in boxes_out:
|
| 21 |
+
draw.rectangle([(b["x1"], b["y1"]), (b["x2"], b["y2"])], outline=(255, 0, 0), width=3)
|
| 22 |
+
buf = io.BytesIO()
|
| 23 |
+
img.save(buf, format="PNG")
|
| 24 |
+
return base64.b64encode(buf.getvalue()).decode("utf-8")
|
| 25 |
+
|
| 26 |
+
def _ask_gemini_with_image(image_bytes: bytes, meta: dict) -> str:
|
| 27 |
+
api_key = os.getenv("API_KEY", "")
|
| 28 |
+
if not api_key:
|
| 29 |
+
return ""
|
| 30 |
+
# Dùng model PRO qua biến môi trường, default dùng model pro
|
| 31 |
+
model_name = os.getenv("MODEL_NAME", "gemini-2.0-pro")
|
| 32 |
+
|
| 33 |
+
client = genai.Client(api_key=api_key)
|
| 34 |
+
|
| 35 |
+
context_lines = [
|
| 36 |
+
f"YOLO is_diseased: {meta.get('is_diseased')}",
|
| 37 |
+
f"YOLO max_confidence: {meta.get('max_confidence')}",
|
| 38 |
+
f"YOLO num_detections: {meta.get('num_detections')}",
|
| 39 |
+
f"Image size: {meta.get('image_width')}x{meta.get('image_height')}",
|
| 40 |
+
f"threshold_conf: {meta.get('threshold_conf')}",
|
| 41 |
+
]
|
| 42 |
+
for i, b in enumerate(meta.get("boxes", []), 1):
|
| 43 |
+
context_lines.append(
|
| 44 |
+
f"Box#{i}: ({b['x1']},{b['y1']},{b['x2']},{b['y2']}), conf={b['conf']}"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
system_instruction = (
|
| 48 |
+
"You are a plant pathology assistant for Brassica (bok choy). "
|
| 49 |
+
"Analyze the annotated image (rectangles show suspicious regions). "
|
| 50 |
+
"Be concise and avoid over-diagnosis beyond visible evidence. "
|
| 51 |
+
"Also, re-evaluate based on your own understanding and not 100% on the data provided."
|
| 52 |
+
)
|
| 53 |
+
user_prompt = (
|
| 54 |
+
"Given the annotated image of bok choy leaves, determine whether disease signs are present.\n"
|
| 55 |
+
"If sick, describe the visible symptoms and suggest possible illnesses with a brief explanation. Also answer in one sentence\n"
|
| 56 |
+
"Context from detector:\n" + "\n".join(context_lines) + "\n"
|
| 57 |
+
"Finally transform result to vietnamese. Only reply result vietnamese (not include english)"
|
| 58 |
+
"Please answer clearly with just one sentence."
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
try:
|
| 62 |
+
resp = client.models.generate_content(
|
| 63 |
+
model=model_name,
|
| 64 |
+
contents=[
|
| 65 |
+
system_instruction,
|
| 66 |
+
types.Part.from_bytes(data=image_bytes, mime_type="image/png"),
|
| 67 |
+
user_prompt,
|
| 68 |
+
],
|
| 69 |
+
)
|
| 70 |
+
return (resp.text or "").strip()
|
| 71 |
+
except Exception:
|
| 72 |
+
return ""
|
| 73 |
+
|
| 74 |
class EndpointHandler:
|
| 75 |
def __init__(self, path: str = ""):
|
| 76 |
self.model = YOLO(f"{path}/best.pt")
|
|
|
|
| 116 |
|
| 117 |
if r.boxes is not None:
|
| 118 |
for b in r.boxes:
|
| 119 |
+
xyxy = b.xyxy[0].tolist()
|
| 120 |
confv = float(b.conf[0])
|
| 121 |
x1, y1, x2, y2 = _to_ints(xyxy)
|
| 122 |
boxes_out.append({
|
| 123 |
+
"cls": "diseased",
|
| 124 |
"conf": confv,
|
| 125 |
"x1": x1, "y1": y1, "x2": x2, "y2": y2,
|
| 126 |
})
|
|
|
|
| 130 |
is_diseased = len(boxes_out) > 0
|
| 131 |
|
| 132 |
# 4) Annotate ảnh -> base64 (nếu có box)
|
| 133 |
+
annotated_b64: Optional[str] = _annotate_to_b64(img.copy(), boxes_out) if is_diseased else None
|
| 134 |
+
|
| 135 |
+
# 5) Gọi Gemini (model PRO) nếu có bệnh và có ảnh annotated
|
| 136 |
+
gemini_text = ""
|
| 137 |
+
if is_diseased and annotated_b64:
|
| 138 |
+
meta = {
|
| 139 |
+
"is_diseased": is_diseased,
|
| 140 |
+
"max_confidence": max_conf,
|
| 141 |
+
"num_detections": len(boxes_out),
|
| 142 |
+
"image_width": W,
|
| 143 |
+
"image_height": H,
|
| 144 |
+
"threshold_conf": conf,
|
| 145 |
+
"boxes": boxes_out,
|
| 146 |
+
}
|
| 147 |
+
try:
|
| 148 |
+
gemini_text = _ask_gemini_with_image(base64.b64decode(annotated_b64), meta)
|
| 149 |
+
except Exception:
|
| 150 |
+
gemini_text = ""
|
| 151 |
|
| 152 |
return {
|
| 153 |
"is_diseased": is_diseased,
|
|
|
|
| 157 |
"image_height": H,
|
| 158 |
"threshold_conf": conf,
|
| 159 |
"boxes": boxes_out,
|
| 160 |
+
"prediction_text": gemini_text,
|
| 161 |
}
|
requirements.txt
CHANGED
|
@@ -2,3 +2,4 @@ ultralytics==8.3.179
|
|
| 2 |
torch==2.8.0
|
| 3 |
pillow
|
| 4 |
numpy
|
|
|
|
|
|
| 2 |
torch==2.8.0
|
| 3 |
pillow
|
| 4 |
numpy
|
| 5 |
+
google-genai==1.30.0
|