Skip to content

Commit 7c829ef

Browse files
committed
Refine spacer assignment and count feedback
1 parent 32116b3 commit 7c829ef

File tree

3 files changed

+87
-14
lines changed

3 files changed

+87
-14
lines changed

evaluation_function/evaluation.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,10 @@
116116
"none_detected": "No spacers were detected clearly. Please make sure both spacers are installed and clearly visible in the photo.",
117117
"short_missing": "The short spacer may be missing or not detected. Please check whether the short spacer is installed and retake the photo if needed.",
118118
"long_missing": "The long spacer may be missing or not detected. Please check whether the long spacer is installed and retake the photo if needed.",
119+
"too_many": "Too many spacers were detected. Please make sure only the required short spacer and long spacer are visible in the photo.",
119120
"count_fail": "A spacer may be missing or not detected. Please check whether both spacers are installed and retake the photo if needed.",
120121
"type_confusion": "The spacer types could not be identified reliably. Please retake the photo from a clearer angle and make sure both spacers are fully visible.",
121-
"assignment_fail": "The spacers were detected, but at least one spacer could not be reliably assigned to a shaft. Please make sure both spacers are correctly installed on the shafts and retake the photo from a clear top view.",
122+
"assignment_fail": "The spacers were detected, but at least one spacer could not be reliably assigned to a shaft. Please make sure both spacers are placed on the shafts. If they are already on the shafts, retake the photo from a clear top view with both shafts and spacers fully visible.",
122123
"position_mismatch": "The spacer positions appear to be incorrect. Please make sure the short spacer is on the short shaft and the long spacer is on the long shaft.",
123124
"distance_order": "The spacer order appears to be incorrect. Please check whether the short spacer is closer to white gear than the long spacer.",
124125
"fail": "Please check the spacer setup again.",
@@ -719,6 +720,9 @@ def advice_text() -> str:
719720
if n_total == 0:
720721
return False, MESSAGE_POLICY["spacer"]["none_detected"]
721722

723+
if n_total > 2:
724+
return False, MESSAGE_POLICY["spacer"]["too_many"]
725+
722726
if n_short == 0 and n_long >= 1:
723727
return False, MESSAGE_POLICY["spacer"]["short_missing"]
724728

@@ -731,6 +735,8 @@ def advice_text() -> str:
731735
if "E_SPACER_COUNT_MISMATCH" in codes:
732736
if n_total == 0:
733737
return False, MESSAGE_POLICY["spacer"]["none_detected"]
738+
if n_total > 2:
739+
return False, MESSAGE_POLICY["spacer"]["too_many"]
734740
return False, MESSAGE_POLICY["spacer"]["count_fail"]
735741

