Navy commited on
Commit
b0f7e66
·
1 Parent(s): ae00b72

rafactor code for images

Browse files
Files changed (2) hide show
  1. app.py +1 -10
  2. core/defect_detection_image.py +99 -109
app.py CHANGED
@@ -108,16 +108,7 @@ async def start_detection(data: Dict):
108
  model_path = model_by_id_metadata(parts['id'])
109
 
110
  logger.info(f"[INFO] Checking model_path")
111
- if isinstance(model_path, str):
112
- if not os.path.exists(model_path):
113
- logger.error(f"[ERROR] Model file not found: {model_path}")
114
- return {
115
- "status": "error",
116
- "message": f"Model file not found: {model_path}"
117
- }
118
- model = YOLO(model_path)
119
- else:
120
- model = model_path
121
 
122
  # =====================================================
123
  # BASE64 IMAGE DETECTION (NOT VIDEO STREAM)
 
108
  model_path = model_by_id_metadata(parts['id'])
109
 
110
  logger.info(f"[INFO] Checking model_path")
111
+ model = model_path
 
 
 
 
 
 
 
 
 
112
 
113
  # =====================================================
114
  # BASE64 IMAGE DETECTION (NOT VIDEO STREAM)
core/defect_detection_image.py CHANGED
@@ -1,5 +1,8 @@
1
- import os, cv2, time, base64, asyncio, httpx
2
  import numpy as np
 
 
 
3
 
4
  from datetime import datetime
5
  from dotenv import load_dotenv
@@ -16,18 +19,25 @@ WEBHOOK_TIMEOUT = float(os.getenv("WEBHOOK_TIMEOUT", "10.0"))
16
  # ============================================================
17
  # DEFECT DETECTION FROM BASE64 IMAGE
18
  # ============================================================
19
- def detect_defect_from_base64(station_id: str, camera_id: str, image_base64: str, model=None):
20
  """
21
  Detect defect from a single Base64 image.
22
- - Decode Base64 → OpenCV image
23
- - Run YOLO model once
24
- - Return result (OK / NG) + annotated image base64
 
25
  """
26
-
27
  try:
28
- img_data = base64.b64decode(image_base64)
29
- np_arr = np.frombuffer(img_data, np.uint8)
30
- frame = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
 
 
 
 
 
 
 
31
  if frame is None:
32
  raise ValueError("Decoded image is None")
33
  except Exception as e:
@@ -39,128 +49,114 @@ def detect_defect_from_base64(station_id: str, camera_id: str, image_base64: str
39
  "status_defect": "",
40
  "image_base64": "",
41
  "detections": [],
42
- "message": f"Invalid base64 image"
43
  }
44
-
45
-
46
- # ========== YOLO DETECTION ==========
 
 
 
 
 
 
47
  if model:
48
- # Predict
49
  results = model.predict(source=frame, conf=0.4, imgsz=640, verbose=False)
50
  boxes = results[0].boxes
51
 
52
- if len(boxes) > 0:
53
- for box in boxes:
54
- cls = int(box.cls[0])
55
- conf = float(box.conf[0])
56
- xyxy = [int(x) for x in box.xyxy[0].tolist()]
57
- defect_name = model.names.get(cls, f"class_{cls}").lower()
58
-
59
- x1, y1, x2, y2 = xyxy
60
-
61
- # Ambil warna berdasarkan defect
62
- try:
63
- color = color_defect(defect_name)
64
- except Exception:
65
- color = color_defect('other')
66
-
67
- # Draw bounding box di frame
68
- cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
69
-
70
- # Label
71
- label = f"{defect_name.upper()} {conf:.2f}"
72
- (w, h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
73
- cv2.rectangle(frame, (x1, y1 - 20), (x1 + w, y1), color, -1)
74
- cv2.putText(frame, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 255), 2)
75
-
76
-
77
- # Convert annotated image ke Base64
78
- _, buffer = cv2.imencode(".jpg", frame)
79
- frame_base64 = base64.b64encode(buffer).decode("utf-8")
80
-
81
- # Save annotated image
82
- # output_dir = "outputs/images"
83
- # os.makedirs(output_dir, exist_ok=True)
84
- # filename = f"{station_id}_{camera_id}_NG_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
85
- # filepath = os.path.join(output_dir, filename)
86
- # cv2.imwrite(filepath, frame)
87
-
88
- # logger.info(f"[SAVED] NG image saved to {filepath}")
89
-
90
- logger.info(f"[DETECTED] Camera {camera_id} → {defect_name} ({conf:.2f})")
91
-
92
- return {
93
- "station_id": station_id,
94
- "camera_id": camera_id,
95
- "status": "success",
96
- "status_defect": "NG",
97
- "image_base64": frame_base64,
98
- "detections": [{
99
- "class": defect_name,
100
- "confidence": conf,
101
- "bbox": xyxy
102
- }],
103
- "message": f"Detected as defect"
104
- }
105
-
106
- # ========== NO DEFECT ==========
107
  _, buffer = cv2.imencode(".jpg", frame)
