Download src/utils/object_mapper.py from SahilCodevally/codevally-vision-language-action: direct link, hf CLI and curl.
- Browser
- Download file 8.42 kB
-
https://huggingface.co/spaces/SahilCodevally/codevally-vision-language-action/resolve/e1c010e5116a062fd970a76c8eaa3dddbf5c4d4b/src/utils/object_mapper.py
- Command line
-
hf download hf://spaces/SahilCodevally/codevally-vision-language-action@e1c010e5116a062fd970a76c8eaa3dddbf5c4d4b/src/utils/object_mapper.py
-
curl -L -o object_mapper.py https://huggingface.co/spaces/SahilCodevally/codevally-vision-language-action/resolve/e1c010e5116a062fd970a76c8eaa3dddbf5c4d4b/src/utils/object_mapper.py
8.42 kB
| """ | |
| Object Mapper — Synonym normalization for bridging YOLO labels | |
| and natural-language user commands. | |
| YOLO may detect "cup" while the user says "mug". This module provides | |
| a mapping layer and fuzzy-match helper to reconcile the two vocabularies. | |
| """ | |
| import logging | |
| from difflib import SequenceMatcher | |
| logger = logging.getLogger(__name__) | |
| # --------------------------------------------------------------------------- | |
| # Synonym Map: user-language → canonical YOLO label | |
| # --------------------------------------------------------------------------- | |
| SYNONYM_MAP: dict[str, str] = { | |
| # --------------------------------------------------------------- | |
| # COCO has 80 classes. This map bridges common user vocabulary | |
| # to the actual YOLO/COCO label strings. | |
| # --------------------------------------------------------------- | |
| # person | |
| "human": "person", | |
| "worker": "person", | |
| "operator": "person", | |
| "man": "person", | |
| "woman": "person", | |
| "people": "person", | |
| "employee": "person", | |
| "technician": "person", | |
| # bicycle | |
| "bike": "bicycle", | |
| "cycle": "bicycle", | |
| # car | |
| "automobile": "car", | |
| "vehicle": "car", | |
| "sedan": "car", | |
| # motorcycle | |
| "motorbike": "motorcycle", | |
| "scooter": "motorcycle", | |
| # truck | |
| "van": "truck", | |
| "lorry": "truck", | |
| "pickup": "truck", | |
| # boat | |
| "ship": "boat", | |
| "vessel": "boat", | |
| "canoe": "boat", | |
| # traffic light | |
| "signal": "traffic light", | |
| "stoplight": "traffic light", | |
| # backpack | |
| "bag": "backpack", | |
| "rucksack": "backpack", | |
| "knapsack": "backpack", | |
| # umbrella | |
| "parasol": "umbrella", | |
| # handbag | |
| "purse": "handbag", | |
| "clutch": "handbag", | |
| # suitcase (toolboxes, cases look like suitcases to YOLO) | |
| "briefcase": "suitcase", | |
| "toolbox": "suitcase", | |
| "case": "suitcase", | |
| "luggage": "suitcase", | |
| "tool box": "suitcase", | |
| # bottle | |
| "water bottle": "bottle", | |
| "flask": "bottle", | |
| "jug": "bottle", | |
| "canteen": "bottle", | |
| "container": "bottle", | |
| # wine glass | |
| "goblet": "wine glass", | |
| "chalice": "wine glass", | |
| "glass": "wine glass", | |
| # cup | |
| "mug": "cup", | |
| "teacup": "cup", | |
| "coffee cup": "cup", | |
| "tumbler": "cup", | |
| # fork | |
| "prong": "fork", | |
| # knife (wrenches, tools often detected as knife-like) | |
| "blade": "knife", | |
| "wrench": "knife", | |
| "spanner": "knife", | |
| "screwdriver": "knife", | |
| "tool": "knife", | |
| # spoon | |
| "ladle": "spoon", | |
| "scoop": "spoon", | |
| # bowl | |
| "tray": "bowl", | |
| "dish": "bowl", | |
| "plate": "bowl", | |
| "bin": "bowl", | |
| "basket": "bowl", | |
| # banana / apple / orange (fruits) | |
| "fruit": "apple", | |
| # sandwich | |
| "sub": "sandwich", | |
| "burger": "sandwich", | |
| "wrap": "sandwich", | |
| # pizza | |
| "pie": "pizza", | |
| # cake | |
| "pastry": "cake", | |
| "dessert": "cake", | |
| # chair | |
| "seat": "chair", | |
| "stool": "chair", | |
| "office chair": "chair", | |
| # couch | |
| "sofa": "couch", | |
| "loveseat": "couch", | |
| "settee": "couch", | |
| # bed | |
| "mattress": "bed", | |
| "cot": "bed", | |
| # dining table | |
| "desk": "dining table", | |
| "table": "dining table", | |
| "workbench": "dining table", | |
| "bench": "dining table", | |
| "counter": "dining table", | |
| # tv | |
| "monitor": "tv", | |
| "screen": "tv", | |
| "display": "tv", | |
| "television": "tv", | |
| # laptop | |
| "notebook": "laptop", | |
| "computer": "laptop", | |
| "macbook": "laptop", | |
| # mouse | |
| "trackpad": "mouse", | |
| # remote | |
| "controller": "remote", | |
| "remote control": "remote", | |
| # keyboard | |
| "keypad": "keyboard", | |
| # cell phone | |
| "mobile": "cell phone", | |
| "phone": "cell phone", | |
| "smartphone": "cell phone", | |
| "iphone": "cell phone", | |
| "handset": "cell phone", | |
| "scanner": "cell phone", | |
| # microwave | |
| "microwave oven": "microwave", | |
| # oven | |
| "stove": "oven", | |
| "furnace": "oven", | |
| # refrigerator | |
| "fridge": "refrigerator", | |
| "cooler": "refrigerator", | |
| "freezer": "refrigerator", | |
| # book | |
| "notebook paper": "book", | |
| "manual": "book", | |
| "textbook": "book", | |
| "binder": "book", | |
| # clock | |
| "watch": "clock", | |
| "timer": "clock", | |
| # vase | |
| "pot": "vase", | |
| "planter": "vase", | |
| "jar": "vase", | |
| # scissors | |
| "cutter": "scissors", | |
| "shears": "scissors", | |
| "clipper": "scissors", | |
| "pliers": "scissors", | |
| "wire cutter": "scissors", | |
| # teddy bear | |
| "stuffed animal": "teddy bear", | |
| "plush": "teddy bear", | |
| "toy": "teddy bear", | |
| # potted plant | |
| "plant": "potted plant", | |
| "houseplant": "potted plant", | |
| "flower": "potted plant", | |
| # ----------------------------------------------------------- | |
| # Domain-specific aliases for industrial / demo contexts | |
| # ----------------------------------------------------------- | |
| "damaged part": "knife", | |
| "defective part": "knife", | |
| "component": "knife", | |
| "metal part": "knife", | |
| "part": "knife", | |
| "piece": "knife", | |
| "red bin": "bowl", | |
| "blue bin": "bowl", | |
| "sorting bin": "bowl", | |
| "reject bin": "bowl", | |
| "accept bin": "bowl", | |
| "circuit board": "keyboard", | |
| "pcb": "keyboard", | |
| "board": "keyboard", | |
| "hard hat": "sports ball", | |
| "helmet": "sports ball", | |
| "safety hat": "sports ball", | |
| "gloves": "backpack", | |
| "safety gloves": "backpack", | |
| "pallet": "suitcase", | |
| "crate": "suitcase", | |
| "carton": "suitcase", | |
| "box": "suitcase", | |
| "hand truck": "bicycle", | |
| "dolly": "bicycle", | |
| "cart": "bicycle", | |
| "forklift": "truck", | |
| "clipboard": "book", | |
| "blueprint": "book", | |
| "stethoscope": "tie", | |
| "syringe": "toothbrush", | |
| "safety goggles": "wine glass", | |
| "goggles": "wine glass", | |
| "oil can": "bottle", | |
| "spray can": "bottle", | |
| "battery": "suitcase", | |
| "car battery": "suitcase", | |
| "multimeter": "cell phone", | |
| "meter": "cell phone", | |
| "soldering iron": "knife", | |
| "magnifying lamp": "clock", | |
| } | |
| def normalize_label(label: str) -> str: | |
| """ | |
| Normalize a label using the synonym map. | |
| Args: | |
| label: Raw label string (from user command or YOLO). | |
| Returns: | |
| The canonical label if a synonym exists, otherwise the | |
| original label lowercased and stripped. | |
| """ | |
| cleaned = label.strip().lower() | |
| return SYNONYM_MAP.get(cleaned, cleaned) | |
| def _similarity(a: str, b: str) -> float: | |
| """Return a 0-1 similarity score between two strings.""" | |
| return SequenceMatcher(None, a.lower(), b.lower()).ratio() | |
| def find_best_match( | |
| query: str, | |
| detected_labels: list[str], | |
| threshold: float = 0.4, | |
| ) -> str | None: | |
| """ | |
| Find the best matching detected label for a user query term. | |
| Strategy: | |
| 1. Exact match after normalization. | |
| 2. Substring containment check. | |
| 3. Fuzzy similarity above *threshold*. | |
| Args: | |
| query: The term from the user command (e.g. "mug"). | |
| detected_labels: Labels returned by the vision detector. | |
| threshold: Minimum similarity score for fuzzy matching. | |
| Returns: | |
| The best matching label, or ``None`` if no match is found. | |
| """ | |
| normalized_query = normalize_label(query) | |
| normalized_detected = {normalize_label(lbl): lbl for lbl in detected_labels} | |
| # 1. Exact match on normalized labels | |
| if normalized_query in normalized_detected: | |
| logger.info( | |
| "Exact match: '%s' → '%s'", | |
| query, | |
| normalized_detected[normalized_query], | |
| ) | |
| return normalized_detected[normalized_query] | |
| # 2. Substring containment | |
| for norm_lbl, orig_lbl in normalized_detected.items(): | |
| if normalized_query in norm_lbl or norm_lbl in normalized_query: | |
| logger.info( | |
| "Substring match: '%s' → '%s'", query, orig_lbl | |
| ) | |
| return orig_lbl | |
| # 3. Fuzzy matching | |
| best_score = 0.0 | |
| best_label: str | None = None | |
| for norm_lbl, orig_lbl in normalized_detected.items(): | |
| score = _similarity(normalized_query, norm_lbl) | |
| if score > best_score: | |
| best_score = score | |
| best_label = orig_lbl | |
| if best_label and best_score >= threshold: | |
| logger.info( | |
| "Fuzzy match: '%s' → '%s' (score=%.2f)", | |
| query, | |
| best_label, | |
| best_score, | |
| ) | |
| return best_label | |
| logger.warning("No match found for query '%s'", query) | |
| return None | |