Test files and initial data gathering completed

This commit is contained in:
2026-08-10 21:33:00 -06:00
commit 2181655801
28 changed files with 510 additions and 0 deletions
+127
View File
@@ -0,0 +1,127 @@
"""Lightweight image analysis for live capture feedback.
All stats are computed on a small downsampled copy so the demo loop stays
fast at full 1080p capture.
"""
from collections import Counter
from PIL import Image
ANALYSIS_SIZE = (320, 180)
_TOP_COLORS = 5
# Harvesting is detected from the single pixel at this window coordinate.
# Harvesting shows a light tan (~#f2c285, luma ~201); idle shows dark brown
# (~#664c33, luma ~81).
HARVEST_PIXEL = (1710, 1068)
HARVEST_LUMA_THRESHOLD = 140
# The small numeric readout just before each bar at the bottom of the panel.
# Regions are (x0, y0, x1, y1) in captured-window pixel coordinates. y0 starts
# below the panel's top border line (y=1032) which would confuse OCR.
NUMBER_REGIONS = {
"mana": (0, 1033, 62, 1048),
"food": (195, 1033, 226, 1048),
"health": (355, 1033, 389, 1048),
"inventory": (520, 1033, 552, 1048),
"action_points": (610, 1033, 714, 1048),
}
_OCR_SCALE = 3
try:
import pytesseract
except ImportError:
pytesseract = None
def _luma(pixel):
r, g, b = pixel
return round(0.299 * r + 0.587 * g + 0.114 * b)
def load_image(path):
"""Load a PNG as RGB; return None if the file is unreadable/corrupt."""
try:
return Image.open(path).convert("RGB")
except OSError:
return None
def is_harvesting(image):
"""Return whether the player is harvesting, based on the harvest pixel."""
rgb = image.convert("RGB")
color = rgb.getpixel(HARVEST_PIXEL)
lum = _luma(color)
return {
"is_harvesting": lum >= HARVEST_LUMA_THRESHOLD,
"color": "#%02x%02x%02x" % color,
"luma": lum,
}
def _ocr_number(image, region):
"""OCR a single number region; return int or None if it can't be read."""
if pytesseract is None:
return None
crop = image.convert("L").crop(region)
crop = crop.resize((crop.width * _OCR_SCALE, crop.height * _OCR_SCALE))
crop = crop.point(lambda p: 0 if p < 140 else 255)
try:
text = pytesseract.image_to_string(
crop,
config="--psm 7 -c tessedit_char_whitelist=0123456789",
)
except Exception:
return None
digits = "".join(ch for ch in text if ch.isdigit())
return int(digits) if digits else None
def read_bar_values(image):
"""Return the numeric readout before each bar, or None if unreadable."""
rgb = image.convert("RGB")
return {
name: _ocr_number(rgb, region)
for name, region in NUMBER_REGIONS.items()
}
def _pixels(image):
px = image.load()
w, h = image.size
return [px[x, y] for y in range(h) for x in range(w)]
def summarize(image):
"""Return size, mean RGB, and dominant colors of a captured frame."""
small = image.convert("RGB").resize(ANALYSIS_SIZE)
pixels = _pixels(small)
n = len(pixels)
total = [0, 0, 0]
for r, g, b in pixels:
total[0] += r
total[1] += g
total[2] += b
mean = tuple(round(c / n) for c in total)
top = Counter(pixels).most_common(_TOP_COLORS)
dominant = [
{"hex": "#%02x%02x%02x" % color, "fraction": round(count / n, 3)}
for color, count in top
]
return {"size": (image.width, image.height), "mean_rgb": mean, "dominant": dominant}
def difference(a, b):
"""Sum of absolute channel differences between two same-size RGB images."""
if a.size != b.size:
return None
pa, pb = a.load(), b.load()
w, h = a.size
diff = 0
for y in range(h):
for x in range(w):
ca, cb = pa[x, y], pb[x, y]
diff += abs(ca[0] - cb[0]) + abs(ca[1] - cb[1]) + abs(ca[2] - cb[2])
return diff