736742
if (

evaluation_function/evaluation_test.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
import numpy as np
77
from lf_toolkit.evaluation import Params
88
from .evaluation import _build_student_message, evaluation_function
9-
from .yolo_pipeline import _quality_advice, compute_image_quality_metrics
9+
from .yolo_pipeline import (
10+
_quality_advice,
11+
compute_image_quality_metrics,
12+
evaluate_spacer_step_errors,
13+
)
1014

1115

1216
def _as_file_uri(path: str) -> str:
@@ -234,6 +238,36 @@ def test_spacer_feedback_reports_no_spacers_detected(self):
234238
self.assertFalse(is_correct)
235239
self.assertIn("no spacers", message.lower())
236240

241+
def test_spacer_feedback_reports_too_many_spacers(self):
242+
is_correct, message = _build_student_message(
243+
task="spacer",
244+
img_bgr=np.zeros((10, 10, 3), dtype=np.uint8),
245+
out={"counts": {"spacer_long": 2, "spacer_short": 1}},
246+
errors=[],
247+
selected_errors=[],
248+
part_type="",
249+
)
250+
251+
self.assertFalse(is_correct)
252+
self.assertIn("too many spacers", message.lower())
253+
254+
def test_spacer_assignment_feedback_mentions_placement_and_photo_quality(self):
255+
errors = [{"code": "E_SPACER_ASSIGNMENT_FAIL", "message": "Assignment failed."}]
256+
257+
is_correct, message = _build_student_message(
258+
task="spacer",
259+
img_bgr=np.zeros((10, 10, 3), dtype=np.uint8),
260+
out={"counts": {"spacer_long": 1, "spacer_short": 1}, "errors": errors},
261+
errors=errors,
262+
selected_errors=errors,
263+
part_type="",
264+
)
265+
lower_message = message.lower()
266+
267+
self.assertFalse(is_correct)
268+
self.assertIn("placed on the shafts", lower_message)
269+
self.assertIn("clear top view", lower_message)
270+
237271
def test_shaft_feedback_reports_short_shaft_missing_from_counts(self):
238272
is_correct, message = _build_student_message(
239273
task="shaft",
@@ -301,6 +335,40 @@ def test_shaft_feedback_reports_too_many_shafts(self):
301335
self.assertFalse(is_correct)
302336
self.assertIn("too many shafts", message.lower())
303337

338+
def test_spacer_assignment_checked_before_distance_order(self):
339+
errors = evaluate_spacer_step_errors(
340+
gears=[{"gid": 11, "center": (0.0, 0.0), "r": 10.0}],
341+
shafts=[
342+
{"center": (10.0, 0.0), "major_len": 40.0},
343+
{"center": (30.0, 0.0), "major_len": 40.0},
344+
],
345+
spacers=[
346+
{"sid": 1, "cls": "short_spacer", "center": (100.0, 0.0)},
347+
{"sid": 2, "cls": "long_spacer", "center": (5.0, 0.0)},
348+
],
349+
gear11_gid=11,
350+
spacer_to_si={2: 1},
351+
)
352+
353+
self.assertEqual(errors[0]["code"], "E_SPACER_ASSIGNMENT_FAIL")
354+
355+
def test_spacer_position_checked_before_distance_order(self):
356+
errors = evaluate_spacer_step_errors(
357+
gears=[{"gid": 11, "center": (0.0, 0.0), "r": 10.0}],
358+
shafts=[
359+
{"center": (10.0, 0.0), "major_len": 40.0},
360+
{"center": (30.0, 0.0), "major_len": 40.0},
361+
],
362+
spacers=[
363+
{"sid": 1, "cls": "short_spacer", "center": (100.0, 0.0)},
364+
{"sid": 2, "cls": "long_spacer", "center": (5.0, 0.0)},
365+
],
366+
gear11_gid=11,
367+
spacer_to_si={1: 1, 2: 0},
368+
)
369+
370+
self.assertEqual(errors[0]["code"], "E_SPACER_POSITION_MISMATCH")
371+
304372

305373
if __name__ == "__main__":
306374
unittest.main()

evaluation_function/yolo_pipeline.py

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1298,18 +1298,6 @@ def evaluate_spacer_step_errors(
12981298
short_sp = short_spacers[0]
12991299
long_sp = long_spacers[0]
13001300

1301-
# Strong direct geometric rule first
1302-
d_short = center_dist(short_sp["center"], c11)
1303-
d_long = center_dist(long_sp["center"], c11)
1304-
tol_px = relative_spacer_distance_tol(shafts, gears)
1305-
1306-
if d_short > d_long + tol_px:
1307-
errs.append({
1308-
"code": "E_SPACER_DISTANCE_ORDER",
1309-
"message": f"The short spacer is not closer to gear11 than the long spacer (tol={tol_px:.1f}px)."
1310-
})
1311-
return errs
1312-
13131301
shaft2_idx, shaft3_idx = get_expected_shaft_indices_for_step(
13141302
gears=gears,
13151303
shafts=shafts,
@@ -1354,6 +1342,17 @@ def evaluate_spacer_step_errors(
13541342
})
13551343
return errs
13561344

1345+
d_short = center_dist(short_sp["center"], c11)
1346+
d_long = center_dist(long_sp["center"], c11)
1347+
tol_px = relative_spacer_distance_tol(shafts, gears)
1348+
1349+
if d_short > d_long + tol_px:
1350+
errs.append({
1351+
"code": "E_SPACER_DISTANCE_ORDER",
1352+
"message": f"The short spacer is not closer to gear11 than the long spacer (tol={tol_px:.1f}px)."
1353+
})
1354+
return errs
1355+
13571356
return errs
13581357

13591358
# =========================

0 commit comments

Comments
 (0)