Navy commited on
Commit
9541352
·
1 Parent(s): a20a645

refactor: payload & response adjusment

Browse files
Files changed (3) hide show
  1. app.py +22 -0
  2. core/defect_detection.py +58 -28
  3. utils.py +45 -2
app.py CHANGED
@@ -72,6 +72,28 @@ async def start_detection(data: Dict):
72
  if not station_id or not parts or not webhook_url or not cameras:
73
  return {"status": "error", "message": "Missing required fields"}
74
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
  logger.info(f"[INFO] Get metadata parts")
76
  model_path = model_by_id_metadata(parts['id'])
77
 
 
72
  if not station_id or not parts or not webhook_url or not cameras:
73
  return {"status": "error", "message": "Missing required fields"}
74
 
75
+ # -------------------------------
76
+ # VALIDATION BEFORE EXECUTION
77
+ # -------------------------------
78
+ required_parts_fields = [
79
+ "id",
80
+ "pin_api",
81
+ "name",
82
+ "sku"
83
+ ]
84
+ validation_errors = validate_input(required_parts_fields, station_id, cameras, parts, webhook_url)
85
+
86
+ if validation_errors:
87
+ logger.error("[VALIDATION FAILED] Input data is invalid.")
88
+ for err in validation_errors:
89
+ logger.error(f" - {err}")
90
+ return {
91
+ "status": "error",
92
+ "station_id": station_id,
93
+ "camera_count": len(cameras),
94
+ "message": " | ".join(validation_errors)
95
+ }
96
+
97
  logger.info(f"[INFO] Get metadata parts")
98
  model_path = model_by_id_metadata(parts['id'])
99
 
core/defect_detection.py CHANGED
@@ -7,13 +7,13 @@ from utils import *
7
 
8
  load_dotenv()
9
 
10
- WEBHOOK_URL = os.getenv("WEBHOOK", "https://webhook.site/2f7a6096-619e-471d-8d26-9a6bb9e4ca14")
11
- MODEL_VERSION = os.getenv("MODEL_VERSION", "v1.0.0")
12
 
13
- MAX_RUNTIME_SEC = float(os.getenv("MAX_RUNTIME_SEC", "20"))
14
- FRAME_FAIL_SLEEP = float(os.getenv("FRAME_FAIL_SLEEP", "0.05"))
15
- DEFAULT_FPS = float(os.getenv("DEFAULT_FPS", "25"))
16
- WEBHOOK_TIMEOUT = float(os.getenv("WEBHOOK_TIMEOUT", "10.0"))
17
 
18
  # ============================================================
19
  # DEFECT DETECTION FROM VIDEO URL
@@ -34,6 +34,9 @@ def detect_defect_from_video_url(station_id, camera_id: str, video_url: str, mod
34
  "station_id": station_id,
35
  "camera_id": camera_id,
36
  "status": "error",
 
 
 
37
  "detections": [],
38
  "message": f"Cannot open video URL: {video_url}"
39
  }
