File size: 15,055 Bytes
3311574 38bba68 18bda20 38bba68 0a993a9 18bda20 3311574 18bda20 0a993a9 38bba68 3311574 18bda20 3311574 18bda20 0a993a9 18bda20 0a993a9 3311574 18bda20 3311574 18bda20 3311574 47fb1ee 18bda20 3311574 47fb1ee 18bda20 3311574 38bba68 3311574 38bba68 3311574 38bba68 0a993a9 38bba68 0a993a9 38bba68 0a993a9 38bba68 3311574 47fb1ee 3311574 47fb1ee 0a993a9 3311574 0a993a9 3311574 0a993a9 3311574 0a993a9 47fb1ee 3311574 0a993a9 3311574 47fb1ee 3311574 47fb1ee 3311574 47fb1ee 3311574 47fb1ee 0a993a9 3311574 0a993a9 3311574 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 | """
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()
|