Skip to content

Commit 89e8607

Browse files
committed
debug: surface pipeline import/runtime errors in feedback output
1 parent 062567e commit 89e8607

2 files changed

Lines changed: 92 additions & 23 deletions

File tree

evaluation_function/evaluation.py

Lines changed: 92 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from __future__ import annotations
22

33
import os
4+
import sys
5+
import time
46
import traceback
57
from typing import Any, Dict, List, Optional, Tuple
68
from urllib.parse import urlparse, unquote
@@ -21,7 +23,9 @@ class ImageUploadError(Exception): # type: ignore
2123
pass
2224

2325

26+
# ----------------------------
2427
# Pipeline import guard
28+
# ----------------------------
2529
PIPELINE_IMPORT_ERROR: Optional[Dict[str, str]] = None
2630
run_yolo_pipeline = None
2731

@@ -37,7 +41,10 @@ class ImageUploadError(Exception): # type: ignore
3741
}
3842
run_yolo_pipeline = None
3943

44+
45+
# ----------------------------
4046
# URL / path helpers
47+
# ----------------------------
4148
def file_url_to_local_path(url: str) -> str:
4249
"""
4350
Convert file:// URL to local path.
@@ -119,7 +126,6 @@ def _items_to_feedback_html(items: List[Tuple[Any, Any]]) -> str:
119126

120127

121128
def _escape_html(s: str) -> str:
122-
# minimal safe escaping for traceback readability
123129
return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
124130

125131

@@ -144,11 +150,34 @@ def _error_dict_to_items(err: Dict[str, str]) -> List[Tuple[str, str]]:
144150

145151
return items
146152

153+
154+
# ----------------------------
147155
# Main entry
156+
# ----------------------------
148157
def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
158+
"""
159+
Expected response for Image input:
160+
response = [{"url": "...", ...}, ...]
161+
"""
162+
t0 = time.perf_counter()
163+
feedback_items: List[Tuple[str, str]] = []
164+
165+
def mark(stage: str) -> None:
166+
dt = time.perf_counter() - t0
167+
msg = f"{dt:.3f}s"
168+
feedback_items.append((f"TIME::{stage}", msg))
169+
# Also print (only visible if platform exposes runtime logs)
170+
try:
171+
print(f"[TIME] {stage}: {msg}", flush=True)
172+
except Exception:
173+
pass
174+
149175
try:
176+
mark("ENTER")
177+
150178
# 0) Pipeline import guard (MOST IMPORTANT)
151179
if run_yolo_pipeline is None:
180+
mark("PIPELINE_IMPORT_FAILED")
152181
if isinstance(PIPELINE_IMPORT_ERROR, dict):
153182
items = _error_dict_to_items(PIPELINE_IMPORT_ERROR)
154183
else:
@@ -157,7 +186,7 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
157186
("ErrorCode", "E_PIPELINE_IMPORT"),
158187
("Message", f"Pipeline import failed: {PIPELINE_IMPORT_ERROR}"),
159188
]
160-
189+
items = items + feedback_items
161190
feedback_html = _items_to_feedback_html(items)
162191
try:
163192
return Result(is_correct=False, feedback=feedback_html, feedback_items=items)
@@ -166,60 +195,92 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
166195

167196
# 1) Validate input
168197
if not isinstance(response, list) or len(response) == 0:
169-
items = [("Response", "Please upload at least one image.")]
198+
mark("BAD_INPUT_EMPTY_RESPONSE")
199+
items = [("Response", "Please upload at least one image.")] + feedback_items
170200
feedback_html = _items_to_feedback_html(items)
171201
try:
172202
return Result(is_correct=False, feedback=feedback_html, feedback_items=items)
173203
except TypeError:
174204
return Result(is_correct=False, feedback_items=items)
175205

176-
# 2) Optional controls
206+
# 2) Debug switches / controls
177207
return_images: bool = bool(_pget(params, "return_images", False))
178208
debug: bool = bool(_pget(params, "debug", False))
179209

210+
# New debug controls to diagnose "general error"
211+
fast_return: bool = bool(_pget(params, "fast_return", False))
212+
single_image: bool = bool(_pget(params, "single_image", True)) # default True for stability
213+
214+
# model filenames (relative under evaluation_function/)
180215
gear_model_rel = str(_pget(params, "gear_model_rel", "gear_model.pt"))
181216
shaft_model_rel = str(_pget(params, "shaft_model_rel", "shaft_model.pt"))
182217

218+
feedback_items.append(("Python", sys.version.replace("\n", " ")))
219+
feedback_items.append(("single_image", str(single_image)))
220+
feedback_items.append(("return_images", str(return_images)))
221+
feedback_items.append(("debug", str(debug)))
222+
feedback_items.append(("gear_model_rel", gear_model_rel))
223+
feedback_items.append(("shaft_model_rel", shaft_model_rel))
224+
225+
mark("PARAMS_PARSED")
226+
227+
# 2.5) Fast return: confirm platform can execute and return Result
228+
if fast_return:
229+
mark("FAST_RETURN_BEFORE_YOLO")
230+
items = [("Result", "fast_return=True: reached evaluation entry successfully (no YOLO).")] + feedback_items
231+
feedback_html = _items_to_feedback_html(items)
232+
try:
233+
return Result(is_correct=False, feedback=feedback_html, feedback_items=items)
234+
except TypeError:
235+
return Result(is_correct=False, feedback_items=items)
236+
183237
# 3) Process images
184238
merged_errors: List[Dict[str, str]] = []
185239
merged_summaries: List[Dict[str, Any]] = []
186240
merged_ratios: List[Dict[str, Any]] = []
187-
feedback_items: List[Tuple[str, str]] = []
241+
242+
mark("LOOP_START")
188243

189244
for idx, item in enumerate(response):
245+
if single_image and idx > 0:
246+
feedback_items.append(("Info", "single_image=True: only processed image[0]"))
247+
break
248+
190249
url = item.get("url") if isinstance(item, dict) else None
191250
if not url:
192251
merged_errors.append({"code": "E_NO_URL", "message": f"Image [{idx}] has no 'url' field."})
193252
continue
194253

254+
if debug:
255+
feedback_items.append((f"Input URL [{idx}]", str(url)))
256+
257+
mark(f"IMG[{idx}]::BEFORE_LOAD")
195258
img_bgr, err = _load_bgr_image_from_url(url)
259+
mark(f"IMG[{idx}]::AFTER_LOAD")
260+
196261
if img_bgr is None:
197262
merged_errors.append({
198263
"code": "E_LOAD_FAIL",
199264
"message": f"Failed to load image [{idx}] from URL. ({err})"
200265
})
201-
if debug:
202-
feedback_items.append((f"Input URL [{idx}]", str(url)))
203266
continue
204267

205268
# ---- Run YOLO pipeline safely per-image ----
206269
try:
207-
out = run_yolo_pipeline(
270+
mark(f"IMG[{idx}]::BEFORE_PIPELINE")
271+
out = run_yolo_pipeline( # type: ignore[misc]
208272
img_bgr=img_bgr,
209273
gear_model_rel=gear_model_rel,
210274
shaft_model_rel=shaft_model_rel,
211275
return_images=return_images,
212276
)
277+
mark(f"IMG[{idx}]::AFTER_PIPELINE")
213278
except Exception as e:
279+
mark(f"IMG[{idx}]::PIPELINE_EXCEPTION")
214280
msg = f"Pipeline failed on image[{idx}]: {type(e).__name__}: {e}"
215281
if debug:
216282
msg += "\n" + traceback.format_exc()
217-
merged_errors.append({
218-
"code": "E_PIPELINE_RUNTIME",
219-
"message": msg
220-
})
221-
if debug:
222-
feedback_items.append((f"Input URL [{idx}]", str(url)))
283+
merged_errors.append({"code": "E_PIPELINE_RUNTIME", "message": msg})
223284
continue
224285

225286
# Collect outputs safely
@@ -232,7 +293,12 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
232293
if isinstance(ratio, dict):
233294
merged_ratios.append(ratio)
234295
if isinstance(errors, list):
235-
merged_errors.extend(errors)
296+
# ensure each error is dict-like
297+
for e in errors:
298+
if isinstance(e, dict):
299+
merged_errors.append({"code": str(e.get("code", "E_ERR")), "message": str(e.get("message", ""))})
300+
else:
301+
merged_errors.append({"code": "E_ERR", "message": str(e)})
236302

237303
# Optional annotated images upload (off by default)
238304
if return_images:
@@ -241,11 +307,13 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
241307
for key in ("det_img", "label_img"):
242308
if key in imgs and isinstance(imgs[key], np.ndarray):
243309
try:
310+
mark(f"IMG[{idx}]::BEFORE_UPLOAD::{key}")
244311
png_bytes = _cv2_bgr_to_png_bytes(imgs[key])
245312
img_url = upload_image(png_bytes, "eduvision")
246313
feedback_items.append(
247314
(f"{key} [{idx}]", f"<a href=\"{img_url}\" target=\"_blank\">{key}</a>")
248315
)
316+
mark(f"IMG[{idx}]::AFTER_UPLOAD::{key}")
249317
except ImageUploadError as e:
250318
merged_errors.append({
251319
"code": "E_UPLOAD_FAIL",
@@ -259,19 +327,18 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
259327
elif upload_image is None and debug:
260328
feedback_items.append(("Images", "return_images=True but upload_image() is not available in this lf_toolkit version."))
261329

262-
if debug:
263-
feedback_items.append((f"Input URL [{idx}]", str(url)))
330+
mark("LOOP_END")
264331

265-
# 4) Decide correctness
332+
# 4) Decide correctness: any E_* means incorrect
266333
has_E = any(str(e.get("code", "")).startswith("E_") for e in merged_errors)
267334
is_correct = (not has_E)
268335

269336
# 5) Text feedback
270337
if merged_summaries:
271-
feedback_items.append(("Summary", str(merged_summaries[-1])))
338+
feedback_items.append(("Summary(last)", str(merged_summaries[-1])))
272339

273340
if merged_ratios:
274-
feedback_items.append(("Ratio", str(merged_ratios[-1])))
341+
feedback_items.append(("Ratio(last)", str(merged_ratios[-1])))
275342

276343
if merged_errors:
277344
lines = [f"- {e.get('code', 'E_ERR')}: {e.get('message', '')}" for e in merged_errors]
@@ -280,6 +347,8 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
280347
if not feedback_items:
281348
feedback_items = [("Result", "No valid images could be processed.")]
282349

350+
mark("BEFORE_RETURN")
351+
283352
feedback_html = _items_to_feedback_html(feedback_items)
284353

285354
try:
@@ -291,14 +360,15 @@ def evaluation_function(response: Any, answer: Any, params: Params) -> Result:
291360
# Absolute last-resort: never crash the platform UI
292361
tb = traceback.format_exc()
293362
safe_tb = _escape_html(tb)
294-
items = [
363+
items: List[Tuple[str, str]] = [
295364
("Stage", "UNHANDLED"),
296365
("ErrorCode", "E_UNHANDLED"),
297366
("ExceptionType", type(e).__name__),
298367
("Message", str(e)),
299368
("Traceback", f"<pre>{safe_tb}</pre>"),
300369
("Traceback(html)", safe_tb.replace("\n", "<br>")),
301-
]
370+
] + feedback_items
371+
302372
feedback_html = _items_to_feedback_html(items)
303373
try:
304374
return Result(is_correct=False, feedback=feedback_html, feedback_items=items)

evaluation_function/yolo_pipeline.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
- Keeps your core logic: object building, shaft assignment, stage chain naming,
88
assembly error checks, and gear-ratio computation.
99
"""
10-
1110
from __future__ import annotations
1211

1312
import math

0 commit comments

Comments
 (0)