| """ |
| Interactive runner for the YOLO object-detection inventory check. |
| |
| Walks you through: |
| 1. Uploading the BASELINE (pickup) photo via a native file-picker dialog |
| 2. Uploading the RETURN photo the same way |
| 3. Detecting objects in both, with a running total per item type |
| 4. Comparing the two counts and reporting exactly what's missing |
| |
| Run: python run_yolo_interactive.py |
| """ |
|
|
| import os |
| import json |
|
|
| from yolo_detection import detect_objects, draw_detections, compare_with_asymmetric_confidence |
| from inventory_check import compare_inventory |
|
|
|
|
| def pick_file(window_title): |
| """Open a native file-picker dialog. Falls back to typed path entry if |
| tkinter isn't available (e.g. headless environments).""" |
| try: |
| import tkinter as tk |
| from tkinter import filedialog |
|
|
| root = tk.Tk() |
| root.withdraw() |
| root.attributes("-topmost", True) |
| path = filedialog.askopenfilename( |
| title=window_title, |
| filetypes=[("Image files", "*.jpg *.jpeg *.png *.bmp *.webp"), ("All files", "*.*")], |
| ) |
| root.destroy() |
| if path: |
| return path |
| print(" No file selected, please try again.") |
| return pick_file(window_title) |
| except Exception: |
| while True: |
| path = input(f"{window_title}\n Enter the full file path: ").strip().strip('"') |
| if os.path.isfile(path): |
| return path |
| print(f" File not found: {path}") |
|
|
|
|
| def print_item_counts(label, counts): |
| print(f"\n{label}:") |
| if not counts: |
| print(" (no objects detected with enough confidence)") |
| return |
| total = sum(counts.values()) |
| for name, count in sorted(counts.items(), key=lambda x: -x[1]): |
| print(f" {name}: {count}") |
| print(f" --- total items: {total} ---") |
|
|
|
|
| def main(): |
| print("=== YOLO Inventory Check ===") |
| item_id = input("Item/kit ID (press Enter for a default): ").strip() or "kit_test_001" |
|
|
| print("\nSelect the BASELINE (pickup) photo...") |
| baseline_path = pick_file("Baseline photo (pickup)") |
| print(f" Baseline: {baseline_path}") |
|
|
| print("Select the RETURN photo...") |
| return_path = pick_file("Return photo") |
| print(f" Return: {return_path}") |
|
|
| print("\nDetecting objects (this may take a few seconds the first time, while the model downloads)...") |
| baseline_detections = detect_objects(baseline_path) |
| return_detections = detect_objects(return_path) |
|
|
| baseline_counts, return_counts = compare_with_asymmetric_confidence(baseline_detections, return_detections) |
| print("\n(Note: baseline uses a strict confidence bar to establish the expected inventory;") |
| print(" the return photo uses a more lenient bar to CONFIRM presence, since a physically") |
| print(" unchanged item can naturally score lower confidence from a different angle/lighting -") |
| print(" only items with no detection at all in the return photo count as missing.)") |
|
|
| print_item_counts("BASELINE - items detected", baseline_counts) |
| print_item_counts("RETURN - items detected", return_counts) |
|
|
| comparison = compare_inventory(baseline_counts, return_counts) |
|
|
| print("\n" + "=" * 50) |
| print("RESULT") |
| print("=" * 50) |
| if comparison.unchanged: |
| print("Nothing missing - all item counts match.") |
| else: |
| print("MISSING ITEMS DETECTED:") |
| for name, count in comparison.missing.items(): |
| print(f" - {name}: {count} missing " |
| f"(expected {baseline_counts.get(name, 0)}, found {return_counts.get(name, 0)})") |
|
|
| if comparison.extra: |
| print("\nExtra items noticed in return photo (unusual, worth a glance):") |
| for name, count in comparison.extra.items(): |
| print(f" - {name}: +{count}") |
|
|
| |
| out_dir = "yolo_crops" |
| os.makedirs(out_dir, exist_ok=True) |
| baseline_out = os.path.join(out_dir, f"{item_id}_baseline_detected.jpg") |
| return_out = os.path.join(out_dir, f"{item_id}_return_detected.jpg") |
| draw_detections(baseline_path, baseline_detections, baseline_out) |
| draw_detections(return_path, return_detections, return_out) |
| print(f"\nAnnotated images saved:\n {baseline_out}\n {return_out}") |
|
|
| result = { |
| "item_id": item_id, |
| "baseline_inventory": baseline_counts, |
| "return_inventory": return_counts, |
| "missing_items": comparison.missing, |
| "extra_items": comparison.extra, |
| "overall_change_detected": not comparison.unchanged, |
| "routing_hint": "agent_4" if not comparison.unchanged else "none", |
| } |
| print("\nFull JSON output:") |
| print(json.dumps(result, indent=2)) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|