11from __future__ import annotations
22
33import os
4+ import sys
5+ import time
46import traceback
57from typing import Any , Dict , List , Optional , Tuple
68from urllib .parse import urlparse , unquote
@@ -21,7 +23,9 @@ class ImageUploadError(Exception): # type: ignore
2123 pass
2224
2325
26+ # ----------------------------
2427# Pipeline import guard
28+ # ----------------------------
2529PIPELINE_IMPORT_ERROR : Optional [Dict [str , str ]] = None
2630run_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+ # ----------------------------
4148def 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
121128def _escape_html (s : str ) -> str :
122- # minimal safe escaping for traceback readability
123129 return s .replace ("&" , "&" ).replace ("<" , "<" ).replace (">" , ">" )
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+ # ----------------------------
148157def 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 )
0 commit comments