VNraizo commited on
Commit
f344f14
·
verified ·
1 Parent(s): 0c63059

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. handler.py +86 -6
  2. requirements.txt +1 -0
handler.py CHANGED
@@ -1,13 +1,76 @@
1
- # handler.py — Hugging Face Inference Endpoint handler KHỚP schemas.py
2
- from typing import Any, Dict, Optional
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() # [x1,y1,x2,y2] float
57
  confv = float(b.conf[0])
58
  x1, y1, x2, y2 = _to_ints(xyxy)
59
  boxes_out.append({
60
- "cls": "diseased", # theo schemas.py: Literal['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
- "annotated_image_base64": annotated_b64,
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