Skip to content

Commit 8fa7718

Browse files
committed
Refactor evaluation feedback structure
1 parent 97f21dc commit 8fa7718

10 files changed

Lines changed: 196 additions & 122 deletions

File tree

.idea/.gitignore

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/EduVision-Gearbox-Assembly.iml

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/Project_Default.xml

Lines changed: 22 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/inspectionProfiles/profiles_settings.xml

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/vcs.xml

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
-2.02 MB
Loading
-2 MB
Loading

evaluation_function/evaluation.py

Lines changed: 129 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# -*- coding: utf-8 -*-
2-
# evaluation_function/evaluation.py
3-
41
from __future__ import annotations
52

63
import os
@@ -25,9 +22,7 @@ class ImageUploadError(Exception): # type: ignore
2522
from .yolo_pipeline import run_yolo_pipeline
2623

2724

28-
# ----------------------------
2925
# URL / path helpers
30-
# ----------------------------
3126
def 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-
# ----------------------------
102106
def 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

Comments
 (0)