Spaces:
Runtime error
Runtime error
| """ | |
| NumZoo training dataset generator. | |
| Generates LoRA training images matching the NumZoo aesthetic using Qwen-Image | |
| via the HuggingFace Inference API (fal-ai provider, billed to your HF Pro credits). | |
| (Qwen-Image beat FLUX.1-dev on multi-animal accuracy; FLUX.2-dev is edit-only.) | |
| Alignment with the live app is guaranteed by construction: | |
| - The "A cute {animals} {places}" prefix is built with image_generator.build_subject | |
| (the SAME function the app uses), from the app's exact ANIMAL_MAP / PLACE_MAP. | |
| - The NUMZOO_STYLE suffix is imported from image_generator. | |
| - Scenes deliberately mix 1β3 animals and 1β3 places, exactly like the app does | |
| when the player selects multiple emojis. | |
| So every caption looks like a real app prompt, plus a rich scene detail clause. | |
| Output: training/image_001.jpg + training/image_001.txt (caption) | |
| Requirements: | |
| ~/miniforge3/bin/pip install huggingface_hub pillow python-dotenv | |
| Setup (HF Pro β just your existing token): | |
| 1. Get an HF token (fine-grained, "Make calls to Inference Providers" permission) | |
| https://huggingface.co/settings/tokens/new?ownUserPermissions=inference.serverless.write&tokenType=fineGrained | |
| 2. Add to .env: HF_TOKEN=hf_... | |
| Usage: | |
| ~/miniforge3/bin/python3 scripts/generate_dataset.py # all scenes | |
| ~/miniforge3/bin/python3 scripts/generate_dataset.py --count 5 # first 5 (test run) | |
| ~/miniforge3/bin/python3 scripts/generate_dataset.py --start 20 # resume from #20 | |
| ~/miniforge3/bin/python3 scripts/generate_dataset.py --dry-run # preview prompts | |
| """ | |
| import os | |
| import sys | |
| import argparse | |
| import time | |
| from pathlib import Path | |
| # Load .env from project root (GEMINI_API_KEY etc.) | |
| try: | |
| from dotenv import load_dotenv | |
| load_dotenv(Path(__file__).parent.parent / ".env") | |
| except ImportError: | |
| pass # dotenv optional β can also export GEMINI_API_KEY manually | |
| # Reuse the app's exact prompt builder + vocabulary so training captions and live | |
| # prompts share the same structure ("A cute {animals} {places}") and emoji mappings. | |
| sys.path.insert(0, str(Path(__file__).parent.parent)) | |
| from image_generator import ( # noqa: E402 | |
| build_subject, | |
| NUMZOO_STYLE as STYLE, | |
| ANIMAL_MAP, | |
| PLACE_MAP, | |
| ) | |
| # --------------------------------------------------------------------------- | |
| # Scenes: (animal emojis, place emojis, scene-detail clause) | |
| # Animals/places use the app's exact emoji keys. The "A cute {animals} {places}" | |
| # prefix is built by image_generator.build_subject; the detail adds rich props, | |
| # activity and lighting for the cozy aesthetic. Deliberately mixes 1β3 animals | |
| # and 1β3 places to mirror multi-emoji selections in the app. | |
| # --------------------------------------------------------------------------- | |
| SCENES: list[tuple[list[str], list[str], str]] = [ | |
| # ββ Solo animal, single place β covers all 12 animals + all 10 places ββ | |
| (["π°"], ["π"], "sitting on a polka-dot toadstool, fireflies and floating spores drifting around, soft lantern glow"), | |
| (["π±"], ["π"], "building a tiny sandcastle with a bucket and shells, gentle waves lapping, warm sunset sky"), | |
| (["πΆ"], ["π‘"], "napping in a flower-filled wheelbarrow, watering can and butterflies nearby, golden afternoon light"), | |
| (["π¦"], ["β"], "curled up on a fluffy cloud cradling a tiny glowing star, glittering night sky"), | |
| (["πΌ"], ["πΈ"], "nibbling a dango skewer as petals fall, paper lanterns strung above, soft pink light"), | |
| (["π¨"], ["π΄"], "hugging a palm trunk with a coconut drink, striped hammock and a parrot, turquoise sea behind"), | |
| (["π¦"], ["π"], "wearing a tiny crown at the end of a rainbow, pastel clouds and floating sparkles"), | |
| (["π―"], ["ποΈ"], "bundled in a knitted scarf on a snowy peak planting a tiny flag, sparkling snow and faint aurora"), | |
| (["πΈ"], ["πΊ"], "perched on a giant hibiscus bloom, dewdrops glistening, big tropical leaves and warm bokeh"), | |
| (["π§"], ["π"], "sitting on the curve of a glowing crescent moon in a knitted hat, scattered twinkling stars"), | |
| (["π¦"], ["πΈ"], "fluttering through cherry blossoms trailing sparkles, pastel petals swirling in the breeze"), | |
| (["π¦"], ["β"], "galloping across a starry sky, rainbow mane glowing, a trail of sparkles behind"), | |
| (["πΆ"], ["π"], "exploring beneath a giant mushroom with a tiny lantern, glowing toadstools and soft moss"), | |
| (["π±"], ["π"], "curled asleep on a crescent moon wearing a nightcap, twinkling stars all around"), | |
| (["π°"], ["π"], "splashing in shallow waves beside a starfish friend, beach pail and spade, pink sunset"), | |
| (["πΌ"], ["π‘"], "tending a vegetable patch in a straw hat, bees and tall sunflowers, warm sun"), | |
| (["π§"], ["ποΈ"], "sliding down a snowy slope on its belly, scarf flying, sparkling powder snow"), | |
| (["π¦"], ["π΄"], "lounging in a hammock between two palms with sunglasses, coconuts and calm ocean"), | |
| (["π¦"], ["πΊ"], "snoozing in a field of tropical flowers, a butterfly on its nose, dappled golden light"), | |
| (["π―"], ["πΈ"], "chasing falling cherry petals, paper lanterns above, soft pink and lilac tones"), | |
| (["πΈ"], ["π"], "playing a tiny flute on a lily pad among glowing mushrooms, fireflies and reeds"), | |
| (["π¦"], ["π"], "standing proudly under a rainbow, flower garland around its neck, pastel clouds"), | |
| # ββ Two animals, single place ββ | |
| (["π°", "π±"], ["π"], "roasting marshmallows over a tiny campfire, fireflies and glowing mushrooms, cozy night"), | |
| (["πΆ", "π¦"], ["π"], "building a sandcastle together with shell flags, gentle waves at golden hour"), | |
| (["πΌ", "π¨"], ["πΈ"], "sharing tea under a blooming cherry tree, paper lanterns, drifting petals"), | |
| (["π¦", "π―"], ["π‘"], "tumbling over a ball of yarn in a cottage garden, picket fence and butterflies"), | |
| (["π§", "π°"], ["ποΈ"], "ice skating on a frozen pond atop a snowy mountain, fairy lights, gentle snowfall"), | |
| (["πΈ", "π¦"], ["πΊ"], "resting together on lily pads among tropical flowers, dragonflies and warm bokeh"), | |
| (["π±", "πΆ"], ["π"], "stargazing from a crescent moon with a tiny brass telescope, soft constellations"), | |
| (["π¦", "π°"], ["π"], "trotting side by side under a rainbow, flower garlands and floating sparkles"), | |
| (["π¦", "πΌ"], ["π"], "reading a glowing storybook under a toadstool, a lantern and curious fireflies"), | |
| (["π¨", "π§"], ["π΄"], "sipping coconut drinks on a tropical island, beach umbrella and gentle surf"), | |
| (["π°", "π¦"], ["β"], "swinging on a swing hung from the stars, sparkles raining down, deep blue night"), | |
| (["π±", "πΈ"], ["π"], "collecting shells in tide pools at low tide, a little net and a pastel sunset"), | |
| # ββ Three animals, single place ββ | |
| (["π°", "π±", "πΆ"], ["π"], "having a picnic on a checkered blanket among glowing mushrooms, lanterns and fireflies"), | |
| (["π¦", "πΌ", "π¨"], ["πΈ"], "a tea party under cherry blossoms with tiny cups, paper lanterns and drifting petals"), | |
| (["π¦", "π―", "πΈ"], ["π‘"], "playing tag through flower beds in a cottage garden, butterflies and warm sun"), | |
| (["π§", "π°", "π±"], ["ποΈ"], "building a snowman on a snowy peak in matching scarves, sparkling snow, aurora above"), | |
| (["π¦", "π¦", "π°"], ["π"], "dancing under a rainbow amid sparkles and flower petals, pastel sky"), | |
| (["πΆ", "π¦", "πΌ"], ["π"], "surfing tiny waves together with a beach ball, palm trees and sunset glow"), | |
| (["π±", "π¨", "πΈ"], ["πΊ"], "weaving flower crowns in a field of tropical flowers, butterflies and golden bokeh"), | |
| (["π¦", "π―", "π°"], ["β"], "huddled on a cloud counting sparkling stars under a shared blanket, soft glow"), | |
| # ββ Two places ββ | |
| (["π°"], ["π", "π"], "hopping from a mushroom grove toward a rainbow, sparkles bridging the two, pastel light"), | |
| (["π±", "πΆ"], ["π", "π΄"], "a beach day between ocean waves and a tropical island, palm shade and scattered shells"), | |
| (["π¦"], ["β", "π"], "soaring past sparkling stars toward a crescent moon, a glowing rainbow trail"), | |
| (["πΌ"], ["πΈ", "π‘"], "wandering from cherry blossoms into a cosy cottage garden, petals and busy bees"), | |
| (["π§", "π°"], ["ποΈ", "β"], "watching sparkling stars from a snowy mountain top, fairy lights and soft snow"), | |
| (["π¦"], ["πΊ", "π΄"], "exploring tropical flowers along a tropical island shore, parrots and warm bokeh"), | |
| (["πΈ", "π¦"], ["πΈ", "πΊ"], "drifting between cherry blossoms and tropical flowers, dewdrops and floating petals"), | |
| (["π¦"], ["π‘", "π"], "lazing in a cottage garden as a rainbow arcs overhead, butterflies and golden light"), | |
| # ββ Three places ββ | |
| (["π°", "π±"], ["π", "π", "β"], "a dreamy journey through a mushroom forest, under a rainbow and beneath sparkling stars, a glowing trail"), | |
| (["π¦"], ["π", "β", "π"], "flying past a crescent moon and sparkling stars toward a rainbow, sparkles everywhere"), | |
| (["πΆ", "π¦", "πΌ"], ["π", "π΄", "πΊ"], "a tropical adventure across a sunny beach, a tropical island and fields of flowers, parrots and surf"), | |
| (["π§", "π°", "π±"], ["ποΈ", "β", "π"], "a starry night on a snowy peak under a crescent moon and sparkling stars, fairy lights and aurora"), | |
| ] | |
| # LoRA trigger word β a nonsense token the style LoRA learns to associate with | |
| # the whole NumZoo aesthetic. The app prepends it at inference once the LoRA is | |
| # trained. Per BFL guidance, training captions = "TRIGGER. <content only>", | |
| # WITHOUT spelling out the style (so the trigger alone summons the look). | |
| TRIGGER = "NUMZOO" | |
| def _scene_name(idx: int, animals: list[str], places: list[str]) -> str: | |
| """Short readable label for logs, e.g. '03_panda_1a1p'.""" | |
| first = ANIMAL_MAP[animals[0]].replace("baby ", "") | |
| return f"{idx:02d}_{first}_{len(animals)}a{len(places)}p" | |
| def _gen_prompt(a: list[str], p: list[str], detail: str) -> str: | |
| """Full STYLED prompt sent to the image model (needs explicit style cues).""" | |
| return f"{build_subject(a, p)}, {detail}, {STYLE}" | |
| def _caption(a: list[str], p: list[str], detail: str) -> str: | |
| """Training caption: trigger word + content only, NO style words (BFL style-LoRA).""" | |
| return f"{TRIGGER}. {build_subject(a, p)}, {detail}" | |
| # (name, generation_prompt, training_caption) for each scene. | |
| PROMPTS: list[tuple[str, str, str]] = [ | |
| (_scene_name(i + 1, a, p), _gen_prompt(a, p, detail), _caption(a, p, detail)) | |
| for i, (a, p, detail) in enumerate(SCENES) | |
| ] | |
| # --------------------------------------------------------------------------- | |
| # Generator using Qwen-Image via HuggingFace Inference API (fal-ai provider) | |
| # --------------------------------------------------------------------------- | |
| # Qwen-Image chosen over FLUX.1-dev: far better at rendering DISTINCT animals in | |
| # multi-animal scenes (critical β the app lets players pick up to 3), and a | |
| # softer painterly storybook style closer to the NumZoo references. | |
| # Note: FLUX.2-dev is edit-only (image-to-image) on every HF provider, so it | |
| # cannot be used for text-to-image dataset generation. | |
| MODEL = "Qwen/Qwen-Image" | |
| PROVIDER = "fal-ai" | |
| RETRIES = 4 # fal-ai occasionally returns transient 504s | |
| def generate_image(prompt: str) -> "PIL.Image.Image": | |
| from huggingface_hub import InferenceClient | |
| # HF Pro token (with "Make calls to Inference Providers" permission) covers | |
| # all providers β no separate provider key needed. | |
| hf_token = os.environ.get("HF_TOKEN") | |
| last_err = None | |
| for attempt in range(1, RETRIES + 1): | |
| try: | |
| client = InferenceClient(provider=PROVIDER, api_key=hf_token) | |
| return client.text_to_image(prompt, model=MODEL, width=1024, height=1024) | |
| except Exception as e: | |
| last_err = e | |
| if attempt < RETRIES: | |
| print(f" β οΈ attempt {attempt}/{RETRIES} failed ({str(e)[:60]}) β retrying") | |
| time.sleep(4) | |
| raise last_err # exhausted retries | |
| def main(): | |
| parser = argparse.ArgumentParser() | |
| parser.add_argument("--start", type=int, default=1, help="Resume from image N (1-based)") | |
| parser.add_argument("--count", type=int, default=None, help="Generate at most N images then stop") | |
| parser.add_argument("--dry-run", action="store_true", help="Print prompts without generating") | |
| parser.add_argument("--captions-only", action="store_true", help="Rewrite .txt captions for existing images (no API calls)") | |
| args = parser.parse_args() | |
| if not args.dry_run and not args.captions_only: | |
| if not os.environ.get("HF_TOKEN"): | |
| print("β HF_TOKEN not found. Add it to .env") | |
| print(" Get yours at https://huggingface.co/settings/tokens") | |
| sys.exit(1) | |
| out_dir = Path(__file__).parent.parent / "training" | |
| out_dir.mkdir(exist_ok=True) | |
| total = len(PROMPTS) | |
| end_at = (args.start - 1 + args.count) if args.count else total # inclusive upper bound (index) | |
| count_label = f"{args.count} images" if args.count else f"all {total} images" | |
| print(f"NumZoo dataset generator β {count_label} β {out_dir}") | |
| print(f"Range: {args.start}β{min(end_at, total)} of {total}") | |
| print() | |
| generated_this_run = 0 | |
| for i, (name, gen_prompt, caption) in enumerate(PROMPTS): | |
| n = i + 1 | |
| if n < args.start: | |
| continue | |
| if n > end_at: | |
| break | |
| img_path = out_dir / f"image_{n:03d}.jpg" | |
| txt_path = out_dir / f"image_{n:03d}.txt" | |
| # Rewrite captions for existing images, no image generation | |
| if args.captions_only: | |
| txt_path.write_text(caption) | |
| print(f"[{n:02d}/{total}] π {name} β caption updated") | |
| continue | |
| if img_path.exists(): | |
| print(f"[{n:02d}/{total}] β {name} β already exists, skipping") | |
| continue | |
| print(f"[{n:02d}/{total}] π¨ {name}") | |
| if args.dry_run: | |
| print(f" gen: {gen_prompt[:90]}β¦") | |
| print(f" caption: {caption[:90]}β¦") | |
| continue | |
| try: | |
| image = generate_image(gen_prompt) | |
| image.save(img_path, "JPEG", quality=95) | |
| txt_path.write_text(caption) | |
| generated_this_run += 1 | |
| print(f" β saved {img_path.name}") | |
| except Exception as e: | |
| print(f" β failed: {e}") | |
| time.sleep(5) # brief pause on error before continuing | |
| total_on_disk = len(list(out_dir.glob("*.jpg"))) | |
| print(f"\nDone. {generated_this_run} generated this run Β· {total_on_disk}/{total} total in {out_dir}") | |
| if __name__ == "__main__": | |
| main() | |