"""One-time ROI calibration. Usage: python calibrate.py samples/full_1080p.png # interactive: drag 10 slot boxes python calibrate.py samples/full_1080p.png --check # draw current config on image Interactive mode: for each of the 10 hero slots (any order), drag a box around the portrait (include the whole parallelogram, exclude neighbors), then press SPACE/ENTER. Press ESC when all 10 are done. Slots are sorted left-to-right and stored as resolution-independent relative coordinates. """ import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) import time import cv2 from common import load_config, save_config, slot_rect_px def calibrate(image_path: str) -> None: img = cv2.imread(image_path) if img is None: sys.exit(f"cannot read image: {image_path}") ih, iw = img.shape[:2] print(f"image size: {iw}x{ih}") print("Drag a box per slot (10 total), SPACE/ENTER to confirm each, ESC to finish.") rois = cv2.selectROIs("calibrate - drag 10 slots", img, showCrosshair=True) cv2.destroyAllWindows() if len(rois) != 10: sys.exit(f"expected 10 boxes, got {len(rois)} - please rerun") rois = sorted(rois.tolist(), key=lambda r: r[0]) cfg = load_config() cfg["slots"] = [] avg_w = sum(r[2] for r in rois) / 10 avg_h = sum(r[3] for r in rois) / 10 cfg["slot_w_rel"] = round(avg_w / ih, 5) cfg["slot_h_rel"] = round(avg_h / ih, 5) for i, (x, y, w, h) in enumerate(rois): cfg["slots"].append( { "index": i + 1, "cx_rel": round((x + w / 2 - iw / 2) / ih, 5), "cy_rel": round((y + h / 2) / ih, 5), } ) cfg["calibrated_on"] = f"{iw}x{ih} {time.strftime('%Y-%m-%d %H:%M')}" save_config(cfg) print(f"saved {len(cfg['slots'])} slots to config.json") check(image_path) def check(image_path: str) -> None: """Draw configured slot rects onto the image for visual verification.""" img = cv2.imread(image_path) if img is None: sys.exit(f"cannot read image: {image_path}") ih, iw = img.shape[:2] cfg = load_config() if not cfg["slots"]: sys.exit("config.json has no slots - run calibration first") for slot in cfg["slots"]: x, y, w, h = slot_rect_px(slot, cfg, iw, ih) cv2.rectangle(img, (x, y), (x + w, y + h), (0, 255, 0), 2) cv2.putText(img, str(slot["index"]), (x, y - 4), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) out = "calibrate_check.png" cv2.imwrite(out, img) print(f"wrote {out} - open it and verify the boxes sit on the 10 portraits") if __name__ == "__main__": if len(sys.argv) < 2: sys.exit(__doc__) if "--check" in sys.argv: check(sys.argv[1]) else: calibrate(sys.argv[1])