Agent2 / class_routing.py
tasnime24's picture
Upload 7 files
9de6bfb verified
Raw
History Blame Contribute Delete
4.98 kB
"""
Class Routing - YOLO vs VLM
------------------------------
Two separate mappings needed for Agent 2's laptop damage detection:
1. TAXONOMY_MAP: every one of the 18 fine-grained Roboflow/YOLO classes maps
to one of the shared 9-category taxonomy used throughout Module 2
(screen_display, structural_body, port_connector, missing_component,
liquid_moisture, input_control, power_boot_failure, cosmetic_wear,
other_unclassified). Needed regardless of the routing decision below,
since Agent 4/blockchain/mobile all expect the shared taxonomy, not
Roboflow's granular labels.
2. YOLO_TRAINED_CLASSES / VLM_FALLBACK_CLASSES: which of the 18 classes the
trained YOLO model is actually responsible for (had enough real source
data - >=100 instances) vs which get routed to the VLM classifier
instead (too little data to trust a trained detector on, per real counts
found by inspection). This does NOT mean those damage types are lost -
it means Agent 2 asks the VLM about them instead of the trained model,
the same way printer/projector already work VLM-only.
"""
# ----- 1. Fine-grained YOLO class -> shared taxonomy category -----
TAXONOMY_MAP = {
"scratch": "cosmetic_wear",
"chip": "cosmetic_wear",
"adhesive_residue": "cosmetic_wear",
"crack": "structural_body",
"broken_other": "structural_body",
"broken_lock": "structural_body",
"disassembled": "structural_body",
"depression": "structural_body",
"damaged_screen": "screen_display",
"dead_pixel": "screen_display",
"display_lines": "screen_display",
"display_spot": "screen_display",
"display_fade": "screen_display",
"missing_button": "missing_component",
"missing_screw": "missing_component",
"keyboard_issue": "input_control",
"broken_button": "input_control",
"normal": None, # not a damage type - "no damage detected here" reference class
}
# ----- 2. Which classes the trained YOLO model actually handles -----
# Based on real per-class instance counts in the training data (before
# oversampling - duplicated copies don't add real information, so the
# threshold is judged against genuine source image counts).
#
# NOTE: "scratch" was originally here (21,656 real instances - by far the
# most data of any class) but is EXCLUDED despite that, moved to
# VLM_FALLBACK_CLASSES instead. Confirmed via real testing across TWO
# model generations (YOLOv8: 0.191 mAP50, YOLO26: 0.206 mAP50) that more
# data does not fix it - scratches are thin, low-contrast, and densely
# packed, which is a detection-difficulty problem, not a data-volume one.
# The VLM's own visual reasoning ("is there a scratch, how bad") doesn't
# need precise bounding boxes the way object detection does, and is
# expected to handle this better.
YOLO_TRAINED_CLASSES = {
"broken_other", "chip", "missing_button", "keyboard_issue",
"missing_screw", "dead_pixel", "damaged_screen", "crack", "display_lines",
"normal", # kept for its negative/reference role, not as a "damage type"
}
VLM_FALLBACK_CLASSES = {
"scratch", "broken_lock", "depression", "disassembled", "display_spot",
"adhesive_residue", "display_fade", "broken_button",
}
def get_shared_category(fine_grained_class: str) -> str:
"""Map a fine-grained YOLO class name to the shared 9-category taxonomy."""
return TAXONOMY_MAP.get(fine_grained_class, "other_unclassified")
def yolo_can_handle(fine_grained_class: str) -> bool:
"""True if the trained YOLO model has enough real data to be trusted on this class."""
return fine_grained_class in YOLO_TRAINED_CLASSES
def route_detection(yolo_detections: list, confidence_threshold: float = 0.4) -> dict:
"""
Given YOLO's raw detections for one crop (list of {"class_name": str,
"confidence": float}), decide whether to trust YOLO's answer or fall
back to the VLM classifier.
Trusts YOLO only if its TOP detection is (a) a class it has enough real
data for, and (b) above the confidence threshold. Otherwise signals a
VLM fallback - covering both "YOLO found nothing confident" and
"YOLO's best guess was one of the data-insufficient classes."
"""
if not yolo_detections:
return {"use_yolo": False, "reason": "no_detections", "fine_grained_class": None}
top = max(yolo_detections, key=lambda d: d["confidence"])
if top["confidence"] < confidence_threshold:
return {"use_yolo": False, "reason": "low_confidence", "fine_grained_class": top["class_name"]}
if not yolo_can_handle(top["class_name"]):
return {
"use_yolo": False,
"reason": "class_needs_more_training_data",
"fine_grained_class": top["class_name"],
}
return {
"use_yolo": True,
"reason": "trained_class_confident",
"fine_grained_class": top["class_name"],
"shared_category": get_shared_category(top["class_name"]),
"confidence": top["confidence"],
}