From a728b529c5f8f10dccc13a739e28cc719ee6acd7 Mon Sep 17 00:00:00 2001 From: brockdarnold Date: Mon, 15 Jun 2026 05:12:55 +0000 Subject: [PATCH] publish eve_rock_watcher.py --- eve_rock_watcher.py | 84 +++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/eve_rock_watcher.py b/eve_rock_watcher.py index c77b015..5596824 100644 --- a/eve_rock_watcher.py +++ b/eve_rock_watcher.py @@ -90,55 +90,57 @@ QUANTITY_RE = re.compile(r"quantit[yvſ]\s*[:.]?\s*([\d.,]{2,})\s*units?", re.I) UNITS_RE = re.compile(r"([\d.,]{3,})\s*units?\b", re.I) -def _ocr_window_text(cp): - """OCR the EVE window with sparse-text mode (psm 11) — far better than uniform-block - (psm 6) at picking scattered UI text off a busy nebula background. Returns flat text.""" +def read_selected_quantity(cp): + """Return (units, ore) for the currently-selected rock from its floating info popup. + Two-pass: (1) locate the word 'Units' anywhere in the window via sparse OCR, then + (2) crop just that line, zoom + grayscale it, and OCR the digits cleanly — small text + over the nebula garbles in a full-window pass ('5b Units'), but reads fine zoomed in.""" import pytesseract + from PIL import Image # noqa: F401 (capture_window already returns a PIL image) tcmd = cp.get("ocr", "tesseract_cmd", fallback="").strip() if tcmd: pytesseract.pytesseract.tesseract_cmd = tcmd img = w.capture_window() if img is None: - return None - out = [] - for psm in (11, 6): # sparse first, then block fallback - try: - out.append(pytesseract.image_to_string(img, config=f"--psm {psm}")) - except Exception: - pass - return " ".join(out).replace("\n", " ") - - -def read_selected_quantity(cp): - """Return (units, ore) for the currently-selected rock from its floating info popup, - found anywhere in the EVE window. (None, None) if no rock is selected/visible.""" - flat = _ocr_window_text(cp) - if flat is None: return None, None - m = QUANTITY_RE.search(flat) - if not m: # "Quantity" garbled? take any "N Units" - for cand in UNITS_RE.finditer(flat): - v = int(re.sub(r"\D", "", cand.group(1)) or 0) - if 100 <= v <= 5_000_000: # plausible asteroid remaining - m = cand - break - if not m: - # leave a breadcrumb so we can see what OCR produced when it misses - try: - with open(os.path.join(w.HERE, "_rockocr.txt"), "w", encoding="utf-8") as fh: - fh.write(flat[:4000]) - except Exception: - pass + try: + d = pytesseract.image_to_data(img, config="--psm 11", + output_type=pytesseract.Output.DICT) + except Exception: return None, None - digits = re.sub(r"\D", "", m.group(1)) - if len(digits) < 2: - return None, None - # the rock's name sits just before its 'Quantity' line in the same popup block - # (e.g. "Omber Distance ... Quantity 47,765 Units") — match there, not the whole - # window (the hold's "Compressed Kernite" would otherwise win). - ore = match_ore(flat[max(0, m.start() - 180):m.start()].lower()) \ - or match_ore(flat.lower()) or "rock" - return int(digits), ore + n = len(d["text"]) + cands = [i for i in range(n) if d["text"][i].strip().lower().startswith("unit")] + dbg = [] + for i in cands: + uy, uh, ux = d["top"][i], d["height"][i], d["left"][i] + # crop the line just LEFT of "Units" — that's "Quantity " + box = img.crop((max(0, ux - 320), max(0, uy - 8), ux + 6, uy + uh + 8)) + z = box.convert("L").resize((box.width * 4, box.height * 4)) + line = pytesseract.image_to_string( + z, config="--psm 7 -c tessedit_char_whitelist=0123456789,QuantiyUns ") + dbg.append(repr(line.strip())) + mm = re.search(r"([\d,]{3,})", line) + if not mm: + continue + v = int(re.sub(r"\D", "", mm.group(1)) or 0) + if not (100 <= v <= 5_000_000): # plausible asteroid remaining + continue + # ore name = a token a little above the 'Units' line, same popup block + ore = "rock" + for j in range(n): + if d["text"][j].strip() and 0 < (uy - d["top"][j]) < 120 \ + and abs(d["left"][j] - (ux - 160)) < 260: + o = match_ore(d["text"][j].strip().lower()) + if o: + ore = o + break + return v, ore + try: + with open(os.path.join(w.HERE, "_rockocr.txt"), "w", encoding="utf-8") as fh: + fh.write(f"units-candidates={len(cands)} crops={dbg}\n") + except Exception: + pass + return None, None def save_rock_region(cp, l, t, wd, ht, mode="survey"):