pxg-tiny / cli.py
Tarul's picture
Upload cli.py with huggingface_hub
181c7ca verified
Raw
History Blame Contribute Delete
4.9 kB
#!/usr/bin/env python3
"""PXG-Tiny CLI (offline, NumPy-only).
python3 cli.py "a golden sword" -o sword.png
python3 cli.py "a golden sword" --variations 4 --sheet row.png
python3 cli.py --ask "can you draw a photo of a cat"
python3 cli.py --sheet-from-prompts prompts.txt --outdir sprites/
"""
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[0]
sys.path.insert(0, str(ROOT / "src"))
from PIL import Image # noqa: E402
from pxg_tiny import config as C # noqa: E402
from pxg_tiny.pipeline import PXGPipeline # noqa: E402
def sheet(grids, path, scale=8):
n = len(grids)
im = Image.new("RGBA", (n * (16 * scale + 4) + 4, 16 * scale + 8),
(24, 24, 30, 255))
from pxg_tiny.render import grid_to_rgba
for i, g in enumerate(grids):
tile = Image.fromarray(grid_to_rgba(g), "RGBA").resize(
(16 * scale, 16 * scale), Image.NEAREST)
im.paste(tile, (4 + i * (16 * scale + 4), 4), tile)
im.save(path)
def main():
ap = argparse.ArgumentParser(prog="pxg-tiny")
ap.add_argument("prompt", nargs="?")
ap.add_argument("-o", "--out", default=None)
ap.add_argument("--seed", type=int, default=0)
ap.add_argument("--temperature", type=float, default=None)
ap.add_argument("--top-k", type=int, default=None)
ap.add_argument("--variations", type=int, default=1)
ap.add_argument("--sheet", action="store_true",
help="save a variations sheet next to the png")
ap.add_argument("--sheet-from-prompts", default=None,
help="path to a text file with one prompt per line; "
"renders every prompt into --outdir")
ap.add_argument("--outdir", default=None,
help="output directory for --sheet-from-prompts")
ap.add_argument("--ask", action="store_true",
help="ask-first mode: clarify/refuse instead of drawing")
ap.add_argument("--bundle", default=str(ROOT / "weights"))
ap.add_argument("--no-quality", action="store_true",
help="disable the grounding auto-retry gate")
args = ap.parse_args()
if args.sheet_from_prompts:
outdir = Path(args.outdir or "pxg_batch_out")
outdir.mkdir(parents=True, exist_ok=True)
lines = [l.strip() for l in Path(args.sheet_from_prompts)
.read_text().splitlines() if l.strip()]
pipe = PXGPipeline(args.bundle)
grids = []
results = []
for i, p in enumerate(lines):
g, m = pipe.generate_pixels(p, seed=args.seed,
enforce_quality=not args.no_quality,
temperature=args.temperature,
top_k=args.top_k)
results.append({"prompt": p, "gate": m.get("gate")})
if g is None:
print(json.dumps({"i": i, "prompt": p, **m}, indent=2))
continue
from pxg_tiny.render import save_png
f = outdir / f"{i+1:02d}_{p.replace(' ', '_')[:24]}.png"
save_png(g, f, scale=8)
grids.append(g)
results[-1]["file"] = str(f)
if grids:
sheet(grids, outdir / "sheet.png")
print(json.dumps({"n_prompts": len(lines), "n_rendered": len(grids),
"outdir": str(outdir), "results": results}, indent=2))
return
if not args.prompt:
ap.error("give me a prompt, e.g. pxg-tiny \"a gold sword\"")
pipe = PXGPipeline(args.bundle)
if args.ask:
label, msg = pipe.should_ask(args.prompt)
print(json.dumps({"label": label, "message": msg}, indent=2))
return
out = Path(args.out) if args.out else Path(
args.prompt.replace(" ", "_")[:24].replace("/", "_") + ".png")
out.parent.mkdir(parents=True, exist_ok=True)
grids, metas = [], []
if args.variations > 1:
res = pipe.variations(args.prompt, k=args.variations,
start_seed=args.seed,
enforce_quality=not args.no_quality,
temperature=args.temperature, top_k=args.top_k)
grids = [g for g, _ in res if g is not None]
metas = [m for _, m in res]
else:
g, m = pipe.generate_pixels(args.prompt, seed=args.seed,
temperature=args.temperature,
top_k=args.top_k,
enforce_quality=not args.no_quality)
if g is None:
print(json.dumps(m, indent=2))
return
grids, metas = [g], [m]
sheet(grids, out)
print(json.dumps({"prompt": args.prompt, "out": str(out),
"n": len(grids), "meta": metas[0]}, indent=2))
if __name__ == "__main__":
main()