Instructions to use yuta24/ui-test-vision-2b-alpha with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- PEFT
How to use yuta24/ui-test-vision-2b-alpha with PEFT:
Task type is invalid.
- MLX
How to use yuta24/ui-test-vision-2b-alpha with MLX:
# Make sure mlx-vlm is installed # pip install --upgrade mlx-vlm from mlx_vlm import load, generate from mlx_vlm.prompt_utils import apply_chat_template from mlx_vlm.utils import load_config # Load the model model, processor = load("yuta24/ui-test-vision-2b-alpha") config = load_config("yuta24/ui-test-vision-2b-alpha") # Prepare input image = ["http://images.cocodataset.org/val2017/000000039769.jpg"] prompt = "Describe this image." # Apply chat template formatted_prompt = apply_chat_template( processor, config, prompt, num_images=1 ) # Generate output output = generate(model, processor, formatted_prompt, image) print(output) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- LM Studio
- Atomic Chat
Download code/make_synthetic.py from yuta24/ui-test-vision-2b-alpha: direct link, hf CLI and curl.
- Browser
- Download file 12.5 kB
-
https://huggingface.co/yuta24/ui-test-vision-2b-alpha/resolve/main/code/make_synthetic.py
- Command line
-
hf download hf://yuta24/ui-test-vision-2b-alpha/code/make_synthetic.py
-
curl -L -o make_synthetic.py https://huggingface.co/yuta24/ui-test-vision-2b-alpha/resolve/main/code/make_synthetic.py
12.5 kB
| """Generate a tiny synthetic dataset of dialog-like iPhone screenshots (PIL) for smoke tests. | |
| !! The "teacher" distributions here are DUMMY (derived from the gold label + noise), NOT Clef outputs. !! | |
| Replace them later with real teacher labels (scripts/label_workers_ai.py or label_local_clef.py). | |
| --box adds Jev-style box questions (`primary_button_box` + presence `primary_button_box_visible`) whose gold is the | |
| exact drawn button rectangle (ground truth, not the teacher). Images are identical with and without --box.""" | |
| import argparse, json, random | |
| from pathlib import Path | |
| from PIL import Image, ImageDraw, ImageFont | |
| FONT_CANDIDATES = ["C:/Windows/Fonts/YuGothM.ttc", "C:/Windows/Fonts/meiryo.ttc", "C:/Windows/Fonts/msgothic.ttc", | |
| "/System/Library/Fonts/ヒラギノ角ゴシック W3.ttc", "/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc", | |
| "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"] | |
| def font(size): | |
| for p in FONT_CANDIDATES: | |
| if Path(p).exists(): | |
| try: | |
| return ImageFont.truetype(p, size) | |
| except OSError: | |
| pass | |
| return ImageFont.load_default(size=size) | |
| TXT = { | |
| "ja": {"err_title": ["エラー", "通信エラー", "保存に失敗しました"], "err_msg": ["ネットワークに接続できません。", "もう一度お試しください。", "サーバーが応答しません。"], | |
| "ok": "OK", "retry": "再試行", "cancel": "キャンセル", "delete": "削除", "confirm_title": ["削除しますか?", "変更を破棄しますか?"], | |
| "confirm_msg": ["この操作は取り消せません。"], "loading": ["読み込み中…", "お待ちください"], "login": "ログイン", | |
| "email": "メールアドレス", "password": "パスワード", "home": ["ホーム", "最近の項目"], "row": ["メモ", "写真", "設定", "お気に入り", "ダウンロード"]}, | |
| "en": {"err_title": ["Error", "Network Error", "Save Failed"], "err_msg": ["Unable to connect to the network.", "Please try again.", "The server is not responding."], | |
| "ok": "OK", "retry": "Retry", "cancel": "Cancel", "delete": "Delete", "confirm_title": ["Delete this item?", "Discard changes?"], | |
| "confirm_msg": ["This action cannot be undone."], "loading": ["Loading…", "Please wait"], "login": "Log In", | |
| "email": "Email", "password": "Password", "home": ["Home", "Recents"], "row": ["Notes", "Photos", "Settings", "Favorites", "Downloads"]}, | |
| } | |
| SCREENS = ["error_dialog", "confirm_dialog", "loading", "login", "home"] | |
| BUTTONS = ["ok", "retry", "delete", "login", "none"] | |
| def questions(lang): | |
| ja = lang == "ja" | |
| return { | |
| "screen": {"type": "choice", | |
| "instructions": "現在の画面状態はどれか?" if ja else "What is the current screen state?", | |
| "criteria": {"error_dialog": "An error alert/dialog is shown / エラーダイアログが表示されている", | |
| "confirm_dialog": "A confirmation dialog asks the user to confirm / 確認ダイアログ", | |
| "loading": "A loading spinner or progress indicator / 読み込み中", | |
| "login": "A login form with email and password / ログインフォーム", | |
| "home": "A normal home/list screen without dialogs / 通常のホーム・一覧画面"}}, | |
| "has_error": {"type": "noul", "instructions": "エラーダイアログが表示されているか?" if ja else "Is an error dialog visible?"}, | |
| "primary_button": {"type": "choice", | |
| "instructions": "最も目立つ操作ボタンはどれか?" if ja else "Which is the primary action button?", | |
| "criteria": {"ok": "OK button", "retry": "Retry / 再試行", "delete": "Delete / 削除", | |
| "login": "Log In / ログイン", "none": "No primary button"}}, | |
| } | |
| def box_questions(lang): | |
| ja = lang == "ja" | |
| return { | |
| "primary_button_box": {"type": "box", | |
| "instructions": "`screen` 内の最も目立つ操作ボタン" if ja else "the primary action button in `screen`"}, | |
| "primary_button_box_visible": {"type": "noul", | |
| "instructions": "`screen` に主要な操作ボタンが見えているか?" if ja else "Is a primary action button visible in `screen`?"}, | |
| } | |
| def box_gold(rect, W, H): | |
| """Pixel rect (x0, y0, x1, y1) -> Jev box gold (fractions of height/width). None -> not present.""" | |
| if rect is None: | |
| return {"present": False} | |
| x0, y0, x1, y1 = rect | |
| return {"top": round(y0 / H, 4), "bottom": round(y1 / H, 4), "left": round(x0 / W, 4), "right": round(x1 / W, 4), "present": True} | |
| def draw_status_bar(d, W, dark): | |
| fg = (255, 255, 255) if dark else (0, 0, 0) | |
| d.text((90, 50), f"{random.randint(9,23)}:{random.randint(0,59):02d}", fill=fg, font=font(44)) | |
| d.rectangle([W - 190, 60, W - 100, 95], outline=fg, width=4) | |
| def dialog(d, W, H, title, msg, buttons, dark, primary_color): | |
| bw, bh = int(W * 0.72), int(H * 0.2) | |
| x0, y0 = (W - bw) // 2, int(H * random.uniform(0.33, 0.45)) | |
| bg = (44, 44, 46) if dark else (242, 242, 247) | |
| fg = (255, 255, 255) if dark else (0, 0, 0) | |
| d.rounded_rectangle([x0, y0, x0 + bw, y0 + bh], radius=40, fill=bg) | |
| d.text((x0 + bw // 2, y0 + 80), title, fill=fg, font=font(52), anchor="mm") | |
| d.text((x0 + bw // 2, y0 + 170), msg, fill=fg, font=font(38), anchor="mm") | |
| d.line([x0, y0 + bh - 140, x0 + bw, y0 + bh - 140], fill=(120, 120, 128), width=2) | |
| n = len(buttons); primary_rect = None | |
| for i, (label, is_primary) in enumerate(buttons): | |
| if is_primary: # the tappable cell of the primary button | |
| primary_rect = (x0 + bw * i // n, y0 + bh - 140, x0 + bw * (i + 1) // n, y0 + bh) | |
| cx = x0 + bw * (2 * i + 1) // (2 * n) | |
| d.text((cx, y0 + bh - 70), label, fill=primary_color if is_primary else (10, 132, 255), font=font(46 if is_primary else 42), anchor="mm") | |
| if i: | |
| d.line([x0 + bw * i // n, y0 + bh - 140, x0 + bw * i // n, y0 + bh], fill=(120, 120, 128), width=2) | |
| return primary_rect | |
| def render(screen, lang, W, H): | |
| t = TXT[lang]; dark = random.random() < 0.3 | |
| img = Image.new("RGB", (W, H), (0, 0, 0) if dark else (255, 255, 255)); d = ImageDraw.Draw(img) | |
| fg = (255, 255, 255) if dark else (0, 0, 0) | |
| draw_status_bar(d, W, dark) | |
| d.text((80, 200), random.choice(t["home"]), fill=fg, font=font(96)) | |
| for i in range(random.randint(4, 9)): # background list | |
| y = 380 + i * 170 | |
| d.text((90, y), random.choice(t["row"]) + f" {random.randint(1, 99)}", fill=fg, font=font(50)) | |
| d.line([90, y + 110, W, y + 110], fill=(200, 200, 205) if not dark else (60, 60, 64), width=2) | |
| primary = "none"; rect = None | |
| if screen in ("error_dialog", "confirm_dialog", "loading"): | |
| ov = Image.new("RGBA", (W, H), (0, 0, 0, random.randint(80, 140))) | |
| img = Image.alpha_composite(img.convert("RGBA"), ov).convert("RGB"); d = ImageDraw.Draw(img) | |
| if screen == "error_dialog": | |
| if random.random() < 0.5: | |
| rect = dialog(d, W, H, random.choice(t["err_title"]), random.choice(t["err_msg"]), [(t["ok"], True)], dark, (10, 132, 255)); primary = "ok" | |
| else: | |
| rect = dialog(d, W, H, random.choice(t["err_title"]), random.choice(t["err_msg"]), [(t["cancel"], False), (t["retry"], True)], dark, (10, 132, 255)); primary = "retry" | |
| elif screen == "confirm_dialog": | |
| rect = dialog(d, W, H, random.choice(t["confirm_title"]), random.choice(t["confirm_msg"]), [(t["cancel"], False), (t["delete"], True)], dark, (255, 59, 48)); primary = "delete" | |
| elif screen == "loading": | |
| cx, cy, r = W // 2, H // 2, 90 | |
| d.rounded_rectangle([cx - 220, cy - 220, cx + 220, cy + 220], radius=40, fill=(60, 60, 64)) | |
| for k in range(12): | |
| d.arc([cx - r, cy - r - 40, cx + r, cy + r - 40], k * 30, k * 30 + 18, fill=(255, 255, 255), width=14) | |
| d.text((cx, cy + 150), random.choice(t["loading"]), fill=(255, 255, 255), font=font(44), anchor="mm") | |
| elif screen == "login": | |
| img = Image.new("RGB", (W, H), (0, 0, 0) if dark else (255, 255, 255)); d = ImageDraw.Draw(img); draw_status_bar(d, W, dark) | |
| d.text((W // 2, 500), t["login"], fill=fg, font=font(96), anchor="mm") | |
| for i, ph in enumerate([t["email"], t["password"]]): | |
| y = 800 + i * 220 | |
| d.rounded_rectangle([100, y, W - 100, y + 150], radius=24, outline=(160, 160, 166), width=4) | |
| d.text((140, y + 75), ph, fill=(140, 140, 146), font=font(48), anchor="lm") | |
| d.rounded_rectangle([100, 1350, W - 100, 1500], radius=30, fill=(10, 132, 255)) | |
| d.text((W // 2, 1425), t["login"], fill=(255, 255, 255), font=font(54), anchor="mm"); primary = "login" | |
| rect = (100, 1350, W - 100, 1500) | |
| return img, {"screen": screen, "has_error": "true" if screen == "error_dialog" else "false", "primary_button": primary}, rect | |
| def dummy_teacher(qs, gold, rng=random): | |
| """DUMMY: gold-centred noisy distribution. Not a real teacher.""" | |
| out = {} | |
| for qid, q in qs.items(): | |
| if q["type"] == "box": # box gold is ground truth; Clef has no box type | |
| continue | |
| opts = ["true", "false"] if q["type"] == "noul" else list(q["criteria"]) | |
| p_gold = rng.uniform(0.55, 0.95) | |
| if rng.random() < 0.08: # occasional "teacher mistake" to mimic label noise | |
| p_gold = rng.uniform(0.05, 0.3) | |
| rest = [rng.random() for _ in opts[1:]]; s = sum(rest) | |
| others = [o for o in opts if o != gold[qid]] | |
| dist = {gold[qid]: p_gold} | |
| for o, r in zip(others, rest): | |
| dist[o] = (1 - p_gold) * r / s | |
| out[qid] = {o: round(dist[o], 4) for o in opts} | |
| return out | |
| def main(): | |
| ap = argparse.ArgumentParser() | |
| ap.add_argument("--out", default="data/synthetic") | |
| ap.add_argument("--n-train", type=int, default=60) | |
| ap.add_argument("--n-eval", type=int, default=20) | |
| ap.add_argument("--width", type=int, default=1179) | |
| ap.add_argument("--height", type=int, default=2556) | |
| ap.add_argument("--seed", type=int, default=0) | |
| ap.add_argument("--box", action="store_true", help="also add Jev-style box questions (+ presence) with ground-truth gold") | |
| args = ap.parse_args(); random.seed(args.seed); box_rng = random.Random(args.seed + 1) | |
| out = Path(args.out); (out / "images").mkdir(parents=True, exist_ok=True) | |
| for split, n in (("train", args.n_train), ("eval", args.n_eval)): | |
| with open(out / f"{split}.jsonl", "w", encoding="utf-8") as f: | |
| for i in range(n): | |
| screen = SCREENS[i % len(SCREENS)]; lang = random.choice(["ja", "en"]) | |
| img, gold, rect = render(screen, lang, args.width, args.height) | |
| rid = f"syn-{split}-{i:04d}"; rel = f"images/{rid}.png"; img.save(out / rel) | |
| qs = questions(lang); teacher = dummy_teacher(qs, gold) | |
| if args.box: # separate RNG: images/labels of the base questions stay identical | |
| bq = box_questions(lang) | |
| gold["primary_button_box"] = box_gold(rect, args.width, args.height) | |
| gold["primary_button_box_visible"] = "true" if rect else "false" | |
| teacher.update(dummy_teacher(bq, gold, box_rng)); qs.update(bq) | |
| rec = {"id": rid, "images": [rel], | |
| "state": {"app": "SyntheticApp", "locale": "ja-JP" if lang == "ja" else "en-US", "device": "iPhone (synthetic)"}, | |
| "questions": qs, "teacher": teacher, "gold": gold, | |
| "meta": {"source": "synthetic_pil", "teacher_model": "DUMMY(gold+noise)", "teacher_is_dummy": True, | |
| "split_group": "SyntheticApp", "lang": lang}} | |
| f.write(json.dumps(rec, ensure_ascii=False) + "\n") | |
| print(f"wrote {args.n_train}+{args.n_eval} records to {out.resolve()}") | |
| if __name__ == "__main__": | |
| main() | |