108
  frame_base64 = base64.b64encode(buffer).decode("utf-8")
109
 
110
  # Save OK image (no bbox)
111
- # output_dir = "outputs/images"
112
- # os.makedirs(output_dir, exist_ok=True)
113
- # filename = f"{station_id}_{camera_id}_OK_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
114
- # filepath = os.path.join(output_dir, filename)
115
- # cv2.imwrite(filepath, frame)
116
- # logger.info(f"[SAVED] OK image saved to {filepath}")
117
-
118
- logger.info(f"[OK] Camera {camera_id} → No defect detected.")
119
- return {
120
- "station_id": station_id,
121
- "camera_id": camera_id,
122
- "status": "success",
123
- "status_defect": "OK",
124
- "image_base64": frame_base64,
125
- "detections": [],
126
- "message": f"Detected as normal (no defect)"
127
- }
 
 
 
 
 
 
 
 
 
 
 
 
128
 
129
  # ============================================================
130
  # ASYNC WRAPPERS
131
  # ============================================================
132
- async def _detect_camera_image(station_id: str, camera: Dict, model=None):
133
  """Run detect_defect_from_base64 in thread for async parallel."""
134
  return await asyncio.to_thread(
135
  detect_defect_from_base64,
136
  station_id,
137
  camera["camera_id"],
138
  camera["image_base64"],
139
- model
140
  )
 
 
 
 
 
 
 
141
 
142
  async def run_detection_group(
143
  station_id: str,
144
  cameras: List[Dict],
145
  webhook_url: str,
146
- model=None,
147
  parts: Dict = None
148
  ):
149
- """
150
- Run defect detection for multiple cameras in parallel and send webhook.
151
- All results are serialized safely for JSON.
152
- """
153
  parts = parts or {}
154
-
155
  logger.info(f"[START] Station {station_id} → {len(cameras)} camera(s)")
156
 
157
- # Run detection async parallel
158
  results = await asyncio.gather(
159
- *[_detect_camera_image(station_id, cam, model) for cam in cameras],
160
  return_exceptions=True
161
  )
162
 
163
- # Convert exceptions to dict
164
  clean_results = []
165
  for r in results:
166
  if isinstance(r, Exception):
@@ -171,31 +167,25 @@ async def run_detection_group(
171
  else:
172
  clean_results.append(r)
173
 
174
- # Determine overall status
175
  has_error = any(r.get("status") == "error" for r in clean_results)
176
  all_error = all(r.get("status") == "error" for r in clean_results)
177
 
 
 
178
  if all_error:
179
- status = "error"
180
- message = "All cameras failed during detection"
181
  elif has_error:
182
- status = "partial_error"
183
- message = "Some cameras failed during detection"
184
- else:
185
- status = "success"
186
- message = "Success detecting defects"
187
 
188
- # Build payload
189
  payload = {
190
  "status": status,
191
- "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime()),
192
  "model_version": MODEL_VERSION,
193
  "message": message,
194
  "parts": parts,
195
- # "data": make_serializable(clean_results),
196
  }
197
 
198
- # Send webhook
199
  try:
200
  async with httpx.AsyncClient(timeout=WEBHOOK_TIMEOUT) as client:
201
  await client.post(webhook_url, json=payload)
 
1
+ import os, cv2, base64, asyncio, httpx
2
  import numpy as np
3
+ from ultralytics import YOLO
4
+ from PIL import Image
5
+ from io import BytesIO
6
 
7
  from datetime import datetime
8
  from dotenv import load_dotenv
 
19
  # ============================================================
20
  # DEFECT DETECTION FROM BASE64 IMAGE
21
  # ============================================================
22
+ def detect_defect_from_base64(station_id: str, camera_id: str, image_base64: str, model_path=None):
23
  """
24
  Detect defect from a single Base64 image.
25
+ Return:
26
+ - status: "OK" / "NG" / "error"
27
+ - annotated image (base64)
28
+ - list of detections
29
  """
 
30
  try:
31
+ # OPTION 1
32
+ # img_data = base64.b64decode(image_base64)
33
+ # np_arr = np.frombuffer(img_data, np.uint8)
34
+ # frame = cv2.imdecode(np_arr, cv2.IMREAD_COLOR)
35
+
36
+ # OPTION 2
37
+ img_data = base64.b64decode(image_base64)
38
+ image = Image.open(BytesIO(img_data)).convert("RGB")
39
+ frame = np.array(image)
40
+
41
  if frame is None:
42
  raise ValueError("Decoded image is None")
43
  except Exception as e:
 
49
  "status_defect": "",
50
  "image_base64": "",
51
  "detections": [],
52
+ "message": "Invalid base64 image"
53
  }
54
+
55
+ detections = []
56
+
57
+ try:
58
+ model = YOLO(f"./{model_path}")
59
+ logger.info(f"[MODEL] Success load model")
60
+ except Exception as e:
61
+ logger.error(f"[ERROR] Cannot load model: {e}")
62
+
63
  if model:
 
