Spaces:
Running on Zero
Running on Zero
Download slotread/tasks.py from infinitylogesh/GUI-Decisions: direct link, hf CLI and curl.
- Browser
- Download file 12.4 kB
-
https://huggingface.co/spaces/infinitylogesh/GUI-Decisions/resolve/main/slotread/tasks.py
- Command line
-
hf download hf://spaces/infinitylogesh/GUI-Decisions/slotread/tasks.py
-
curl -L -o tasks.py https://huggingface.co/spaces/infinitylogesh/GUI-Decisions/resolve/main/slotread/tasks.py
12.4 kB
| """Tasks: what the model sees and answers. A task gives its examples, its slot layout for an arm, | |
| the soft targets, and how a read turns back into a prediction and a score. | |
| agent computer use: action + point (+ swipe end, key, scroll), AGUVIS steps + Wave-UI clicks | |
| box an element's or object's box as centre + log size, Wave-UI elements + RefCOCO referents | |
| seg a referred object's mask as a centre + K ray lengths, RefCOCO | |
| The assistant turn is the slot layout, each slot ending in the placeholder: | |
| action: ___ x: ___ y: ___ x2: ___ y2: ___ key: ___ sdir: ___ samt: ___ | |
| and a slot's answer is the next-token distribution at the token before its placeholder. | |
| """ | |
| import math | |
| from . import labels as L | |
| from .data import aguvis, refcoco, waveui | |
| from .geometry import box_iou, mask_iou, poly_mask, radial_decode, radial_encode | |
| from .images import image_of, image_size | |
| from .numbers import LOG_MIN, number_targets, one_hot, read_number, slots_for, unwarp, warp | |
| def coarse_desc(arm, what): | |
| """How a number's slots are described in a system prompt.""" | |
| return f"the tens digit (00-99) and then the fine code of {what}" if arm.coarse else f"the code of {what}" | |
| class Task: | |
| name = "" | |
| numbers = {} # number -> "lin" | "log" | |
| def layout(self, arm): | |
| raise NotImplementedError | |
| def system(self, arm): | |
| raise NotImplementedError | |
| def user(self, ex): | |
| raise NotImplementedError | |
| def number_of(self, slot): | |
| return slot[:-1] if slot.endswith("c") and slot[:-1] in self.numbers else slot | |
| def messages(self, ex, arm, assistant=None): | |
| return [{"role": "system", "content": [{"type": "text", "text": self.system(arm)}]}, | |
| {"role": "user", "content": [{"type": "image", "image": image_of(ex)}, | |
| {"type": "text", "text": self.user(ex)}]}, | |
| {"role": "assistant", "content": [{"type": "text", "text": assistant if assistant is not None | |
| else " ".join(f"{s}: ___" for s in self.layout(arm))}]}] | |
| def labels(self, arm, tok): | |
| out = {} | |
| for s in self.layout(arm): | |
| n = self.number_of(s) | |
| out[s] = L.DIGITS if (n in self.numbers and s.endswith("c") and n != s) else ( | |
| arm.fine if n in self.numbers else self.categorical_labels(s, tok)) | |
| return out | |
| def categorical_labels(self, slot, tok): | |
| raise KeyError(slot) | |
| def config(self, arm, tok): | |
| """Everything a reader needs to rebuild this task's prompt and labels (saved in config.json).""" | |
| return dict(slots=self.layout(arm), system=self.system(arm), numbers=self.numbers, | |
| labels=self.labels(arm, tok)) | |
| class AgentTask(Task): | |
| """Computer use. Only the slots an action uses are trained on that example (MASK), so x2 is free | |
| to mean nothing on a click. Text arguments (what to type, the final answer) are not slots: the base | |
| model writes them.""" | |
| name = "agent" | |
| numbers = {"x": "lin", "y": "lin", "x2": "lin", "y2": "lin"} | |
| MASK = {"click": ["x", "y"], "select": ["x", "y"], "swipe": ["x", "y", "x2", "y2"], | |
| "scroll": ["sdir", "samt"], "press": ["key"]} | |
| def layout(self, arm): | |
| return (["action"] + slots_for("x", arm) + slots_for("y", arm) + slots_for("x2", arm) | |
| + slots_for("y2", arm) + ["key", "sdir", "samt"]) | |
| def system(self, arm): | |
| return ("You are a GUI agent. Given a screenshot, the task and the previous actions, answer the next step. " | |
| "action: " + ", ".join(aguvis.ACTIONS) + ". " | |
| f"For x, {coarse_desc(arm, 'the horizontal position of the point, from the left edge to the right edge')}; " | |
| f"for y, the same vertically from the top to the bottom. x2 and y2: the end point of a swipe, written " | |
| f"the same way. key: the key to press. sdir and samt: scroll direction and amount.") | |
| def user(self, ex): | |
| return ex["user"] | |
| def categorical_labels(self, slot, tok): | |
| return {"action": L.word_label_ids(tok, "action", aguvis.ACTIONS), | |
| "key": L.word_label_ids(tok, "key", aguvis.KEYS), | |
| "sdir": L.word_label_ids(tok, "sdir", aguvis.SDIR), "samt": L.DIGITS}[slot] | |
| def targets(self, ex, arm): | |
| a, g = ex["action"], ex["args"] | |
| tg = {"action": one_hot(len(aguvis.ACTIONS), aguvis.ACTIONS.index(a))} | |
| for n in self.MASK.get(a, []): | |
| if n in self.numbers: | |
| if g.get(n) is None: | |
| continue | |
| w = g.get("box" if n.startswith("x") else "boy", 0.02) # the target's width / height, if known | |
| tg.update(number_targets(n, g[n], 0.25 * w, arm)) | |
| elif n == "key": | |
| tg["key"] = one_hot(len(aguvis.KEYS), aguvis.KEYS.index(g.get("key", "other"))) | |
| elif n == "sdir" and g.get("sdir") in aguvis.SDIR: | |
| tg["sdir"] = one_hot(len(aguvis.SDIR), aguvis.SDIR.index(g["sdir"])) | |
| elif n == "samt" and g.get("samt") is not None: | |
| tg["samt"] = one_hot(10, int(g["samt"])) | |
| return tg | |
| def decode(self, lp, arm, how="default"): | |
| out = dict(action=aguvis.ACTIONS[int(lp["action"].argmax())], | |
| x=read_number(lp, "x", arm, how), y=read_number(lp, "y", arm, how)) | |
| if "x2" in lp: | |
| out.update(x2=read_number(lp, "x2", arm, how), y2=read_number(lp, "y2", arm, how)) | |
| return out | |
| def score(self, pred, ex, thresh=0.14): | |
| """A step is right when the action is, and (for a click, select or swipe) the point is within | |
| 0.14 of the target on each axis.""" | |
| ok_a = pred["action"] == ex["action"] | |
| ok_p = True | |
| if ex["action"] in ("click", "select", "swipe"): | |
| ok_p = abs(pred["x"] - ex["args"]["x"]) <= thresh and abs(pred["y"] - ex["args"]["y"]) <= thresh | |
| return dict(action=float(ok_a), point=float(ok_p) if ex["action"] in ("click", "select", "swipe") else None, | |
| step=float(ok_a and ok_p)) | |
| def examples(self, n_agent, n_click, n_val): | |
| """(train, val): AGUVIS steps + Wave-UI clicks; val is the first n_val steps of the same pool | |
| (held out from this run; use aguvis.test_steps() to compare runs).""" | |
| pool = aguvis.steps(n_agent + n_val) | |
| val, train = pool[:n_val], pool[n_val:] | |
| return train + (waveui.clicks(n_click) if n_click else []), val | |
| class BoxTask(Task): | |
| """An element's or object's box as centre + size; sizes log-warped.""" | |
| name = "box" | |
| numbers = {"cx": "lin", "cy": "lin", "w": "log", "h": "log"} | |
| def layout(self, arm): | |
| return slots_for("cx", arm) + slots_for("cy", arm) + slots_for("w", arm) + slots_for("h", arm) | |
| def system(self, arm): | |
| return ("You locate an element or object in an image and give its bounding box as a centre and a size. " | |
| f"For cx, {coarse_desc(arm, 'the horizontal position of the centre, from the left edge to the right edge')}; " | |
| "for cy, the same vertically from the top to the bottom. " | |
| f"For w, {coarse_desc(arm, 'the box width on a scale from tiny to the whole image')}; " | |
| "for h, the same for the height.") | |
| def user(self, ex): | |
| return f"Element: {ex['instruction']}" | |
| def norm_box(self, ex): | |
| W, H = image_size(ex) | |
| x1, y1, x2, y2 = ex["box"] | |
| return [x1 / W, y1 / H, x2 / W, y2 / H] if not ex.get("norm") else list(ex["box"]) | |
| def targets(self, ex, arm): | |
| x1, y1, x2, y2 = self.norm_box(ex) | |
| w, h = max(x2 - x1, LOG_MIN), max(y2 - y1, LOG_MIN) | |
| tg = {} | |
| tg.update(number_targets("cx", (x1 + x2) / 2, 0.12 * w, arm)) | |
| tg.update(number_targets("cy", (y1 + y2) / 2, 0.12 * h, arm)) | |
| tg.update(number_targets("w", warp(w, "log"), 0.02, arm)) | |
| tg.update(number_targets("h", warp(h, "log"), 0.02, arm)) | |
| return tg | |
| def decode(self, lp, arm, how="default"): | |
| cx, cy = read_number(lp, "cx", arm, how), read_number(lp, "cy", arm, how) | |
| w, h = unwarp(read_number(lp, "w", arm, how), "log"), unwarp(read_number(lp, "h", arm, how), "log") | |
| return [max(0.0, cx - w / 2), max(0.0, cy - h / 2), min(1.0, cx + w / 2), min(1.0, cy + h / 2)] | |
| def score(self, pred, ex): | |
| b = self.norm_box(ex) | |
| i = box_iou(pred, b) | |
| cx, cy = (pred[0] + pred[2]) / 2, (pred[1] + pred[3]) / 2 | |
| return dict(iou=i, iou50=float(i >= 0.5), iou75=float(i >= 0.75), | |
| center_in=float(b[0] <= cx <= b[2] and b[1] <= cy <= b[3])) | |
| def examples(self, n_train, n_val, n_photo=0, seed=0): | |
| """(train, val): Wave-UI element boxes, plus n_photo RefCOCO referent boxes (general objects). | |
| val: Wave-UI validation boxes, and val_photo: RefCOCO val referents (None without photos).""" | |
| train = waveui.boxes(n_train) | |
| val = waveui.val_boxes(n_val) | |
| val_photo = None | |
| if n_photo: | |
| train += refcoco.photo_boxes(n_photo, "train", seed + 100) | |
| val_photo = refcoco.photo_boxes(n_val, "validation", seed + 100) | |
| return train, val, val_photo | |
| class SegTask(Task): | |
| """A referred object's mask as a centre + K ray lengths; rays are fractions of the image diagonal, | |
| log-warped.""" | |
| name = "seg" | |
| def __init__(self, K=24): | |
| self.K = K | |
| self.numbers = {"cx": "lin", "cy": "lin", **{f"r{k}": "log" for k in range(K)}} | |
| def layout(self, arm): | |
| out = slots_for("cx", arm) + slots_for("cy", arm) | |
| for k in range(self.K): | |
| out += slots_for(f"r{k}", arm) | |
| return out | |
| def system(self, arm): | |
| K = self.K | |
| return ("You outline an object in an image. cx and cy: a point well inside the object; " | |
| f"r0 to r{K - 1}: how far the object reaches from that point along {K} directions, r0 pointing right " | |
| f"and the directions turning clockwise in equal steps. For cx, " | |
| f"{coarse_desc(arm, 'the horizontal position of the point, from the left edge to the right edge')}; " | |
| "for cy, the same vertically from the top to the bottom. For each r, " | |
| f"{coarse_desc(arm, 'the distance on a scale from tiny to the whole image diagonal')}.") | |
| def user(self, ex): | |
| return f"Object: {ex['sentence']}" | |
| def encoded(self, ex): | |
| """(cx, cy, d) in pixels, computed once per example.""" | |
| if "radial" not in ex: | |
| W, H = ex["size"] | |
| ex["radial"] = radial_encode(poly_mask(ex["polys"], W, H), self.K) | |
| return ex["radial"] | |
| def targets(self, ex, arm): | |
| W, H = ex["size"] | |
| cx, cy, d = self.encoded(ex) | |
| bx1, by1, bx2, by2 = ex["bbox"] | |
| diag = math.hypot(W, H) | |
| tg = {} | |
| tg.update(number_targets("cx", cx / W, 0.12 * (bx2 - bx1) / W, arm)) | |
| tg.update(number_targets("cy", cy / H, 0.12 * (by2 - by1) / H, arm)) | |
| for k in range(self.K): | |
| tg.update(number_targets(f"r{k}", warp(d[k] / diag, "log"), 0.03, arm)) | |
| return tg | |
| def decode(self, lp, arm, how="default"): | |
| cx, cy = read_number(lp, "cx", arm, how), read_number(lp, "cy", arm, how) | |
| r = [unwarp(read_number(lp, f"r{k}", arm, how), "log") for k in range(self.K)] | |
| return dict(cx=cx, cy=cy, r=r) | |
| def score(self, pred, ex): | |
| W, H = ex["size"] | |
| diag = math.hypot(W, H) | |
| m, pts = radial_decode(pred["cx"] * W, pred["cy"] * H, [r * diag for r in pred["r"]], W, H) | |
| gt = poly_mask(ex["polys"], W, H) | |
| iou, inter, union = mask_iou(m, gt) | |
| xs, ys = [p[0] for p in pts], [p[1] for p in pts] | |
| pb = [max(0, min(xs)), max(0, min(ys)), min(W, max(xs)), min(H, max(ys))] | |
| rec = box_iou([pb[0] / W, pb[1] / H, pb[2] / W, pb[3] / H], | |
| [ex["bbox"][0] / W, ex["bbox"][1] / H, ex["bbox"][2] / W, ex["bbox"][3] / H]) | |
| return dict(iou=float(iou), inter=inter, union=union, rec50=float(rec >= 0.5)) | |
| def examples(self, n_train, n_val, seed=0): | |
| train = refcoco.examples(refcoco.rows("train"), n_train, seed) | |
| val = refcoco.examples(refcoco.rows("validation"), n_val, seed + 1, one_sentence=False) | |
| return train, val | |
| TASKS = {"agent": AgentTask, "box": BoxTask, "seg": SegTask} | |