GUI-Decisions / slotread /images.py
infinitylogesh's picture
GUI-Decisions Space
d0e7c02 verified
Raw History Blame Contribute Delete
2.39 kB
"""Images: opening examples, capping their size, and encoding screenshots for the server."""
import base64, io, math, os
from PIL import Image
# max pixels per image for training and serving (0: as given). GUI-Owl uses 1,048,576 so training and
# serving see the same image; Gemma 4 resizes to its own fixed token budget either way.
IMAGE_CAP = int(os.environ.get("IMAGE_CAP", 0))
def cap_image(im, cap=None, factor=32):
"""Downscale to at most `cap` pixels, sides multiples of `factor` (Qwen3-VL: patch 16 x merge 2),
so the processor keeps the size."""
cap = IMAGE_CAP if cap is None else cap
W, H = im.size
if not cap or W * H <= cap:
return im
s = math.sqrt(cap / (W * H))
w, h = max(factor, int(W * s) // factor * factor), max(factor, int(H * s) // factor * factor)
return im.resize((w, h), Image.BICUBIC)
def image_of(ex):
"""An example's image as RGB; a path is opened only when needed (keeps thousands of examples cheap)."""
im = ex["image"]
if isinstance(im, str):
im = Image.open(im)
return cap_image(im.convert("RGB") if im.mode != "RGB" else im)
def image_size(ex):
if "size" in ex:
return ex["size"]
im = ex["image"]
if isinstance(im, str):
with Image.open(im) as f:
return f.size
return im.size
# How screenshots travel to the server. The fast path, and the default for serving: JPEG at the model's
# input size (Gemma 4's encoder sees about 896 x 896 at its default budget): ~8 ms to build instead of
# ~118 ms for a full-size PNG, and cheaper for the server to decode. IMG_FMT=png IMG_MAX_PX=0 sends the
# lossless full-size image instead.
IMG_FMT = os.environ.get("IMG_FMT", "jpeg")
IMG_MAX_PX = int(os.environ.get("IMG_MAX_PX", 896 * 896))
def data_url(im, fmt=None, max_px=None):
fmt = fmt or IMG_FMT
max_px = IMG_MAX_PX if max_px is None else max_px
im = im.convert("RGB")
if max_px and im.width * im.height > max_px:
s = (max_px / (im.width * im.height)) ** 0.5
im = im.resize((max(1, round(im.width * s)), max(1, round(im.height * s))), Image.BICUBIC)
b = io.BytesIO()
if fmt == "jpeg":
im.save(b, "JPEG", quality=90)
return "data:image/jpeg;base64," + base64.b64encode(b.getvalue()).decode()
im.save(b, "PNG")
return "data:image/png;base64," + base64.b64encode(b.getvalue()).decode()