64
  results = model.predict(source=frame, conf=0.4, imgsz=640, verbose=False)
65
  boxes = results[0].boxes
66
 
67
+ for box in boxes:
68
+ cls = int(box.cls[0])
69
+ conf = float(box.conf[0])
70
+ xyxy = [int(x) for x in box.xyxy[0].tolist()]
71
+ defect_name = model.names.get(cls, f"class_{cls}").lower()
72
+
73
+ x1, y1, x2, y2 = xyxy
74
+ color = color_defect(defect_name) if defect_name else color_defect('other')
75
+
76
+ # Draw bbox + label
77
+ cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
78
+ label = f"{defect_name.upper()} {conf:.2f}"
79
+ (w, h), _ = cv2.getTextSize(label, cv2.FONT_HERSHEY_SIMPLEX, 0.6, 2)
80
+ cv2.rectangle(frame, (x1, y1 - 20), (x1 + w, y1), color, -1)
81
+ cv2.putText(frame, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255,255,255), 2)
82
+
83
+ detections.append({
84
+ "class": defect_name,
85
+ "confidence": conf,
86
+ "bbox": xyxy
87
+ })
88
+
89
+ # Convert annotated frame ke Base64
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  _, buffer = cv2.imencode(".jpg", frame)
91
  frame_base64 = base64.b64encode(buffer).decode("utf-8")
92
 
93
  # Save OK image (no bbox)
94
+ output_dir = "outputs/images"
95
+ os.makedirs(output_dir, exist_ok=True)
96
+ filename = f"{station_id}_{camera_id}_OK_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
97
+ filepath = os.path.join(output_dir, filename)
98
+ cv2.imwrite(filepath, frame)
99
+ logger.info(f"[SAVED] OK image saved to {filepath}")
100
+
101
+ if detections:
102
+ logger.info(f"[DETECTED] Camera {camera_id} → {len(detections)} defect(s)")
103
+ return {
104
+ "station_id": station_id,
105
+ "camera_id": camera_id,
106
+ "status": "success",
107
+ "status_defect": "NG",
108
+ "image_base64": frame_base64,
109
+ "detections": detections,
110
+ "message": "Detected as defect"
111
+ }
112
+ else:
113
+ logger.info(f"[OK] Camera {camera_id} → No defect detected.")
114
+ return {
115
+ "station_id": station_id,
116
+ "camera_id": camera_id,
117
+ "status": "success",
118
+ "status_defect": "OK",
119
+ "image_base64": frame_base64,
120
+ "detections": [],
121
+ "message": "Detected as normal (no defect)"
122
+ }
123
 
124
  # ============================================================
125
  # ASYNC WRAPPERS
126
  # ============================================================
127
+ async def _detect_camera_image(station_id: str, camera: Dict, model_path=None):
128
  """Run detect_defect_from_base64 in thread for async parallel."""
129
  return await asyncio.to_thread(
130
  detect_defect_from_base64,
131
  station_id,
132
  camera["camera_id"],
133
  camera["image_base64"],
134
+ model_path
135
  )
136
+ # return await asyncio.to_thread(
137
+ # testing,
138
+ # station_id,
139
+ # camera["camera_id"],
140
+ # camera["image_base64"],
141
+ # model
142
+ # )
143
 
144
  async def run_detection_group(
145
  station_id: str,
146
  cameras: List[Dict],
147
  webhook_url: str,
148
+ model_path=None,
149
  parts: Dict = None
150
  ):
 
 
 
 
151
  parts = parts or {}
 
152
  logger.info(f"[START] Station {station_id} → {len(cameras)} camera(s)")
153
 
 
154
  results = await asyncio.gather(
155
+ *[_detect_camera_image(station_id, cam, model_path) for cam in cameras],
156
  return_exceptions=True
157
  )
158
 
159
+ # Bersihkan exception
160
  clean_results = []
161
  for r in results:
162
  if isinstance(r, Exception):
 
167
  else:
168
  clean_results.append(r)
169
 
 
170
  has_error = any(r.get("status") == "error" for r in clean_results)
171
  all_error = all(r.get("status") == "error" for r in clean_results)
172
 
173
+ status = "success"
174
+ message = "Success detecting defects"
175
  if all_error:
176
+ status, message = "error", "All cameras failed during detection"
 
177
  elif has_error:
178
+ status, message = "partial_error", "Some cameras failed during detection"
 
 
 
 
179
 
 
180
  payload = {
181
  "status": status,
182
+ "timestamp": datetime.now().isoformat(),
183
  "model_version": MODEL_VERSION,
184
  "message": message,
185
  "parts": parts,
186
+ "data": make_serializable(clean_results),
187
  }
188
 
 
189
  try:
190
  async with httpx.AsyncClient(timeout=WEBHOOK_TIMEOUT) as client:
191
  await client.post(webhook_url, json=payload)