@@ -96,10 +99,10 @@ def detect_defect_from_video_url(station_id, camera_id: str, video_url: str, mod
96
  frame_base64 = base64.b64encode(buffer).decode("utf-8")
97
 
98
  # Save annotated image
99
- output_dir = "outputs/images"
100
  # os.makedirs(output_dir, exist_ok=True)
101
- filename = f"{station_id}_{camera_id}_NG_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
102
- filepath = os.path.join(output_dir, filename)
103
  # cv2.imwrite(filepath, frame)
104
  # logger.info(f"[SAVED] NG image saved to {filepath}")
105
 
@@ -109,14 +112,16 @@ def detect_defect_from_video_url(station_id, camera_id: str, video_url: str, mod
109
  return {
110
  "station_id": station_id,
111
  "camera_id": camera_id,
 
112
  "status_defect": "NG",
113
  "image_base64": frame_base64,
114
- "image_path": filepath,
115
  "detections": [{
116
  "class": defect_name,
117
  "confidence": conf,
118
  "bbox": xyxy
119
- }]
 
120
  }
121
 
122
  # --- no defect detected ---
@@ -127,12 +132,12 @@ def detect_defect_from_video_url(station_id, camera_id: str, video_url: str, mod
127
  frame_base64 = base64.b64encode(buffer).decode("utf-8")
128
 
129
  # Save OK image (no bbox)
130
- output_dir = "outputs/images"
131
- os.makedirs(output_dir, exist_ok=True)
132
- filename = f"{station_id}_{camera_id}_OK_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
133
- filepath = os.path.join(output_dir, filename)
134
- cv2.imwrite(filepath, last_frame)
135
- logger.info(f"[SAVED] OK image saved to {filepath}")
136
  else:
137
  frame_base64 = ""
138
  filepath = None
@@ -140,10 +145,12 @@ def detect_defect_from_video_url(station_id, camera_id: str, video_url: str, mod
140
  return {
141
  "station_id": station_id,
142
  "camera_id": camera_id,
 
143
  "status_defect": "OK",
144
  "image_base64": frame_base64,
145
- "image_path": filepath,
146
- "detections": []
 
147
  }
148
 
149
 
@@ -158,25 +165,47 @@ async def _detect_camera_video(station_id: str, camera: Dict, stop_flag: Dict, m
158
  async def run_detection_group(station_id: str, cameras: List[Dict], webhook_url: str, model=None, parts=str):
159
  """
160
  Run detection for all cameras in parallel.
 
161
  Send webhook with NG/OK status.
162
  """
 
163
  stop_flag = {"stop": False}
164
  logger.info(f"[START] Station {station_id} → {len(cameras)} camera(s)")
165
-
166
- results = await asyncio.gather(*[
167
- _detect_camera_video(station_id, cam, stop_flag, model)
168
- for cam in cameras
169
- ])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
 
171
  payload = {
172
- "status": "success",
173
- "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
174
  "model_version": MODEL_VERSION,
175
- "parts" : parts,
 
176
  "data": results,
177
  }
178
 
179
- # Send webhook
180
  try:
181
  async with httpx.AsyncClient(timeout=WEBHOOK_TIMEOUT) as client:
182
  await client.post(webhook_url, json=payload)
@@ -184,4 +213,5 @@ async def run_detection_group(station_id: str, cameras: List[Dict], webhook_url:
184
  except Exception as e:
185
  logger.error(f"[ERROR] Webhook failed: {e}")
186
 
 
187
  # return payload
 
7
 
8
  load_dotenv()
9
 
10
+ MODEL_VERSION = os.getenv("MODEL_VERSION")
11
+ WEBHOOK_URL = os.getenv("WEBHOOK_URL")
12
 
13
+ MAX_RUNTIME_SEC = float(os.getenv("MAX_RUNTIME_SEC"))
14
+ FRAME_FAIL_SLEEP = float(os.getenv("FRAME_FAIL_SLEEP"))
15
+ DEFAULT_FPS = float(os.getenv("DEFAULT_FPS"))
16
+ WEBHOOK_TIMEOUT = float(os.getenv("WEBHOOK_TIMEOUT"))
17
 
18
  # ============================================================
19
  # DEFECT DETECTION FROM VIDEO URL
 
34
  "station_id": station_id,
35
  "camera_id": camera_id,
36
  "status": "error",
37
+ "status_defect": "",
38
+ "image_base64": "",
39
+ "image_path": "",
40
  "detections": [],
41
  "message": f"Cannot open video URL: {video_url}"
42
  }
 
99
  frame_base64 = base64.b64encode(buffer).decode("utf-8")
100
 
101
  # Save annotated image
102
+ # output_dir = "outputs/images"
103
  # os.makedirs(output_dir, exist_ok=True)
104
+ # filename = f"{station_id}_{camera_id}_NG_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
105
+ # filepath = os.path.join(output_dir, filename)
106
  # cv2.imwrite(filepath, frame)
107
  # logger.info(f"[SAVED] NG image saved to {filepath}")
108
 
 
112
  return {
113
  "station_id": station_id,
114
  "camera_id": camera_id,
115
+ "status": "success",
116
  "status_defect": "NG",
117
  "image_base64": frame_base64,
118
+ # "image_path": filepath,
119
  "detections": [{
120
  "class": defect_name,
121
  "confidence": conf,
122
  "bbox": xyxy
123
+ }],
124
+ "message": f"Detected as defect"
125
  }
126
 
127
  # --- no defect detected ---
 
132
  frame_base64 = base64.b64encode(buffer).decode("utf-8")
133
 
134
  # Save OK image (no bbox)
135
+ # output_dir = "outputs/images"
136
+ # os.makedirs(output_dir, exist_ok=True)
137
+ # filename = f"{station_id}_{camera_id}_OK_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg"
138
+ # filepath = os.path.join(output_dir, filename)
139
+ # cv2.imwrite(filepath, last_frame)
140
+ # logger.info(f"[SAVED] OK image saved to {filepath}")
141
  else:
142
  frame_base64 = ""
143
  filepath = None
 
145
  return {
146
  "station_id": station_id,
147
  "camera_id": camera_id,
148
+ "status": "success",
149
  "status_defect": "OK",
150
  "image_base64": frame_base64,
151
+ # "image_path": filepath,
152
+ "detections": [],
153
+ "message": f"Detected as normal (no defect)"
154
  }
155
 
156
 
 
165
  async def run_detection_group(station_id: str, cameras: List[Dict], webhook_url: str, model=None, parts=str):
166
  """
167
  Run detection for all cameras in parallel.
168
+ Validate input before detection.
169
  Send webhook with NG/OK status.
170
  """
171
+
172
  stop_flag = {"stop": False}
173
  logger.info(f"[START] Station {station_id} → {len(cameras)} camera(s)")
174
+
175
+ results = await asyncio.gather(
176
+ *[_detect_camera_video(station_id, cam, stop_flag, model) for cam in cameras],
177
+ return_exceptions=True
178
+ )
179
+
180
+ # misalnya results sudah berisi hasil dari tiap kamera
181
+ has_error = any(
182
+ isinstance(r, Exception) or (isinstance(r, dict) and r.get("status") == "error")
183
+ for r in results
184
+ )
185
+ all_error = all(
186
+ isinstance(r, Exception) or (isinstance(r, dict) and r.get("status") == "error")
187
+ for r in results
188
+ )
189
+
190
+ if all_error:
191
+ status = "error"
192
+ message = "All cameras failed during detection"
193
+ elif has_error:
194
+ status = "partial_error"
195
+ message = "Some cameras failed during detection"
196
+ else:
197
+ status = "success"
198
+ message = "Success detecting defects"
199
 
200
  payload = {
201
+ "status": status,
202
+ "timestamp": time.strftime("%Y-%m-%dT%H:%M:%S", time.localtime()),
203
  "model_version": MODEL_VERSION,
204
+ "message": message,
205
+ "parts": parts,
206
  "data": results,
207
  }
208
 
 
209
  try:
210
  async with httpx.AsyncClient(timeout=WEBHOOK_TIMEOUT) as client:
211
  await client.post(webhook_url, json=payload)
 
213
  except Exception as e:
214
  logger.error(f"[ERROR] Webhook failed: {e}")
215
 
216
+ return "DONE"
217
  # return payload
utils.py CHANGED
@@ -1,5 +1,6 @@
1
- import logging, sys, asyncio, random, json
2
  from pathlib import Path
 
3
 
4
  # ============================================================
5
  # LOGGER SETUP
@@ -22,6 +23,37 @@ async def async_sleep_random(min_s=0.2, max_s=0.8):
22
  """
23
  durasi = random.uniform(min_s, max_s)
24
  await asyncio.sleep(durasi)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  # ============================================================
27
  # HELPER
@@ -83,4 +115,15 @@ def color_defect(defect_name):
83
  r = int(hex_color[0:2], 16)
84
  g = int(hex_color[2:4], 16)
85
  b = int(hex_color[4:6], 16)
86
- return (b, g, r) # Convert to BGR
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging, sys, asyncio, random, json, requests, os
2
  from pathlib import Path
3
+ from dotenv import load_dotenv
4
 
5
  # ============================================================
6
  # LOGGER SETUP
 
23
  """
24
  durasi = random.uniform(min_s, max_s)
25
  await asyncio.sleep(durasi)
26
+
27
+ # ==============================================================
28
+ # VALIDATION FUNCTION
29
+ # ==============================================================
30
+ def validate_input(required_parts_fields, station_id, cameras, parts, webhook_url):
31
+ errors = []
32
+
33
+ # Validate station_id
34
+ if not station_id or not str(station_id).strip():
35
+ errors.append("station_id is missing or empty")
36
+
37
+ # Validate parts
38
+ for field in required_parts_fields:
39
+ if field not in parts or not str(parts[field]).strip():
40
+ errors.append(f"parts.{field} is missing or empty")
41
+
42
+ # Validate cameras list
43
+ if not isinstance(cameras, list) or len(cameras) == 0:
44
+ errors.append("cameras must be a non-empty list")
45
+ else:
46
+ for index, cam in enumerate(cameras):
47
+ if "camera_id" not in cam or not str(cam["camera_id"]).strip():
48
+ errors.append(f"camera[{index}].camera_id missing or empty")
49
+ if "rtsp_url" not in cam or not str(cam["rtsp_url"]).strip():
50
+ errors.append(f"camera[{index}].rtsp_url missing or empty")
51
+
52
+ # Validate webhook
53
+ if not webhook_url or not webhook_url.startswith("http"):
54
+ errors.append("webhook_url is invalid or missing")
55
+
56
+ return errors
57
 
58
  # ============================================================
59
  # HELPER
 
115
  r = int(hex_color[0:2], 16)
116
  g = int(hex_color[2:4], 16)
117
  b = int(hex_color[4:6], 16)
118
+ return (b, g, r) # Convert to BGR
119
+
120
+ def load_json(path):
121
+ with open(path, "r") as f:
122
+ return json.load(f)
123
+
124
+ def start_detection(base_url, payload):
125
+ return requests.post(f"{base_url}/start-detection", json=payload)
126
+
127
+ def load_config():
128
+ load_dotenv()
129
+ return os.getenv("BASE_URL"), os.getenv("WEBHOOK")