1- # -*- coding: utf-8 -*-
2- # evaluation_function/evaluation.py
3-
41from __future__ import annotations
52
63import os
@@ -25,9 +22,7 @@ class ImageUploadError(Exception): # type: ignore
2522from .yolo_pipeline import run_yolo_pipeline
2623
2724
28- # ----------------------------
2925# URL / path helpers
30- # ----------------------------
3126def file_url_to_local_path (url : str ) -> str :
3227 """
3328 Convert file:// URL to local path.
@@ -95,133 +90,145 @@ def _pget(params: Params, key: str, default: Any) -> Any:
9590 except Exception :
9691 return default
9792
93+ def _items_to_feedback_html (items ):
94+ lines = []
95+ for k , v in items :
96+ k = str (k ).strip () if k is not None else ""
97+ v = str (v ).strip () if v is not None else ""
98+ if k :
99+ lines .append (f"{ k } : { v } " )
100+ else :
101+ lines .append (v )
102+ return "<br>" .join (lines )
103+
98104
99- # ----------------------------
100105# Main entry
101- # ----------------------------
102106def evaluation_function (response : Any , answer : Any , params : Params ) -> Result :
103- """
104- Lambda Feedback evaluation entry.
105-
106- Expected response format for an image response area:
107- response = [{"url": "https://..."}, {"url": "https://..."}]
108- Local dev also allows:
109- "url": "file:///C:/path/to/image.jpg"
110-
111- This function will:
112- - load each image by URL
113- - run YOLO pipeline
114- - return feedback + (optional) annotated images via upload_image()
115- """
107+ try :
108+ # Validate input
109+ if not isinstance (response , list ) or len (response ) == 0 :
110+ items = [("Response" , "Please upload at least one image." )]
111+ feedback_html = _items_to_feedback_html (items )
112+ try :
113+ return Result (is_correct = False , feedback = feedback_html , feedback_items = items )
114+ except TypeError :
115+ return Result (is_correct = False , feedback_items = items )
116+
117+ # Optional controls (safe defaults)
118+ # Default: NO image upload (text only), to match your current goal
119+ return_images : bool = bool (_pget (params , "return_images" , False ))
120+ debug : bool = bool (_pget (params , "debug" , False ))
121+
122+ # Relative model filenames stored in evaluation_function/
123+ gear_model_rel = str (_pget (params , "gear_model_rel" , "gear_model.pt" ))
124+ shaft_model_rel = str (_pget (params , "shaft_model_rel" , "shaft_model.pt" ))
125+
126+
127+ # Process images
128+ merged_errors : List [Dict [str , str ]] = []
129+ merged_summaries : List [Dict [str , Any ]] = []
130+ merged_ratios : List [Dict [str , Any ]] = []
131+
132+ feedback_items : List [Tuple [str , str ]] = []
133+
134+ for idx , item in enumerate (response ):
135+ url = item .get ("url" ) if isinstance (item , dict ) else None
136+ if not url :
137+ merged_errors .append ({
138+ "code" : "NO_URL" ,
139+ "message" : f"Image [{ idx } ] has no 'url' field."
140+ })
141+ continue
142+
143+ img_bgr , err = _load_bgr_image_from_url (url )
144+ if img_bgr is None :
145+ merged_errors .append ({
146+ "code" : "LOAD_FAIL" ,
147+ "message" : f"Failed to load image [{ idx } ] from URL. ({ err } )"
148+ })
149+ if debug :
150+ feedback_items .append ((f"Input URL [{ idx } ]" , str (url )))
151+ continue
152+
153+ # Run pipeline (your external YOLO pipeline)
154+ out = run_yolo_pipeline (
155+ img_bgr = img_bgr ,
156+ gear_model_rel = gear_model_rel ,
157+ shaft_model_rel = shaft_model_rel ,
158+ return_images = return_images ,
159+ )
160+
161+ # Collect outputs safely
162+ summary = out .get ("summary" , {})
163+ ratio = out .get ("ratio" , {})
164+ errors = out .get ("errors" , [])
165+
166+ if isinstance (summary , dict ):
167+ merged_summaries .append (summary )
168+ if isinstance (ratio , dict ):
169+ merged_ratios .append (ratio )
170+ if isinstance (errors , list ):
171+ merged_errors .extend (errors )
172+
173+ # Optional annotated images upload (disabled by default)
174+ if return_images :
175+ imgs = out .get ("images" , None )
176+ if isinstance (imgs , dict ) and upload_image is not None :
177+ for key in ("det_img" , "label_img" ):
178+ if key in imgs and isinstance (imgs [key ], np .ndarray ):
179+ try :
180+ png_bytes = _cv2_bgr_to_png_bytes (imgs [key ])
181+ img_url = upload_image (png_bytes , "eduvision" )
182+ feedback_items .append (
183+ (f"{ key } [{ idx } ]" , f"<a href=\" { img_url } \" target=\" _blank\" >{ key } </a>" )
184+ )
185+ except ImageUploadError as e :
186+ merged_errors .append ({
187+ "code" : "UPLOAD_FAIL" ,
188+ "message" : f"Failed to upload { key } for image[{ idx } ]: { e } "
189+ })
190+ except Exception as e :
191+ merged_errors .append ({
192+ "code" : "UPLOAD_FAIL" ,
193+ "message" : f"Failed to encode/upload { key } for image[{ idx } ]: { e } "
194+ })
195+ elif upload_image is None and debug :
196+ feedback_items .append (("Images" , "return_images=True but upload_image() is not available in this lf_toolkit version." ))
116197
117- # ---------------------------
118- # Validate input
119- # ---------------------------
120- if not isinstance (response , list ) or len (response ) == 0 :
121- return Result (
122- is_correct = False ,
123- feedback_items = [("Response" , "Please upload at least one image." )]
124- )
125-
126- # Optional controls (safe defaults)
127- return_images : bool = bool (_pget (params , "return_images" , False ))
128- debug : bool = bool (_pget (params , "debug" , False ))
129-
130- # Relative model filenames stored in evaluation_function/
131- gear_model_rel = str (_pget (params , "gear_model_rel" , "gear_model.pt" ))
132- shaft_model_rel = str (_pget (params , "shaft_model_rel" , "shaft_model.pt" ))
133-
134- # ---------------------------
135- # Process images
136- # ---------------------------
137- merged_errors : List [Dict [str , str ]] = []
138- merged_summaries : List [Dict [str , Any ]] = []
139- merged_ratios : List [Dict [str , Any ]] = []
140-
141- feedback_items : List [Tuple [str , str ]] = []
142-
143- for idx , item in enumerate (response ):
144- url = item .get ("url" ) if isinstance (item , dict ) else None
145- if not url :
146- merged_errors .append ({
147- "code" : "NO_URL" ,
148- "message" : f"Image [{ idx } ] has no 'url' field."
149- })
150- continue
151-
152- img_bgr , err = _load_bgr_image_from_url (url )
153- if img_bgr is None :
154- merged_errors .append ({
155- "code" : "LOAD_FAIL" ,
156- "message" : f"Failed to load image [{ idx } ] from URL. ({ err } )"
157- })
158198 if debug :
159199 feedback_items .append ((f"Input URL [{ idx } ]" , str (url )))
160- continue
161-
162- # Run pipeline
163- out = run_yolo_pipeline (
164- img_bgr = img_bgr ,
165- gear_model_rel = gear_model_rel ,
166- shaft_model_rel = shaft_model_rel ,
167- return_images = return_images ,
168- )
169-
170- merged_summaries .append (out .get ("summary" , {}))
171- merged_ratios .append (out .get ("ratio" , {}))
172- merged_errors .extend (out .get ("errors" , []))
173-
174- # Upload annotated images (optional, only if upload_image exists)
175- if return_images :
176- imgs = out .get ("images" , None )
177- if isinstance (imgs , dict ) and upload_image is not None :
178- for key in ("det_img" , "label_img" ):
179- if key in imgs and isinstance (imgs [key ], np .ndarray ):
180- try :
181- png_bytes = _cv2_bgr_to_png_bytes (imgs [key ])
182- img_url = upload_image (png_bytes , "eduvision" ) # folder/tag
183- feedback_items .append (
184- (f"{ key } [{ idx } ]" , f"<a href=\" { img_url } \" target=\" _blank\" >{ key } </a>" )
185- )
186- except ImageUploadError as e :
187- merged_errors .append ({
188- "code" : "UPLOAD_FAIL" ,
189- "message" : f"Failed to upload { key } for image[{ idx } ]: { e } "
190- })
191- except Exception as e :
192- merged_errors .append ({
193- "code" : "UPLOAD_FAIL" ,
194- "message" : f"Failed to encode/upload { key } for image[{ idx } ]: { e } "
195- })
196- elif return_images and upload_image is None :
197- # Don't fail; just inform in debug mode
198- if debug :
199- feedback_items .append (("Images" , "return_images=True but upload_image() not available in this lf_toolkit version." ))
200200
201- if debug :
202- feedback_items .append ((f"Input URL [{ idx } ]" , str (url )))
201+ # Decide correctness
202+ # Your rule: incorrect if any error code starts with "E_"
203+ has_E = any (str (e .get ("code" , "" )).startswith ("E_" ) for e in merged_errors )
204+ is_correct = (not has_E )
205+
206+ # Text feedback
207+ if merged_summaries :
208+ feedback_items .append (("Summary" , str (merged_summaries [- 1 ])))
203209
204- # ---------------------------
205- # Decide correctness
206- # ---------------------------
207- # Your rule: incorrect if any error code starts with "E_"
208- has_E = any (str (e .get ("code" , "" )).startswith ("E_" ) for e in merged_errors )
209- is_correct = (not has_E )
210+ if merged_ratios :
211+ feedback_items .append (("Ratio" , str (merged_ratios [- 1 ])))
210212
211- # ---------------------------
212- # Text feedback
213- # ---------------------------
214- if merged_summaries :
215- feedback_items .append (("Summary" , str (merged_summaries [- 1 ])))
213+ if merged_errors :
214+ lines = [f"- { e .get ('code' , 'ERR' )} : { e .get ('message' , '' )} " for e in merged_errors ]
215+ feedback_items .append (("Issues" , "\n " .join (lines )))
216216
217- if merged_ratios :
218- feedback_items . append (( "Ratio " , str ( merged_ratios [ - 1 ])))
217+ if not feedback_items :
218+ feedback_items = [( "Result " , "No valid images could be processed." )]
219219
220- if merged_errors :
221- lines = [f"- { e .get ('code' , 'ERR' )} : { e .get ('message' , '' )} " for e in merged_errors ]
222- feedback_items .append (("Issues" , "\n " .join (lines )))
220+ feedback_html = _items_to_feedback_html (feedback_items )
223221
224- if not feedback_items :
225- feedback_items = [("Result" , "No valid images could be processed." )]
222+ try :
223+ return Result (is_correct = is_correct , feedback = feedback_html , feedback_items = feedback_items )
224+ except TypeError :
225+ return Result (is_correct = is_correct , feedback_items = feedback_items )
226226
227- return Result (is_correct = is_correct , feedback_items = feedback_items )
227+ except Exception as e :
228+ # Absolute last-resort: never crash the platform UI
229+ items = [("Error" , f"{ type (e ).__name__ } : { e } " )]
230+ feedback_html = _items_to_feedback_html (items )
231+ try :
232+ return Result (is_correct = False , feedback = feedback_html , feedback_items = items )
233+ except TypeError :
234+ return Result (is_correct = False , feedback_items = items )
0 commit comments