From 7c2a1e68a586e35a5848601372004b287f1c175a Mon Sep 17 00:00:00 2001 From: Marvin <52848568+mleem97@users.noreply.github.com> Date: Sun, 10 Aug 2025 05:35:47 +0200 Subject: [PATCH] Add detailed OCR debug logging --- Src/bot_core.py | 38 ++++++++++++++++++++++++++++++++------ Src/ocr_utils.py | 37 +++++++++++++++++++++++++++++++++++-- 2 files changed, 67 insertions(+), 8 deletions(-) diff --git a/Src/bot_core.py b/Src/bot_core.py index 7299550..d5f1072 100644 --- a/Src/bot_core.py +++ b/Src/bot_core.py @@ -94,6 +94,7 @@ def __init__(self, device=None): self.unit_series ) = self.merge_series = self.df_groups = self.info = self.combat_step = None self.logger = logging.getLogger("__main__") + logging.getLogger("ocr_utils").setLevel(logging.DEBUG) if device is None: device = port_scan.get_device() if not device: @@ -702,7 +703,20 @@ def mana_level(self, cards, hero_power=False): # Start a dungeon floor from PvE page def play_dungeon(self, floor=5): self.logger.debug(f"Starting Dungeon floor {floor}") - chapter_num = int(np.ceil(floor / 3)) + # Explicit mapping of floors to chapters + floor_map = { + 1: range(1, 4), + 2: range(4, 7), + 3: range(7, 10), + 4: range(10, 13), + 5: [13], + 6: [14], + } + chapter_num = 1 + for chap, floors in floor_map.items(): + if floor in floors: + chapter_num = chap + break self.logger.debug(f"Looking for chapter {chapter_num}") pos = np.array([0, 0]) avail_buttons = self.get_current_icons(available=True) @@ -711,7 +725,13 @@ def play_dungeon(self, floor=5): # Swipe to the top [self.swipe([0, 0], [2, 0]) for _ in range(14)] self.click(30, 600, 5) # stop scroll and scan screen for buttons - expanded = 0 + self.getScreen() + # Log visible floors for debugging + visible = ocr_utils.find_chapter_headers(self.screenRGB) + for chap, xy in visible.items(): + floors_here = ocr_utils.read_floor_from_chapter(self.screenRGB, xy) + self.logger.debug(f"Visible chapter {chap} floors: {floors_here}") + for i in range(12): self.getScreen() chapters = ocr_utils.find_chapter_headers(self.screenRGB) @@ -719,9 +739,12 @@ def play_dungeon(self, floor=5): if chapter_num in chapters: pos = np.array(chapters[chapter_num]) self.logger.info(f"Found chapter {chapter_num} at {pos}") - if not expanded: - expanded = 1 - self.click_button(pos + [500, 90]) + # Expand chapter only if a collapse icon is detected + icons = self.get_current_icons(available=True) + chapter_icon = f"chapter_{chapter_num}.png" + if (icons["icon"] == chapter_icon).any(): + self.logger.debug(f"Chapter {chapter_num} collapsed, expanding") + self.click_button(pos) self.getScreen() if pos[1] < 550 and floor % 3 != 0: break @@ -760,7 +783,10 @@ def play_dungeon(self, floor=5): chosen_offset = slot_offsets[3] self.click_button(pos + chosen_offset) - self.click_button((500, 600)) + # Play selected floor then choose random partner + self.click_button((500, 600)) # Play + time.sleep(0.5) + self.click_button((500, 800)) # Random for i in range(10): time.sleep(2) avail_buttons = self.get_current_icons(available=True) diff --git a/Src/ocr_utils.py b/Src/ocr_utils.py index 81b06c4..8976473 100644 --- a/Src/ocr_utils.py +++ b/Src/ocr_utils.py @@ -7,9 +7,12 @@ from typing import Dict, Optional, Tuple +import logging import cv2 import numpy as np +logger = logging.getLogger(__name__) + try: import os import shutil @@ -21,6 +24,7 @@ _tess_path = os.getenv("TESSERACT_PATH") if _tess_path and os.path.exists(_tess_path): pytesseract.pytesseract.tesseract_cmd = _tess_path + logger.debug("Using tesseract executable at %s", _tess_path) else: # Common Windows locations candidates = [ @@ -31,35 +35,47 @@ for c in candidates: if os.path.exists(c): pytesseract.pytesseract.tesseract_cmd = c + logger.debug("Found tesseract executable at %s", c) found = True break if not found: which = shutil.which("tesseract.exe") or shutil.which("tesseract") if which: pytesseract.pytesseract.tesseract_cmd = which + logger.debug("Using tesseract executable from PATH: %s", which) except Exception: _TESS = False + logger.warning("Tesseract not available, OCR functions will be disabled") def _prep_digits(img_bgr: np.ndarray) -> np.ndarray: """Preprocess ROI to improve OCR on digits: grayscale, CLAHE, blur, threshold, morph.""" + logger.debug("Preprocessing digits ROI with shape %s", img_bgr.shape) gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY) + logger.debug("Converted ROI to grayscale") # Local contrast boost clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) gray = clahe.apply(gray) + logger.debug("Applied CLAHE for local contrast enhancement") # De-noise but keep edges gray = cv2.bilateralFilter(gray, d=5, sigmaColor=40, sigmaSpace=40) + logger.debug("Applied bilateral filter to reduce noise") # Adaptive threshold th = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 31, 5) + logger.debug("Applied adaptive thresholding") # Morph to unify characters kernel = np.ones((2, 2), np.uint8) th = cv2.morphologyEx(th, cv2.MORPH_OPEN, kernel, iterations=1) th = cv2.dilate(th, kernel, iterations=1) + logger.debug("Performed morphological operations") # Scale up for better OCR h, w = th.shape scale = 2 if max(h, w) < 80 else 1 if scale != 1: th = cv2.resize(th, (w * scale, h * scale), interpolation=cv2.INTER_CUBIC) + logger.debug("Resized ROI by factor %s", scale) + else: + logger.debug("No resizing applied (scale=1)") return th @@ -68,21 +84,26 @@ def ocr_digits(img_bgr: np.ndarray, psm: int = 7) -> Tuple[Optional[int], float] psm 7=single line, 6=block; tries strict whitelist for 0-9. """ if not _TESS: + logger.debug("Tesseract not available, skipping OCR") return None, 0.0 + logger.debug("Running digit OCR with psm=%s", psm) proc = _prep_digits(img_bgr) + logger.debug("Prepared ROI for OCR with shape %s", proc.shape) cfg = f"--psm {psm} -c tessedit_char_whitelist=0123456789" try: txt = pytesseract.image_to_string(proc, config=cfg).strip() + logger.debug("Tesseract raw output: '%s'", txt) # Optional: confidences via image_to_data data = pytesseract.image_to_data(proc, config=cfg, output_type=pytesseract.Output.DICT) confs = [float(c) for c in data.get("conf", []) if c not in ("-1", None)] conf = (sum(confs) / len(confs)) / 100.0 if confs else 0.0 - # Keep only digits digits = "".join(ch for ch in txt if ch.isdigit()) + logger.debug("Filtered digits='%s', confidence=%.2f", digits, conf) if digits == "": return None, conf return int(digits), conf - except Exception: + except Exception as e: + logger.debug("pytesseract.image_to_string failed: %s", e) return None, 0.0 @@ -107,17 +128,23 @@ def read_floor_from_chapter( for slot, (rx, ry, rw, rh) in rois.items(): rx0, ry0 = max(0, rx), max(0, ry) rx1, ry1 = min(w, rx + rw), min(h, ry + rh) + logger.debug("Slot %s ROI: x=%s y=%s w=%s h=%s", slot, rx, ry, rw, rh) if rx1 <= rx0 or ry1 <= ry0: + logger.debug("Slot %s ROI out of bounds", slot) results[slot] = (None, 0.0) continue roi = screen_bgr[ry0:ry1, rx0:rx1] # Try psm 7 first, fallback psm 6 val, conf = ocr_digits(roi, psm=7) + logger.debug("Slot %s psm7 result: %s (conf=%.2f)", slot, val, conf) if val is None or conf < 0.55: + logger.debug("Slot %s falling back to psm6", slot) val2, conf2 = ocr_digits(roi, psm=6) + logger.debug("Slot %s psm6 result: %s (conf=%.2f)", slot, val2, conf2) if (val2 is not None and conf2 >= conf) or val is None: val, conf = val2, conf2 results[slot] = (val, conf) + logger.debug("Slot %s final result: %s (conf=%.2f)", slot, val, conf) return results @@ -126,15 +153,19 @@ def find_chapter_headers(screen_bgr: np.ndarray) -> Dict[int, Tuple[int, int]]: Returns mapping {chapter_number: (x, y)} for the top-left of the word 'Chapter'. """ if not _TESS: + logger.debug("Tesseract not available, cannot find chapter headers") return {} + logger.debug("Running OCR to find chapter headers") try: data = pytesseract.image_to_data(screen_bgr, output_type=pytesseract.Output.DICT) except Exception as e: if isinstance(e, (KeyboardInterrupt, SystemExit)): raise + logger.debug("image_to_data failed: %s", e) return {} results: Dict[int, Tuple[int, int]] = {} words = [w.strip().lower() for w in data.get("text", [])] + logger.debug("OCR detected %d words", len(words)) for i, w in enumerate(words): if w == "chapter" and i + 1 < len(words): try: @@ -142,6 +173,8 @@ def find_chapter_headers(screen_bgr: np.ndarray) -> Dict[int, Tuple[int, int]]: x = int(data["left"][i]) y = int(data["top"][i]) results[num] = (x, y) + logger.debug("Found chapter %s at (%s, %s)", num, x, y) except (ValueError, TypeError): + logger.debug("Skipping invalid chapter entry at index %s", i) continue return results